当前位置: 首页 > 科技观察

iOS中如何正确实现行距和行高

时间:2023-03-13 00:18:09 科技观察

最近打算给VirtualView-iOS的text元素添加一个lineHeight属性,以便在使用VirtualView-Android时更准确的保证两个平台的一致性.在Google和StackOverflow上编程了一段时间后,发现能找到的资料大多是关于如何实现lineSpacing属性的,而不是lineHeight。但是我只想实现一个lineHeight,因为iOS和Android默认的lineSpacing不一致!还是自己动手吧,整理成文章,造福后人。关于行间距lineSpacing,先贴一个iOS中UILabel的默认布局样式:大家可以看到在默认布局样式下,文字的行间距很小,导致文字很拥挤。这时候设计师就会提出行间距的需求,希望能够让文字显示的更加美观。类似的注解会是这样的:一般来说,既然设计者要求行间距,那我们直接设置lineSpacing就可以了。但是UILabel没有这么直接暴露的属性。如果我们要修改lineSpacing,需要使用NSAttributedString来实现,说明代码:NSMutableParagraphStyle*paragraphStyle=[NSMutableParagraphStylenew];paragraphStyle.lineSpacing=10;NSMutableDictionary*attributes=[NSMutableDictionarydictionary];[attributessetObject:paragraphStyleforKey:NSParagraphStyleAttributeName];label.attributedText=[[NSAttributedStringalloc]initWithString:label.textattributes:attributes];运行观察效果:虽然用我们的眼睛看似乎没有问题,但是设计师的眼尖是可以看出来的,和设计稿的要求有差距:怎么会这样!?这和约定的不一样吧!?不要惊慌,让我详细解释。正确实现行间距,先看示意图:红色区域为单行文字默认绘制所占的区域。可以看到文字上下都有一些空白(蓝色和红色重叠的部分)。设计者希望蓝色区域的高度为10pt,但是我们直接设置lineSpacing将两行红色区域之间的绿色区域的高度设置为10pt,这就是问题的根源。那么这个红色区域的高度是多少呢?答案是label.font.lineHeight,它是使用指定字体绘制单行文本的原始行高。知道原因后,问题就可以解决了。我们在设置lineSpacing的时候需要减去系统自带的margin:NSMutableParagraphStyle*paragraphStyle=[NSMutableParagraphStylenew];paragraphStyle.lineSpacing=10-(label.font.lineHeight-label.font.pointSize);NSMutableDictionary*attributes=[NSMutableDictionarydictionary];[attributessetObject:paragraphStyleforKey:NSParagraphStyleAttributeName];label.attributedText=[[NSAttributedStringalloc]initWithString:label.textattributes:attributes];lineHeight如果你只关心iOS设备上的文字显示效果,那么这个就够了。但是我需要的是iOS和Android表现出完全一样的效果,单靠行间距是不能满足需求的。主要原因在前言中也提到了。Android设备上的文字默认留空(上节图中蓝色和红色重叠部分)与iOS设备不一致:左边是iOS设备,右边是Android设备.device,可以看到同样显示的是20字号,Android的行高会高一些。不同的安卓设备使用不同的字体,可能会有更多差异。如果不想抹平这种差异,就无法实现真正??意义上的双端一致性。这时候我们可以设置lineHeight,让每行文字的高度保持一致。当lineHeight设置为30pt时,一行文字的高度必须为30pt,两行文字的高度必须为60pt。虽然文字的渲染会有细微的差异,但布局上的差异将被完全消除。lineHeight同样可以借助NSAttributedString来实现,示意代码:NSMutableParagraphStyle*paragraphStyle=[NSMutableParagraphStylenew];paragraphStyle.maximumLineHeight=lineHeight;paragraphStyle.minimumLineHeight=lineHeight;NSMutableDictionary*attributes=[NSMutableDictionarydictionary];[attributessetObject:paragraphStyleforKey:NSParagraphStyleAttributeName];label.attributedText=[[NSAttributedStringalloc]initWithString:label.textattributes:attributes];运行观察效果:debug模式下,确认文字高度正确,但为什么文字显示在行的底部?修正行高增加后的文字我们可以使用baselineOffset属性来修改文字在行中显示的位置。这个属性非常有用,在实现上标下标等需求的时候经常用到。调试后发现最合适的值是(lineHeight-label.font.lineHeight)/4(一直没搞明白为什么除以4而不是2,希望知道的老司机指教可以给我一些指点)。最终的代码示例如下:NSMutableParagraphStyle*paragraphStyle=[NSMutableParagraphStylenew];paragraphStyle.maximumLineHeight=lineHeight;paragraphStyle.minimumLineHeight=lineHeight;NSMutableDictionary*attributes=[NSMutableDictionarydictionary];[attributessetObject:paragraphStyleforKey:NSParagraphStyleAttributeName];CGFloatbaselineOffset=(lineHeight-label.font.lineHeight)/4;[attributessetObject:@(baselineOffset)forKey:NSBaselineOffsetAttributeName];label.attributedText=[[NSAttributedStringalloc]initWithString:label.textattributes:attributes];粘贴不同字号和行高下的显示效果:行高A同时使用行间距的问题不得不说,行高和行间距都可以完美实现,但是当我尝试同时使用它们时,我发现了iOS的一个bug(当然可能是一个feature,毕竟没有crash不一定是bug):彩色区域为文字绘制区域,橙色区域为lineSpacing,绿色区域为line高度。但是为什么单行文本系统也会显示一个lineSpacing!?是骗局!?幸运的是,我们通常会根据不同的需求独立使用行高和行距,单独使用时不会触发此问题。所以在VirtualView-iOS库中,我暂时保持了和系统一致的高度计算逻辑。综上所述,我们已经成功地为VirtualView-iOS添加了对lineHeight属性的支持。更多实现细节可以直接查看开源库中的源码。希望我们的Tangram解决方案能够更加完善,帮助更多人同时开发两端,用七巧板拼出世界。