這裏是工作代碼:
#import <Foundation/Foundation.h>
@interface NSAttributedString (AttributedFormat)
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat, ...;
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat arguments:(va_list)arguments;
@end
@implementation NSAttributedString (AttributedFormat)
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat, ... {
va_list args;
va_start(args, attrFormat);
self = [self initWithFormat:attrFormat arguments:args];
va_end(args);
return self;
}
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat arguments:(va_list)arguments {
NSRegularExpression *regex;
regex = [[NSRegularExpression alloc] initWithPattern: @"(%.*?[@%dDuUxXoOfeEgGccsSpaAF])"
options: 0
error: nil];
NSString *format = attrFormat.string;
format = [regex stringByReplacingMatchesInString: format
options: 0
range: NSMakeRange(0, format.length)
withTemplate: @"\0$1\0"];
NSString *result = [[NSString alloc] initWithFormat:format arguments:arguments];
NSMutableArray *f_comps = [format componentsSeparatedByString:@"\0"].mutableCopy;
NSMutableArray *r_comps = [result componentsSeparatedByString:@"\0"].mutableCopy;
NSMutableAttributedString *output = [[NSMutableAttributedString alloc] init];
__block int consumed_length = 0;
[attrFormat enumerateAttributesInRange:NSMakeRange(0, attrFormat.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
NSMutableString *substr = [NSMutableString string];
while(f_comps.count > 0 && NSMaxRange(range) >= consumed_length + [(NSString *)f_comps[0] length]){
NSString *f_str = f_comps[0];
NSString *r_str = r_comps[0];
[substr appendString:r_str];
[f_comps removeObjectAtIndex:0];
[r_comps removeObjectAtIndex:0];
consumed_length += f_str.length;
}
NSUInteger idx = NSMaxRange(range) - consumed_length;
if(f_comps.count > 0 && idx > 0) {
NSString *f_str = f_comps[0];
NSString *leading = [f_str substringToIndex:idx];
[substr appendString:leading];
NSString *trailing = [f_str substringFromIndex:idx];
[f_comps replaceObjectAtIndex:0 withObject:trailing];
[r_comps replaceObjectAtIndex:0 withObject:trailing];
consumed_length += idx;
}
[output appendAttributedString:[[NSAttributedString alloc] initWithString:substr attributes:attrs]];
}];
return [self initWithAttributedString:output];
}
@end
用例:
NSMutableAttributedString *fmt = [[NSMutableAttributedString alloc] initWithString:@"test: "];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: @"Some%%string"
attributes: @{
NSFontAttributeName: [UIFont systemFontOfSize:17]
}]];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: @"bold %@ template %.3f %d"
attributes: @{
NSFontAttributeName: [UIFont boldSystemFontOfSize:20],
NSForegroundColorAttributeName: [UIColor redColor]
}]];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: @"%@ blah blah blah"
attributes: @{
NSFontAttributeName: [UIFont systemFontOfSize:16],
NSForegroundColorAttributeName: [UIColor blueColor]
}]];
NSAttributedString *result = [[NSAttributedString alloc] initWithFormat:fmt, @"[foo]", 1.23, 56, @"[[bar]]"];
結果:
![screenshot](https://i.stack.imgur.com/dOa4a.png)
也許這還是有一些缺陷,但它應該在大多數工作案例。
(%.*?[@%dDuUxXoOfeEgGccsSpaAF])
此正則表達式匹配"Format Specifiers"。說明符以%
開頭並以列出的字符結尾。並可能在它們之間有一些修飾符。這不是完美的,例如這種非法格式"%__s"
應該被忽略,但我的正則表達式匹配這整個字符串。但只要說明符是合法的,它就應該有效。
我的代碼與它匹配,並插入周圍的符分隔符:
I'm %s.
I'm <delimiter>%s<delimiter>.
我用\0
作爲分隔符。
I'm \0%s\0.
然後插入它。
I'm \0rintaro\0.
然後分裂的格式和與所述分隔符的結果:
f_comps: ["I'm ", "%s", "."]
r_comps: ["I'm ", "rintaro", "."]
這裏,f_comps
總串長度是精確的相同原始歸因格式。然後,用enumerateAttributesInRange
迭代屬性,我們可以將這些屬性應用到結果中。
我很抱歉,但它是很難解釋清楚的作業裏面enumerateAttributesInRange
,用我的英語不好:)
是;請參閱'va_start()','va_next()'和'va_end()'。 – trojanfoe 2014-11-25 07:43:01
@trojanfoe我指的是佔位符百分比令牌,而不是參數本身。如果我可以確定每個百分比標記的位置,那麼我可以迭代參數列表並替換屬性字符串中的標記並保留屬性(粗體,斜體等) – SomeGuy 2014-11-25 08:21:02
查找每個'%'說明符都很簡單,一個'NSScanner',但我不清楚你想做什麼。 「我不想構建我自己的%實現...」是什麼意思? – 2014-11-25 10:16:14