很久没来更新博客了,现在终于有点时间来写点东西了。
关于要写的东西,就是目前在项目中遇到的一个小问题,这个坑是苹果给埋的,当然苹果也是出于好意,能让显示的内容更美观。
这个问题就是iOS中UILabel的文字展示的东西。先说说问题从哪里出来的。
项目中需要展示一段文字,一句英文,这句英文有长有短,在结尾处要跟一个播放的icon。
要做这个需求,首先想到的是用NSAttributeString来处理做成NSTextAttachment的图片。但是问题是这个icon是一个gif,需要支持动画,那如果用NSTextAttachment来做,一直去改变NSAttributionString的话就很恶心,而且还有一种情况,如果刚好文本填满一行,icon就会换行,所以NSTextAttachment的方案PASS。
那么就普通的UILabel做展示,同层级加一个UIImageView来做animation就够了(P.S UIImageView处理gif其实很不舒服,可以用别的成熟的第三方gif框架)。那么第一个问题来了,如何获取UILabel中最后一个字符的frame呢?万能的google中查到了解决方案:
1 - (CGRect)characterRectAtIndex:(NSInteger)index { 2 NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[self attributedText]]; 3 NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; 4 [textStorage addLayoutManager:layoutManager]; 5 NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size]; 6 textContainer.lineFragmentPadding = 0; 7 [layoutManager addTextContainer:textContainer]; 8 NSRange glyphRange; 9 [layoutManager characterRangeForGlyphRange:NSMakeRange(index, 1) actualGlyphRange:&glyphRange]; 10 return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]; 11 }
在UILabel的category中加上这个方法,就能获取到了,perfect!
功能完成,继续开发别的功能~
... ...
... ...
... ...
等下,有问题了!
icon位置不对!
写个demo看下`characterRectAtIndex:`方法有啥问题没。
checking...
没问题啊,都没问题啊,为什么就是显示不对呢???
checking...
发现问题了!
明明我的UILabel这行还能放一个单词,但是这个单词居然换到下一行显示了!为啥?咋整?
googleing...
发现Stack Overflow有大佬早就遇到过相同的问题了,在*这篇帖子中,就有提到这个问题,如下图,of换行了!为什么!
OK,大佬给了链接,Rags, Widows & Orphans,总结起来,苹果不想让UILabel展示出现“寡妇”单词。
形如下图中左图,最后一个单词all就是一个“寡妇”单词,在段尾且独占一行。寡妇是印刷术中的概念,文章中还介绍了“抹布”和“孤儿”。
我们现在问题是“寡妇”,就看“寡妇”这个问题吧。“寡妇”这种情况会被认为是一种不美观的排版,因为它在段落之间或页面底部留下了太多的空白,这会打断读者的眼睛并降低可读性。
所以,苹果就在iOS11中更新了排版的逻辑。
但是!自定义NSTextStorage和NSLayoutManager却不会去理会“寡妇”,导致获取字符frame出错!
那怎么办?Stack Overflow中大佬给的解决方案,改用UITextView。emmmmm,那就试试吧,这个地方只能弃用UILabel,改用UITextView。
1 UITextView *textView = [[UITextView alloc] init]; 2 textView.showsVerticalScrollIndicator = NO; 3 textView.showsHorizontalScrollIndicator = NO; 4 textView.editable = NO; 5 textView.selectable = NO; 6 textView.backgroundColor = [UIColor clearColor]; 7 textView.textContainer.lineFragmentPadding = 0; 8 textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0); 9 textView.scrollEnabled = NO; 10 textView.bounces = NO;
把UITextView能关的,改关的都关了,做成一个UILabel的样子,build and run。。。成了!
最后,我们还是要把icon放上去,同样用NSTextStorage和NSLayoutManager来获取最后一个字符的位置。
1 - (CGRect)frameOfLastCharacter { 2 NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:self.textView.attributedText]; 3 NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init]; 4 [textStorage addLayoutManager:layoutManager]; 5 NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self.textView bounds].size]; 6 textContainer.lineFragmentPadding = 0; 7 [layoutManager addTextContainer:textContainer]; 8 NSRange glyphRange; 9 [layoutManager characterRangeForGlyphRange:NSMakeRange(self.textView.attributedText.string.length - 1, 1) actualGlyphRange:&glyphRange]; 10 CGRect sentenceLastCharFrame = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]; 11 CGRect frame = [self.textView convertRect:sentenceLastCharFrame toView:self.view]; 12 return frame; 13 }
完工!