diff --git a/CCHLinkTextView Example/CCHLinkTextView Example.xcodeproj/project.pbxproj b/CCHLinkTextView Example/CCHLinkTextView Example.xcodeproj/project.pbxproj index a19b880..2173ed2 100644 --- a/CCHLinkTextView Example/CCHLinkTextView Example.xcodeproj/project.pbxproj +++ b/CCHLinkTextView Example/CCHLinkTextView Example.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* 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 */; }; @@ -41,9 +39,6 @@ 623AC49418C11BAD000962A0 /* CCHLinkTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkTextView.m; sourceTree = ""; }; 623AC49618C11EAA000962A0 /* CCHLinkTextViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CCHLinkTextViewDelegate.h; sourceTree = ""; }; 626F837818C75FBC004FEABB /* CCHLinkTextViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkTextViewTests.m; sourceTree = ""; }; - 629350BD18C8841B000BFBA5 /* CCHLinkGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CCHLinkGestureRecognizer.h; sourceTree = ""; }; - 629350BE18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkGestureRecognizer.m; sourceTree = ""; }; - 629350C018C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCHLinkGestureRecognizerTests.m; sourceTree = ""; }; 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; }; @@ -94,8 +89,6 @@ 623AC49318C11BAD000962A0 /* CCHLinkTextView.h */, 623AC49418C11BAD000962A0 /* CCHLinkTextView.m */, 623AC49618C11EAA000962A0 /* CCHLinkTextViewDelegate.h */, - 629350BD18C8841B000BFBA5 /* CCHLinkGestureRecognizer.h */, - 629350BE18C8841B000BFBA5 /* CCHLinkGestureRecognizer.m */, ); name = CCHLinkTextView; path = ../CCHLinkTextView; @@ -161,7 +154,6 @@ isa = PBXGroup; children = ( 626F837818C75FBC004FEABB /* CCHLinkTextViewTests.m */, - 629350C018C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m */, 62F55CE618C1180200A7E1CC /* Supporting Files */, ); path = "CCHLinkTextView ExampleTests"; @@ -276,7 +268,6 @@ 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; @@ -285,7 +276,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 629350C118C89619000BFBA5 /* CCHLinkGestureRecognizerTests.m in Sources */, 626F837918C75FBC004FEABB /* CCHLinkTextViewTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CCHLinkTextView Example/CCHLinkTextView Example/Base.lproj/Main.storyboard b/CCHLinkTextView Example/CCHLinkTextView Example/Base.lproj/Main.storyboard index b2d33fc..56a63c6 100644 --- a/CCHLinkTextView Example/CCHLinkTextView Example/Base.lproj/Main.storyboard +++ b/CCHLinkTextView Example/CCHLinkTextView Example/Base.lproj/Main.storyboard @@ -15,15 +15,15 @@ - - + + - + - + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. @@ -43,7 +43,7 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/CCHLinkTextView Example/CCHLinkTextView Example/ViewController.m b/CCHLinkTextView Example/CCHLinkTextView Example/ViewController.m index 71ee97f..3b6790e 100644 --- a/CCHLinkTextView Example/CCHLinkTextView Example/ViewController.m +++ b/CCHLinkTextView Example/CCHLinkTextView Example/ViewController.m @@ -28,7 +28,7 @@ // Data detectors self.storyboardTextView.editable = NO; -// self.storyboardTextView.selectable = NO; + self.storyboardTextView.selectable = NO; [self.storyboardTextView addLinkForRange:NSMakeRange(0, 10)]; [self.storyboardTextView addLinkForRange:NSMakeRange(100, 5)]; @@ -52,14 +52,25 @@ return NO; } -- (void)linkTextViewDidTap:(CCHLinkTextView *)linkTextView -{ - [self performSegueWithIdentifier:@"tableViewToDetail" sender:self]; -} - - (void)linkTextView:(CCHLinkTextView *)linkTextView didTapLinkAtCharacterIndex:(NSUInteger)characterIndex { NSLog(@"Link tapped"); } +- (void)linkTextViewDidTap:(CCHLinkTextView *)linkTextView +{ + NSLog(@"Tap"); + [self performSegueWithIdentifier:@"tableViewToDetail" sender:self]; +} + +- (void)linkTextView:(CCHLinkTextView *)linkTextView didLongPressLinkAtCharacterIndex:(NSUInteger)characterIndex +{ + NSLog(@"Link long pressed"); +} + +- (void)linkTextViewDidLongPress:(CCHLinkTextView *)linkTextView +{ + NSLog(@"Long press"); +} + @end diff --git a/CCHLinkTextView Example/CCHLinkTextView ExampleTests/CCHLinkGestureRecognizerTests.m b/CCHLinkTextView Example/CCHLinkTextView ExampleTests/CCHLinkGestureRecognizerTests.m deleted file mode 100644 index bc63436..0000000 --- a/CCHLinkTextView Example/CCHLinkTextView ExampleTests/CCHLinkGestureRecognizerTests.m +++ /dev/null @@ -1,44 +0,0 @@ -// -// 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 -#import - -@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 diff --git a/CCHLinkTextView/CCHLinkGestureRecognizer.h b/CCHLinkTextView/CCHLinkGestureRecognizer.h deleted file mode 100644 index 64e1714..0000000 --- a/CCHLinkTextView/CCHLinkGestureRecognizer.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// CCHLinkGestureRecognizer.h -// CCHLinkTextView Example -// -// Created by Hoefele, Claus on 06.03.14. -// Copyright (c) 2014 Claus Höfele. All rights reserved. -// - -#import - -/** - 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 diff --git a/CCHLinkTextView/CCHLinkGestureRecognizer.m b/CCHLinkTextView/CCHLinkGestureRecognizer.m deleted file mode 100644 index 095087a..0000000 --- a/CCHLinkTextView/CCHLinkGestureRecognizer.m +++ /dev/null @@ -1,123 +0,0 @@ -// -// 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 - -#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 diff --git a/CCHLinkTextView/CCHLinkTextView.h b/CCHLinkTextView/CCHLinkTextView.h index 033d862..0df5ef8 100644 --- a/CCHLinkTextView/CCHLinkTextView.h +++ b/CCHLinkTextView/CCHLinkTextView.h @@ -9,12 +9,10 @@ #import @protocol CCHLinkTextViewDelegate; -@class CCHLinkGestureRecognizer; @interface CCHLinkTextView : UITextView @property (nonatomic, weak) id linkDelegate; -@property (nonatomic, strong, readonly) CCHLinkGestureRecognizer *linkGestureRecognizer; - (void)addLinkForRange:(NSRange)range; - (BOOL)enumerateLinkRangesIncludingCharacterIndex:(NSUInteger)characterIndex usingBlock:(void (^)(NSRange range))block; diff --git a/CCHLinkTextView/CCHLinkTextView.m b/CCHLinkTextView/CCHLinkTextView.m index 071baa3..6d991e2 100644 --- a/CCHLinkTextView/CCHLinkTextView.m +++ b/CCHLinkTextView/CCHLinkTextView.m @@ -11,7 +11,6 @@ #import "CCHLinkTextView.h" #import "CCHLinkTextViewDelegate.h" -#import "CCHLinkGestureRecognizer.h" // Use subclass of UITextViewDelegate // Replace linkRanges with NSLinkAttribute attributes @@ -19,7 +18,6 @@ @interface CCHLinkTextView () @property (nonatomic, strong) NSMutableArray *linkRanges; -@property (nonatomic, strong) CCHLinkGestureRecognizer *linkGestureRecognizer; @end @@ -42,14 +40,20 @@ - (void)setUp { self.linkRanges = [NSMutableArray array]; + + UILongPressGestureRecognizer *touchUpDownGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(touchUpDown:)]; + touchUpDownGestureRecognizer.minimumPressDuration = 0; + touchUpDownGestureRecognizer.delegate = self; + [self addGestureRecognizer:touchUpDownGestureRecognizer]; + + UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; + longPressGestureRecognizer.delegate = self; + [self addGestureRecognizer:longPressGestureRecognizer]; - self.linkGestureRecognizer = [[CCHLinkGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; - self.linkGestureRecognizer.delegate = self; -// self.linkGestureRecognizer.longPressEnabled = NO; - [self addGestureRecognizer:self.linkGestureRecognizer]; - -// UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; -// [self addGestureRecognizer:tapGestureRecognizer]; + UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)]; + tapGestureRecognizer.delegate = self; + [tapGestureRecognizer requireGestureRecognizerToFail:longPressGestureRecognizer]; + [self addGestureRecognizer:tapGestureRecognizer]; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer @@ -57,25 +61,64 @@ return YES; } -- (void)textTapped:(CCHLinkGestureRecognizer *)recognizer +- (void)touchUpDown:(UIGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateBegan) { - NSLog(@"touch down"); + NSUInteger characterIndex = [self characterIndexForGestureRecognizer:recognizer]; + [self didTouchDownAtCharacterIndex:characterIndex]; } 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]; -// } + NSUInteger characterIndex = [self characterIndexForGestureRecognizer:recognizer]; + [self didTouchUpAtCharacterIndex:characterIndex]; + } +} + +- (void)didTouchDownAtCharacterIndex:(NSUInteger)characterIndex +{ +} + +- (void)didTouchUpAtCharacterIndex:(NSUInteger)characterIndex +{ +} + +- (void)longPress:(UIGestureRecognizer *)recognizer +{ + if (recognizer.state == UIGestureRecognizerStateBegan) { + NSUInteger characterIndex = [self characterIndexForGestureRecognizer:recognizer]; + BOOL linkFound = [self enumerateLinkRangesIncludingCharacterIndex:characterIndex usingBlock:^(NSRange range) { + [self didLongPressLinkAtCharacterIndex:characterIndex range:range]; + }]; + + if (!linkFound) { + [self linkTextViewDidLongPress]; + } + } +} + +- (void)didLongPressLinkAtCharacterIndex:(NSUInteger)characterIndex range:(NSRange)range +{ + if ([self.linkDelegate respondsToSelector:@selector(linkTextView:didLongPressLinkAtCharacterIndex:)]) { + [self.linkDelegate linkTextView:self didLongPressLinkAtCharacterIndex:characterIndex]; + } +} + +- (void)linkTextViewDidLongPress +{ + if ([self.linkDelegate respondsToSelector:@selector(linkTextViewDidLongPress:)]) { + [self.linkDelegate linkTextViewDidLongPress:self]; + } +} + +- (void)tap:(UIGestureRecognizer *)recognizer +{ + if (recognizer.state == UIGestureRecognizerStateEnded) { + NSUInteger characterIndex = [self characterIndexForGestureRecognizer:recognizer]; + BOOL linkFound = [self enumerateLinkRangesIncludingCharacterIndex:characterIndex usingBlock:^(NSRange range) { + [self didTapLinkAtCharacterIndex:characterIndex range:range]; + }]; + + if (!linkFound) { + [self linkTextViewDidTap]; + } } } @@ -93,6 +136,33 @@ } } +- (void)textTapped:(UIGestureRecognizer *)recognizer +{ + if (recognizer.state == UIGestureRecognizerStateBegan) { + NSLog(@"touch down"); + } else if (recognizer.state == UIGestureRecognizerStateEnded) { + NSLog(@"touch up"); + NSUInteger characterIndex = [self characterIndexForGestureRecognizer:recognizer]; + BOOL linkFound = [self enumerateLinkRangesIncludingCharacterIndex:characterIndex usingBlock:^(NSRange range) { + [self didTapLinkAtCharacterIndex:characterIndex range:range]; + }]; + + if (!linkFound) { + [self linkTextViewDidTap]; + } + } +} + +- (NSUInteger)characterIndexForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer +{ + CGPoint location = [gestureRecognizer locationInView:self]; + location.x -= self.textContainerInset.left; + location.y -= self.textContainerInset.top; + + NSUInteger characterIndex = [self.layoutManager characterIndexForPoint:location inTextContainer:self.textContainer fractionOfDistanceBetweenInsertionPoints:NULL]; + return characterIndex; +} + - (void)addLinkForRange:(NSRange)range { [self.linkRanges addObject:[NSValue valueWithRange:range]]; diff --git a/CCHLinkTextView/CCHLinkTextViewDelegate.h b/CCHLinkTextView/CCHLinkTextViewDelegate.h index bb5f741..2d2d9bb 100644 --- a/CCHLinkTextView/CCHLinkTextViewDelegate.h +++ b/CCHLinkTextView/CCHLinkTextViewDelegate.h @@ -13,5 +13,7 @@ @optional - (void)linkTextView:(CCHLinkTextView *)linkTextView didTapLinkAtCharacterIndex:(NSUInteger)characterIndex; - (void)linkTextViewDidTap:(CCHLinkTextView *)linkTextView; +- (void)linkTextView:(CCHLinkTextView *)linkTextView didLongPressLinkAtCharacterIndex:(NSUInteger)characterIndex; +- (void)linkTextViewDidLongPress:(CCHLinkTextView *)linkTextView; @end