xcode中的label文字居中怎么设置_曲折的“修改 attributeText 的文字”尝试

39ff4bfd113546a7429c5253bd53faf2.png

需求

在商品列表的设计中,很多商品卡片的商品名称需要换行。效果如,

1c1cb44693f1af515377add64748c311.png

如“耐穿又耐看, 男式基础休闲牛津纺衬衫”, 用 UILabel 实现。但样式不能用以下代码来实现,

label.textColor = [UIColor gray2Color];
label.font = [UIFont bold14];

因为设计稿中,文字是带有行高、间距、baselineOffset 等信息,所以需要使用 attributedText来实现。举例;

        NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];
style.minimumLineHeight = height;
NSDictionary *attribute = @{
NSFontAttributeName:font,
NSForegroundColorAttributeName:textColor,
NSParagraphStyleAttributeName:style,
NSBaselineOffsetAttributeName:@(baselineOffset)};
//
NSAttributedString *str = [[NSAttributedString alloc] initWithString:text?:@" " attributes:attribute];
label.attributedText = str;

上述代码,很常见,很长时间大家都是这么用,或再一步封装。随着开发和视觉同学确认视觉规范后,事情变的不简单起来了。

引入视觉规范

• 经过视觉同学梳理,上述的所有样式被归纳为一个 code,即 14_gray2_bold,即设置上述 attributedText 文字时,简化为一行代码;

[StyleSpec setLabelStyle:label withCode:YXCode_14_gray2_bold text:@"耐穿又耐看, 男式基础休闲牛津纺衬衫"];

• 开发同学,对上述代码不够满意——因为设置样式和文字内容不一定是一起进行。比较普遍的情况是,在 loadSubview的时候设置样式,在数据返回后设置文字内容,期望的调用方式:

- (void)loadSubView{
label.styleCode = YXCode_14_gray2_bold;
label.text = @"占位";// 可有可无,和 label.styleCode 设置顺序无关
}
- (void)fetchData{
label.text = self.data.userName;
}

上面代码调用对开发很自然、友好,但是实现起来有个难点:

生成 NSAttributedString 时是需要有文字内容的,如果 label.text 为空,这设置 attributes 的属性会丢失。即 

 label = [UILabel new];
label.styleCode = YXCode_14_gray2_bold;

这样设置是无效的,后续设置 label.text = @"some words"会显示默认 17px 黑色 regular 的样式。如果在设置 .styleCode = 之前就有文案,即;

 label = [UILabel new];
label.text = @"initial";
label.styleCode = YXCode_14_gray2_bold;

经过测试,后续修改文案可以生效,但这对调用方提出了要求,有两种方式:

• 先设置文案,再设置样式 (缺点:开发容易忘记、犯错)

• 调用样式的时候同时设置文案(缺点:在更新文案时,很不友好——loadSubview 的时候设置样式,后续修改文案还需要设置样式)

去掉 ”设置 styleCode 时对 text “ 的依赖。

理想的情况是顺序无关,即

 label.text = @"initial";
label.styleCode = YXCode_14_gray2_bold;
// 等价于
label.styleCode = YXCode_14_gray2_bold;
label.text = @"initial";

// 后续有更新内容时,修改文字
label.text = @"changed";

这样就没有调用顺序的问题,而且后续修改文字,也用最自然的方式,非常棒。

如何实现呢?

+ (void)setLabelStyle:(UILabel *)label withCode:(YXStyleCode *)code text:(NSString *)text{
UIColor *textColor = [self colorWithCode:code];
UIFont *font = [self fontWithCode:code];

BOOL readMode = [code hasSuffix:kReadModeSuffix];
NSDictionary *attrs = [self getAttributes:readMode font:font textColor:textColor];
if (attrs) {
// @" " 是为了能够让 attributes 能设置成功
NSAttributedString *str = [[NSAttributedString alloc] initWithString:text?:@" " attributes:attrs];
label.attributedText = str;
} else {
label.textColor = textColor;
label.font = font;
label.text = text;
}
}

最重要的逻辑:如果设置样式时,没有文字内容,则以 ” “ 空字符串来创建 attributedText , 这样初次渲染时,样式内容都创建了,在界面短暂显示空字符串,对用户无干扰。当需要设置后端返回的数据时,调用label.text = @"服务器返回字段";接口。

