AttributedString——不仅仅让文字更漂亮

2022-07-28 12:55:19 浏览数 (1)

AttributedString——不仅仅让文字更漂亮

在WWDC 2021上,苹果为开发者带来了有一个期待已久的功能——AttributedString,这意味着Swift开发人员不再需要使用基于Objective-C的NSAttributedString来创建样式化文本。本文将对其做全面的介绍并演示如何创建自定义属性。

如果想获得更好的阅读体验,请访问我的博客www.fatbobman.com

初步印象

AttributedString是具有单个字符或字符范围的属性的字符串。属性提供了一些特征,如用于显示的视觉风格、用于无障碍引导以及用于在数据源之间进行链接的超链接数据等。

下面的代码将生成一个包含粗体以及超链接的属性字符串。

代码语言:javascript复制
var attributedString = AttributedString("请访问肘子的博客")let zhouzi = attributedString.range(of: "肘子")!  // 获取肘子二字的范围(Range)attributedString[zhouzi].inlinePresentationIntent = .stronglyEmphasized // 设置属性——粗体let blog = attributedString.range(of: "博客")! attributedString[blog].link = URL(string: "https://www.fatbobman.com")! // 设置属性——超链接

image-20211007165456612

在WWDC 2021之前,SwiftUI没有提供对属性字符串的支持,如果我们希望显示具有丰富样式的文本,通常会采用以下三种方式:

•将UIKit或AppKit控件包装成SwiftUI控件,在其中显示NSAttributedString•通过代码将NSAttributedString转换成对应的SwiftUI布局代码•使用SwiftUI的原生控件组合显示

下面的文字随着SwiftUI版本的变化,可采取的手段也在不断地增加(不使用NSAttributedString):

image-20211006163659029

SwiftUI 1.0

代码语言:javascript复制
    @ViewBuilder    var helloView:some View{        HStack(alignment:.lastTextBaseline, spacing:0){            Text("Hello").font(.title).foregroundColor(.red)            Text(" world").font(.callout).foregroundColor(.cyan)        }    }

SwiftUI 2.0

SwiftUI 2.0增强了Text的功能,我们可以将不同的Text通过 合并显示

代码语言:javascript复制
    var helloText:Text {        Text("Hello").font(.title).foregroundColor(.red)   Text(" world").font(.callout).foregroundColor(.cyan)    }

SwiftUI 3.0

除了上述的方法外,Text添加了对AttributedString的原生支持

代码语言:javascript复制
    var helloAttributedString:AttributedString {        var hello = AttributedString("Hello")        hello.font = .title.bold()        hello.foregroundColor = .red        var world = AttributedString(" world")        world.font = .callout        world.foregroundColor = .cyan        return hello   world    }        Text(helloAttributedString)

单纯看上面的例子,并不能看到AttributedString有什么优势。相信随着继续阅读本文,你会发现AttributedString可以实现太多以前想做而无法做到的功能和效果。

AttributedString vs NSAttributedString

AttributedString基本上可以看作是NSAttributedString的Swift实现,两者在功能和内在逻辑上差别不大。但由于形成年代、核心代码语言等,两者之间仍有不少的区别。本节将从多个方面对它们进行比较。

类型

AttributedString是值类型的,这也是它同由Objective-C构建的NSAttributedString(引用类型)之间最大的区别。这意味着它可以通过Swift的值语义,像其他值一样被传递、复制和改变。

NSAttributedString 可变或不可变需不同的定义

代码语言:javascript复制
let hello = NSMutableAttributedString("hello")let world = NSAttributedString(" world")hello.append(world)

AttributedString

代码语言:javascript复制
var hello = AttributedString("hello")let world = AttributedString(" world")hello.append(world)

安全性

在AttributedString中需要使用Swift的点或键语法按名称访问属性,不仅可以保证类型安全,而且可以获得编译时检查的优势。

AttributedString中基本不采用NSAttributedString如下的属性访问方式,极大的减少出错几率

代码语言:javascript复制
// 可能出现类型不匹配let attributes: [NSAttributedString.Key: Any] = [    .font: UIFont.systemFont(ofSize: 72),    .foregroundColor: UIColor.white,]

