YYLabel.m 47 KB

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