样式接口提交后,大家在模拟器开发没什么问题,等我跑 iPhone 6 的适配代码时,我发现 iOS 12 设置的字体显示不对,一个”Pro 会员“ 的商品文字标签,超出了背景色,典型的默认样式—— 上述用 @" " 来占位的方式失效了。解决方案,把 UILabel setText:hook 住;

@implementation UILabel (StyleSpec)

+ (void)load {
if (SystemVersionHigherThanOrEqualTo(@"13.0")) {
//
} else {
// iOS 13 以下的有问题,需要 hook
// 交换 spec_setText: 和 setText:
}
}

- (void)spec_setText:(NSString *)text{
NSAttributedString *attrStr = self.attributedText;
if (attrStr && text.length > 0) {
NSMutableAttributedString *newAttrStr = [attrStr mutableCopy];
[newAttrStr.mutableString setString:text];
self.attributedText = newAttrStr;
} else {
[self spec_setText:text];
}
}
@end

上述方案,在上线一个版本后,陆陆续续发现有些用 UILabel 实现的 带背景色的按钮、标签,无法垂直对齐了,如图中的倒计时。

3734a901f17781dcae03c4162a13aae8.png

而这个问题出现在所有 iOS 版本,包括 iOS 13。所以上述 UILabel setText: hook 方案修改为也包含 iOS 13,解决垂直不对齐的问题。

为什么对不齐

 label.text = @"initial";
label.styleCode = YXCode_14_gray2_bold;

// 后续有更新内容时,修改文字,此时会出现无法对齐的问题。
label.text = @"changed";

但是如果第二次修改文字时,同时设置样式:

[StyleSpec setLabelStyle:label withCode:YXCode_14_gray2_bold text:@"changed"];

则不会出现此问题。经过两种方式输出对应的 attributedString 的对象,发现属性全部都一样,只是在渲染时有所不同。

这是为什么呢?有两种猜测;

• 使用 label.attributedText = NSAttributedString 设置的文字样式,就不应该使用label.text = @"changed"; 来更新。至于现在 iOS 13 以上,继承了大部分属性貌似是可以,可以理解为是没有特殊处理,导致的现象,不是 Apple 的意图,是个巧合。

• iOS 13,苹果对于简单的 attributedText(指单个样式),故意实现了用 .text = 去修改 attributedText = 的功能,只是实现的有些 bug。对于如划线价+原价这种复杂的 attributedText,则使用默认样式渲染"changed"文字。

9af5073aa0a82b8b368f8c3b2c1e68a4.png

705e95f7f35e38d254e8480efb5c0b9f.png

总结

使用 .text = 去修改 attributedText = 的功能的最佳实践;

• 使用空字符 ” “ 首先设置 styleCode 来设置的样式属性

• hook 掉 UILabel setText:在更新的时候,自动获取旧的 attributes 属性,更新文案。

• 如果遇到复杂的 attributedText(如划线价+促销价格),还是使用来更新文字内容(如果用 setText 来更新赋值样式,则会用 attributes 里前一组来渲染文案。

欢迎大家勘误。

严选的字号 -> 行高、边距的配置

// 普通模式
config = @{@9:@{@"height":@12, @"lineSpace":@1, @"baseline":@0.4},
@10:@{@"height":@15, @"lineSpace":@2, @"baseline":@0.8},
@11:@{@"height":@16, @"lineSpace":@2, @"baseline":@0.8},
@12:@{@"height":@18, @"lineSpace":@2.5, @"baseline":@1},
@14:@{@"height":@20, @"lineSpace":@3, @"baseline":@0.8},
@15:@{@"height":@22, @"lineSpace":@3.5, @"baseline":@1.1},
@16:@{@"height":@24, @"lineSpace":@4, @"baseline":@1.1},
@18:@{@"height":@26, @"lineSpace":@4, @"baseline":@1.2},
@22:@{@"height":@32, @"lineSpace":@4.5, @"baseline":@1.5},
@24:@{@"height":@36, @"lineSpace":@5, @"baseline":@2},
@27:@{@"height":@40, @"lineSpace":@6, @"baseline":@2},
};
//阅读模式,如评论中
readModeConfig = @{@14:@{@"height":@22, @"lineSpace":@4, @"baseline":@1.4}, //阅读模式
};
推荐阅读
• Facebook “SDK 攻击” 后的一些思考 • 一道Block面试题的深入挖掘 • 一个基于汇编的中心重定向框架 • 「iOS」高仿【少数派】客户端 代码+思路讲解

就差您点一下了 ???

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值