ZSWTappableLabel 3.3.2

ZSWTappableLabel 3.3.2

测试已测试
Lang语言 Obj-CObjective C
许可 MIT
发布最新版本2021年8月

Zachary West维护。



ZSWTappableLabel

Version License Platform

ZSWTappableLabel是一个可点击、长按、3D触控和VoiceOver的UILabel子类。它具有可选的高亮行为,本身不绘制文本。其目标是尽可能接近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秒。

3D Touch

如果您已注册用于预览的标签或包含标签的视图,您可以使用 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点?

我们可以将 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 是一个无障碍性容器,它将您的属性字符串中的子字符串公开为不同的元素。例如,上述字符串分为以下几部分:

  1. 查看我们的(静态文本)
  2. 隐私政策(链接)
  3. (静态文本)
  4. 服务条款(链接)

这与 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.2"

许可协议

ZSWTappableLabel遵循MIT许可协议。此库在开发Free项目时创建,该项目的允许此代码库开源。