MLLabel.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. //
  2. // MLLabel.m
  3. // MLLabel
  4. //
  5. // Created by molon on 15/5/18.
  6. // Copyright (c) 2015年 molon. All rights reserved.
  7. //
  8. #import "MLLabel.h"
  9. #import "NSMutableAttributedString+MLLabel.h"
  10. #import "MLLabelLayoutManager.h"
  11. #import "NSString+MLLabel.h"
  12. #define kAdjustFontSizeEveryScalingFactor (M_E / M_PI)
  13. //总得有个极限
  14. static CGFloat kMLLabelFloatMax = 10000000.0f;
  15. static CGFloat kMLLabelAdjustMinFontSize = 1.0f;
  16. static CGFloat kMLLabelAdjustMinScaleFactor = 0.01f;
  17. static NSArray * kMLLabelStylePropertyNames() {
  18. static NSArray *_stylePropertyNames = nil;
  19. static dispatch_once_t onceToken;
  20. dispatch_once(&onceToken, ^{
  21. //TODO: 这个highlighted在tableview滚动到的时候会设置下(即使cell的selectStyle为None),然后就造成resetText,很鸡巴,耗费性能,这个似乎没辙,实在有必要,后期+个属性来开关
  22. _stylePropertyNames = @[@"font",@"textAlignment",@"textColor",@"highlighted",
  23. @"highlightedTextColor",@"shadowColor",@"shadowOffset",@"enabled",@"lineHeightMultiple",@"lineSpacing"];
  24. });
  25. return _stylePropertyNames;
  26. }
  27. static inline CGSize _MLLabel_CGSizePixelRound(CGSize size) {
  28. static CGFloat scale = 0.0f;
  29. static dispatch_once_t onceToken;
  30. dispatch_once(&onceToken, ^{
  31. scale = [UIScreen mainScreen].scale;
  32. });
  33. return CGSizeMake(round(size.width * scale) / scale,
  34. round(size.height * scale) / scale);
  35. }
  36. @interface MLLabelStylePropertyRecord : NSObject
  37. @property (nonatomic, strong) UIFont *font;
  38. @property (nonatomic, assign) NSTextAlignment textAlignment;
  39. @property (nonatomic, strong) UIColor *textColor;
  40. @property (nonatomic, assign) BOOL highlighted;
  41. @property (nonatomic, strong) UIColor *highlightedTextColor;
  42. @property (nonatomic, strong) UIColor *shadowColor;
  43. @property (nonatomic, assign) CGSize shadowOffset;
  44. @property (nonatomic, assign) BOOL enabled;
  45. @property (nonatomic, assign) CGFloat lineHeightMultiple; //行高的multiple
  46. @property (nonatomic, assign) CGFloat lineSpacing; //行间距
  47. @end
  48. @implementation MLLabelStylePropertyRecord
  49. @end
  50. @interface MLLabel()<NSLayoutManagerDelegate>
  51. @property (nonatomic, strong) NSTextStorage *textStorage;
  52. @property (nonatomic, strong) MLLabelLayoutManager *layoutManager;
  53. @property (nonatomic, strong) NSTextContainer *textContainer;
  54. @property (nonatomic, assign) MLLastTextType lastTextType;
  55. @property (nonatomic, strong) MLLabelStylePropertyRecord *styleRecord;
  56. //为什么需要这个,是因为setAttributedText之后内部可能会对其进行了改动,然后例如再次更新style属性,然后更新绘制会出问题。索性都以记录的最原始的为准。
  57. @property (nonatomic, copy) NSAttributedString *lastAttributedText;
  58. //读取的时候需要
  59. @property (nonatomic, copy) NSString *lastText;
  60. @end
  61. @implementation MLLabel
  62. #pragma mark - init
  63. - (instancetype)initWithFrame:(CGRect)frame
  64. {
  65. self = [super initWithFrame:frame];
  66. if (self) {
  67. [self commonInit];
  68. }
  69. return self;
  70. }
  71. - (id)initWithCoder:(NSCoder *)aDecoder
  72. {
  73. self = [super initWithCoder:aDecoder];
  74. if (self)
  75. {
  76. [self commonInit];
  77. }
  78. return self;
  79. }
  80. - (void)commonInit
  81. {
  82. self.lineHeightMultiple = 1.0f;
  83. //设置TextKit初始相关
  84. [self.textStorage addLayoutManager:self.layoutManager];
  85. [self.layoutManager addTextContainer:self.textContainer];
  86. //label helper相关
  87. if ([super attributedText]) {
  88. self.attributedText = [super attributedText];
  89. }else{
  90. self.text = [super text];
  91. }
  92. //kvo 监视style属性
  93. for (NSString *key in kMLLabelStylePropertyNames()) {
  94. [self.styleRecord setValue:[self valueForKey:key] forKey:key];
  95. //不直接使用NSKeyValueObservingOptionInitial来初始化赋值record,是防止无用的resettext
  96. [self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
  97. }
  98. }
  99. - (void)dealloc
  100. {
  101. //kvo 移除监视style属性
  102. for (NSString *key in kMLLabelStylePropertyNames()) {
  103. [self removeObserver:self forKeyPath:key context:nil];
  104. }
  105. }
  106. #pragma mark - KVO
  107. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  108. {
  109. if ([kMLLabelStylePropertyNames() containsObject:keyPath]) {
  110. //存到记录对象里
  111. [_styleRecord setValue:[object valueForKey:keyPath] forKey:keyPath];
  112. id old = change[NSKeyValueChangeOldKey];
  113. id new = change[NSKeyValueChangeNewKey];
  114. if ([old isEqual:new]||(!old&&!new)) {
  115. return;
  116. }
  117. [self reSetText];
  118. }else{
  119. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  120. }
  121. }
  122. #pragma mark - getter
  123. - (NSTextStorage *)textStorage
  124. {
  125. if (!_textStorage) {
  126. _textStorage = [NSTextStorage new];
  127. }
  128. return _textStorage;
  129. }
  130. - (MLLabelLayoutManager *)layoutManager
  131. {
  132. if (!_layoutManager) {
  133. _layoutManager = [MLLabelLayoutManager new];
  134. _layoutManager.allowsNonContiguousLayout = NO;
  135. _layoutManager.delegate = self;
  136. }
  137. return _layoutManager;
  138. }
  139. - (NSTextContainer *)textContainer
  140. {
  141. if (!_textContainer) {
  142. _textContainer = [NSTextContainer new];
  143. _textContainer.maximumNumberOfLines = self.numberOfLines;
  144. _textContainer.lineBreakMode = self.lineBreakMode;
  145. _textContainer.lineFragmentPadding = 0.0f;
  146. _textContainer.size = self.frame.size;
  147. }
  148. return _textContainer;
  149. }
  150. - (MLLabelStylePropertyRecord *)styleRecord
  151. {
  152. if (!_styleRecord) {
  153. _styleRecord = [MLLabelStylePropertyRecord new];
  154. }
  155. return _styleRecord;
  156. }
  157. - (NSAttributedString *)attributedText
  158. {
  159. return _lastTextType==MLLastTextTypeAttributed?_lastAttributedText:[self attributedTextForTextStorageFromLabelProperties];
  160. }
  161. - (NSString*)text
  162. {
  163. return _lastTextType==MLLastTextTypeAttributed?_lastAttributedText.string:_lastText;
  164. }
  165. #pragma mark - set text
  166. - (void)setLastTextType:(MLLastTextType)lastTextType
  167. {
  168. _lastTextType = lastTextType;
  169. //重置下
  170. self.lastText = nil;
  171. self.lastAttributedText = nil;
  172. }
  173. - (void)setText:(NSString *)text
  174. {
  175. NSAssert(!text||[text isKindOfClass:[NSString class]], @"text must be NSString");
  176. self.lastTextType = MLLastTextTypeNormal;
  177. self.lastText = text;
  178. [self invalidateIntrinsicContentSize];
  179. // [super setText:text];
  180. [_textStorage setAttributedString:[self attributedTextForTextStorageFromLabelProperties]];
  181. //如果text和原本的一样的话 super 是不会触发redraw的,但是很遗憾我们的label比较灵活,验证起来很麻烦,所以还是都重绘吧
  182. [self setNeedsDisplay];
  183. }
  184. - (void)setAttributedText:(NSAttributedString *)attributedText
  185. {
  186. NSAssert(!attributedText||[attributedText isKindOfClass:[NSAttributedString class]], @"text must be NSAttributedString");
  187. self.lastTextType = MLLastTextTypeAttributed;
  188. self.lastAttributedText = attributedText;
  189. [self invalidateIntrinsicContentSize];
  190. // [super setAttributedText:attributedText];
  191. [_textStorage setAttributedString:[self attributedTextForTextStorageFromLabelProperties]];
  192. //如果text和原本的一样的话 super 是不会触发redraw的,但是很遗憾我们的label比较灵活,验证起来很麻烦,所以还是都重绘吧
  193. [self setNeedsDisplay];
  194. // NSLog(@"set attr text %p",self);
  195. }
  196. #pragma mark - common helper
  197. - (CGSize)drawTextSizeWithBoundsSize:(CGSize)size
  198. {
  199. //bounds改了之后,要根据insets调整绘制区域的大小
  200. CGFloat width = fmax(0, size.width-_textInsets.left-_textInsets.right);
  201. CGFloat height = fmax(0, size.height-_textInsets.top-_textInsets.bottom);
  202. return CGSizeMake(width, height);
  203. }
  204. - (void)reSetText
  205. {
  206. if (_lastTextType == MLLastTextTypeNormal) {
  207. self.text = _lastText;
  208. }else{
  209. self.attributedText = _lastAttributedText;
  210. }
  211. }
  212. /**
  213. * 根据label的属性来进行处理并返回给textStorage使用的
  214. */
  215. - (NSMutableAttributedString*)attributedTextForTextStorageFromLabelProperties
  216. {
  217. if (_lastTextType==MLLastTextTypeNormal) {
  218. if (!_lastText) {
  219. return [[NSMutableAttributedString alloc]initWithString:@""];
  220. }
  221. //根据text和label默认的一些属性得到attributedText
  222. return [[NSMutableAttributedString alloc] initWithString:_lastText attributes:[self attributesFromLabelProperties]];
  223. }
  224. if (!_lastAttributedText) {
  225. return [[NSMutableAttributedString alloc]initWithString:@""];
  226. }
  227. //遍历并且添加Label默认的属性
  228. NSMutableAttributedString *newAttrStr = [[NSMutableAttributedString alloc]initWithString:_lastAttributedText.string attributes:[self attributesFromLabelProperties]];
  229. [_lastAttributedText enumerateAttributesInRange:NSMakeRange(0, newAttrStr.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
  230. if (attrs.count>0) {
  231. // [newAttrStr removeAttributes:[attrs allKeys] range:range];
  232. [newAttrStr addAttributes:attrs range:range];
  233. }
  234. }];
  235. return newAttrStr;
  236. }
  237. - (NSDictionary *)attributesFromLabelProperties
  238. {
  239. //颜色
  240. UIColor *color = self.styleRecord.textColor;
  241. if (!_styleRecord.enabled) {
  242. color = [UIColor lightGrayColor];
  243. }
  244. else if (_styleRecord.highlighted) {
  245. color = _styleRecord.highlightedTextColor;
  246. }
  247. if (!color) {
  248. color = _styleRecord.textColor;
  249. if (!color) {
  250. color = [UIColor darkTextColor];
  251. }
  252. }
  253. //阴影
  254. NSShadow *shadow = shadow = [[NSShadow alloc] init];
  255. if (_styleRecord.shadowColor) {
  256. shadow.shadowColor = _styleRecord.shadowColor;
  257. shadow.shadowOffset = _styleRecord.shadowOffset;
  258. }else {
  259. shadow.shadowOffset = CGSizeMake(0, -1);
  260. shadow.shadowColor = nil;
  261. }
  262. //水平位置
  263. NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
  264. paragraph.alignment = _styleRecord.textAlignment;
  265. paragraph.lineSpacing = _styleRecord.lineSpacing;
  266. paragraph.lineHeightMultiple = _styleRecord.lineHeightMultiple;
  267. if (!_styleRecord.font) {
  268. _styleRecord.font = [UIFont systemFontOfSize:17.0f];
  269. }
  270. //最终
  271. NSDictionary *attributes = @{NSFontAttributeName : _styleRecord.font,
  272. NSForegroundColorAttributeName : color,
  273. NSShadowAttributeName : shadow,
  274. NSParagraphStyleAttributeName : paragraph,
  275. };
  276. return attributes;
  277. }
  278. - (CGRect)textRectForBounds:(CGRect)bounds attributedString:(NSAttributedString*)attributedString limitedToNumberOfLines:(NSInteger)numberOfLines lineCount:(NSInteger*)lineCount
  279. {
  280. //这种算是特殊情况,如果为空字符串,那就没必要必要了,也忽略textInset,这样比较合理
  281. if (attributedString.length<=0) {
  282. bounds.size = CGSizeZero;
  283. return bounds;
  284. }
  285. CGSize drawTextSize = [self drawTextSizeWithBoundsSize:bounds.size];
  286. if (drawTextSize.width<=0||drawTextSize.height<=0){
  287. CGRect textBounds = CGRectZero;
  288. textBounds.origin = bounds.origin;
  289. textBounds.size = CGSizeMake(fmin(_textInsets.left+_textInsets.right,CGRectGetWidth(bounds)), fmin(_textInsets.top+_textInsets.bottom,CGRectGetHeight(bounds)));
  290. return textBounds;
  291. }
  292. CGRect textBounds = CGRectZero;
  293. @autoreleasepool {
  294. CGSize savedTextContainerSize = _textContainer.size;
  295. NSInteger savedTextContainerNumberOfLines = _textContainer.maximumNumberOfLines;
  296. //这一句的原因参考resizeTextContainerSize
  297. if (drawTextSize.height<kMLLabelFloatMax) {
  298. drawTextSize.height += self.lineSpacing;
  299. }
  300. _textContainer.size = drawTextSize;
  301. _textContainer.maximumNumberOfLines = numberOfLines;
  302. NSAttributedString *savedAttributedString = nil;
  303. if (![_textStorage isEqual:attributedString]) {
  304. savedAttributedString = [_textStorage copy];
  305. [_textStorage setAttributedString:attributedString];
  306. }
  307. NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
  308. if (lineCount) {
  309. [_layoutManager enumerateLineFragmentsForGlyphRange:glyphRange usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
  310. (*lineCount)++;
  311. }];
  312. //在最后字符为换行符的情况下,实际绘制出来的是会多那个一行,这里作为AppleBUG修正
  313. if ([_textStorage.string isNewlineCharacterAtEnd]) {
  314. (*lineCount)++;
  315. }
  316. }
  317. textBounds = [_layoutManager usedRectForTextContainer:_textContainer];
  318. //还原
  319. if (savedAttributedString) {
  320. [_textStorage setAttributedString:savedAttributedString];
  321. }
  322. _textContainer.size = savedTextContainerSize;
  323. _textContainer.maximumNumberOfLines = savedTextContainerNumberOfLines;
  324. }
  325. //最终修正
  326. textBounds.size.width = fmin(ceilf(textBounds.size.width), drawTextSize.width);
  327. textBounds.size.height = fmin(ceilf(textBounds.size.height), drawTextSize.height);
  328. textBounds.origin = bounds.origin;
  329. textBounds.size = CGSizeMake(fmin(CGRectGetWidth(textBounds)+_textInsets.left+_textInsets.right,CGRectGetWidth(bounds)), fmin(CGRectGetHeight(textBounds)+_textInsets.top+_textInsets.bottom,CGRectGetHeight(bounds)));
  330. // NSLog(@"bounds:%@ result:%@ %p",NSStringFromCGRect(bounds),NSStringFromCGRect(textBounds),self);
  331. return textBounds;
  332. }
  333. #pragma mark - draw
  334. - (BOOL)adjustsCurrentFontSizeToFitWidthWithScaleFactor:(CGFloat)scaleFactor numberOfLines:(NSInteger)numberOfLines originalAttributedText:(NSAttributedString*)originalAttributedText bounds:(CGRect)bounds resultAttributedString:(NSAttributedString**)resultAttributedString
  335. {
  336. __block BOOL mustReturnYES = NO;
  337. if (self.minimumScaleFactor > scaleFactor) {
  338. scaleFactor = self.minimumScaleFactor; //这个的话 就不能在循环验证了
  339. mustReturnYES = YES;
  340. }
  341. //总得有个极限
  342. scaleFactor = fmax(scaleFactor, kMLLabelAdjustMinScaleFactor);
  343. //遍历并且设置一个新的字体
  344. NSMutableAttributedString *attrStr = [originalAttributedText mutableCopy];
  345. if (scaleFactor!=1.0f) { //如果是1.0f的话就没有调整font size的必要
  346. [attrStr enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attrStr.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
  347. UIFont *font = (UIFont *)value;
  348. if (font&&[font isKindOfClass:[UIFont class]]) {
  349. NSString *fontName = font.fontName;
  350. CGFloat newSize = font.pointSize*scaleFactor;
  351. if (newSize<kMLLabelAdjustMinFontSize) { //字体的极限
  352. mustReturnYES = YES;
  353. }
  354. UIFont *newFont = [UIFont fontWithName:fontName size:newSize];
  355. // [attrStr removeAttribute:NSFontAttributeName range:range];
  356. [attrStr addAttribute:NSFontAttributeName value:newFont range:range];
  357. }
  358. }];
  359. }
  360. //返回是否需要继续调整字体大小
  361. if (mustReturnYES) {
  362. if (resultAttributedString) {
  363. (*resultAttributedString) = attrStr;
  364. }
  365. return YES;
  366. }
  367. CGSize currentTextSize = CGSizeZero;
  368. if (numberOfLines>0) {
  369. NSInteger lineCount = 0;
  370. currentTextSize = [self textRectForBounds:CGRectMake(0, 0, CGRectGetWidth(bounds), kMLLabelFloatMax) attributedString:attrStr limitedToNumberOfLines:0 lineCount:&lineCount].size;
  371. //如果求行数大于设置行数,也不认为塞满了
  372. if (lineCount>numberOfLines) {
  373. return NO;
  374. }
  375. }else{
  376. currentTextSize = [self textRectForBounds:CGRectMake(0, 0, CGRectGetWidth(bounds), kMLLabelFloatMax) attributedString:attrStr limitedToNumberOfLines:0 lineCount:NULL].size;
  377. }
  378. //大小已经足够就认作OK
  379. if (currentTextSize.width<=CGRectGetWidth(bounds)&&currentTextSize.height<=CGRectGetHeight(bounds)) {
  380. if (resultAttributedString) {
  381. (*resultAttributedString) = attrStr;
  382. }
  383. return YES;
  384. }
  385. return NO;
  386. }
  387. - (void)drawTextInRect:(CGRect)rect
  388. {
  389. // NSLog(@"draw text %p",self);
  390. //不调用super方法
  391. // [super drawTextInRect:rect]; //这里调用可以查看是否绘制和原来的不一致
  392. //如果绘制区域本身就为0,就应该直接返回,不做多余操作。
  393. CGSize drawSize = [self drawTextSizeWithBoundsSize:self.bounds.size];
  394. if (drawSize.width<=0||drawSize.height<=0){
  395. return;
  396. }
  397. if (self.adjustsFontSizeToFitWidth) {
  398. //初始scale,每次adjust都需要从头开始,因为也可能有当前font被adjust小过需要还原。
  399. CGFloat scaleFactor = 1.0f;
  400. BOOL mustContinueAdjust = YES;
  401. NSAttributedString *attributedString = [self attributedTextForTextStorageFromLabelProperties];
  402. //numberOfLine>0时候可以直接尝试找寻一个preferredScale
  403. if (self.numberOfLines>0) {
  404. //一点点矫正,以使得内容能放到当前的size里
  405. //找到当前text绘制在一行时候需要占用的宽度,其实这个值很可能不够,因为多行时候可能会因为wordwrap的关系多行+起的总宽度会多。但是这个能找到一个合适的矫正过程的开始值,大大减少矫正次数。
  406. //还有一种情况就是,有可能由于字符串里带换行符的关系造成压根不可能绘制到一行,这时候应该取会显示的最长的那一行。所以这里需要先截除必然不会显示的部分。
  407. NSUInteger stringlineCount = [attributedString.string lineCount];
  408. if (stringlineCount>self.numberOfLines) {
  409. //这里说明必然要截取
  410. attributedString = [attributedString attributedSubstringFromRange:NSMakeRange(0, [attributedString.string lengthToLineIndex:self.numberOfLines-1])];
  411. }
  412. CGFloat textWidth = [self textRectForBounds:CGRectMake(0, 0, kMLLabelFloatMax, kMLLabelFloatMax) attributedString:attributedString limitedToNumberOfLines:0 lineCount:NULL].size.width;
  413. textWidth = fmax(0, textWidth-_textInsets.left-_textInsets.right);
  414. if (textWidth>0) {
  415. CGFloat availableWidth = _textContainer.size.width*self.numberOfLines;
  416. if (textWidth > availableWidth) {
  417. //这里得到的scaleFactor肯定是大于这个的是必然不满足的,目的就是找这个,以能减少下面的矫正次数。
  418. scaleFactor = availableWidth / textWidth;
  419. }
  420. }else{
  421. mustContinueAdjust = NO;
  422. }
  423. }
  424. if (mustContinueAdjust) {
  425. //一点点矫正,以使得内容能放到当前的size里
  426. NSAttributedString *resultAttributedString = attributedString;
  427. while (![self adjustsCurrentFontSizeToFitWidthWithScaleFactor:scaleFactor numberOfLines:self.numberOfLines originalAttributedText:attributedString bounds:self.bounds resultAttributedString:&resultAttributedString]){
  428. scaleFactor *= kAdjustFontSizeEveryScalingFactor;
  429. };
  430. [_textStorage setAttributedString:resultAttributedString];
  431. }
  432. }
  433. //这里根据container的size和manager布局属性以及字符串来得到实际绘制的range区间
  434. NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
  435. //获取绘制区域大小
  436. CGRect drawBounds = [_layoutManager usedRectForTextContainer:_textContainer];
  437. //因为label是默认垂直居中的,所以需要根据实际绘制区域的bounds来调整出居中的offset
  438. CGPoint textOffset = [self textOffsetWithTextSize:drawBounds.size];
  439. if (_doBeforeDrawingTextBlock) {
  440. //而实际上drawBounds的宽度可能不是我们想要的,我们想要的是_textContainer的宽度,但是高度需要是真实绘制高度
  441. CGSize drawSize = CGSizeMake(_textContainer.size.width, drawBounds.size.height);
  442. _doBeforeDrawingTextBlock(rect,textOffset,drawSize);
  443. }
  444. //绘制文字
  445. [_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textOffset];
  446. [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textOffset];
  447. }
  448. //这个计算出来的是绘制起点
  449. - (CGPoint)textOffsetWithTextSize:(CGSize)textSize
  450. {
  451. CGPoint textOffset = CGPointZero;
  452. //根据insets和默认垂直居中来计算出偏移
  453. textOffset.x = _textInsets.left;
  454. CGFloat paddingHeight = (self.bounds.size.height- _textInsets.top - _textInsets.bottom - textSize.height) / 2.0f;
  455. textOffset.y = paddingHeight+_textInsets.top;
  456. return textOffset;
  457. }
  458. //- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager
  459. // shouldGenerateGlyphs:(const CGGlyph *)glyphs
  460. // properties:(const NSGlyphProperty *)props
  461. // characterIndexes:(const NSUInteger *)charIndexes
  462. // font:(UIFont *)aFont
  463. // forGlyphRange:(NSRange)glyphRange
  464. //{
  465. // NSLog(@"shouldGenerateGlyphs: start:%ld end:%ld",*charIndexes,charIndexes[glyphRange.length-1]);
  466. //// if (*charIndexes>=100) {
  467. //// return 0;
  468. //// }
  469. // [layoutManager setGlyphs:glyphs properties:props characterIndexes:charIndexes font:aFont forGlyphRange:glyphRange];
  470. // return glyphRange.length;
  471. //}
  472. #pragma mark - sizeThatsFit
  473. - (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
  474. {
  475. //fit实现与drawTextInRect大部分一个屌样,所以不写注释了。
  476. if (numberOfLines>1&&self.adjustsFontSizeToFitWidth) {
  477. CGFloat scaleFactor = 1.0f;
  478. NSAttributedString *attributedString = [self attributedTextForTextStorageFromLabelProperties];
  479. NSUInteger stringlineCount = [attributedString.string lineCount];
  480. if (stringlineCount>self.numberOfLines) {
  481. //这里说明必然要截取
  482. attributedString = [attributedString attributedSubstringFromRange:NSMakeRange(0, [attributedString.string lengthToLineIndex:self.numberOfLines-1])];
  483. }
  484. CGFloat textWidth = [self textRectForBounds:CGRectMake(0, 0, kMLLabelFloatMax, kMLLabelFloatMax) attributedString:attributedString limitedToNumberOfLines:0 lineCount:NULL].size.width;
  485. textWidth = fmax(0, textWidth-_textInsets.left-_textInsets.right);
  486. if (textWidth>0) {
  487. CGFloat availableWidth = _textContainer.size.width*numberOfLines;
  488. if (textWidth > availableWidth) {
  489. //这里得到的scaleFactor肯定是大于这个的是必然不满足的,目的就是找这个,以能减少下面的矫正次数。
  490. scaleFactor = availableWidth / textWidth;
  491. }
  492. //一点点矫正,以使得内容能放到当前的size里
  493. NSAttributedString *resultAttributedString = attributedString;
  494. while (![self adjustsCurrentFontSizeToFitWidthWithScaleFactor:scaleFactor numberOfLines:numberOfLines originalAttributedText:attributedString bounds:bounds resultAttributedString:&resultAttributedString]){
  495. scaleFactor *= kAdjustFontSizeEveryScalingFactor;
  496. };
  497. //计算当前adjust之后的合适大小,为什么不用adjust里面的 因为也可能有异常情况,例如压根adjust就没走到计算大小那一步啊什么的。
  498. CGRect textBounds = [self textRectForBounds:bounds attributedString:resultAttributedString limitedToNumberOfLines:numberOfLines lineCount:NULL];
  499. return textBounds;
  500. }
  501. }
  502. return [self textRectForBounds:bounds attributedString:_textStorage limitedToNumberOfLines:numberOfLines lineCount:NULL];
  503. }
  504. - (CGSize)sizeThatFits:(CGSize)size
  505. {
  506. size = [super sizeThatFits:size];
  507. // if (size.height>0) {
  508. // size.height++;
  509. // }
  510. return _MLLabel_CGSizePixelRound(size);
  511. }
  512. - (CGSize)intrinsicContentSize {
  513. CGSize size = [super intrinsicContentSize];
  514. // if (size.height>0) {
  515. // size.height++;
  516. // }
  517. return _MLLabel_CGSizePixelRound(size);
  518. }
  519. - (CGSize)preferredSizeWithMaxWidth:(CGFloat)maxWidth
  520. {
  521. CGSize size = [self sizeThatFits:CGSizeMake(maxWidth, kMLLabelFloatMax)];
  522. size.width = fmin(size.width, maxWidth); //在numberOfLine为1模式下返回的可能会比maxWidth大,所以这里我们限制下
  523. return size;
  524. }
  525. #pragma mark - set 修改container size相关
  526. - (void)resizeTextContainerSize
  527. {
  528. if (_textContainer) {
  529. //usedRectForTextContainer的BUG,textContainer提供的容器大小一定要假设最后一行也有底部行间距的大小的,否则结果会少一行。
  530. //计算结果来说的话就没有最后一行的行间距,很奇葩。
  531. //https://github.com/molon/MLLabel/issues/60
  532. CGSize size = [self drawTextSizeWithBoundsSize:self.bounds.size];
  533. if (size.height<kMLLabelFloatMax) {
  534. size.height+=self.lineSpacing;
  535. }
  536. _textContainer.size = size;
  537. }
  538. }
  539. - (void)setFrame:(CGRect)frame
  540. {
  541. [super setFrame:frame];
  542. [self resizeTextContainerSize];
  543. }
  544. - (void)setBounds:(CGRect)bounds
  545. {
  546. [super setBounds:bounds];
  547. [self resizeTextContainerSize];
  548. }
  549. - (void)setTextInsets:(UIEdgeInsets)insets
  550. {
  551. _textInsets = insets;
  552. [self resizeTextContainerSize];
  553. [self invalidateIntrinsicContentSize];
  554. }
  555. #pragma mark - set container相关属性
  556. - (void)setNumberOfLines:(NSInteger)numberOfLines
  557. {
  558. BOOL isChanged = (numberOfLines!=_textContainer.maximumNumberOfLines);
  559. [super setNumberOfLines:numberOfLines];
  560. _textContainer.maximumNumberOfLines = numberOfLines;
  561. if (isChanged) {
  562. [self invalidateIntrinsicContentSize];
  563. [self setNeedsDisplay];
  564. }
  565. }
  566. - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode
  567. {
  568. [super setLineBreakMode:lineBreakMode];
  569. _textContainer.lineBreakMode = lineBreakMode;
  570. [self invalidateIntrinsicContentSize];
  571. }
  572. #pragma mark - set 其他
  573. - (void)setMinimumScaleFactor:(CGFloat)minimumScaleFactor
  574. {
  575. [super setMinimumScaleFactor:minimumScaleFactor];
  576. [self invalidateIntrinsicContentSize];
  577. [self setNeedsDisplay];
  578. }
  579. - (void)setDoBeforeDrawingTextBlock:(void (^)(CGRect rect,CGPoint beginOffset,CGSize drawSize))doBeforeDrawingTextBlock
  580. {
  581. _doBeforeDrawingTextBlock = [doBeforeDrawingTextBlock copy];
  582. [self setNeedsDisplay];
  583. }
  584. #pragma mark - UIResponder
  585. - (BOOL)canBecomeFirstResponder {
  586. return YES;
  587. }
  588. - (BOOL)canPerformAction:(SEL)action
  589. withSender:(__unused id)sender
  590. {
  591. return (action == @selector(copy:));
  592. }
  593. #pragma mark - UIResponderStandardEditActions
  594. - (void)copy:(__unused id)sender {
  595. [[UIPasteboard generalPasteboard] setString:self.text];
  596. }
  597. @end