本地化支持

Attributed提供了原生的本地化字符串支持,并可为本地化字符串添加了特定属性。

代码语言:javascript复制
var localizableString = AttributedString(localized: "Hello (Date.now,format: .dateTime) world",locale: Locale(identifier: "zh-cn"),option:.applyReplacementIndexAttribute)

Formatter支持

同为WWDC 2021中推出的新Formatter API全面支持了AttributedString类型的格式化输出。我们可以轻松实现过去无法完成的工作。

代码语言:javascript复制
var dateString: AttributedString {        var attributedString = Date.now.formatted(.dateTime            .hour()            .minute()            .weekday()            .attributed        )        let weekContainer = AttributeContainer()            .dateField(.weekday)        let colorContainer = AttributeContainer()            .foregroundColor(.red)        attributedString.replaceAttributes(weekContainer, with: colorContainer)        return attributedString}Text(dateString)

image-20211006183053713

更多关于新Formatter API同AttributedString配合范例,请参阅WWDC 2021新Formatter API:新老比较及如何自定义[1]

SwiftUI集成

SwiftUI的Text组件提供了对AttributedString的原生支持,改善了一个SwiftUI的长期痛点(不过TextField、TextEdit仍不支持)。

AttributedString同时提供了SwiftUI、UIKit、AppKit三种框架的可用属性。UIKit或AppKit的控件同样可以渲染AttributedString(需经过转换)。

支持的文件格式

AttributedString目前仅具备对Markdown格式文本进行解析的能力。同NSAttributedString支持Markdown、rtf、doc、HTML相比仍有很大差距。

转换

苹果为AttributedString和NSAttributedString提供了相互转换的能力。

代码语言:javascript复制
// AttributedString -> NSAttributedStringlet nsString = NSMutableAttributedString("hello")var attributedString = AttributedString(nsString)// NSAttribuedString -> AttributedStringvar attString = AttributedString("hello")attString.uiKit.foregroundColor = .redlet nsString1 = NSAttributedString(attString)

开发者可以充分利用两者各自的优势进行开发。比如:

•用NSAttributedString解析HTML,然后转换成AttributedString调用•用AttributedString创建类型安全的字符串,在显示时转换成NSAttributedString

基础

本节中,我们将对AttributedString中的一些重要概念做介绍,并通过代码片段展示AttributedString更多的用法。

AttributedStringKey

AttributedStringKey定义了AttributedString属性名称和类型。通过点语法或KeyPath,在保证类型安全的前提进行快捷访问。

代码语言:javascript复制
var string = AttributedString("hello world")// 使用点语法string.font = .calloutlet font = string.font // 使用KeyPathlet font = string[keyPath:.font] 

除了使用系统预置的大量属性外,我们也可以创建自己的属性。例如:

