关于iOS11中UILabel的问题

Posted garyzhang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于iOS11中UILabel的问题相关的知识,希望对你有一定的参考价值。

很久没来更新博客了,现在终于有点时间来写点东西了。

关于要写的东西,就是目前在项目中遇到的一个小问题,这个坑是苹果给埋的,当然苹果也是出于好意,能让显示的内容更美观。

这个问题就是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有大佬早就遇到过相同的问题了,在stackoverflow这篇帖子中,就有提到这个问题,如下图,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 }

完工!

以上是关于关于iOS11中UILabel的问题的主要内容,如果未能解决你的问题,请参考以下文章

iOS UILabel.appearance().textColor 不可用

UILabel 未在 iOS8 中显示子视图

无法在 IOS 6 中将 UILabel 添加到 UITableViewCell

在 iOS 应用程序中使用 CoreAnimation / QuartzCore 动画 UILabel

iOS开发CGRectGetMidX. CGRectGetMidY.CGRectGetMinY. CGRectGetMaxY. CGRectGetMinX. CGRectGetMaxX的使用(代码片段

iOS:多行 uilabel 仅显示带有自动布局的一行