Skip to content

Commit

Permalink
Merge pull request #18 from zacwest/long-press
Browse files Browse the repository at this point in the history
Long-press recognition
  • Loading branch information
zacwest committed Dec 20, 2015
2 parents 0727374 + 9aa6acd commit 4369f39
Show file tree
Hide file tree
Showing 12 changed files with 430 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.3

- Long press support: set a longPressDelegate (like tapDelegate) and be notified when the user long-presses.

# 1.2 (2015-12-05)

- Adds annotations for nullability and generics to ease Swift import.
Expand Down
18 changes: 18 additions & 0 deletions Example/ZSWTappableLabel.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
110D6E4B1C25FEBA004E8551 /* SimpleSwiftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D6E4A1C25FEBA004E8551 /* SimpleSwiftViewController.swift */; };
110D6E4E1C25FEC6004E8551 /* SimpleObjectiveCViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 110D6E4D1C25FEC6004E8551 /* SimpleObjectiveCViewController.m */; };
110D6E501C25FF5C004E8551 /* RootExampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110D6E4F1C25FF5C004E8551 /* RootExampleCell.swift */; };
11110E441C27543300E67ACD /* LongPressSwiftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11110E431C27543300E67ACD /* LongPressSwiftViewController.swift */; };
11110E471C2762A900E67ACD /* LongPressObjectiveCViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 11110E461C2762A900E67ACD /* LongPressObjectiveCViewController.m */; };
116126D11C26094A003B4E43 /* MultipleSwiftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116126D01C26094A003B4E43 /* MultipleSwiftViewController.swift */; };
116126D41C260961003B4E43 /* MultipleObjectiveCViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 116126D31C260961003B4E43 /* MultipleObjectiveCViewController.m */; };
116126DD1C260F67003B4E43 /* DataDetectorsSwiftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116126DC1C260F67003B4E43 /* DataDetectorsSwiftViewController.swift */; };
Expand Down Expand Up @@ -53,6 +55,9 @@
110D6E4C1C25FEC6004E8551 /* SimpleObjectiveCViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleObjectiveCViewController.h; sourceTree = "<group>"; };
110D6E4D1C25FEC6004E8551 /* SimpleObjectiveCViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimpleObjectiveCViewController.m; sourceTree = "<group>"; };
110D6E4F1C25FF5C004E8551 /* RootExampleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootExampleCell.swift; sourceTree = "<group>"; };
11110E431C27543300E67ACD /* LongPressSwiftViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LongPressSwiftViewController.swift; sourceTree = "<group>"; };
11110E451C2762A900E67ACD /* LongPressObjectiveCViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LongPressObjectiveCViewController.h; sourceTree = "<group>"; };
11110E461C2762A900E67ACD /* LongPressObjectiveCViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LongPressObjectiveCViewController.m; sourceTree = "<group>"; };
116126D01C26094A003B4E43 /* MultipleSwiftViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleSwiftViewController.swift; sourceTree = "<group>"; };
116126D21C260961003B4E43 /* MultipleObjectiveCViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipleObjectiveCViewController.h; sourceTree = "<group>"; };
116126D31C260961003B4E43 /* MultipleObjectiveCViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipleObjectiveCViewController.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -136,6 +141,16 @@
name = "Simple string";
sourceTree = "<group>";
};
11110E421C27541700E67ACD /* Long press */ = {
isa = PBXGroup;
children = (
11110E431C27543300E67ACD /* LongPressSwiftViewController.swift */,
11110E451C2762A900E67ACD /* LongPressObjectiveCViewController.h */,
11110E461C2762A900E67ACD /* LongPressObjectiveCViewController.m */,
);
name = "Long press";
sourceTree = "<group>";
};
116126CF1C26092B003B4E43 /* Multiple links */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -224,6 +239,7 @@
116126CF1C26092B003B4E43 /* Multiple links */,
116126DB1C260F55003B4E43 /* Data Detectors */,
116126E11C2612A3003B4E43 /* Interface Builder */,
11110E421C27541700E67ACD /* Long press */,
);
name = "Example for ZSWTappableLabel";
path = ZSWTappableLabel;
Expand Down Expand Up @@ -477,8 +493,10 @@
110D6E4E1C25FEC6004E8551 /* SimpleObjectiveCViewController.m in Sources */,
110D6E501C25FF5C004E8551 /* RootExampleCell.swift in Sources */,
116126DD1C260F67003B4E43 /* DataDetectorsSwiftViewController.swift in Sources */,
11110E441C27543300E67ACD /* LongPressSwiftViewController.swift in Sources */,
110D6E471C25FC33004E8551 /* AppDelegate.swift in Sources */,
110D6E451C25FC04004E8551 /* RootController.swift in Sources */,
11110E471C2762A900E67ACD /* LongPressObjectiveCViewController.m in Sources */,
116126D41C260961003B4E43 /* MultipleObjectiveCViewController.m in Sources */,
116126E41C2612BD003B4E43 /* InterfaceBuilderSwiftViewController.swift in Sources */,
116126E01C260F74003B4E43 /* DataDetectorsObjectiveCViewController.m in Sources */,
Expand Down
10 changes: 10 additions & 0 deletions Example/ZSWTappableLabel/InterfaceBuilderSwiftViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ import SafariServices
class InterfaceBuilderSwiftViewController: UIViewController, ZSWTappableLabelTapDelegate {
@IBOutlet weak var label: ZSWTappableLabel!

// for iOS 8
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: "InterfaceBuilderSwiftViewController", bundle: nil)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// end for iOS 8