代码语言:javascript复制
enum OutlineColorAttribute : AttributedStringKey {    typealias Value = Color // 属性类型    static let name = "OutlineColor" // 属性名称}string.outlineColor = .blue

我们可以使用点语法或KeyPath对 AttributedString、AttributedSubString、AttributeContainer以及AttributedString.Runs.Run的属性进行访问。更多用法参照本文其他的代码片段。

AttributeContainer

AttributeContainer是属性容器。通过配置container,我们可以一次性地为属性字符串(或片段)设置、替换、合并大量的属性。

设置属性

代码语言:javascript复制
var attributedString = AttributedString("Swift")string.foregroundColor = .red var container = AttributeContainer()container.inlinePresentationIntent = .strikethroughcontainer.font = .captioncontainer.backgroundColor = .pinkcontainer.foregroundColor = .green //将覆盖原来的redattributedString.setAttributes(container) // attributdString此时拥有四个属性内容

替换属性

代码语言:javascript复制
var container = AttributeContainer()container.inlinePresentationIntent = .strikethroughcontainer.font = .captioncontainer.backgroundColor = .pinkcontainer.foregroundColor = .greenattributedString.setAttributes(container)// 此时attributedString有四个属性内容 font、backgroundColor、foregroundColor、inlinePresentationIntent// 被替换的属性var container1 = AttributeContainer()container1.foregroundColor = .greencontainer1.font = .caption// 将要替换的属性var container2 = AttributeContainer()container2.link = URL(string: "https://www.swift.org")// 被替换属性contianer1的属性键值内容全部符合才可替换,比如continaer1的foregroundColor为.red将不进行替换attributedString.replaceAttributes(container1, with: container2)// 替换后attributedString有三个属性内容 backgroundColor、inlinePresentationIntent、link

合并属性

代码语言:javascript复制
var container = AttributeContainer()container.inlinePresentationIntent = .strikethroughcontainer.font = .captioncontainer.backgroundColor = .pinkcontainer.foregroundColor = .greenattributedString.setAttributes(container)// 此时attributedString有四个属性内容 font、backgroundColor、foregroundColor、inlinePresentationIntentvar container2 = AttributeContainer()container2.foregroundColor = .redcontainer2.link = URL(string: "www.swift.org")attributedString.mergeAttributes(container2,mergePolicy: .keepNew)// 合并后attributedString有五个属性 ,font、backgroundColor、foregroundColor、inlinePresentationIntent及link // foreground为.red// 属性冲突时,通过mergePolicy选择合并策略 .keepNew(默认) 或 .keepCurrent

AttributeScope

属性范围是系统框架定义的属性集合,将适合某个特定域中的属性定义在一个范围内,一方面便于管理,另一方面也解决了不同框架下相同属性名称对应类型不一致的问题。

目前,AttributedString提供了5个预置的Scope,分别为

•foundation包含有关Formatter、Markdown、URL以及语言变形方面的属性•swiftUI可以在SwiftUI下被渲染的属性,例如foregroundColor、backgroundColor、font等。目前支持的属性明显少于uiKit和appKit。估计待日后SwiftUI提供更多的显示支持后会逐步补上其他暂不支持的属性。•uiKit可以在UIKit下被渲染的属性。•appKit可以在AppKit下被渲染的属性•accessibility适用于无障碍的属性,用于提高引导访问的可用性。

在swiftUI、uiKit和appKit三个scope中存在很多的同名属性(比如foregroundColor),在访问时需注意以下几点:

•当Xcode无法正确推断该适用哪个Scope中的属性时,请显式标明对应的AttributeScope

代码语言:javascript复制
uiKitString.uiKit.foregroundColor = .red //UIColorappKitString.appKit.backgroundColor = .yellow //NSColor

•三个框架的同名属性并不能互转,如想字符串同时支持多框架显示(代码复用),请分别为不同Scope的同名属性赋值

代码语言:javascript复制
attributedString.swiftUI.foregroundColor = .redattributedString.uiKit.foregroundColor = .redattributedString.appKit.foregroundColor = .red// 转换成NSAttributedString,可以只转换指定的Scope属性let nsString = try! NSAttributedString(attributedString, including: .uiKit)

•为了提高兼容性,部分功能相同的属性,可以在foundation中设置。

代码语言:javascript复制
attributedString.inlinePresentationIntent = .stronglyEmphasized //相当于 bold

•swiftUI、uiKit和appKit三个Scope在定义时,都已经分别包含了foundation和accessibility。因此在转换时即使只指定单一框架,foundation和accessibility的属性也均可正常转换。我们在自定义Scope时,最好也遵守该原则。

代码语言:javascript复制
let nsString = try! NSAttributedString(attributedString, including: .appKit)// attributedString中属于foundation和accessibility的属性也将一并被转换

视图

在属性字符串中,属性和文本可以被独立访问,AttributedString提供了三种视图方便开发者从另一个维度访问所需的内容。

Character和unicodeScalar视图

这两个视图提供了类似NSAttributedString的string属性的功能,让开发者可以在纯文本的维度操作数据。两个视图的唯一区别是类型不同,简单来说,你可以把ChareacterView看作是Charecter集合,而UnicodeScalarView看作是Unicode标量合集。

字符串长度

代码语言:javascript复制
var attributedString = AttributedString("Swift")attributedString.characters.count // 5

长度2

代码语言:javascript复制
let attributedString = AttributedString("hello 


	

0 人点赞