Handle long and short taps in custom gesture recognizer

This commit is contained in:
Claus Höfele
2014-03-06 19:30:42 +01:00
parent a6af3e48b9
commit ab61fed1ec
7 changed files with 233 additions and 23 deletions

View File

@@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
623AC49518C11BAD000962A0 /* CCHLinkTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 623AC49418C11BAD000962A0 /* CCHLinkTextView.m */; };
626F837918C75FBC004FEABB /* CCHLinkTextViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 626F837818C75FBC004FEABB /* CCHLinkTextViewTests.m */; };
629350BF18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 629350BE18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m */; };
629350C118C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 629350C018C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m */; };
62F55CC118C1180200A7E1CC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62F55CC018C1180200A7E1CC /* Foundation.framework */; };
62F55CC318C1180200A7E1CC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62F55CC218C1180200A7E1CC /* CoreGraphics.framework */; };
62F55CC518C1180200A7E1CC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62F55CC418C1180200A7E1CC /* UIKit.framework */; };
@@ -39,6 +41,9 @@
623AC49418C11BAD000962A0 /* CCHLinkTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkTextView.m; sourceTree = "<group>"; };
623AC49618C11EAA000962A0 /* CCHLinkTextViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CCHLinkTextViewDelegate.h; sourceTree = "<group>"; };
626F837818C75FBC004FEABB /* CCHLinkTextViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkTextViewTests.m; sourceTree = "<group>"; };
629350BD18C8841B000BFBA5 /* CCHLinkGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCHLinkGestureRecognizer.h; sourceTree = "<group>"; };
629350BE18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkGestureRecognizer.m; sourceTree = "<group>"; };
629350C018C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkGestureRecognizerTests.m; sourceTree = "<group>"; };
62F55CBD18C1180200A7E1CC /* CCHLinkTextView Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CCHLinkTextView Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
62F55CC018C1180200A7E1CC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
62F55CC218C1180200A7E1CC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -89,6 +94,8 @@
623AC49318C11BAD000962A0 /* CCHLinkTextView.h */,
623AC49418C11BAD000962A0 /* CCHLinkTextView.m */,
623AC49618C11EAA000962A0 /* CCHLinkTextViewDelegate.h */,
629350BD18C8841B000BFBA5 /* CCHLinkGestureRecognizer.h */,
629350BE18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m */,
);
name = CCHLinkTextView;
path = ../CCHLinkTextView;
@@ -154,6 +161,7 @@
isa = PBXGroup;
children = (
626F837818C75FBC004FEABB /* CCHLinkTextViewTests.m */,
629350C018C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m */,
62F55CE618C1180200A7E1CC /* Supporting Files */,
);
path = "CCHLinkTextView ExampleTests";
@@ -268,6 +276,7 @@
623AC49518C11BAD000962A0 /* CCHLinkTextView.m in Sources */,
62F55CD718C1180200A7E1CC /* ViewController.m in Sources */,
62F55CD118C1180200A7E1CC /* AppDelegate.m in Sources */,
629350BF18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m in Sources */,
62F55CCD18C1180200A7E1CC /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -276,6 +285,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
629350C118C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m in Sources */,
626F837918C75FBC004FEABB /* CCHLinkTextViewTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -23,6 +23,10 @@
{
[super viewDidLoad];
// selecteable = YES + copy & paste
// NSLinkAttribute -> textViewDelegate
// Data detectors
self.storyboardTextView.editable = NO;
self.storyboardTextView.selectable = NO;

View File

@@ -0,0 +1,44 @@
//
// CCHLinkGestureRecognizerTests.m
// CCHLinkTextView Example
//
// Created by Hoefele, Claus on 06.03.14.
// Copyright (c) 2014 Claus Höfele. All rights reserved.
//
#import "CCHLinkGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import <XCTest/XCTest.h>
@interface CCHLinkGestureRecognizerTests : XCTestCase
@property (nonatomic, strong) CCHLinkGestureRecognizer *linkGestureRecognizer;
@end
@implementation CCHLinkGestureRecognizerTests
- (void)setUp
{
[super setUp];
self.linkGestureRecognizer = [[CCHLinkGestureRecognizer alloc] init];
}
- (void)testStateEnded
{
UITouch *touch = [[UITouch alloc] init];
NSSet *touches = [NSSet setWithObject:touch];
[self.linkGestureRecognizer touchesBegan:touches withEvent:nil];
XCTAssertEqual(self.linkGestureRecognizer.state, UIGestureRecognizerStateBegan);
[self.linkGestureRecognizer touchesMoved:touches withEvent:nil];
XCTAssertEqual(self.linkGestureRecognizer.state, UIGestureRecognizerStateBegan);
[self.linkGestureRecognizer touchesEnded:touches withEvent:nil];
XCTAssertEqual(self.linkGestureRecognizer.state, UIGestureRecognizerStateEnded);
}
@end

View File

@@ -0,0 +1,22 @@
//
// CCHLinkGestureRecognizer.h
// CCHLinkTextView Example
//
// Created by Hoefele, Claus on 06.03.14.
// Copyright (c) 2014 Claus Höfele. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
A discreet gesture recognizer that sends action messages for touch down
(UIGestureRecognizerStateBegan), touch up for a tap (UIGestureRecognizerStateRecognized,
isLongPress == NO), and touch up for a long press (UIGestureRecognizerStateRecognized, isLongPress == NO).
*/
@interface CCHLinkGestureRecognizer : UIGestureRecognizer
@property (nonatomic, assign) CFTimeInterval minimumPressDuration;
@property (nonatomic, assign, readonly, getter = isLongPress) BOOL longPress;
@property (nonatomic, assign, getter = isLongPressEnabled) BOOL longPressEnabled;
@end

