ZSWTaggedString 4.2

ZSWTaggedString 4.2

测试已测试
Lang语言 Obj-CObjective C
许可证 MIT
发布最后发布2019 年 5 月

Zachary West 维护。



ZSWTaggedString

CI Status Version License Platform

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),它在找到未定义的标签时调用。两者都有三个参数

  1. tagName 标签的名称,例如上述的 story
  2. tagAttributes 标签的属性,例如上述的 ["type": "1"]
  3. 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许可证。如果您通过拉取请求进行贡献,请包括针对您修复的错误或添加的功能的适当测试。