ZSWTappableLabelTouchHandling.m 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. //
  2. // ZSWTappableLabelTouchHandling.m
  3. // ZSWTappableLabel
  4. //
  5. // Copyright (c) 2019 Zachary West. All rights reserved.
  6. //
  7. // MIT License
  8. // https://github.com/zacwest/ZSWTappableLabel
  9. //
  10. #import "ZSWTappableLabelTouchHandling.h"
  11. #import "../ZSWTappableLabel.h"
  12. @interface ZSWTappableLabelTouchHandling()
  13. @property (nonatomic, readwrite) NSAttributedString *unmodifiedAttributedString;
  14. @property (nonatomic, readwrite) NSTextStorage *textStorage;
  15. @property (nonatomic, readwrite) NSLayoutManager *layoutManager;
  16. @property (nonatomic, readwrite) NSTextContainer *textContainer;
  17. @property (nonatomic, readwrite) CGPoint pointOffset;
  18. @property (nonatomic, readwrite) CGRect bounds;
  19. @end
  20. @implementation ZSWTappableLabelTouchHandling
  21. - (instancetype)initWithTextStorage:(NSTextStorage *)textStorage pointOffset:(CGPoint)pointOffset bounds:(CGRect)bounds {
  22. if ((self = [super init])) {
  23. self.unmodifiedAttributedString = [[NSAttributedString alloc] initWithAttributedString:textStorage];
  24. self.textStorage = textStorage;
  25. self.layoutManager = textStorage.layoutManagers.lastObject;
  26. self.textContainer = textStorage.layoutManagers.lastObject.textContainers.lastObject;
  27. self.bounds = bounds;
  28. self.pointOffset = pointOffset;
  29. }
  30. return self;
  31. }
  32. - (NSUInteger)characterIndexAtPoint:(CGPoint)point {
  33. point.x -= self.pointOffset.x;
  34. point.y -= self.pointOffset.y;
  35. CGFloat fractionOfDistanceBetween;
  36. NSUInteger characterIdx = [self.layoutManager characterIndexForPoint:point
  37. inTextContainer:self.textContainer
  38. fractionOfDistanceBetweenInsertionPoints:&fractionOfDistanceBetween];
  39. characterIdx = MIN(self.textStorage.length - 1, characterIdx + fractionOfDistanceBetween);
  40. NSRange glyphRange = [self.layoutManager glyphRangeForCharacterRange:NSMakeRange(characterIdx, 1) actualCharacterRange:NULL];
  41. CGRect glyphRect = [self.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:self.textContainer];
  42. // plus some padding to make it easier in some cases
  43. glyphRect = CGRectInset(glyphRect, -10, -10);
  44. if (!CGRectContainsPoint(glyphRect, point)) {
  45. characterIdx = NSNotFound;
  46. }
  47. return characterIdx;
  48. }
  49. - (BOOL)isTappableRegionAtPoint:(CGPoint)point {
  50. return [self isTappableRegionAtCharacterIndex:[self characterIndexAtPoint:point]];
  51. }
  52. - (BOOL)isTappableRegionAtCharacterIndex:(NSUInteger)characterIdx {
  53. if (characterIdx == NSNotFound) {
  54. return NO;
  55. }
  56. NSNumber *attribute = [self.textStorage attribute:ZSWTappableLabelTappableRegionAttributeName
  57. atIndex:characterIdx
  58. effectiveRange:NULL];
  59. return [attribute boolValue];
  60. }
  61. - (CGRect)frameForCharacterRange:(NSRange)characterRange {
  62. NSRange glyphRange = [self.layoutManager glyphRangeForCharacterRange:characterRange actualCharacterRange:NULL];
  63. CGRect viewFrame = [self.layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:self.textContainer];
  64. viewFrame.origin.x += self.pointOffset.x;
  65. viewFrame.origin.y += self.pointOffset.y;
  66. return viewFrame;
  67. }
  68. @end