override func viewDidLoad() {
super.viewDidLoad()

Expand Down
13 changes: 13 additions & 0 deletions Example/ZSWTappableLabel/LongPressObjectiveCViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// LongPressObjectiveCViewController.h
// ZSWTappableLabel
//
// Created by Zachary West on 12/20/15.
// Copyright © 2015 Zachary West. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface LongPressObjectiveCViewController : UIViewController

@end
95 changes: 95 additions & 0 deletions Example/ZSWTappableLabel/LongPressObjectiveCViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// LongPressObjectiveCViewController.m
// ZSWTappableLabel
//
// Created by Zachary West on 12/20/15.
// Copyright © 2015 Zachary West. All rights reserved.
//

#import "LongPressObjectiveCViewController.h"

@import Masonry;
@import ZSWTappableLabel;
@import ZSWTaggedString;
@import SafariServices;

static NSString *const URLAttributeName = @"URL";

@interface LongPressObjectiveCViewController() <ZSWTappableLabelTapDelegate, ZSWTappableLabelLongPressDelegate>
@property (nonatomic) ZSWTappableLabel *label;
@end

@implementation LongPressObjectiveCViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.view.backgroundColor = [UIColor whiteColor];

self.label = ^{
ZSWTappableLabel *label = [[ZSWTappableLabel alloc] init];
label.textAlignment = NSTextAlignmentJustified;
label.tapDelegate = self;
label.longPressDelegate = self;
label.longPressAccessibilityActionName = NSLocalizedString(@"Share", nil);
return label;
}();

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(@"Please, feel free to peruse and enjoy our wonderful and alluring <link type='privacy'>Privacy Policy</link> or if you'd really like to understand what you're allowed or not allowed to do, reading our <link type='tos'>Terms of Service</link> is sure to be enlightening", nil);
self.label.attributedText = [[ZSWTaggedString stringWithString:string] attributedStringWithOptions:options];

[self.view addSubview:self.label];
[self.label mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
}

#pragma mark - ZSWTappableLabelTapDelegate

- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel tappedAtIndex:(NSInteger)idx withAttributes:(NSDictionary<NSString *,id> *)attributes {
NSURL *URL = attributes[URLAttributeName];
if ([URL isKindOfClass:[NSURL class]]) {
if ([SFSafariViewController class] != nil) {
[self showViewController:[[SFSafariViewController alloc] initWithURL:URL] sender:self];
} else {
[[UIApplication sharedApplication] openURL:URL];
}
}
}

#pragma mark - ZSWTappableLabelLongPressDelegate

- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel longPressedAtIndex:(NSInteger)idx withAttributes:(NSDictionary<NSString *,id> *)attributes {
NSURL *URL = attributes[URLAttributeName];
if ([URL isKindOfClass:[NSURL class]]) {
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[ URL ] applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
}

@end
95 changes: 95 additions & 0 deletions Example/ZSWTappableLabel/LongPressSwiftViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// LongPressSwiftViewController.swift
// ZSWTappableLabel
//
// Created by Zachary West on 12/20/15.
// Copyright © 2015 Zachary West. All rights reserved.
//

import ZSWTappableLabel
import ZSWTaggedString
import SafariServices

class LongPressSwiftViewController: UIViewController, ZSWTappableLabelTapDelegate, ZSWTappableLabelLongPressDelegate {
let label: ZSWTappableLabel = {
let label = ZSWTappableLabel()
label.textAlignment = .Justified
return label
}()

static let URLAttributeName = "URL"

enum LinkType: String {
case Privacy = "privacy"
case TermsOfService = "tos"

var URL: NSURL {
switch self {
case .Privacy:
return NSURL(string: "http://google.com/search?q=privacy")!
case .TermsOfService:
return NSURL(string: "http://google.com/search?q=tos")!
}
}
}

override func viewDidLoad() {
super.viewDidLoad()

view.backgroundColor = UIColor.whiteColor()

label.tapDelegate = self
label.longPressDelegate = self
label.longPressAccessibilityActionName = NSLocalizedString("Share", comment: "")

let options = ZSWTaggedStringOptions()
options["link"] = .Dynamic({ tagName, tagAttributes, stringAttributes in
guard let typeString = tagAttributes["type"] as? String,
let type = LinkType(rawValue: typeString) else {
return [String: AnyObject]()
}

return [
ZSWTappableLabelTappableRegionAttributeName: true,
ZSWTappableLabelHighlightedBackgroundAttributeName: UIColor.lightGrayColor(),
ZSWTappableLabelHighlightedForegroundAttributeName: UIColor.whiteColor(),
NSForegroundColorAttributeName: UIColor.blueColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue,
MultipleSwiftViewController.URLAttributeName: type.URL
]
})

let string = NSLocalizedString("Please, feel free to peruse and enjoy our wonderful and alluring <link type='privacy'>Privacy Policy</link> or if you'd really like to understand what you're allowed or not allowed to do, reading our <link type='tos'>Terms of Service</link> is sure to be enlightening", comment: "")
label.attributedText = try? ZSWTaggedString(string: string).attributedStringWithOptions(options)

view.addSubview(label)
label.snp_makeConstraints { make in
make.edges.equalTo(view)
}
}

// MARK: - ZSWTappableLabelTapDelegate

func tappableLabel(tappableLabel: ZSWTappableLabel, tappedAtIndex idx: Int, withAttributes attributes: [String : AnyObject]) {
guard let URL = attributes[SimpleSwiftViewController.URLAttributeName] as? NSURL else {
return
}

if #available(iOS 9, *) {
showViewController(SFSafariViewController(URL: URL), sender: self)
} else {
UIApplication.sharedApplication().openURL(URL)
}
}

// MARK: - ZSWTappableLabelLongPressDelegate

func tappableLabel(tappableLabel: ZSWTappableLabel, longPressedAtIndex idx: Int, withAttributes attributes: [String : AnyObject]) {
guard let URL = attributes[SimpleSwiftViewController.URLAttributeName] as? NSURL else {
return
}

let activityController = UIActivityViewController(activityItems: [URL], applicationActivities: nil)
presentViewController(activityController, animated: true, completion: nil)
}
}
6 changes: 6 additions & 0 deletions Example/ZSWTappableLabel/RootController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class RootController: UIViewController, UITableViewDelegate, UITableViewDataSour
return InterfaceBuilderObjectiveCViewController()
}))

