Skip to content

Commit 3a04d67

Browse files
committed
优化UILabel qmui_textAttributes 和 setAttributedText 混用时的表现
1 parent 45e6286 commit 3a04d67

2 files changed

Lines changed: 91 additions & 13 deletions

File tree

QMUI/QMUIKit/UIKitExtensions/UILabel+QMUI.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,18 @@
1313
- (instancetype)initWithFont:(UIFont *)font textColor:(UIColor *)textColor;
1414

1515
/**
16-
* 当设置了这个属性后,setText: 方法会自动把文字转成 NSAttributedString 然后再添加上去,无需每次都自己构造 attributedString
17-
* @note 如果先调用 setText: 然后再设置这个 textAttributes,则已经设置过的 text 仍然会被转换成 NSAttributedString
18-
* @note 如果包含了 NSKernAttributeName ,则此方法会自动帮你去掉最后一个字的 kern 效果,否则容易导致文字整体在视觉上不居中
19-
* 默认为nil
16+
* @brief 在需要特殊样式时,可通过此属性直接给整个 label 添加 NSAttributeName 系列样式,然后 setText 即可,无需使用繁琐的 attributedText
17+
*
18+
* @note 即使先调用 setText/attributedText ,然后再设置此属性,此属性仍然会生效
19+
* @note 如果此属性包含了 NSKernAttributeName ,则最后一个字的 kern 效果会自动被移除,否则容易导致文字在视觉上不居中
20+
*
21+
* 现在你有三种方法控制 label 的样式:
22+
* 1. 本身的样式属性(如 textColor, font 等)
23+
* 2. qmui_textAttributes
24+
* 3. 构造 NSAttributedString
25+
* 这三种方式可以同时使用,如果样式发生冲突(比如先通过方法1将文字设成红色,又通过方法2将文字设成蓝色),则绝大部分情况下代码执行顺序靠后的会最终生效
26+
* 唯一例外的极端情况是:先用方法2将文字设成红色,再用方法1将文字设成蓝色,最后再 setText,这时虽然代码执行顺序靠后的是方法1,但最终生效的会是方法2,为了避免这种极端情况的困扰,建议不要同时使用方法1和方法2去设置同一种样式。
27+
*
2028
*/
2129
@property(nonatomic, copy) NSDictionary<NSString *, id> *qmui_textAttributes;
2230

QMUI/QMUIKit/UIKitExtensions/UILabel+QMUI.m

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,100 @@ + (void)load {
1717
static dispatch_once_t onceToken;
1818
dispatch_once(&onceToken, ^{
1919
ReplaceMethod([self class], @selector(setText:), @selector(qmui_setText:));
20+
ReplaceMethod([self class], @selector(setAttributedText:), @selector(qmui_setAttributedText:));
2021
});
2122
}
2223

