YYLabel.m 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306
  1. //
  2. // YYLabel.m
  3. // YYText <https://github.com/ibireme/YYText>
  4. //
  5. // Created by ibireme on 15/2/25.
  6. // Copyright (c) 2015 ibireme.
  7. //
  8. // This source code is licensed under the MIT-style license found in the
  9. // LICENSE file in the root directory of this source tree.
  10. //
  11. #import "YYLabel.h"
  12. #import "YYTextAsyncLayer.h"
  13. #import "YYTextWeakProxy.h"
  14. #import "YYTextUtilities.h"
  15. #import "NSAttributedString+YYText.h"
  16. #import <libkern/OSAtomic.h>
  17. static dispatch_queue_t YYLabelGetReleaseQueue() {
  18. return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
  19. }
  20. #define kLongPressMinimumDuration 0.5 // Time in seconds the fingers must be held down for long press gesture.
  21. #define kLongPressAllowableMovement 9.0 // Maximum movement in points allowed before the long press fails.
  22. #define kHighlightFadeDuration 0.15 // Time in seconds for highlight fadeout animation.
  23. #define kAsyncFadeDuration 0.08 // Time in seconds for async display fadeout animation.
  24. @interface YYLabel() <YYTextDebugTarget, YYTextAsyncLayerDelegate> {
  25. NSMutableAttributedString *_innerText; ///< nonnull
  26. YYTextLayout *_innerLayout;
  27. YYTextContainer *_innerContainer; ///< nonnull
  28. NSMutableArray *_attachmentViews;
  29. NSMutableArray *_attachmentLayers;
  30. NSRange _highlightRange; ///< current highlight range
  31. YYTextHighlight *_highlight; ///< highlight attribute in `_highlightRange`
  32. YYTextLayout *_highlightLayout; ///< when _state.showingHighlight=YES, this layout should be displayed
  33. YYTextLayout *_shrinkInnerLayout;
  34. YYTextLayout *_shrinkHighlightLayout;
  35. NSTimer *_longPressTimer;
  36. CGPoint _touchBeganPoint;
  37. struct {
  38. unsigned int layoutNeedUpdate : 1;
  39. unsigned int showingHighlight : 1;
  40. unsigned int trackingTouch : 1;
  41. unsigned int swallowTouch : 1;
  42. unsigned int touchMoved : 1;
  43. unsigned int hasTapAction : 1;
  44. unsigned int hasLongPressAction : 1;
  45. unsigned int contentsNeedFade : 1;
  46. } _state;
  47. }
  48. @end
  49. @implementation YYLabel
  50. #pragma mark - Private
  51. - (void)_updateIfNeeded {
  52. if (_state.layoutNeedUpdate) {
  53. _state.layoutNeedUpdate = NO;
  54. [self _updateLayout];
  55. [self.layer setNeedsDisplay];
  56. }
  57. }
  58. - (void)_updateLayout {
  59. _innerLayout = [YYTextLayout layoutWithContainer:_innerContainer text:_innerText];
  60. _shrinkInnerLayout = [YYLabel _shrinkLayoutWithLayout:_innerLayout];
  61. }
  62. - (void)_setLayoutNeedUpdate {
  63. _state.layoutNeedUpdate = YES;
  64. [self _clearInnerLayout];
  65. [self _setLayoutNeedRedraw];
  66. }
  67. - (void)_setLayoutNeedRedraw {
  68. [self.layer setNeedsDisplay];
  69. }
  70. - (void)_clearInnerLayout {
  71. if (!_innerLayout) return;
  72. YYTextLayout *layout = _innerLayout;
  73. _innerLayout = nil;
  74. _shrinkInnerLayout = nil;
  75. dispatch_async(YYLabelGetReleaseQueue(), ^{
  76. NSAttributedString *text = [layout text]; // capture to block and release in background
  77. if (layout.attachments.count) {
  78. dispatch_async(dispatch_get_main_queue(), ^{
  79. [text length]; // capture to block and release in main thread (maybe there's UIView/CALayer attachments).
  80. });
  81. }
  82. });
  83. }
  84. - (YYTextLayout *)_innerLayout {
  85. return _shrinkInnerLayout ? _shrinkInnerLayout : _innerLayout;
  86. }
  87. - (YYTextLayout *)_highlightLayout {
  88. return _shrinkHighlightLayout ? _shrinkHighlightLayout : _highlightLayout;
  89. }
  90. + (YYTextLayout *)_shrinkLayoutWithLayout:(YYTextLayout *)layout {
  91. if (layout.text.length && layout.lines.count == 0) {
  92. YYTextContainer *container = layout.container.copy;
  93. container.maximumNumberOfRows = 1;
  94. CGSize containerSize = container.size;
  95. if (!container.verticalForm) {
  96. containerSize.height = YYTextContainerMaxSize.height;
  97. } else {
  98. containerSize.width = YYTextContainerMaxSize.width;
  99. }
  100. container.size = containerSize;
  101. return [YYTextLayout layoutWithContainer:container text:layout.text];
  102. } else {
  103. return nil;
  104. }
  105. }
  106. - (void)_startLongPressTimer {
  107. [_longPressTimer invalidate];
  108. _longPressTimer = [NSTimer timerWithTimeInterval:kLongPressMinimumDuration
  109. target:[YYTextWeakProxy proxyWithTarget:self]
  110. selector:@selector(_trackDidLongPress)
  111. userInfo:nil
  112. repeats:NO];
  113. [[NSRunLoop currentRunLoop] addTimer:_longPressTimer forMode:NSRunLoopCommonModes];
  114. }
  115. - (void)_endLongPressTimer {
  116. [_longPressTimer invalidate];
  117. _longPressTimer = nil;
  118. }
  119. - (void)_trackDidLongPress {
  120. [self _endLongPressTimer];
  121. if (_state.hasLongPressAction && _textLongPressAction) {
  122. NSRange range = NSMakeRange(NSNotFound, 0);
  123. CGRect rect = CGRectNull;
  124. CGPoint point = [self _convertPointToLayout:_touchBeganPoint];
  125. YYTextRange *textRange = [self._innerLayout textRangeAtPoint:point];
  126. CGRect textRect = [self._innerLayout rectForRange:textRange];
  127. textRect = [self _convertRectFromLayout:textRect];
  128. if (textRange) {
  129. range = textRange.asRange;
  130. rect = textRect;
  131. }
  132. _textLongPressAction(self, _innerText, range, rect);
  133. }
  134. if (_highlight) {
  135. YYTextAction longPressAction = _highlight.longPressAction ? _highlight.longPressAction : _highlightLongPressAction;
  136. if (longPressAction) {
  137. YYTextPosition *start = [YYTextPosition positionWithOffset:_highlightRange.location];
  138. YYTextPosition *end = [YYTextPosition positionWithOffset:_highlightRange.location + _highlightRange.length affinity:YYTextAffinityBackward];
  139. YYTextRange *range = [YYTextRange rangeWithStart:start end:end];
  140. CGRect rect = [self._innerLayout rectForRange:range];
  141. rect = [self _convertRectFromLayout:rect];
  142. longPressAction(self, _innerText, _highlightRange, rect);
  143. [self _removeHighlightAnimated:YES];
  144. _state.trackingTouch = NO;
  145. }
  146. }
  147. }
  148. - (YYTextHighlight *)_getHighlightAtPoint:(CGPoint)point range:(NSRangePointer)range {
  149. if (!self._innerLayout.containsHighlight) return nil;
  150. point = [self _convertPointToLayout:point];
  151. YYTextRange *textRange = [self._innerLayout textRangeAtPoint:point];
  152. if (!textRange) return nil;
  153. NSUInteger startIndex = textRange.start.offset;
  154. if (startIndex == _innerText.length) {
  155. if (startIndex > 0) {
  156. startIndex--;
  157. }
  158. }
  159. NSRange highlightRange = {0};
  160. YYTextHighlight *highlight = [_innerText attribute:YYTextHighlightAttributeName
  161. atIndex:startIndex
  162. longestEffectiveRange:&highlightRange
  163. inRange:NSMakeRange(0, _innerText.length)];
  164. if (!highlight) return nil;
  165. if (range) *range = highlightRange;
  166. return highlight;
  167. }
  168. - (void)_showHighlightAnimated:(BOOL)animated {
  169. if (!_highlight) return;
  170. if (!_highlightLayout) {
  171. NSMutableAttributedString *hiText = _innerText.mutableCopy;
  172. NSDictionary *newAttrs = _highlight.attributes;
  173. [newAttrs enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
  174. [hiText yy_setAttribute:key value:value range:_highlightRange];
  175. }];
  176. _highlightLayout = [YYTextLayout layoutWithContainer:_innerContainer text:hiText];
  177. _shrinkHighlightLayout = [YYLabel _shrinkLayoutWithLayout:_highlightLayout];
  178. if (!_highlightLayout) _highlight = nil;
  179. }
  180. if (_highlightLayout && !_state.showingHighlight) {
  181. _state.showingHighlight = YES;
  182. _state.contentsNeedFade = animated;
  183. [self _setLayoutNeedRedraw];
  184. }
  185. }
  186. - (void)_hideHighlightAnimated:(BOOL)animated {
  187. if (_state.showingHighlight) {
  188. _state.showingHighlight = NO;
  189. _state.contentsNeedFade = animated;
  190. [self _setLayoutNeedRedraw];
  191. }
  192. }
  193. - (void)_removeHighlightAnimated:(BOOL)animated {
  194. [self _hideHighlightAnimated:animated];
  195. _highlight = nil;
  196. _highlightLayout = nil;
  197. _shrinkHighlightLayout = nil;
  198. }
  199. - (void)_endTouch {
  200. [self _endLongPressTimer];
  201. [self _removeHighlightAnimated:YES];
  202. _state.trackingTouch = NO;
  203. }
  204. - (CGPoint)_convertPointToLayout:(CGPoint)point {
  205. CGSize boundingSize = self._innerLayout.textBoundingSize;
  206. if (self._innerLayout.container.isVerticalForm) {
  207. CGFloat w = self._innerLayout.textBoundingSize.width;
  208. if (w < self.bounds.size.width) w = self.bounds.size.width;
  209. point.x += self._innerLayout.container.size.width - w;
  210. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  211. point.x += (self.bounds.size.width - boundingSize.width) * 0.5;
  212. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  213. point.x += (self.bounds.size.width - boundingSize.width);
  214. }
  215. return point;
  216. } else {
  217. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  218. point.y -= (self.bounds.size.height - boundingSize.height) * 0.5;
  219. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  220. point.y -= (self.bounds.size.height - boundingSize.height);
  221. }
  222. return point;
  223. }
  224. }
  225. - (CGPoint)_convertPointFromLayout:(CGPoint)point {
  226. CGSize boundingSize = self._innerLayout.textBoundingSize;
  227. if (self._innerLayout.container.isVerticalForm) {
  228. CGFloat w = self._innerLayout.textBoundingSize.width;
  229. if (w < self.bounds.size.width) w = self.bounds.size.width;
  230. point.x -= self._innerLayout.container.size.width - w;
  231. if (boundingSize.width < self.bounds.size.width) {
  232. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  233. point.x -= (self.bounds.size.width - boundingSize.width) * 0.5;
  234. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  235. point.x -= (self.bounds.size.width - boundingSize.width);
  236. }
  237. }
  238. return point;
  239. } else {
  240. if (boundingSize.height < self.bounds.size.height) {
  241. if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
  242. point.y += (self.bounds.size.height - boundingSize.height) * 0.5;
  243. } else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
  244. point.y += (self.bounds.size.height - boundingSize.height);
  245. }
  246. }
  247. return point;
  248. }
  249. }
  250. - (CGRect)_convertRectToLayout:(CGRect)rect {
  251. rect.origin = [self _convertPointToLayout:rect.origin];
  252. return rect;
  253. }
  254. - (CGRect)_convertRectFromLayout:(CGRect)rect {
  255. rect.origin = [self _convertPointFromLayout:rect.origin];
  256. return rect;
  257. }
  258. - (UIFont *)_defaultFont {
  259. return [UIFont systemFontOfSize:17];
  260. }
  261. - (NSShadow *)_shadowFromProperties {
  262. if (!_shadowColor || _shadowBlurRadius < 0) return nil;
  263. NSShadow *shadow = [NSShadow new];
  264. shadow.shadowColor = _shadowColor;
  265. #if !TARGET_INTERFACE_BUILDER
  266. shadow.shadowOffset = _shadowOffset;
  267. #else
  268. shadow.shadowOffset = CGSizeMake(_shadowOffset.x, _shadowOffset.y);
  269. #endif
  270. shadow.shadowBlurRadius = _shadowBlurRadius;
  271. return shadow;
  272. }
  273. - (void)_updateOuterLineBreakMode {
  274. if (_innerContainer.truncationType) {
  275. switch (_innerContainer.truncationType) {
  276. case YYTextTruncationTypeStart: {
  277. _lineBreakMode = NSLineBreakByTruncatingHead;
  278. } break;
  279. case YYTextTruncationTypeEnd: {
  280. _lineBreakMode = NSLineBreakByTruncatingTail;
  281. } break;
  282. case YYTextTruncationTypeMiddle: {
  283. _lineBreakMode = NSLineBreakByTruncatingMiddle;
  284. } break;
  285. default:break;
  286. }
  287. } else {
  288. _lineBreakMode = _innerText.yy_lineBreakMode;
  289. }
  290. }
  291. - (void)_updateOuterTextProperties {
  292. _text = [_innerText yy_plainTextForRange:NSMakeRange(0, _innerText.length)];
  293. _font = _innerText.yy_font;
  294. if (!_font) _font = [self _defaultFont];
  295. _textColor = _innerText.yy_color;
  296. if (!_textColor) _textColor = [UIColor blackColor];
  297. _textAlignment = _innerText.yy_alignment;
  298. _lineBreakMode = _innerText.yy_lineBreakMode;
  299. NSShadow *shadow = _innerText.yy_shadow;
  300. _shadowColor = shadow.shadowColor;
  301. #if !TARGET_INTERFACE_BUILDER
  302. _shadowOffset = shadow.shadowOffset;
  303. #else
  304. _shadowOffset = CGPointMake(shadow.shadowOffset.width, shadow.shadowOffset.height);
  305. #endif
  306. _shadowBlurRadius = shadow.shadowBlurRadius;
  307. _attributedText = _innerText;
  308. [self _updateOuterLineBreakMode];
  309. }
  310. - (void)_updateOuterContainerProperties {
  311. _truncationToken = _innerContainer.truncationToken;
  312. _numberOfLines = _innerContainer.maximumNumberOfRows;
  313. _textContainerPath = _innerContainer.path;
  314. _exclusionPaths = _innerContainer.exclusionPaths;
  315. _textContainerInset = _innerContainer.insets;
  316. _verticalForm = _innerContainer.verticalForm;
  317. _linePositionModifier = _innerContainer.linePositionModifier;
  318. [self _updateOuterLineBreakMode];
  319. }
  320. - (void)_clearContents {
  321. CGImageRef image = (__bridge_retained CGImageRef)(self.layer.contents);
  322. self.layer.contents = nil;
  323. if (image) {
  324. dispatch_async(YYLabelGetReleaseQueue(), ^{
  325. CFRelease(image);
  326. });
  327. }
  328. }
  329. - (void)_initLabel {
  330. ((YYTextAsyncLayer *)self.layer).displaysAsynchronously = NO;
  331. self.layer.contentsScale = [UIScreen mainScreen].scale;
  332. self.contentMode = UIViewContentModeRedraw;
  333. _attachmentViews = [NSMutableArray new];
  334. _attachmentLayers = [NSMutableArray new];
  335. _debugOption = [YYTextDebugOption sharedDebugOption];
  336. [YYTextDebugOption addDebugTarget:self];
  337. _font = [self _defaultFont];
  338. _textColor = [UIColor blackColor];
  339. _textVerticalAlignment = YYTextVerticalAlignmentCenter;
  340. _numberOfLines = 1;
  341. _textAlignment = NSTextAlignmentNatural;
  342. _lineBreakMode = NSLineBreakByTruncatingTail;
  343. _innerText = [NSMutableAttributedString new];
  344. _innerContainer = [YYTextContainer new];
  345. _innerContainer.truncationType = YYTextTruncationTypeEnd;
  346. _innerContainer.maximumNumberOfRows = _numberOfLines;
  347. _clearContentsBeforeAsynchronouslyDisplay = YES;
  348. _fadeOnAsynchronouslyDisplay = YES;
  349. _fadeOnHighlight = YES;
  350. self.isAccessibilityElement = YES;
  351. }
  352. #pragma mark - Override
  353. - (instancetype)initWithFrame:(CGRect)frame {
  354. self = [super initWithFrame:CGRectZero];
  355. if (!self) return nil;
  356. self.backgroundColor = [UIColor clearColor];
  357. self.opaque = NO;
  358. [self _initLabel];
  359. self.frame = frame;
  360. return self;
  361. }
  362. - (void)dealloc {
  363. [YYTextDebugOption removeDebugTarget:self];
  364. [_longPressTimer invalidate];
  365. }
  366. + (Class)layerClass {
  367. return [YYTextAsyncLayer class];
  368. }
  369. - (void)setFrame:(CGRect)frame {
  370. CGSize oldSize = self.bounds.size;
  371. [super setFrame:frame];
  372. CGSize newSize = self.bounds.size;
  373. if (!CGSizeEqualToSize(oldSize, newSize)) {
  374. _innerContainer.size = self.bounds.size;
  375. if (!_ignoreCommonProperties) {
  376. _state.layoutNeedUpdate = YES;
  377. }
  378. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  379. [self _clearContents];
  380. }
  381. [self _setLayoutNeedRedraw];
  382. }
  383. }
  384. - (void)setBounds:(CGRect)bounds {
  385. CGSize oldSize = self.bounds.size;
  386. [super setBounds:bounds];
  387. CGSize newSize = self.bounds.size;
  388. if (!CGSizeEqualToSize(oldSize, newSize)) {
  389. _innerContainer.size = self.bounds.size;
  390. if (!_ignoreCommonProperties) {
  391. _state.layoutNeedUpdate = YES;
  392. }
  393. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  394. [self _clearContents];
  395. }
  396. [self _setLayoutNeedRedraw];
  397. }
  398. }
  399. - (CGSize)sizeThatFits:(CGSize)size {
  400. if (_ignoreCommonProperties) {
  401. return _innerLayout.textBoundingSize;
  402. }
  403. if (!_verticalForm && size.width <= 0) size.width = YYTextContainerMaxSize.width;
  404. if (_verticalForm && size.height <= 0) size.height = YYTextContainerMaxSize.height;
  405. if ((!_verticalForm && size.width == self.bounds.size.width) ||
  406. (_verticalForm && size.height == self.bounds.size.height)) {
  407. [self _updateIfNeeded];
  408. YYTextLayout *layout = self._innerLayout;
  409. BOOL contains = NO;
  410. if (layout.container.maximumNumberOfRows == 0) {
  411. if (layout.truncatedLine == nil) {
  412. contains = YES;
  413. }
  414. } else {
  415. if (layout.rowCount <= layout.container.maximumNumberOfRows) {
  416. contains = YES;
  417. }
  418. }
  419. if (contains) {
  420. return layout.textBoundingSize;
  421. }
  422. }
  423. if (!_verticalForm) {
  424. size.height = YYTextContainerMaxSize.height;
  425. } else {
  426. size.width = YYTextContainerMaxSize.width;
  427. }
  428. YYTextContainer *container = [_innerContainer copy];
  429. container.size = size;
  430. YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];
  431. return layout.textBoundingSize;
  432. }
  433. - (NSString *)accessibilityLabel {
  434. return [_innerLayout.text yy_plainTextForRange:_innerLayout.text.yy_rangeOfAll];
  435. }
  436. #pragma mark - NSCoding
  437. - (void)encodeWithCoder:(NSCoder *)aCoder {
  438. [super encodeWithCoder:aCoder];
  439. [aCoder encodeObject:_attributedText forKey:@"attributedText"];
  440. [aCoder encodeObject:_innerContainer forKey:@"innerContainer"];
  441. }
  442. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  443. self = [super initWithCoder:aDecoder];
  444. [self _initLabel];
  445. YYTextContainer *innerContainer = [aDecoder decodeObjectForKey:@"innerContainer"];
  446. if (innerContainer) {
  447. _innerContainer = innerContainer;
  448. } else {
  449. _innerContainer.size = self.bounds.size;
  450. }
  451. [self _updateOuterContainerProperties];
  452. self.attributedText = [aDecoder decodeObjectForKey:@"attributedText"];
  453. [self _setLayoutNeedUpdate];
  454. return self;
  455. }
  456. #pragma mark - Touches
  457. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  458. [self _updateIfNeeded];
  459. UITouch *touch = touches.anyObject;
  460. CGPoint point = [touch locationInView:self];
  461. _highlight = [self _getHighlightAtPoint:point range:&_highlightRange];
  462. _highlightLayout = nil;
  463. _shrinkHighlightLayout = nil;
  464. _state.hasTapAction = _textTapAction != nil;
  465. _state.hasLongPressAction = _textLongPressAction != nil;
  466. if (_highlight || _textTapAction || _textLongPressAction) {
  467. _touchBeganPoint = point;
  468. _state.trackingTouch = YES;
  469. _state.swallowTouch = YES;
  470. _state.touchMoved = NO;
  471. [self _startLongPressTimer];
  472. if (_highlight) [self _showHighlightAnimated:NO];
  473. } else {
  474. _state.trackingTouch = NO;
  475. _state.swallowTouch = NO;
  476. _state.touchMoved = NO;
  477. }
  478. if (!_state.swallowTouch) {
  479. [super touchesBegan:touches withEvent:event];
  480. }
  481. }
  482. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  483. [self _updateIfNeeded];
  484. UITouch *touch = touches.anyObject;
  485. CGPoint point = [touch locationInView:self];
  486. if (_state.trackingTouch) {
  487. if (!_state.touchMoved) {
  488. CGFloat moveH = point.x - _touchBeganPoint.x;
  489. CGFloat moveV = point.y - _touchBeganPoint.y;
  490. if (fabs(moveH) > fabs(moveV)) {
  491. if (fabs(moveH) > kLongPressAllowableMovement) _state.touchMoved = YES;
  492. } else {
  493. if (fabs(moveV) > kLongPressAllowableMovement) _state.touchMoved = YES;
  494. }
  495. if (_state.touchMoved) {
  496. [self _endLongPressTimer];
  497. }
  498. }
  499. if (_state.touchMoved && _highlight) {
  500. YYTextHighlight *highlight = [self _getHighlightAtPoint:point range:NULL];
  501. if (highlight == _highlight) {
  502. [self _showHighlightAnimated:_fadeOnHighlight];
  503. } else {
  504. [self _hideHighlightAnimated:_fadeOnHighlight];
  505. }
  506. }
  507. }
  508. if (!_state.swallowTouch) {
  509. [super touchesMoved:touches withEvent:event];
  510. }
  511. }
  512. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  513. UITouch *touch = touches.anyObject;
  514. CGPoint point = [touch locationInView:self];
  515. if (_state.trackingTouch) {
  516. [self _endLongPressTimer];
  517. if (!_state.touchMoved && _textTapAction) {
  518. NSRange range = NSMakeRange(NSNotFound, 0);
  519. CGRect rect = CGRectNull;
  520. CGPoint point = [self _convertPointToLayout:_touchBeganPoint];
  521. YYTextRange *textRange = [self._innerLayout textRangeAtPoint:point];
  522. CGRect textRect = [self._innerLayout rectForRange:textRange];
  523. textRect = [self _convertRectFromLayout:textRect];
  524. if (textRange) {
  525. range = textRange.asRange;
  526. rect = textRect;
  527. }
  528. _textTapAction(self, _innerText, range, rect);
  529. }
  530. if (_highlight) {
  531. if (!_state.touchMoved || [self _getHighlightAtPoint:point range:NULL] == _highlight) {
  532. YYTextAction tapAction = _highlight.tapAction ? _highlight.tapAction : _highlightTapAction;
  533. if (tapAction) {
  534. YYTextPosition *start = [YYTextPosition positionWithOffset:_highlightRange.location];
  535. YYTextPosition *end = [YYTextPosition positionWithOffset:_highlightRange.location + _highlightRange.length affinity:YYTextAffinityBackward];
  536. YYTextRange *range = [YYTextRange rangeWithStart:start end:end];
  537. CGRect rect = [self._innerLayout rectForRange:range];
  538. rect = [self _convertRectFromLayout:rect];
  539. tapAction(self, _innerText, _highlightRange, rect);
  540. }
  541. }
  542. [self _removeHighlightAnimated:_fadeOnHighlight];
  543. }
  544. }
  545. if (!_state.swallowTouch) {
  546. [super touchesEnded:touches withEvent:event];
  547. }
  548. }
  549. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  550. [self _endTouch];
  551. if (!_state.swallowTouch) [super touchesCancelled:touches withEvent:event];
  552. }
  553. #pragma mark - Properties
  554. - (void)setText:(NSString *)text {
  555. if (_text == text || [_text isEqualToString:text]) return;
  556. _text = text.copy;
  557. BOOL needAddAttributes = _innerText.length == 0 && text.length > 0;
  558. [_innerText replaceCharactersInRange:NSMakeRange(0, _innerText.length) withString:text ? text : @""];
  559. [_innerText yy_removeDiscontinuousAttributesInRange:NSMakeRange(0, _innerText.length)];
  560. if (needAddAttributes) {
  561. _innerText.yy_font = _font;
  562. _innerText.yy_color = _textColor;
  563. _innerText.yy_shadow = [self _shadowFromProperties];
  564. _innerText.yy_alignment = _textAlignment;
  565. switch (_lineBreakMode) {
  566. case NSLineBreakByWordWrapping:
  567. case NSLineBreakByCharWrapping:
  568. case NSLineBreakByClipping: {
  569. _innerText.yy_lineBreakMode = _lineBreakMode;
  570. } break;
  571. case NSLineBreakByTruncatingHead:
  572. case NSLineBreakByTruncatingTail:
  573. case NSLineBreakByTruncatingMiddle: {
  574. _innerText.yy_lineBreakMode = NSLineBreakByWordWrapping;
  575. } break;
  576. default: break;
  577. }
  578. }
  579. if ([_textParser parseText:_innerText selectedRange:NULL]) {
  580. [self _updateOuterTextProperties];
  581. }
  582. if (!_ignoreCommonProperties) {
  583. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  584. [self _clearContents];
  585. }
  586. [self _setLayoutNeedUpdate];
  587. [self _endTouch];
  588. [self invalidateIntrinsicContentSize];
  589. }
  590. }
  591. - (void)setFont:(UIFont *)font {
  592. if (!font) {
  593. font = [self _defaultFont];
  594. }
  595. if (_font == font || [_font isEqual:font]) return;
  596. _font = font;
  597. _innerText.yy_font = _font;
  598. if (_innerText.length && !_ignoreCommonProperties) {
  599. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  600. [self _clearContents];
  601. }
  602. [self _setLayoutNeedUpdate];
  603. [self _endTouch];
  604. [self invalidateIntrinsicContentSize];
  605. }
  606. }
  607. - (void)setTextColor:(UIColor *)textColor {
  608. if (!textColor) {
  609. textColor = [UIColor blackColor];
  610. }
  611. if (_textColor == textColor || [_textColor isEqual:textColor]) return;
  612. _textColor = textColor;
  613. _innerText.yy_color = textColor;
  614. if (_innerText.length && !_ignoreCommonProperties) {
  615. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  616. [self _clearContents];
  617. }
  618. [self _setLayoutNeedUpdate];
  619. }
  620. }
  621. - (void)setShadowColor:(UIColor *)shadowColor {
  622. if (_shadowColor == shadowColor || [_shadowColor isEqual:shadowColor]) return;
  623. _shadowColor = shadowColor;
  624. _innerText.yy_shadow = [self _shadowFromProperties];
  625. if (_innerText.length && !_ignoreCommonProperties) {
  626. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  627. [self _clearContents];
  628. }
  629. [self _setLayoutNeedUpdate];
  630. }
  631. }
  632. #if !TARGET_INTERFACE_BUILDER
  633. - (void)setShadowOffset:(CGSize)shadowOffset {
  634. if (CGSizeEqualToSize(_shadowOffset, shadowOffset)) return;
  635. _shadowOffset = shadowOffset;
  636. _innerText.yy_shadow = [self _shadowFromProperties];
  637. if (_innerText.length && !_ignoreCommonProperties) {
  638. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  639. [self _clearContents];
  640. }
  641. [self _setLayoutNeedUpdate];
  642. }
  643. }
  644. #else
  645. - (void)setShadowOffset:(CGPoint)shadowOffset {
  646. if (CGPointEqualToPoint(_shadowOffset, shadowOffset)) return;
  647. _shadowOffset = shadowOffset;
  648. _innerText.yy_shadow = [self _shadowFromProperties];
  649. if (_innerText.length && !_ignoreCommonProperties) {
  650. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  651. [self _clearContents];
  652. }
  653. [self _setLayoutNeedUpdate];
  654. }
  655. }
  656. #endif
  657. - (void)setShadowBlurRadius:(CGFloat)shadowBlurRadius {
  658. if (_shadowBlurRadius == shadowBlurRadius) return;
  659. _shadowBlurRadius = shadowBlurRadius;
  660. _innerText.yy_shadow = [self _shadowFromProperties];
  661. if (_innerText.length && !_ignoreCommonProperties) {
  662. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  663. [self _clearContents];
  664. }
  665. [self _setLayoutNeedUpdate];
  666. }
  667. }
  668. - (void)setTextAlignment:(NSTextAlignment)textAlignment {
  669. if (_textAlignment == textAlignment) return;
  670. _textAlignment = textAlignment;
  671. _innerText.yy_alignment = textAlignment;
  672. if (_innerText.length && !_ignoreCommonProperties) {
  673. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  674. [self _clearContents];
  675. }
  676. [self _setLayoutNeedUpdate];
  677. [self _endTouch];
  678. [self invalidateIntrinsicContentSize];
  679. }
  680. }
  681. - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode {
  682. if (_lineBreakMode == lineBreakMode) return;
  683. _lineBreakMode = lineBreakMode;
  684. _innerText.yy_lineBreakMode = lineBreakMode;
  685. // allow multi-line break
  686. switch (lineBreakMode) {
  687. case NSLineBreakByWordWrapping:
  688. case NSLineBreakByCharWrapping:
  689. case NSLineBreakByClipping: {
  690. _innerContainer.truncationType = YYTextTruncationTypeNone;
  691. _innerText.yy_lineBreakMode = lineBreakMode;
  692. } break;
  693. case NSLineBreakByTruncatingHead:{
  694. _innerContainer.truncationType = YYTextTruncationTypeStart;
  695. _innerText.yy_lineBreakMode = NSLineBreakByWordWrapping;
  696. } break;
  697. case NSLineBreakByTruncatingTail:{
  698. _innerContainer.truncationType = YYTextTruncationTypeEnd;
  699. _innerText.yy_lineBreakMode = NSLineBreakByWordWrapping;
  700. } break;
  701. case NSLineBreakByTruncatingMiddle: {
  702. _innerContainer.truncationType = YYTextTruncationTypeMiddle;
  703. _innerText.yy_lineBreakMode = NSLineBreakByWordWrapping;
  704. } break;
  705. default: break;
  706. }
  707. if (_innerText.length && !_ignoreCommonProperties) {
  708. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  709. [self _clearContents];
  710. }
  711. [self _setLayoutNeedUpdate];
  712. [self _endTouch];
  713. [self invalidateIntrinsicContentSize];
  714. }
  715. }
  716. - (void)setTextVerticalAlignment:(YYTextVerticalAlignment)textVerticalAlignment {
  717. if (_textVerticalAlignment == textVerticalAlignment) return;
  718. _textVerticalAlignment = textVerticalAlignment;
  719. if (_innerText.length && !_ignoreCommonProperties) {
  720. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  721. [self _clearContents];
  722. }
  723. [self _setLayoutNeedUpdate];
  724. [self _endTouch];
  725. [self invalidateIntrinsicContentSize];
  726. }
  727. }
  728. - (void)setTruncationToken:(NSAttributedString *)truncationToken {
  729. if (_truncationToken == truncationToken || [_truncationToken isEqual:truncationToken]) return;
  730. _truncationToken = truncationToken.copy;
  731. _innerContainer.truncationToken = truncationToken;
  732. if (_innerText.length && !_ignoreCommonProperties) {
  733. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  734. [self _clearContents];
  735. }
  736. [self _setLayoutNeedUpdate];
  737. [self _endTouch];
  738. [self invalidateIntrinsicContentSize];
  739. }
  740. }
  741. - (void)setNumberOfLines:(NSUInteger)numberOfLines {
  742. if (_numberOfLines == numberOfLines) return;
  743. _numberOfLines = numberOfLines;
  744. _innerContainer.maximumNumberOfRows = numberOfLines;
  745. if (_innerText.length && !_ignoreCommonProperties) {
  746. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  747. [self _clearContents];
  748. }
  749. [self _setLayoutNeedUpdate];
  750. [self _endTouch];
  751. [self invalidateIntrinsicContentSize];
  752. }
  753. }
  754. - (void)setAttributedText:(NSAttributedString *)attributedText {
  755. if (attributedText.length > 0) {
  756. _innerText = attributedText.mutableCopy;
  757. switch (_lineBreakMode) {
  758. case NSLineBreakByWordWrapping:
  759. case NSLineBreakByCharWrapping:
  760. case NSLineBreakByClipping: {
  761. _innerText.yy_lineBreakMode = _lineBreakMode;
  762. } break;
  763. case NSLineBreakByTruncatingHead:
  764. case NSLineBreakByTruncatingTail:
  765. case NSLineBreakByTruncatingMiddle: {
  766. _innerText.yy_lineBreakMode = NSLineBreakByWordWrapping;
  767. } break;
  768. default: break;
  769. }
  770. } else {
  771. _innerText = [NSMutableAttributedString new];
  772. }
  773. [_textParser parseText:_innerText selectedRange:NULL];
  774. if (!_ignoreCommonProperties) {
  775. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  776. [self _clearContents];
  777. }
  778. [self _updateOuterTextProperties];
  779. [self _setLayoutNeedUpdate];
  780. [self _endTouch];
  781. [self invalidateIntrinsicContentSize];
  782. }
  783. }
  784. - (void)setTextContainerPath:(UIBezierPath *)textContainerPath {
  785. if (_textContainerPath == textContainerPath || [_textContainerPath isEqual:textContainerPath]) return;
  786. _textContainerPath = textContainerPath.copy;
  787. _innerContainer.path = textContainerPath;
  788. if (!_textContainerPath) {
  789. _innerContainer.size = self.bounds.size;
  790. _innerContainer.insets = _textContainerInset;
  791. }
  792. if (_innerText.length && !_ignoreCommonProperties) {
  793. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  794. [self _clearContents];
  795. }
  796. [self _setLayoutNeedUpdate];
  797. [self _endTouch];
  798. [self invalidateIntrinsicContentSize];
  799. }
  800. }
  801. - (void)setExclusionPaths:(NSArray *)exclusionPaths {
  802. if (_exclusionPaths == exclusionPaths || [_exclusionPaths isEqual:exclusionPaths]) return;
  803. _exclusionPaths = exclusionPaths.copy;
  804. _innerContainer.exclusionPaths = exclusionPaths;
  805. if (_innerText.length && !_ignoreCommonProperties) {
  806. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  807. [self _clearContents];
  808. }
  809. [self _setLayoutNeedUpdate];
  810. [self _endTouch];
  811. [self invalidateIntrinsicContentSize];
  812. }
  813. }
  814. - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset {
  815. if (UIEdgeInsetsEqualToEdgeInsets(_textContainerInset, textContainerInset)) return;
  816. _textContainerInset = textContainerInset;
  817. _innerContainer.insets = textContainerInset;
  818. if (_innerText.length && !_ignoreCommonProperties) {
  819. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  820. [self _clearContents];
  821. }
  822. [self _setLayoutNeedUpdate];
  823. [self _endTouch];
  824. [self invalidateIntrinsicContentSize];
  825. }
  826. }
  827. - (void)setVerticalForm:(BOOL)verticalForm {
  828. if (_verticalForm == verticalForm) return;
  829. _verticalForm = verticalForm;
  830. _innerContainer.verticalForm = verticalForm;
  831. if (_innerText.length && !_ignoreCommonProperties) {
  832. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  833. [self _clearContents];
  834. }
  835. [self _setLayoutNeedUpdate];
  836. [self _endTouch];
  837. [self invalidateIntrinsicContentSize];
  838. }
  839. }
  840. - (void)setLinePositionModifier:(id<YYTextLinePositionModifier>)linePositionModifier {
  841. if (_linePositionModifier == linePositionModifier) return;
  842. _linePositionModifier = linePositionModifier;
  843. _innerContainer.linePositionModifier = linePositionModifier;
  844. if (_innerText.length && !_ignoreCommonProperties) {
  845. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  846. [self _clearContents];
  847. }
  848. [self _setLayoutNeedUpdate];
  849. [self _endTouch];
  850. [self invalidateIntrinsicContentSize];
  851. }
  852. }
  853. - (void)setTextParser:(id<YYTextParser>)textParser {
  854. if (_textParser == textParser || [_textParser isEqual:textParser]) return;
  855. _textParser = textParser;
  856. if ([_textParser parseText:_innerText selectedRange:NULL]) {
  857. [self _updateOuterTextProperties];
  858. if (!_ignoreCommonProperties) {
  859. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  860. [self _clearContents];
  861. }
  862. [self _setLayoutNeedUpdate];
  863. [self _endTouch];
  864. [self invalidateIntrinsicContentSize];
  865. }
  866. }
  867. }
  868. - (void)setTextLayout:(YYTextLayout *)textLayout {
  869. _innerLayout = textLayout;
  870. _shrinkInnerLayout = nil;
  871. if (_ignoreCommonProperties) {
  872. _innerText = (NSMutableAttributedString *)textLayout.text;
  873. _innerContainer = textLayout.container.copy;
  874. } else {
  875. _innerText = textLayout.text.mutableCopy;
  876. if (!_innerText) {
  877. _innerText = [NSMutableAttributedString new];
  878. }
  879. [self _updateOuterTextProperties];
  880. _innerContainer = textLayout.container.copy;
  881. if (!_innerContainer) {
  882. _innerContainer = [YYTextContainer new];
  883. _innerContainer.size = self.bounds.size;
  884. _innerContainer.insets = self.textContainerInset;
  885. }
  886. [self _updateOuterContainerProperties];
  887. }
  888. if (_displaysAsynchronously && _clearContentsBeforeAsynchronouslyDisplay) {
  889. [self _clearContents];
  890. }
  891. _state.layoutNeedUpdate = NO;
  892. [self _setLayoutNeedRedraw];
  893. [self _endTouch];
  894. [self invalidateIntrinsicContentSize];
  895. }
  896. - (YYTextLayout *)textLayout {
  897. [self _updateIfNeeded];
  898. return _innerLayout;
  899. }
  900. - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously {
  901. _displaysAsynchronously = displaysAsynchronously;
  902. ((YYTextAsyncLayer *)self.layer).displaysAsynchronously = displaysAsynchronously;
  903. }
  904. #pragma mark - AutoLayout
  905. - (void)setPreferredMaxLayoutWidth:(CGFloat)preferredMaxLayoutWidth {
  906. if (_preferredMaxLayoutWidth == preferredMaxLayoutWidth) return;
  907. _preferredMaxLayoutWidth = preferredMaxLayoutWidth;
  908. [self invalidateIntrinsicContentSize];
  909. }
  910. - (CGSize)intrinsicContentSize {
  911. if (_preferredMaxLayoutWidth == 0) {
  912. YYTextContainer *container = [_innerContainer copy];
  913. container.size = YYTextContainerMaxSize;
  914. YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];
  915. return layout.textBoundingSize;
  916. }
  917. CGSize containerSize = _innerContainer.size;
  918. if (!_verticalForm) {
  919. containerSize.height = YYTextContainerMaxSize.height;
  920. containerSize.width = _preferredMaxLayoutWidth;
  921. if (containerSize.width == 0) containerSize.width = self.bounds.size.width;
  922. } else {
  923. containerSize.width = YYTextContainerMaxSize.width;
  924. containerSize.height = _preferredMaxLayoutWidth;
  925. if (containerSize.height == 0) containerSize.height = self.bounds.size.height;
  926. }
  927. YYTextContainer *container = [_innerContainer copy];
  928. container.size = containerSize;
  929. YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:_innerText];
  930. return layout.textBoundingSize;
  931. }
  932. #pragma mark - YYTextDebugTarget
  933. - (void)setDebugOption:(YYTextDebugOption *)debugOption {
  934. BOOL needDraw = _debugOption.needDrawDebug;
  935. _debugOption = debugOption.copy;
  936. if (_debugOption.needDrawDebug != needDraw) {
  937. [self _setLayoutNeedRedraw];
  938. }
  939. }
  940. #pragma mark - YYTextAsyncLayerDelegate
  941. - (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask {
  942. // capture current context
  943. BOOL contentsNeedFade = _state.contentsNeedFade;
  944. NSAttributedString *text = _innerText;
  945. YYTextContainer *container = _innerContainer;
  946. YYTextVerticalAlignment verticalAlignment = _textVerticalAlignment;
  947. YYTextDebugOption *debug = _debugOption;
  948. NSMutableArray *attachmentViews = _attachmentViews;
  949. NSMutableArray *attachmentLayers = _attachmentLayers;
  950. BOOL layoutNeedUpdate = _state.layoutNeedUpdate;
  951. BOOL fadeForAsync = _displaysAsynchronously && _fadeOnAsynchronouslyDisplay;
  952. __block YYTextLayout *layout = (_state.showingHighlight && _highlightLayout) ? self._highlightLayout : self._innerLayout;
  953. __block YYTextLayout *shrinkLayout = nil;
  954. __block BOOL layoutUpdated = NO;
  955. if (layoutNeedUpdate) {
  956. text = text.copy;
  957. container = container.copy;
  958. }
  959. // create display task
  960. YYTextAsyncLayerDisplayTask *task = [YYTextAsyncLayerDisplayTask new];
  961. task.willDisplay = ^(CALayer *layer) {
  962. [layer removeAnimationForKey:@"contents"];
  963. // If the attachment is not in new layout, or we don't know the new layout currently,
  964. // the attachment should be removed.
  965. for (UIView *view in attachmentViews) {
  966. if (layoutNeedUpdate || ![layout.attachmentContentsSet containsObject:view]) {
  967. if (view.superview == self) {
  968. [view removeFromSuperview];
  969. }
  970. }
  971. }
  972. for (CALayer *layer in attachmentLayers) {
  973. if (layoutNeedUpdate || ![layout.attachmentContentsSet containsObject:layer]) {
  974. if (layer.superlayer == self.layer) {
  975. [layer removeFromSuperlayer];
  976. }
  977. }
  978. }
  979. [attachmentViews removeAllObjects];
  980. [attachmentLayers removeAllObjects];
  981. };
  982. task.display = ^(CGContextRef context, CGSize size, BOOL (^isCancelled)(void)) {
  983. if (isCancelled()) return;
  984. if (text.length == 0) return;
  985. YYTextLayout *drawLayout = layout;
  986. if (layoutNeedUpdate) {
  987. layout = [YYTextLayout layoutWithContainer:container text:text];
  988. shrinkLayout = [YYLabel _shrinkLayoutWithLayout:layout];
  989. if (isCancelled()) return;
  990. layoutUpdated = YES;
  991. drawLayout = shrinkLayout ? shrinkLayout : layout;
  992. }
  993. CGSize boundingSize = drawLayout.textBoundingSize;
  994. CGPoint point = CGPointZero;
  995. if (verticalAlignment == YYTextVerticalAlignmentCenter) {
  996. if (drawLayout.container.isVerticalForm) {
  997. point.x = -(size.width - boundingSize.width) * 0.5;
  998. } else {
  999. point.y = (size.height - boundingSize.height) * 0.5;
  1000. }
  1001. } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
  1002. if (drawLayout.container.isVerticalForm) {
  1003. point.x = -(size.width - boundingSize.width);
  1004. } else {
  1005. point.y = (size.height - boundingSize.height);
  1006. }
  1007. }
  1008. point = YYTextCGPointPixelRound(point);
  1009. [drawLayout drawInContext:context size:size point:point view:nil layer:nil debug:debug cancel:isCancelled];
  1010. };
  1011. task.didDisplay = ^(CALayer *layer, BOOL finished) {
  1012. YYTextLayout *drawLayout = layout;
  1013. if (layoutUpdated && shrinkLayout) {
  1014. drawLayout = shrinkLayout;
  1015. }
  1016. if (!finished) {
  1017. // If the display task is cancelled, we should clear the attachments.
  1018. for (YYTextAttachment *a in drawLayout.attachments) {
  1019. if ([a.content isKindOfClass:[UIView class]]) {
  1020. if (((UIView *)a.content).superview == layer.delegate) {
  1021. [((UIView *)a.content) removeFromSuperview];
  1022. }
  1023. } else if ([a.content isKindOfClass:[CALayer class]]) {
  1024. if (((CALayer *)a.content).superlayer == layer) {
  1025. [((CALayer *)a.content) removeFromSuperlayer];
  1026. }
  1027. }
  1028. }
  1029. return;
  1030. }
  1031. [layer removeAnimationForKey:@"contents"];
  1032. __strong YYLabel *view = (YYLabel *)layer.delegate;
  1033. if (!view) return;
  1034. if (view->_state.layoutNeedUpdate && layoutUpdated) {
  1035. view->_innerLayout = layout;
  1036. view->_shrinkInnerLayout = shrinkLayout;
  1037. view->_state.layoutNeedUpdate = NO;
  1038. }
  1039. CGSize size = layer.bounds.size;
  1040. CGSize boundingSize = drawLayout.textBoundingSize;
  1041. CGPoint point = CGPointZero;
  1042. if (verticalAlignment == YYTextVerticalAlignmentCenter) {
  1043. if (drawLayout.container.isVerticalForm) {
  1044. point.x = -(size.width - boundingSize.width) * 0.5;
  1045. } else {
  1046. point.y = (size.height - boundingSize.height) * 0.5;
  1047. }
  1048. } else if (verticalAlignment == YYTextVerticalAlignmentBottom) {
  1049. if (drawLayout.container.isVerticalForm) {
  1050. point.x = -(size.width - boundingSize.width);
  1051. } else {
  1052. point.y = (size.height - boundingSize.height);
  1053. }
  1054. }
  1055. point = YYTextCGPointPixelRound(point);
  1056. [drawLayout drawInContext:nil size:size point:point view:view layer:layer debug:nil cancel:NULL];
  1057. for (YYTextAttachment *a in drawLayout.attachments) {
  1058. if ([a.content isKindOfClass:[UIView class]]) [attachmentViews addObject:a.content];
  1059. else if ([a.content isKindOfClass:[CALayer class]]) [attachmentLayers addObject:a.content];
  1060. }
  1061. if (contentsNeedFade) {
  1062. CATransition *transition = [CATransition animation];
  1063. transition.duration = kHighlightFadeDuration;
  1064. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  1065. transition.type = kCATransitionFade;
  1066. [layer addAnimation:transition forKey:@"contents"];
  1067. } else if (fadeForAsync) {
  1068. CATransition *transition = [CATransition animation];
  1069. transition.duration = kAsyncFadeDuration;
  1070. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  1071. transition.type = kCATransitionFade;
  1072. [layer addAnimation:transition forKey:@"contents"];
  1073. }
  1074. };
  1075. return task;
  1076. }
  1077. @end
  1078. @interface YYLabel(IBInspectableProperties)
  1079. @end
  1080. @implementation YYLabel (IBInspectableProperties)
  1081. - (BOOL)fontIsBold_:(UIFont *)font {
  1082. if (![font respondsToSelector:@selector(fontDescriptor)]) return NO;
  1083. return (font.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold) > 0;
  1084. }
  1085. - (UIFont *)boldFont_:(UIFont *)font {
  1086. if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
  1087. return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize];
  1088. }
  1089. - (UIFont *)normalFont_:(UIFont *)font {
  1090. if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
  1091. return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:0] size:font.pointSize];
  1092. }
  1093. - (void)setFontName_:(NSString *)fontName {
  1094. if (!fontName) return;
  1095. UIFont *font = self.font;
  1096. if ((fontName.length == 0 || [fontName.lowercaseString isEqualToString:@"system"]) && ![self fontIsBold_:font]) {
  1097. font = [UIFont systemFontOfSize:font.pointSize];
  1098. } else if ([fontName.lowercaseString isEqualToString:@"system bold"]) {
  1099. font = [UIFont boldSystemFontOfSize:font.pointSize];
  1100. } else {
  1101. if ([self fontIsBold_:font] && ([fontName.lowercaseString rangeOfString:@"bold"].location == NSNotFound)) {
  1102. font = [UIFont fontWithName:fontName size:font.pointSize];
  1103. font = [self boldFont_:font];
  1104. } else {
  1105. font = [UIFont fontWithName:fontName size:font.pointSize];
  1106. }
  1107. }
  1108. if (font) self.font = font;
  1109. }
  1110. - (void)setFontSize_:(CGFloat)fontSize {
  1111. if (fontSize <= 0) return;
  1112. UIFont *font = self.font;
  1113. font = [font fontWithSize:fontSize];
  1114. if (font) self.font = font;
  1115. }
  1116. - (void)setFontIsBold_:(BOOL)fontBold {
  1117. UIFont *font = self.font;
  1118. if ([self fontIsBold_:font] == fontBold) return;
  1119. if (fontBold) {
  1120. font = [self boldFont_:font];
  1121. } else {
  1122. font = [self normalFont_:font];
  1123. }
  1124. if (font) self.font = font;
  1125. }
  1126. - (void)setInsetTop_:(CGFloat)textInsetTop {
  1127. UIEdgeInsets insets = self.textContainerInset;
  1128. insets.top = textInsetTop;
  1129. self.textContainerInset = insets;
  1130. }
  1131. - (void)setInsetBottom_:(CGFloat)textInsetBottom {
  1132. UIEdgeInsets insets = self.textContainerInset;
  1133. insets.bottom = textInsetBottom;
  1134. self.textContainerInset = insets;
  1135. }
  1136. - (void)setInsetLeft_:(CGFloat)textInsetLeft {
  1137. UIEdgeInsets insets = self.textContainerInset;
  1138. insets.left = textInsetLeft;
  1139. self.textContainerInset = insets;
  1140. }
  1141. - (void)setInsetRight_:(CGFloat)textInsetRight {
  1142. UIEdgeInsets insets = self.textContainerInset;
  1143. insets.right = textInsetRight;
  1144. self.textContainerInset = insets;
  1145. }
  1146. - (void)setDebugEnabled_:(BOOL)enabled {
  1147. if (!enabled) {
  1148. self.debugOption = nil;
  1149. } else {
  1150. YYTextDebugOption *debugOption = [YYTextDebugOption new];
  1151. debugOption.baselineColor = [UIColor redColor];
  1152. debugOption.CTFrameBorderColor = [UIColor redColor];
  1153. debugOption.CTLineFillColor = [UIColor colorWithRed:0.000 green:0.463 blue:1.000 alpha:0.180];
  1154. debugOption.CGGlyphBorderColor = [UIColor colorWithRed:1.000 green:0.524 blue:0.000 alpha:0.200];
  1155. self.debugOption = debugOption;
  1156. }
  1157. }
  1158. @end