ios label居中 label内容居中_xcode中的label文字居中怎么设置

需求

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

ios label居中 label内容居中_iOS_02

如“耐穿又耐看, 男式基础休闲牛津纺衬衫”, 用 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 实现的 带背景色的按钮、标签,无法垂直对齐了,如图中的倒计时。

ios label居中 label内容居中_xcode中的label文字居中怎么设置_03

而这个问题出现在所有 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"文字。

ios label居中 label内容居中_ios label居中_04

ios label居中 label内容居中_ios label居中_05

总结

使用 .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}, //阅读模式
                       };


推荐阅读

就差您点一下了 ???