2324
- (void)qmui_setText:(NSString *)text {
24-
[self qmui_setText:text];
25-
if (self.qmui_textAttributes && text) {
26-
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text attributes:self.qmui_textAttributes];
27-
// NSKernAttributeName 会给最后一个字的右侧也添加间距,这常常不是想要的效果(因为会导致文字整体在视觉上不居中),因此这里把最后一个字的 kern 效果去掉
28-
if (attributedString.length) {
29-
[attributedString removeAttribute:NSKernAttributeName range:NSMakeRange(attributedString.length - 1, 1)];
30-
}
31-
self.attributedText = [[NSAttributedString alloc] initWithAttributedString:attributedString];
25+
if (!self.qmui_textAttributes.count || !text) {
26+
[self qmui_setText:text];
27+
return;
3228
}
29+
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:text attributes:self.qmui_textAttributes];
30+
[self qmui_setAttributedText:[self attributedStringWithEndKernRemoved:attributedString]];
3331
}
32+
33+
// 在 qmui_textAttributes 样式基础上添加用户传入的 attributedString 中包含的新样式。换句话说,如果这个方法里有样式冲突,则以 attributedText 为准
34+
- (void)qmui_setAttributedText:(NSAttributedString *)text {
35+
if (!self.qmui_textAttributes.count || !text) {
36+
[self qmui_setAttributedText:text];
37+
return;
38+
}
39+
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text.string attributes:self.qmui_textAttributes];
40+
attributedString = [[self attributedStringWithEndKernRemoved:attributedString] mutableCopy];
41+
[text enumerateAttributesInRange:NSMakeRange(0, text.length) options:0 usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
42+
[attributedString addAttributes:attrs range:range];
43+
}];
44+
[self qmui_setAttributedText:attributedString];
45+
}
46+
3447
static char kAssociatedObjectKey_textAttributes;
48+
// 在现有样式基础上增加 qmui_textAttributes 样式。换句话说,如果这个方法里有样式冲突,则以 qmui_textAttributes 为准
3549
- (void)setQmui_textAttributes:(NSDictionary<NSString *, id> *)qmui_textAttributes {
50+
NSDictionary *prevTextAttributes = self.qmui_textAttributes;
51+
if ([prevTextAttributes isEqualToDictionary:qmui_textAttributes]) {
52+
return;
53+
}
54+
3655
objc_setAssociatedObject(self, &kAssociatedObjectKey_textAttributes, qmui_textAttributes, OBJC_ASSOCIATION_COPY_NONATOMIC);
37-
[self setText:self.text];
56+
57+
if (!self.text.length) {
58+
return;
59+
}
60+
NSMutableAttributedString *string = [self.attributedText mutableCopy];
61+
NSRange fullRange = NSMakeRange(0, string.length);
62+
63+
// 1)清除掉旧的通过 qmui_textAttributes 设置的样式
64+
if (prevTextAttributes) {
65+
// 找出现在 attributedText 中哪些 attrs 是通过上次的 qmui_textAttributes 设置的
66+
NSMutableArray *willRemovedAttributes = [NSMutableArray array];
67+
[string enumerateAttributesInRange:NSMakeRange(0, string.length) options:0 usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
68+
// 如果存在 kern 属性,则只有 range 是第一个字至倒数第二个字,才有可能是通过 qmui_textAttribtus 设置的
69+
if (NSEqualRanges(range, NSMakeRange(0, string.length - 1)) && [attrs[NSKernAttributeName] isEqualToNumber:prevTextAttributes[NSKernAttributeName]]) {
70+
[string removeAttribute:NSKernAttributeName range:NSMakeRange(0, string.length - 1)];
71+
}
72+
// 上面排除掉 kern 属性后,如果 range 不是整个字符串,那肯定不是通过 qmui_textAttributes 设置的
73+
if (!NSEqualRanges(range, fullRange)) {
74+
return;
75+
}
76+
[attrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull attr, id _Nonnull value, BOOL * _Nonnull stop) {
77+
if (prevTextAttributes[attr] == value) {
78+
[willRemovedAttributes addObject:attr];
79+
}
80+
}];
81+
}];
82+
[willRemovedAttributes enumerateObjectsUsingBlock:^(id _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
83+
[string removeAttribute:attr range:fullRange];
84+
}];
85+
}
86+
87+
// 2)添加新样式
88+
if (qmui_textAttributes) {
89+
[string addAttributes:qmui_textAttributes range:fullRange];
90+
}
91+
// 不能调用 setAttributedText: ,否则若遇到样式冲突,那个方法会让用户传进来的 NSAttributedString 样式覆盖 qmui_textAttributes 的样式
92+
[self qmui_setAttributedText:[self attributedStringWithEndKernRemoved:string]];
3893
}
3994

4095
- (NSDictionary *)qmui_textAttributes {
4196
return (NSDictionary *)objc_getAssociatedObject(self, &kAssociatedObjectKey_textAttributes);
4297
}
4398

99+
// 去除最后一个字的 kern 效果,使得文字整体在视觉上居中
100+
- (NSAttributedString *)attributedStringWithEndKernRemoved:(NSAttributedString *)string {
101+
if (!string || !string.length) {
102+
return string;
103+
}
104+
NSMutableAttributedString *attributedString = nil;
105+
if ([string isKindOfClass:[NSMutableAttributedString class]]) {
106+
attributedString = (NSMutableAttributedString *)string;
107+
} else {
108+
attributedString = [string mutableCopy];
109+
}
110+
[attributedString removeAttribute:NSKernAttributeName range:NSMakeRange(string.length - 1, 1)];
111+
return [[NSAttributedString alloc] initWithAttributedString:attributedString];
112+
}
113+
44114
- (instancetype)initWithFont:(UIFont *)font textColor:(UIColor *)textColor {
45115
if (self = [super init]) {
46116
self.font = font;

0 commit comments

Comments
 (0)