{"id":15632672,"url":"https://github.com/zacwest/zswtappablelabel","last_synced_at":"2025-04-30T21:35:08.048Z","repository":{"id":32885805,"uuid":"36480232","full_name":"zacwest/ZSWTappableLabel","owner":"zacwest","description":"UILabel subclass for links which are tappable, long-pressable, 3D Touchable, and VoiceOverable.","archived":false,"fork":false,"pushed_at":"2021-08-12T02:57:12.000Z","size":572,"stargazers_count":169,"open_issues_count":5,"forks_count":37,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-01-17T07:05:30.217Z","etag":null,"topics":["ios","label","links","objective-c","tappable","ui","uilabel"],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zacwest.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"zacwest"}},"created_at":"2015-05-29T03:20:24.000Z","updated_at":"2025-01-12T22:36:11.000Z","dependencies_parsed_at":"2022-08-21T05:50:27.442Z","dependency_job_id":null,"html_url":"https://github.com/zacwest/ZSWTappableLabel","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zacwest%2FZSWTappableLabel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zacwest%2FZSWTappableLabel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zacwest%2FZSWTappableLabel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zacwest%2FZSWTappableLabel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zacwest","download_url":"https://codeload.github.com/zacwest/ZSWTappableLabel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235448546,"owners_count":18991894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ios","label","links","objective-c","tappable","ui","uilabel"],"created_at":"2024-10-03T10:44:56.971Z","updated_at":"2025-01-24T14:12:48.766Z","avatar_url":"https://github.com/zacwest.png","language":"Objective-C","funding_links":["https://github.com/sponsors/zacwest"],"categories":[],"sub_categories":[],"readme":"# ZSWTappableLabel\n\n\u003c!--[![CI Status](http://img.shields.io/travis/zacwest/ZSWTappableLabel.svg?style=flat)](https://travis-ci.org/zacwest/ZSWTappableLabel)--\u003e\n[![Version](https://img.shields.io/cocoapods/v/ZSWTappableLabel.svg?style=flat)](http://cocoapods.org/pods/ZSWTappableLabel)\n[![License](https://img.shields.io/cocoapods/l/ZSWTappableLabel.svg?style=flat)](http://cocoapods.org/pods/ZSWTappableLabel)\n[![Platform](https://img.shields.io/cocoapods/p/ZSWTappableLabel.svg?style=flat)](http://cocoapods.org/pods/ZSWTappableLabel)\n\nZSWTappableLabel is a UILabel subclass for links which are tappable, long-pressable, 3D Touchable, and VoiceOverable. It has optional highlighting behavior, and does not draw text itself. Its goal is to be as minimally different from UILabel as possible, and only executes additional code when the user is interacting with a tappable region.\n\n## A basic, tappable link\n\nLet's create a string that's entirely tappable and shown with an underline:\n\n```swift\nlet string = NSLocalizedString(\"Privacy Policy\", comment: \"\")\nlet attributes: [NSAttributedString.Key: Any] = [\n  .tappableRegion: true,\n  .tappableHighlightedBackgroundColor: UIColor.lightGray,\n  .tappableHighlightedForegroundColor: UIColor.white,\n  .foregroundColor: UIColor.blue,\n  .underlineStyle: NSUnderlineStyle.single.rawValue,\n  .link: URL(string: \"http://imgur.com/gallery/VgXCk\")!\n]\n\nlabel.attributedText = NSAttributedString(string: string, attributes: attributes)\n```\n\n```objective-c\nNSString *s = NSLocalizedString(@\"Privacy Policy\", nil);\nNSDictionary *a = @{\n  ZSWTappableLabelTappableRegionAttributeName: @YES,\n  ZSWTappableLabelHighlightedBackgroundAttributeName: [UIColor lightGrayColor],\n  ZSWTappableLabelHighlightedForegroundAttributeName: [UIColor whiteColor],\n  NSForegroundColorAttributeName: [UIColor blueColor],\n  NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),\n  NSLinkAttributeName: [NSURL URLWithString:@\"http://imgur.com/gallery/VgXCk\"],\n};\n\nlabel.attributedText = [[NSAttributedString alloc] initWithString:s attributes:a];\n```\n\nThis results in a label which renders like:\n\n\u003e [Privacy Policy](https://github.com/zacwest/zswtappablelabel)\n\nSetting your controller as the `tapDelegate` of the label results in the following method call when tapped:\n\n```swift\nfunc tappableLabel(\n  _ tappableLabel: ZSWTappableLabel, \n  tappedAt idx: Int, \n  withAttributes attributes: [NSAttributedString.Key : Any]\n) {\n  if let url = attributes[.link] as? URL {\n    UIApplication.shared.openURL(url)\n  }\n}\n```\n\n```objective-c\n- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel\n        tappedAtIndex:(NSInteger)idx\n       withAttributes:(NSDictionary\u003cNSAttributedStringKey, id\u003e *)attributes {\n  [[UIApplication sharedApplication] openURL:attributes[@\"URL\"]];\n}\n```\n\n## Long-presses\n\nYou may optionally support long-presses by setting a `longPressDelegate` on the label. This behaves very similarly to the `tapDelegate`:\n\n```swift\nfunc tappableLabel(\n  _ tappableLabel: ZSWTappableLabel, \n  longPressedAt idx: Int, \n  withAttributes attributes: [NSAttributedString.Key : Any]\n) {\n  guard let URL = attributes[.link] as? URL else {\n    return\n  }\n  \n  let activityController = UIActivityViewController(activityItems: [URL], applicationActivities: nil)\n  present(activityController, animated: true, completion: nil)\n}\n```\n\n```objective-c\n- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel \n   longPressedAtIndex:(NSInteger)idx \n       withAttributes:(NSDictionary\u003cNSAttributedStringKey, id\u003e *)attributes {\n  NSURL *URL = attributes[NSLinkAttributeName];\n  if ([URL isKindOfClass:[NSURL class]]) {\n    UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[ URL ] applicationActivities:nil];\n    [self presentViewController:activityController animated:YES completion:nil];\n  }\n}\n```\n\nYou can configure the `longPressDuration` for how long until a long-press is recognized. This defaults to 0.5 seconds.\n\n## 3D Touch\n\nIf you've registered either the label or a view containing the label for previewing using peek/pop, you can get information about the tappable regions at a point using one of the two `tappableRegionInfo` methods on `ZSWTappableLabel`. See [the header file](https://github.com/zacwest/ZSWTappableLabel/blob/master/ZSWTappableLabel/ZSWTappableLabel.h) for more information.\n\nWhen you're queried for previewing information, you can respond using the information from these methods. For example, to preview an SFSafariViewController:\n\n```swift\nfunc previewingContext(\n  _ previewingContext: UIViewControllerPreviewing, \n  viewControllerForLocation location: CGPoint\n) -\u003e UIViewController? {\n  guard let regionInfo = label.tappableRegionInfo(\n    forPreviewingContext: previewingContext, \n    location: location\n  ) else {\n    return nil\n  }\n\n  guard let URL = regionInfo.attributes[.link] as? URL else {\n    return nil\n  }\n\n  // convenience method that sets the rect of the previewing context\n  regionInfo.configure(previewingContext: previewingContext)\n  return SFSafariViewController(url: URL)\n}\n```\n\n```objc\n- (UIViewController *)previewingContext:(id\u003cUIViewControllerPreviewing\u003e)previewingContext\n              viewControllerForLocation:(CGPoint)location {\n  id\u003cZSWTappableLabelTappableRegionInfo\u003e regionInfo = \n    [self.label tappableRegionInfoForPreviewingContext:previewingContext location:location];\n  if (!regionInfo) {\n    return nil;\n  }\n  [regionInfo configurePreviewingContext:previewingContext];\n  return [[SFSafariViewController alloc] initWithURL:regionInfo.attributes[NSLinkAttributeName]];\n}\n```\n\n## Data detectors\n\nLet's use `NSDataDetector` to find the substrings in a given string that we might want to turn into links:\n\n```swift\nlet string = \"check google.com or call 415-555-5555? how about friday at 5pm?\"\n\nlet detector = try! NSDataDetector(types: NSTextCheckingAllSystemTypes)\nlet attributedString = NSMutableAttributedString(string: string, attributes: nil)\nlet range = NSRange(location: 0, length: (string as NSString).length)\n\ndetector.enumerateMatches(in: attributedString.string, options: [], range: range) { (result, flags, _) in\n  guard let result = result else { return }\n  \n  var attributes = [NSAttributedString.Key: Any]()\n  attributes[.tappableRegion] = true\n  attributes[.tappableHighlightedBackgroundColor] = UIColor.lightGray\n  attributes[.tappableHighlightedForegroundColor] = UIColor.white\n  attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue\n  attributes[.init(rawValue: \"NSTextCheckingResult\")] = result\n  attributedString.addAttributes(attributes, range: result.range)\n}\nlabel.attributedText = attributedString\n```\n\n```objective-c\nNSString *string = @\"check google.com or call 415-555-5555? how about friday at 5pm?\";\n\nNSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingAllSystemTypes error:NULL];\nNSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:nil];\n// the next line throws an exception if string is nil - make sure you check\n[detector enumerateMatchesInString:string options:0 range:NSMakeRange(0, string.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {\n  NSMutableDictionary *attributes = [NSMutableDictionary dictionary];\n  attributes[ZSWTappableLabelTappableRegionAttributeName] = @YES;\n  attributes[ZSWTappableLabelHighlightedBackgroundAttributeName] = [UIColor lightGrayColor];\n  attributes[ZSWTappableLabelHighlightedForegroundAttributeName] = [UIColor whiteColor];\n  attributes[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);\n  attributes[@\"NSTextCheckingResult\"] = result;\n  [attributedString addAttributes:attributes range:result.range];\n}];\nlabel.attributedText = attributedString;\n```\n\nThis results in a label which renders like:\n\n\u003e check [google.com](#) or call [415-555-5555](#)? how about [friday at 5pm](#)?\n\nWe can wire up the `tapDelegate` to receive the checking result and handle each result type when the user taps on the link:\n\n```swift\nfunc tappableLabel(\n  tappableLabel: ZSWTappableLabel, \n  tappedAtIndex idx: Int, \n  withAttributes attributes: [NSAttributedString.Key : Any]) {\n  if let result = attributes[.init(rawValue: \"NSTextCheckingResult\")] as? NSTextCheckingResult {\n    switch result.resultType {\n    case [.address]:\n      print(\"Address components: \\(result.addressComponents)\")\n    case [.phoneNumber]:\n      print(\"Phone number: \\(result.phoneNumber)\")\n    case [.date]:\n      print(\"Date: \\(result.date)\")\n    case [.link]:\n      print(\"Link: \\(result.url)\")\n    default:\n      break\n    }\n  }\n}\n```\n\n```objective-c\n- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel\n        tappedAtIndex:(NSInteger)idx\n       withAttributes:(NSDictionary\u003cNSAttributedStringKey, id\u003e *)attributes {\n  NSTextCheckingResult *result = attributes[@\"NSTextCheckingResult\"];\n  if (result) {\n    switch (result.resultType) {\n      case NSTextCheckingTypeAddress:\n        NSLog(@\"Address components: %@\", result.addressComponents);\n        break;\n          \n      case NSTextCheckingTypePhoneNumber:\n        NSLog(@\"Phone number: %@\", result.phoneNumber);\n        break;\n          \n      case NSTextCheckingTypeDate:\n        NSLog(@\"Date: %@\", result.date);\n        break;\n          \n      case NSTextCheckingTypeLink:\n        NSLog(@\"Link: %@\", result.URL);\n        break;\n\n      default:\n        break;\n    }\n  }\n}\n```\n\n## Substring linking\n\nFor substring linking, I suggest you use [ZSWTaggedString](https://github.com/zacwest/zswtaggedstring) which creates these attributed strings painlessly and localizably. Let's create a more advanced 'privacy policy' link using this library:\n\n\u003e View our [Privacy Policy](https://github.com/zacwest/zswtappablelabel) or [Terms of Service](https://github.com/zacwest/zswtappablelabel)\n\nYou can create such a string using a simple ZSWTaggedString:\n\n```swift\nlet options = ZSWTaggedStringOptions()\noptions[\"link\"] = .dynamic({ tagName, tagAttributes, stringAttributes in\n  guard let type = tagAttributes[\"type\"] as? String else {\n    return [NSAttributedString.Key: Any]()\n  }\n  \n  var foundURL: URL?\n  \n  switch type {\n  case \"privacy\":\n    foundURL = URL(string: \"http://google.com/search?q=privacy\")!\n  case \"tos\":\n    foundURL = URL(string: \"http://google.com/search?q=tos\")!\n  default:\n    break\n  }\n  \n  guard let URL = foundURL else {\n    return [NSAttributedString.Key: Any]()\n  }\n  \n  return [\n    .tappableRegion: true,\n    .tappableHighlightedBackgroundColor: UIColor.lightGray,\n    .tappableHighlightedForegroundColor: UIColor.white,\n    .foregroundColor: UIColor.blue,\n    .underlineStyle: NSUnderlineStyle.single.rawValue,\n    .link: foundURL\n  ]\n})\n\nlet string = NSLocalizedString(\"View our \u003clink type='privacy'\u003ePrivacy Policy\u003c/link\u003e or \u003clink type='tos'\u003eTerms of Service\u003c/link\u003e\", comment: \"\")\nlabel.attributedText = try? ZSWTaggedString(string: string).attributedString(with: options)\n```\n\n```objective-c\nZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];\n[options setDynamicAttributes:^NSDictionary *(NSString *tagName, \n                                              NSDictionary *tagAttributes,\n                                              NSDictionary *existingStringAttributes) {\n  NSURL *URL;\n  if ([tagAttributes[@\"type\"] isEqualToString:@\"privacy\"]) {\n    URL = [NSURL URLWithString:@\"http://google.com/search?q=privacy\"];\n  } else if ([tagAttributes[@\"type\"] isEqualToString:@\"tos\"]) {\n    URL = [NSURL URLWithString:@\"http://google.com/search?q=tos\"];\n  }\n\n  if (!URL) {\n    return nil;\n  }\n\n  return @{\n    ZSWTappableLabelTappableRegionAttributeName: @YES,\n    ZSWTappableLabelHighlightedBackgroundAttributeName: [UIColor lightGrayColor],\n    ZSWTappableLabelHighlightedForegroundAttributeName: [UIColor whiteColor],\n    NSForegroundColorAttributeName: [UIColor blueColor],\n    NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),\n    @\"URL\": URL\n  };\n} forTagName:@\"link\"];\n\nNSString *string = NSLocalizedString(@\"View our \u003clink type='privacy'\u003ePrivacy Policy\u003c/link\u003e or \u003clink type='tos'\u003eTerms of Service\u003c/link\u003e\", nil);\nlabel.attributedText = [[ZSWTaggedString stringWithString:string] attributedStringWithOptions:options];\n```\n\n## Accessibility\n\nZSWTappableLabel is an accessibility container, which exposes the substrings in your attributed string as distinct elements. For example, the above string breaks down into:\n\n1. `View our` (static text)\n1. `Privacy Policy` (link)\n1. ` or ` (static text)\n1. `Terms of Service` (link)\n\nThis is similar behavior to Safari, which breaks up elements into discrete chunks.\n\nWhen you set a `longPressDelegate`, an additional action on links is added to perform the long-press gesture. You should configure the `longPressAccessibilityActionName` to adjust what is read to users.\n\nWhen you set an `accessibilityDelegate`, you can add custom actions to a particular link, for example:\n\n```swift\nfunc tappableLabel(\n  _ tappableLabel: ZSWTappableLabel, \n  accessibilityCustomActionsForCharacterRange characterRange: NSRange, \n  withAttributesAtStart attributes: [NSAttributedString.Key : Any] = [:]\n) -\u003e [UIAccessibilityCustomAction] {\n  return [\n    UIAccessibilityCustomAction(\n      name: NSLocalizedString(\"View Link Address\", comment: \"\"),\n      target: self,\n      selector: #selector(viewLink(_:))\n    )\n  ]\n}\n```\n\n```objc\n- (NSArray\u003cUIAccessibilityCustomAction *\u003e *)tappableLabel:(ZSWTappableLabel *)tappableLabel\n              accessibilityCustomActionsForCharacterRange:(NSRange)characterRange\n                                    withAttributesAtStart:(NSDictionary\u003cNSAttributedStringKey,id\u003e *)attributes {\n  return @[\n    [[UIAccessibilityCustomAction alloc] initWithName:NSLocalizedString(@\"View Link Address\", nil) \n                                               target:self\n                                             selector:@selector(viewLink:)]\n  ];\n}\n```\n\nYou can also change the `accessibilityLabel` of the created accessibility elements, for example:\n\n```swift\nfunc tappableLabel(\n  _ tappableLabel: ZSWTappableLabel, \n  accessibilityLabelForCharacterRange characterRange: NSRange, \n  withAttributesAtStart attributes: [NSAttributedString.Key : Any] = [:]\n) -\u003e String? {\n  if attributes[.link] != nil {\n    return \"Some Custom Label\"\n  } else {\n    return nil\n  }\n}\n```\n\n```objc\n- (nullable NSString *)tappableLabel:(nonnull ZSWTappableLabel *)tappableLabel \n accessibilityLabelForCharacterRange:(NSRange)characterRange \n               withAttributesAtStart:(nonnull NSDictionary\u003cNSAttributedStringKey,id\u003e *)attributes {\n  if (attributes[NSLinkAttributeName] != nil) {\n    return @\"Some Custom Label\";\n  } else {\n    return nil;\n  }\n}\n```\n\n\n## Installation\n\nZSWTappableLabel is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:\n\n```ruby\npod \"ZSWTappableLabel\", \"~\u003e 3.3\"\n```\n\nZSWTappableLabel is available through [Swift Package Manager](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app) in a `Package.swift` like:\n\n```swift\n.package(url: \"https://github.com/zacwest/ZSWTappableLabel.git\", majorVersion: 3)\n```\n\nTo add it to an Xcode project, add the URL via File \u003e Swift Packages -\u003e Add Package Dependency.\n\n## License\n\nZSWTappableLabel is available under the [MIT license](https://github.com/zacwest/ZSWTappableLabel/blob/master/LICENSE). This library was created while working on [Free](https://www.producthunt.com/posts/free) who allowed this to be open-sourced.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzacwest%2Fzswtappablelabel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzacwest%2Fzswtappablelabel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzacwest%2Fzswtappablelabel/lists"}