@@ -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+
3447static 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