ZSWTappableLabel.h 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // This file is based on third party code, see below for the original author
  2. // and original license.
  3. // Modifications are (c) by Threema GmbH and licensed under the AGPLv3.
  4. //
  5. // ZSWTappableLabel.h
  6. // ZSWTappableLabel
  7. //
  8. // Copyright (c) 2019 Zachary West. All rights reserved.
  9. //
  10. // MIT License
  11. // https://github.com/zacwest/ZSWTappableLabel
  12. //
  13. #import <UIKit/UIKit.h>
  14. /***** BEGIN THREEMA MODIFICATION: Import TTTAttributedLabel *********/
  15. #import "TTTAttributedLabel.h"
  16. /***** END THREEMA MODIFICATION: Import TTTAttributedLabel *********/
  17. @class ZSWTappableLabel;
  18. NS_ASSUME_NONNULL_BEGIN
  19. #pragma mark - Attributes you include in strings
  20. /*!
  21. * @brief Highlight the background color behind when selected
  22. *
  23. * Value is a UIColor. When a touch event occurs within this range, the attribute \a NSBackgroundColorAttributeName is
  24. * applied to the tappable region.
  25. */
  26. extern NSAttributedStringKey const ZSWTappableLabelHighlightedBackgroundAttributeName
  27. NS_SWIFT_NAME(tappableHighlightedBackgroundColor);
  28. /*!
  29. * @brief Highlight the text color when selected
  30. *
  31. * Value is a UIColor. When a touch event occurs within this range, the attribute \a NSForegroundColorAttributeName is
  32. * applied to the tappable region.
  33. */
  34. extern NSAttributedStringKey const ZSWTappableLabelHighlightedForegroundAttributeName
  35. NS_SWIFT_NAME(tappableHighlightedForegroundColor);
  36. /*!
  37. * @brief A highlighted region - enables interaction
  38. *
  39. * Value is an NSNumber (BOOL). If the location of a touch has this attribute, the \a tapDelegate will be invoked.
  40. */
  41. extern NSAttributedStringKey const ZSWTappableLabelTappableRegionAttributeName
  42. NS_SWIFT_NAME(tappableRegion);
  43. #pragma mark - Delegates
  44. /**
  45. * @brief Delegate for handling taps
  46. */
  47. @protocol ZSWTappableLabelTapDelegate
  48. /*!
  49. * @brief A tap was completed
  50. *
  51. * @param tappableLabel The label
  52. * @param idx The string index closest to the touch
  53. * @param attributes The attributes from the attributed string at the given index
  54. *
  55. * This method is only invoked if \a ZSWTappableLabelTappableRegionAttributeName is specified in the attributes
  56. * under the touch.
  57. */
  58. - (void)tappableLabel:(ZSWTappableLabel *)tappableLabel
  59. tappedAtIndex:(NSInteger)idx
  60. withAttributes:(NSDictionary<NSAttributedStringKey, id> *)attributes;
  61. @end
  62. /**
  63. * @brief Delegate for handling long presses
  64. */
  65. @protocol ZSWTappableLabelLongPressDelegate
  66. /*!
  67. * @brief A long press was completed
  68. *
  69. * @param tappableLabel The label
  70. * @param idx The string index closest to the touch
  71. * @param attributes The attributes from the attributed string at the given index
  72. *
  73. * This method is only invoked if \a ZSWTappableLabelTappableRegionAttributeName is specified in the attributes
  74. * under the touch.
  75. *
  76. * If the user presses and holds at one spot for at least \a longPressDuration seconds, this delegate method
  77. * will be invoked.
  78. *
  79. * It may also be invoked by users with accessibility enabled. You should set \a longPressAccessibilityActionName
  80. * to give your users a better description.
  81. */
  82. - (void)tappableLabel:(ZSWTappableLabel *)tappableLabel
  83. longPressedAtIndex:(NSInteger)idx
  84. withAttributes:(NSDictionary<NSAttributedStringKey, id> *)attributes;
  85. @end
  86. /**
  87. * @brief Delegate for handling accessibility
  88. */
  89. @protocol ZSWTappableLabelAccessibilityDelegate
  90. /*!
  91. * @brief Provide custom actions for a given element
  92. *
  93. * @param tappableLabel The label
  94. * @param characterRange The range of characters represented by an element
  95. * @param attributes The attributes from the attributed string for the first location of the range
  96. *
  97. * Any returned actions will be included after (in the array) a long-press action, if \a longPressDelegate is set.
  98. *
  99. * @note Only the attributes for the first character of the string are included since the attributes can vary
  100. * over the substring.
  101. *
  102. * @return The additional elements to include, or empty array to include none
  103. */
  104. - (NSArray<UIAccessibilityCustomAction *> *)tappableLabel:(ZSWTappableLabel *)tappableLabel
  105. accessibilityCustomActionsForCharacterRange:(NSRange)characterRange
  106. withAttributesAtStart:(NSDictionary<NSAttributedStringKey, id> *)attributes;
  107. @end
  108. #pragma mark - Data protocols
  109. /**
  110. * @brief Protocol given in response to requests to the label
  111. *
  112. * You do not need to implement this protocol to use the label.
  113. * See the -tappableRegionInfo... methods on ZSWTappableLabel.
  114. */
  115. @protocol ZSWTappableLabelTappableRegionInfo
  116. /*!
  117. * @brief The frame of the tappable region in the label's coordinate space
  118. *
  119. * If you are setting this as the sourceRect for the previewingContext of a 3D Touch event
  120. * you will need to convert it to the sourceView's coordinate space, for example:
  121. *
  122. * previewingContext.sourceRect = previewingContext.sourceView.convert(regionInfo.frame, from: label)
  123. *
  124. * in Swift, or in Objective-C:
  125. *
  126. * previewingContext.sourceRect = [previewingContext.sourceView convertRect:regionInfo.frame fromView:self.label];
  127. *
  128. * Since this is easy to get wrong, see \a -configurePreviewingContext:
  129. */
  130. @property (nonatomic, readonly) CGRect frame;
  131. /*!
  132. * @brief The attributed string attributes at the point requested
  133. */
  134. @property (nonatomic, readonly) NSDictionary<NSAttributedStringKey, id> *attributes;
  135. /*!
  136. * @brief Convenience method for 3D Touch
  137. *
  138. * Configures the previewing context with the correct frame information for this tappable region info.
  139. */
  140. - (void)configurePreviewingContext:(id<UIViewControllerPreviewing>)previewingContext
  141. NS_SWIFT_NAME(configure(previewingContext:));
  142. @end
  143. #pragma mark - Primary class
  144. /***** BEGIN THREEMA MODIFICATION: Subclass from TTTAttributedLabel *********/
  145. @interface ZSWTappableLabel : TTTAttributedLabel
  146. /***** END THREEMA MODIFICATION: Subclass from TTTAttributedLabel *********/
  147. #pragma mark - Configuration
  148. /**
  149. * @brief Delegate for handling taps
  150. * @see ZSWTappableLabelTapDelegate
  151. */
  152. @property (nullable, nonatomic, weak) IBOutlet id<ZSWTappableLabelTapDelegate> tapDelegate;
  153. /**
  154. * @brief Delegate for handling long presses
  155. *
  156. * You should also configure \a longPressAccessibilityActionName.
  157. * You are also able to configure \a longPressDuration, though the default value should be sufficient.
  158. *
  159. * @see ZSWTappableLabelLongPressDelegate
  160. */
  161. @property (nullable, nonatomic, weak) IBOutlet id<ZSWTappableLabelLongPressDelegate> longPressDelegate;
  162. /**
  163. * @brief Delegate for handling accessibility actions
  164. * @see ZSWTappableLabelAccessibilityDelegate
  165. */
  166. @property (nullable, nonatomic, weak) IBOutlet id<ZSWTappableLabelAccessibilityDelegate> accessibilityDelegate;
  167. /*!
  168. * @brief Long press duration
  169. *
  170. * How long, in seconds, the user must long press without lifting before the touch should be recognized as a long press.
  171. *
  172. * If you do not set a \a longPressDelegate, a long press does not occur.
  173. *
  174. * This defaults to 0.5 seconds.
  175. */
  176. @property (nonatomic) NSTimeInterval longPressDuration;
  177. /*!
  178. * @brief Accessibility label for long press
  179. *
  180. * Your users will be read this localized string when they choose to dig into the custom actions a link has.
  181. *
  182. * If you do not set a \a longPressDelegate, this action is not included.
  183. *
  184. * This defaults to 'Open Menu', but localization will not occur as this library currently has no translations.
  185. */
  186. @property (null_resettable, nonatomic, copy) IBInspectable NSString *longPressAccessibilityActionName;
  187. #pragma mark - Getting information
  188. /*!
  189. * @brief Get the tappable region info at a point
  190. *
  191. * @param point The point in the label's coordinate space to look for a tappable region
  192. *
  193. * This is particularly useful if you need to inspect the label's current regions, for example you are responding
  194. * to a 3D Touch previewing event and all you know is the point the event is occurring at.
  195. *
  196. * It is very important that you convert to the label's coordinate space when asking for this point information.
  197. * Because of this, a convenience method is available as \a -tappableRegionInfoForPreviewingContext:location:
  198. *
  199. * Without the convenience method, for example, from a 3D Touch previewing event you would use:
  200. *
  201. * @code
  202. * label.convert(location, from: previewingContext.sourceView)
  203. * @endcode
  204. *
  205. * in Swift, or in Objective-C:
  206. *
  207. * @code
  208. * [self.label convertPoint:location fromView:previewingContext.sourceView]
  209. * @endcode
  210. *
  211. * @return The region information if a region is at the point, nil otherwise
  212. *
  213. * See \a ZSWTappableLabelTappableRegionInfo for the information returned
  214. */
  215. - (nullable id<ZSWTappableLabelTappableRegionInfo>)tappableRegionInfoAtPoint:(CGPoint)point;
  216. /*!
  217. * @brief Convenience method to get tappable region for 3D Touch
  218. *
  219. * Since the point/hierarchy management is easy to get confused on, this method is provided to turn a 3D Touch
  220. * previewing context into a tappable region info.
  221. *
  222. * You may also find the \a ZSWTappableLabelTappableRegionInfo convenience method for configuring a preview
  223. * context to be useful.
  224. *
  225. * @return The region information for the preview context if there's a region underneath, nil otherwise
  226. *
  227. * See \a ZSWTappableLabelTappableRegionInfo for the information returned
  228. */
  229. - (nullable id<ZSWTappableLabelTappableRegionInfo>)
  230. tappableRegionInfoForPreviewingContext:(id<UIViewControllerPreviewing>)previewingContext location:(CGPoint)location;
  231. - (NSDictionary *)checkIsPointAction:(CGPoint)longPressPoint;
  232. @end
  233. NS_ASSUME_NONNULL_END