ZSWTaggedString
ZSWTaggedString 将带有标签的 String
/NSString
转换为 NSAttributedString
。标签类似于 HTML,除了您定义每个标签表示的内容。
此库的目标是在分开呈现和字符串生成的同时,使创建具有属性的字符串变得更容易。这样,您可以在不连接或使用难以本地化的子串搜索的情况下对字符串进行装饰。
最常用的例子是为字符串的一部分应用样式更改。让我们格式化一个类似 "tます.MONTHLY kau neu se quick" 的字符串。
let localizedString = NSLocalizedString("bowties are <b>cool</b>", comment: "");
let taggedString = ZSWTaggedString(string: localizedString)
let options = ZSWTaggedStringOptions()
options["b"] = .static([
.font: UIFont.boldSystemFont(ofSize: 18.0)
])
let attributedString = try! taggedString.attributedString(with: options)
print(attributedString)
NSString *localizedString = NSLocalizedString(@"bowties are <b>cool</b>", nil);
ZSWTaggedString *taggedString = [ZSWTaggedString stringWithString:localizedString];
ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];
[options setAttributes:@{
NSFontAttributeName: [UIFont boldSystemFontOfSize:18.0]
} forTagName:@"b"];
NSLog(@"%@", [taggedString attributedStringWithOptions:options]);
这将生成一个具有属性的字符串,其中 "cool" 子串是粗体的,其余部分没有定义。
bowties are {
}cool{
NSFont = "<UICTFont: …> …; font-weight: bold; …; font-size: 18.00pt";
}
动态属性
您可以根据字符串中包含的元数据应用样式。让我们斜体化一个子串,同时根据故事类型更改其颜色
let story1 = Story(type: .One, name: "on<e")
let story2 = Story(type: .Two, name: "tw<o")
func storyWrap(_ story: Story) -> String {
// You should separate data-level tags from the localized strings
// so you can iterate on their definition without the .strings changing
// Ideally you'd place this on the Story class itself.
return String(format: "<story type='%d'>%@</story>",
story.type.rawValue, ZSWEscapedStringForString(story.name))
}
let format = NSLocalizedString("Pick: %@ <i>or</i> %@", comment: "On the story, ...");
let string = ZSWTaggedString(format: format, storyWrap(story1), storyWrap(story2))
let options = ZSWTaggedStringOptions()
// Base attributes apply to the whole string, before any tag attributes.
options.baseAttributes = [
.font: UIFont.systemFont(ofSize: 14.0),
.foregroundColor: UIColor.gray
]
// Normal attributes just add their attributes to the attributed string.
options["i"] = .static([
.font: UIFont.italicSystemFont(ofSize: 14.0)
])
// Dynamic attributes give you an opportunity to decide what to do for each tag
options["story"] = .dynamic({ tagName, tagAttributes, existingAttributes in
var attributes = [NSAttributedString.Key: AnyObject]()
guard let typeString = tagAttributes["type"] as? String,
let type = Story.StoryType(rawValue: typeString) else {
return attributes
}
switch type {
case .One:
attributes[.foregroundColor] = UIColor.red
case .Two:
attributes[.foregroundColor] = UIColor.orange
}
return attributes
})
Story *story1 = …, *story2 = …;
NSString *(^sWrap)(Story *) = ^(Story *story) {
// You should separate data-level tags from the localized strings
// so you can iterate on their definition without the .strings changing
// Ideally you'd place this on the Story class itself.
return [NSString stringWithFormat:@"<story type='%@'>%@</story>",
@(story.type), ZSWEscapedStringForString(story.name)];
};
NSString *fmt = NSLocalizedString(@"Pick: %@ <i>or</i> %@", @"On the story, ...");
ZSWTaggedString *string = [ZSWTaggedString stringWithFormat:fmt,
sWrap(story1), sWrap(story2)];
ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];
// Base attributes apply to the whole string, before any tag attributes.
[options setBaseAttributes:@{
NSFontAttributeName: [UIFont systemFontOfSize:14.0],
NSForegroundColorAttributeName: [UIColor grayColor]
}];
// Normal attributes just add their attributes to the attributed string.
[options setAttributes:@{
NSFontAttributeName: [UIFont italicSystemFontOfSize:14.0]
} forTagName:@"i"];
// Dynamic attributes give you an opportunity to decide what to do for each tag
[options setDynamicAttributes:^(NSString *tagName,
NSDictionary *tagAttributes,
NSDictionary *existingStringAttributes) {
switch ((StoryType)[tagAttributes[@"type"] integerValue]) {
case StoryTypeOne:
return @{ NSForegroundColorAttributeName: [UIColor redColor] };
case StoryTypeTwo:
return @{ NSForegroundColorAttributeName: [UIColor orangeColor] };
}
return @{ NSForegroundColorAttributeName: [UIColor blueColor] };
} forTagName:@"story"];
您的本地化器现在看到了一个更合理的本地化字符串
/* On the story, ... */
"Pick: %@ <i>or</i> %@" = "Pick: %1$@ <i>or</i> %2$@";
您不必求助于使用 .rangeOfString()
来格式化任何子串,这对于我们上述所期望的是非常困难的。
您可以使用两种类型的动态属性:上述类似标签特定的一个,或者嵌套全局的 unknownTagAttributes
(在 Objective-C 中为 unknownTagDynamicAttributes
),它在找到未定义的标签时调用。两者都有三个参数
tagName
标签的名称,例如上述的story
。tagAttributes
标签的属性,例如上述的["type": "1"]
。existingStringAttributes
已存在于字符串中的属性,例如[NSForegroundColorAttributeName: UIColor.redColor()]
。
您可以使用 existingStringAttributes
来处理已建立好的键。例如,让我们自动创建 <b>
、<i>
和 <u>
标签。
let options = ZSWTaggedStringOptions()
options.baseAttributes = [
.font: UIFont.systemFont(ofSize: 12.0)
]
options.unknownTagAttributes = .dynamic({ tagName, tagAttributes, existingAttributes in
var attributes = [NSAttributedString.Key: Any]()
if let font = existingAttributes[.font] as? UIFont {
switch tagName {
case "b":
attributes[.font] = UIFont.boldSystemFont(ofSize: font.pointSize)
case "i":
attributes[.font] = UIFont.italicSystemFont(ofSize: font.pointSize)
default:
break
}
}
if tagName == "u" {
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
}
return attributes
})
ZSWTaggedStringOptions *options = [ZSWTaggedStringOptions options];
[options setBaseAttributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:12.0] }];
[options setUnknownTagDynamicAttributes:^(NSString *tagName,
NSDictionary *tagAttributes,
NSDictionary *existingStringAttributes) {
BOOL isBold = [tagName isEqualToString:@"b"];
BOOL isItalic = [tagName isEqualToString:@"i"];
BOOL isUnderline = [tagName isEqualToString:@"u"];
UIFont *font = existingStringAttributes[NSFontAttributeName];
if ((isBold || isItalic) && font) {
if (isBold) {
return @{ NSFontAttributeName: [UIFont boldSystemFontOfSize:font.pointSize] };
} else if (isItalic) {
return @{ NSFontAttributeName: [UIFont italicSystemFontOfSize:font.pointSize] };
}
} else if (isUnderline) {
return @{ NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle) };
}
return @{};
}];
库默认不提供此功能,因为自定义或无法明确定义的字体和动态类型使得这种行为不可预测。您可以使用 ZSWTaggedStringOptions.registerDefaultOptions()
来使用类似于上面的全局默认选项集。
快速去除字符串
去除标签可以使您创建一个适用于快速高度计算(假设没有字体变化)、数据统计等操作的 String
,而不需要标签的开销。您可以通过在 ZSWTaggedString
上使用 .string()
方法来实现,而不是使用 .attributedString()
方法。
错误处理
非终术语符(如 <a>taco!
)或用户输入未转义的字符串(参见 坑点)被认为是程序员考虑的错误。
对于 Swift 用户,如果您提供无效输入,所有方法都会抛出异常。
对于 Objective-C 用户,有返回可选 NSError
的方法,所有方法在出错时均返回 nil
。
坑点
如果您创建的任何组合字符串中包含不在标签内的 <
字符,您必须使用 ZSWEscapedStringForString()
将该字符串包装起来。在实际情况中,这种重要之处很少见,但是您必须处理它。
安装
ZSWTaggedString 通过 CocoaPods 提供。在您的 Podfile 中添加以下行
pod "ZSWTaggedString", "~> 4.2"
pod "ZSWTaggedString/Swift", "~> 4.2" # Optional, for Swift support
许可证
ZSWTaggedString基于MIT许可证。如果您通过拉取请求进行贡献,请包括针对您修复的错误或添加的功能的适当测试。