examples.append(ExampleRow(name: "Long press", body: "Long press triggers an Activity View Controller, tapping opens the link.", constructorSwift: { () -> UIViewController in
return LongPressSwiftViewController()
}, constructorObjectiveC: { () -> UIViewController in
return LongPressObjectiveCViewController()
}))

self.examples = examples

super.init(nibName: nil, bundle: nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
#import "MultipleObjectiveCViewController.h"
#import "DataDetectorsObjectiveCViewController.h"
#import "InterfaceBuilderObjectiveCViewController.h"
#import "LongPressObjectiveCViewController.h"
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![License](https://img.shields.io/cocoapods/l/ZSWTappableLabel.svg?style=flat)](http://cocoapods.org/pods/ZSWTappableLabel)
[![Platform](https://img.shields.io/cocoapods/p/ZSWTappableLabel.svg?style=flat)](http://cocoapods.org/pods/ZSWTappableLabel)

ZSWTappableLabel is a `UILabel` subclass powered by NSAttributedStrings which allows you to tap on certain regions, with optional highlight behavior. It does not draw text itself and executes a minimal amount of code unless the user is interacting with a tappable region.
ZSWTappableLabel is a `UILabel` subclass powered by NSAttributedStrings which allows you to tap or long-press on certain regions, with optional highlight behavior. It does not draw text itself and executes a minimal amount of code unless the user is interacting with a tappable region.

## A basic, tappable link

Expand Down Expand Up @@ -65,6 +65,35 @@ func tappableLabel(tappableLabel: ZSWTappableLabel, tappedAtIndex idx: Int, with
}
```

## Long-presses

You may optionally support long-presses by setting a `longPressDelegate` on the label. This behaves very similarly to the `tapDelegate`:

```swift
func tappableLabel(tappableLabel: ZSWTappableLabel, longPressedAtIndex idx: Int, withAttributes attributes: [String : AnyObject]) {
guard let URL = attributes["URL"] as? NSURL else {
return
}

let activityController = UIActivityViewController(activityItems: [URL], applicationActivities: nil)
presentViewController(activityController, animated: true, completion: nil)
}
```

```objectivec
- (void)tappableLabel:(ZSWTappableLabel *)tappableLabel
longPressedAtIndex:(NSInteger)idx
withAttributes:(NSDictionary<NSString *,id> *)attributes {
NSURL *URL = attributes[URLAttributeName];
if ([URL isKindOfClass:[NSURL class]]) {
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[ URL ] applicationActivities:nil];
[self presentViewController:activityController animated:YES completion:nil];
}
}
```

You can configure the `longPressDuration` for how long until a long-press is recognized. This defaults to 0.5 seconds.

## Data detectors

Let's use `NSDataDetector` to find the substrings in a given string that we might want to turn into links:
Expand Down Expand Up @@ -245,6 +274,8 @@ ZSWTappableLabel is an accessibility container, which exposes the substrings in
1. ` or ` (static text)
1. `Terms of Service` (link)
When 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.
## Interaction with gesture recognizers
ZSWTappableLabel uses gesture recognizers internally and works well with other gesture recognizers:
Expand All @@ -260,7 +291,7 @@ For example, if you place a UITapGestureRecognizer on the label, it will only fi
ZSWTappableLabel is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile:
```ruby
pod "ZSWTappableLabel", "~> 1.2"
pod "ZSWTappableLabel", "~> 1.3"
```

## License
Expand Down
4 changes: 2 additions & 2 deletions ZSWTappableLabel.podspec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Pod::Spec.new do |s|
s.name = "ZSWTappableLabel"
s.version = "1.2"
s.version = "1.3"
s.summary = "UILabel subclass in which substrings links can be tapped"
s.description = <<-DESC
NSAttributedStrings presented in ZSWTappableLabel can be tapped
NSAttributedStrings presented in ZSWTappableLabel can be tapped or long-pressed
in subranges you specify using attributes.
Read more: https://github.com/zacwest/ZSWTappableLabel
DESC
Expand Down
Loading

0 comments on commit 4369f39

Please sign in to comment.