View File

@@ -0,0 +1,123 @@
//
// CCHLinkGestureRecognizer.m
// CCHLinkTextView Example
//
// Created by Hoefele, Claus on 06.03.14.
// Copyright (c) 2014 Claus Höfele. All rights reserved.
//
#import "CCHLinkGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#define MAX_SQUARED_DISTANCE (10 * 10)
@interface CCHLinkGestureRecognizer ()
@property (nonatomic, assign) CGPoint initialPoint;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign, getter = isLongPress) BOOL longPress;
@end
// disable long tap
@implementation CCHLinkGestureRecognizer
- (id)init
{
self = [super init];
if (self) {
[self setUp];
}
return self;
}
- (id)initWithTarget:(id)target action:(SEL)action
{
self = [super initWithTarget:target action:action];
if (self) {
[self setUp];
}
return self;
}
- (void)setUp
{
self.minimumPressDuration = 0.3;
self.longPressEnabled = YES;
}
- (void)reset
{
[super reset];
self.longPress = NO;
self.initialPoint = CGPointZero;
[self.timer invalidate];
self.timer = nil;
}
- (void)longPressed:(NSTimer *)timer
{
[timer invalidate];
if (self.isLongPressEnabled) {
self.longPress = YES;
self.state = UIGestureRecognizerStateRecognized;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
UITouch *touch = touches.anyObject;
self.initialPoint = [touch locationInView:self.view];
self.state = UIGestureRecognizerStateBegan;
self.longPress = NO;
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.minimumPressDuration target:self selector:@selector(longPressed:) userInfo:nil repeats:NO];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (![self touchIsCloseToInitialPoint:touches.anyObject]) {
self.state = UIGestureRecognizerStateFailed;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if ([self touchIsCloseToInitialPoint:touches.anyObject]) {
self.state = UIGestureRecognizerStateRecognized;
} else {
self.state = UIGestureRecognizerStateFailed;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
self.state = UIGestureRecognizerStateFailed;
}
- (BOOL)touchIsCloseToInitialPoint:(UITouch *)touch
{
CGPoint point = [touch locationInView:self.view];
CGFloat xDistance = (self.initialPoint.x - point.x);
CGFloat yDistance = (self.initialPoint.y - point.y);
CGFloat squaredDistance = (xDistance * xDistance) + (yDistance * yDistance);
BOOL isClose = (squaredDistance <= MAX_SQUARED_DISTANCE);
return isClose;
}
@end

View File

@@ -9,10 +9,12 @@
#import <UIKit/UIKit.h>
@protocol CCHLinkTextViewDelegate;
@class CCHLinkGestureRecognizer;
@interface CCHLinkTextView : UITextView
@property (nonatomic, weak) id<CCHLinkTextViewDelegate> linkDelegate;
@property (nonatomic, strong, readonly) CCHLinkGestureRecognizer *linkGestureRecognizer;
- (void)addLinkForRange:(NSRange)range;
- (BOOL)enumerateLinkRangesIncludingCharacterIndex:(NSUInteger)characterIndex usingBlock:(void (^)(NSRange range))block;

View File

@@ -11,18 +11,15 @@
#import "CCHLinkTextView.h"
#import "CCHLinkTextViewDelegate.h"
#import "CCHLinkGestureRecognizer.h"
// NSLinkAttributeName, linkTextAttributes + UITextViewDelegate - (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
// setAutomaticLinkDetectionEnabled
// TTTAttributedLabel / OH...
// TweetLabel
// http://flyosity.com/mac-os-x/clickable-tweet-links-hashtags-usernames-in-a-custom-nstextview.php
// http://shapeof.com/archives/2010/12/customizing_links_in_an_nstextview.html
// http://stackoverflow.com/questions/15628133/uitapgesturerecognizer-make-it-work-on-touch-down-not-touch-up
// Use subclass of UITextViewDelegate
// Replace linkRanges with NSLinkAttribute attributes
@interface CCHLinkTextView ()
@property (nonatomic, strong) NSMutableArray *linkRanges;
@property (nonatomic, strong) CCHLinkGestureRecognizer *linkGestureRecognizer;
@end
@@ -46,25 +43,33 @@
{
self.linkRanges = [NSMutableArray array];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
[self addGestureRecognizer:tapGestureRecognizer];
self.linkGestureRecognizer = [[CCHLinkGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
// self.linkGestureRecognizer.longPressEnabled = NO;
[self addGestureRecognizer:self.linkGestureRecognizer];
// UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)];
// [self addGestureRecognizer:tapGestureRecognizer];
}
- (void)textTapped:(UITapGestureRecognizer *)recognizer
- (void)textTapped:(CCHLinkGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint location = [recognizer locationInView:self];
location.x -= self.textContainerInset.left;
location.y -= self.textContainerInset.top;
NSUInteger characterIndex = [self.layoutManager characterIndexForPoint:location inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
BOOL linkTapped = [self enumerateLinkRangesIncludingCharacterIndex:characterIndex usingBlock:^(NSRange range) {
[self didTapLinkAtCharacterIndex:characterIndex range:range];
}];
if (!linkTapped) {
[self linkTextViewDidTap];
}
if (recognizer.state == UIGestureRecognizerStateBegan) {
NSLog(@"touch down");
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// NSLog(@"touch up");
NSLog(@"touch up %@", recognizer.isLongPress ? @"Y" : @"N");
// CGPoint location = [recognizer locationInView:self];
// location.x -= self.textContainerInset.left;
// location.y -= self.textContainerInset.top;
//
// NSUInteger characterIndex = [self.layoutManager characterIndexForPoint:location inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:NULL];
// BOOL linkTapped = [self enumerateLinkRangesIncludingCharacterIndex:characterIndex usingBlock:^(NSRange range) {
// [self didTapLinkAtCharacterIndex:characterIndex range:range];
// }];
//
// if (!linkTapped) {
// [self linkTextViewDidTap];
// }
}
}