ZSWTappableLabel 是 UILabel 的子类,用于创建可点击、可长按、支持 3D Touch 和 VoiceOver 的链接。它具有可选的高亮行为,并且不自行绘制文本。其目标是尽可能地与 UILabel 保持最小差异,仅在用户与可点击区域交互时执行额外的代码。
让我们创建一个完全可点击并带有下划线的字符串
let string = NSLocalizedString("Privacy Policy", comment: "")
let attributes: [NSAttributedString.Key: Any] = [
.tappableRegion: true,
.tappableHighlightedBackgroundColor: UIColor.lightGray,
.tappableHighlightedForegroundColor: UIColor.white,
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.rawValue,
.link: URL(string: "http://imgur.com/gallery/VgXCk")!
]
label.attributedText = NSAttributedString(string: string, attributes: attributes)
NSString *s = NSLocalizedString(@"Privacy Policy", nil);
NSDictionary *a = @{
ZSWTappableLabelTappableRegionAttributeName: @YES,
ZSWTappableLabelHighlightedBackgroundAttributeName: [UIColor lightGrayColor],
ZSWTappableLabelHighlightedForegroundAttributeName: [UIColor whiteColor],
NSForegroundColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
NSLinkAttributeName: [NSURL URLWithString:@"http://imgur.com/gallery/VgXCk"],
};
label.attributedText = [[NSAttributedString alloc] initWithString:s attributes:a];
这将生成一个像下面这样的标签
将您的控制器设置为标签的 tapDelegate
会在点击时导致以下方法调用
func tappableLabel(
_ tappableLabel: ZSWTappableLabel,
tappedAt idx: Int,
withAttributes attributes: [NSAttributedString.Key : Any]
) {
if let url = attributes[.link] as? URL {
UIApplication.shared.openURL(url)
}
}
- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel
tappedAtIndex:(NSInteger)idx
withAttributes:(NSDictionary<NSAttributedStringKey, id> *)attributes {
[[UIApplication sharedApplication] openURL:attributes[@"URL"]];
}
您可以选择通过在标签上设置 longPressDelegate
来支持长按。这与 tapDelegate
的行为非常相似
func tappableLabel(
_ tappableLabel: ZSWTappableLabel,
longPressedAt idx: Int,
withAttributes attributes: [NSAttributedString.Key : Any]
) {
guard let URL = attributes[.link] as? URL else {
return
}
let activityController = UIActivityViewController(activityItems: [URL], applicationActivities: nil)
present(activityController, animated: true, completion: nil)
}
- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel
longPressedAtIndex:(NSInteger)idx
withAttributes:(NSDictionary<NSAttributedStringKey, id> *)attributes {
NSURL *URL = attributes[NSLinkAttributeName];
if ([URL isKindOfClass:[NSURL class]]) {
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[ URL ] applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
}
您可以配置 longPressDuration
来设置识别长按所需的时间。默认值为 0.5 秒。
如果您已使用 peek/pop 注册了标签或包含该标签的视图以进行预览,则可以使用 ZSWTappableLabel
上的两个 tappableRegionInfo
方法之一来获取有关某个点上可点击区域的信息。有关更多信息,请参阅 头文件。
当您被查询预览信息时,您可以使用来自这些方法的信息进行响应。例如,要预览 SFSafariViewController
func previewingContext(
_ previewingContext: UIViewControllerPreviewing,
viewControllerForLocation location: CGPoint
) -> UIViewController? {
guard let regionInfo = label.tappableRegionInfo(
forPreviewingContext: previewingContext,
location: location
) else {
return nil
}
guard let URL = regionInfo.attributes[.link] as? URL else {
return nil
}
// convenience method that sets the rect of the previewing context
regionInfo.configure(previewingContext: previewingContext)
return SFSafariViewController(url: URL)
}
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext
viewControllerForLocation:(CGPoint)location {
id<ZSWTappableLabelTappableRegionInfo> regionInfo =
[self.label tappableRegionInfoForPreviewingContext:previewingContext location:location];
if (!regionInfo) {
return nil;
}
[regionInfo configurePreviewingContext:previewingContext];
return [[SFSafariViewController alloc] initWithURL:regionInfo.attributes[NSLinkAttributeName]];
}
让我们使用 NSDataDetector
来查找给定字符串中我们可能想要转换为链接的子字符串
let string = "check google.com or call 415-555-5555? how about friday at 5pm?"
let detector = try! NSDataDetector(types: NSTextCheckingAllSystemTypes)
let attributedString = NSMutableAttributedString(string: string, attributes: nil)
let range = NSRange(location: 0, length: (string as NSString).length)
detector.enumerateMatches(in: attributedString.string, options: [], range: range) { (result, flags, _) in
guard let result = result else { return }
var attributes = [NSAttributedString.Key: Any]()
attributes[.tappableRegion] = true
attributes[.tappableHighlightedBackgroundColor] = UIColor.lightGray
attributes[.tappableHighlightedForegroundColor] = UIColor.white
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
attributes[.init(rawValue: "NSTextCheckingResult")] = result
attributedString.addAttributes(attributes, range: result.range)
}
label.attributedText = attributedString
NSString *string = @"check google.com or call 415-555-5555? how about friday at 5pm?";
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingAllSystemTypes error:NULL];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:nil];
// the next line throws an exception if string is nil - make sure you check
[detector enumerateMatchesInString:string options:0 range:NSMakeRange(0, string.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
attributes[ZSWTappableLabelTappableRegionAttributeName] = @YES;
attributes[ZSWTappableLabelHighlightedBackgroundAttributeName] = [UIColor lightGrayColor];
attributes[ZSWTappableLabelHighlightedForegroundAttributeName] = [UIColor whiteColor];
attributes[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
attributes[@"NSTextCheckingResult"] = result;
[attributedString addAttributes:attributes range:result.range];
}];
label.attributedText = attributedString;
这将生成一个像下面这样的标签
查看 google.com 或致电 415-555-5555?星期五下午 5 点怎么样?friday at 5pm?
我们可以连接 tapDelegate
以接收检查结果,并在用户点击链接时处理每种结果类型
func tappableLabel(
tappableLabel: ZSWTappableLabel,
tappedAtIndex idx: Int,
withAttributes attributes: [NSAttributedString.Key : Any]) {
if let result = attributes[.init(rawValue: "NSTextCheckingResult")] as? NSTextCheckingResult {
switch result.resultType {
case [.address]:
print("Address components: \(result.addressComponents)")
case [.phoneNumber]:
print("Phone number: \(result.phoneNumber)")
case [.date]:
print("Date: \(result.date)")
case [.link]:
print("Link: \(result.url)")
default:
break
}
}
}
- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel
tappedAtIndex:(NSInteger)idx
withAttributes:(NSDictionary<NSAttributedStringKey, id> *)attributes {
NSTextCheckingResult *result = attributes[@"NSTextCheckingResult"];
if (result) {
switch (result.resultType) {
case NSTextCheckingTypeAddress:
NSLog(@"Address components: %@", result.addressComponents);
break;
case NSTextCheckingTypePhoneNumber:
NSLog(@"Phone number: %@", result.phoneNumber);
break;
case NSTextCheckingTypeDate:
NSLog(@"Date: %@", result.date);
break;
case NSTextCheckingTypeLink:
NSLog(@"Link: %@", result.URL);
break;
default:
break;
}
}
}
对于子字符串链接,我建议您使用 ZSWTaggedString,它可以轻松且可本地化地创建这些属性字符串。让我们使用这个库创建一个更高级的“隐私政策”链接
您可以使用简单的 ZSWTaggedString 创建这样的字符串
let options = ZSWTaggedStringOptions()
options["link"] = .dynamic({ tagName, tagAttributes, stringAttributes in
guard let type = tagAttributes["type"] as? String else {
return [NSAttributedString.Key: Any]()
}
var foundURL: URL?
switch type {
case "privacy":
foundURL = URL(string: "http://google.com/search?q=privacy")!
case "tos":
foundURL = URL(string: "http://google.com/search?q=tos")!
default:
break
}
guard let URL = foundURL else {
return [NSAttributedString.Key: Any]()
}
return [
.tappableRegion: true,
.tappableHighlightedBackgroundColor: UIColor.lightGray,
.tappableHighlightedForegroundColor: UIColor.white,
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.rawValue,
.link: foundURL
]
})
let string = NSLocalizedString("View our <link type='privacy'>Privacy Policy</link> or <link type='tos'>Terms of Service</link>", comment: "")
label.attributedText = try? ZSWTaggedString(string: string).attributedString(with: options)
ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];
[options setDynamicAttributes:^NSDictionary *(NSString *tagName,
NSDictionary *tagAttributes,
NSDictionary *existingStringAttributes) {
NSURL *URL;
if ([tagAttributes[@"type"] isEqualToString:@"privacy"]) {
URL = [NSURL URLWithString:@"http://google.com/search?q=privacy"];
} else if ([tagAttributes[@"type"] isEqualToString:@"tos"]) {
URL = [NSURL URLWithString:@"http://google.com/search?q=tos"];
}
if (!URL) {
return nil;
}
return @{
ZSWTappableLabelTappableRegionAttributeName: @YES,
ZSWTappableLabelHighlightedBackgroundAttributeName: [UIColor lightGrayColor],
ZSWTappableLabelHighlightedForegroundAttributeName: [UIColor whiteColor],
NSForegroundColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
@"URL": URL
};
} forTagName:@"link"];
NSString *string = NSLocalizedString(@"View our <link type='privacy'>Privacy Policy</link> or <link type='tos'>Terms of Service</link>", nil);
label.attributedText = [[ZSWTaggedString stringWithString:string] attributedStringWithOptions:options];
ZSWTappableLabel 是一个辅助功能容器,它将您的属性字符串中的子字符串作为不同的元素公开。例如,上面的字符串分解为
查看我们的
(静态文本)隐私政策
(链接)或
(静态文本)服务条款
(链接)这与 Safari 的行为类似,Safari 将元素分解为离散的块。
当您设置 longPressDelegate
时,链接上会添加一个额外的操作来执行长按手势。您应该配置 longPressAccessibilityActionName
以调整向用户读取的内容。
当您设置 accessibilityDelegate
时,您可以为特定链接添加自定义操作,例如
func tappableLabel(
_ tappableLabel: ZSWTappableLabel,
accessibilityCustomActionsForCharacterRange characterRange: NSRange,
withAttributesAtStart attributes: [NSAttributedString.Key : Any] = [:]
) -> [UIAccessibilityCustomAction] {
return [
UIAccessibilityCustomAction(
name: NSLocalizedString("View Link Address", comment: ""),
target: self,
selector: #selector(viewLink(_:))
)
]
}
- (NSArray<UIAccessibilityCustomAction *> *)tappableLabel:(ZSWTappableLabel *)tappableLabel
accessibilityCustomActionsForCharacterRange:(NSRange)characterRange
withAttributesAtStart:(NSDictionary<NSAttributedStringKey,id> *)attributes {
return @[
[[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@"View Link Address", nil)
target:self
selector:@selector(viewLink:)]
];
}
您还可以更改创建的辅助功能元素的 accessibilityLabel
,例如
func tappableLabel(
_ tappableLabel: ZSWTappableLabel,
accessibilityLabelForCharacterRange characterRange: NSRange,
withAttributesAtStart attributes: [NSAttributedString.Key : Any] = [:]
) -> String? {
if attributes[.link] != nil {
return "Some Custom Label"
} else {
return nil
}
}
- (nullable NSString *)tappableLabel:(nonnull ZSWTappableLabel *)tappableLabel
accessibilityLabelForCharacterRange:(NSRange)characterRange
withAttributesAtStart:(nonnull NSDictionary<NSAttributedStringKey,id> *)attributes {
if (attributes[NSLinkAttributeName] != nil) {
return @"Some Custom Label";
} else {
return nil;
}
}
ZSWTappableLabel 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile
pod "ZSWTappableLabel", "~> 3.3"
ZSWTappableLabel 可通过 Swift Package Manager 在 Package.swift
中获得,例如
.package(url: "https://github.com/zacwest/ZSWTappableLabel.git", majorVersion: 3)
要将其添加到 Xcode 项目,请通过 File > Swift Packages -> Add Package Dependency 添加 URL。