前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AttributedString——不仅仅让文字更漂亮

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

作者头像
东坡肘子
发布于 2022-07-28 04:55:19
发布于 2022-07-28 04:55:19
4K00
代码可运行
举报
运行总次数:0
代码可运行

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

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

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

初步印象

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
    @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
代码运行次数:0
运行
AI代码解释
复制
    var helloText:Text {        Text("Hello").font(.title).foregroundColor(.red) + Text(" world").font(.callout).foregroundColor(.cyan)    }

SwiftUI 3.0

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    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
代码运行次数:0
运行
AI代码解释
复制
let hello = NSMutableAttributedString("hello")let world = NSAttributedString(" world")hello.append(world)

AttributedString

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var hello = AttributedString("hello")let world = AttributedString(" world")hello.append(world)

安全性

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

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

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

本地化支持

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var localizableString = AttributedString(localized: "Hello \(Date.now,format: .dateTime) world",locale: Locale(identifier: "zh-cn"),option:.applyReplacementIndexAttribute)

Formatter支持

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
// 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
代码运行次数:0
运行
AI代码解释
复制
var string = AttributedString("hello world")// 使用点语法string.font = .calloutlet font = string.font // 使用KeyPathlet font = string[keyPath:\.font] 

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

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

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

AttributeContainer

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

设置属性

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

替换属性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
uiKitString.uiKit.foregroundColor = .red //UIColorappKitString.appKit.backgroundColor = .yellow //NSColor

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
attributedString.inlinePresentationIntent = .stronglyEmphasized //相当于 bold

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

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

视图

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

Character和unicodeScalar视图

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

字符串长度

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var attributedString = AttributedString("Swift")attributedString.characters.count // 5

长度2

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let attributedString = AttributedString("hello 👩🏽‍🦳")attributedString.characters.count // 7attributedString.unicodeScalars.count // 10

转换成字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String(attributedString.characters) // "Swift"

替换字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var attributedString = AttributedString("hello world")let range = attributedString.range(of: "hello")!attributedString.characters.replaceSubrange(range, with: "good")// good world ,替换后的good仍会保留hello所在位置的所有属性
Runs视图

AttributedString的属性视图。每个Run对应一个属性完全一致的字符串片段。用for-in语法来迭代AttributedString的runs属性。

**只有一个Run **

整个属性字符串中所有的字符属性都一致

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let attributedString = AttribuedString("Core Data")print(attributedString)// Core Data {}print(attributedString.runs.count) // 1

两个Run

属性字符串coreDataCore Data两个片段的属性不相同,因此产生了两个Run

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var coreData = AttributedString("Core")coreData.font = .titlecoreData.foregroundColor = .greencoreData.append(AttributedString(" Data"))for run in coreData.runs { //runs.count = 2    print(run)}// Core { //             SwiftUI.Font = Font(provider: SwiftUI.(unknown context at $7fff5cd3a0a0).FontBox<SwiftUI.Font.(unknown context at $7fff5cd66db0).TextStyleProvider>)//            SwiftUI.ForegroundColor = green//            }// Data {}

多个Run

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var multiRunString = AttributedString("The attributed runs of the attributed string, as a view into the underlying string.")while let range = multiRunString.range(of: "attributed") {    multiRunString.characters.replaceSubrange(range, with: "attributed".uppercased())    multiRunString[range].inlinePresentationIntent = .stronglyEmphasized}var n = 0for run in multiRunString.runs {    n += 1}// n = 5

最终结果:The ATTRIBUTED runs of the ATTRIBUTED string, as a view into the underlying string.

利用Run的range进行属性设置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 继续使用上文的multiRunString// 将所有非强调字符设置为黄色for run in multiRunString.runs {    guard run.inlinePresentationIntent != .stronglyEmphasized else {continue}    multiRunString[run.range].foregroundColor = .yellow}

通过Runs获取指定的属性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 将颜色为黄色且为粗体的文字改成红色for (color,intent,range) in multiRunString.runs[\.foregroundColor,\.inlinePresentationIntent] {    if color == .yellow && intent == .stronglyEmphasized {        multiRunString[range].foregroundColor = .red    }}

通过Run的attributes收集所有使用到的属性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var totalKeysContainer = AttributeContainer()for run in multiRunString.runs{    let container = run.attributes    totalKeysContainer.merge(container)}

使用Runs视图可以方便的从众多属性中获取到需要的信息

不使用Runs视图,达到类似的效果

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
multiRunString.transformingAttributes(\.foregroundColor,\.font){ color,font in    if color.value == .yellow && font.value == .title {        multiRunString[color.range].backgroundColor = .green    }}

尽管没有直接调用Runs视图,不过transformingAttributes闭包的调用时机同Runs的时机是一致的。transformingAttributes最多支持获取5个属性。

Range

在本文之前的代码中,已经多次使用过Range来对属性字符串的内容进行访问或修改。

对属性字符串中局部内容的属性进行修改可以使用两种方式:

•通过Range•通过AttributedContainer

通过关键字获取Range

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 从属性字符串的结尾向前查找,返回第一个满足关键字的range(忽略大小写)if let range = multiRunString.range(of: "Attributed", options: [.backwards, .caseInsensitive]) {    multiRunString[range].link = URL(string: "https://www.apple.com")}

使用Runs或transformingAttributes获取Range

之前的例子中已反复使用

通过本文视图获取Range

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if let lowBound = multiRunString.characters.firstIndex(of: "r"),   let upperBound = multiRunString.characters.firstIndex(of: ","),   lowBound < upperBound{    multiRunString[lowBound...upperBound].foregroundColor = .brown}

本地化

创建本地化属性字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Localizable Chinese"hello" = "你好";// Localizable English"hello" = "hello";let attributedString = AttributedString(localized: "hello")

在英文和中文环境中,将分别显示为hello你好

目前本地化的AttributedString只能显示为当前系统设置的语言,并不能指定成某个特定的语言

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var hello = AttributedString(localized: "hello")if let range = hello.range(of: "h") {    hello[range].foregroundColor = .red}

本地化字符串的文字内容将随系统语言而变化,上面的代码在中文环境下将无法获取到range。需针对不同的语言做调整。

replacementIndex

可以为本地化字符串的插值内容设定index(通过applyReplacementIndexAttribute),方便在本地化内容中查找

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// Localizable Chinese"world %@ %@" = "%@ 世界 %@";// Localizable English"world %@ %@" = "world %@ %@";var world = AttributedString(localized: "world \("👍") \("🥩")",options: .applyReplacementIndexAttribute) // 创建属性字符串时,将按照插值顺序设定index ,👍 index == 1 🥩 index == 2for (index,range) in world.runs[\.replacementIndex] {    switch index {        case 1:            world[range].baselineOffset = 20            world[range].font = .title        case 2:            world[range].backgroundColor = .blue        default:            world[range].inlinePresentationIntent = .strikethrough    }}

在中文和英文环境中,分别为:

image-20211007083048701

image-20211007083115822

使用locale设定字符串插值中的Formatter

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 AttributedString(localized: "\(Date.now, format: Date.FormatStyle(date: .long))", locale: Locale(identifier: "zh-cn"))// 即使在英文环境中也会显示 2021年10月7日

用Formatter生成属性字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        var dateString = Date.now.formatted(.dateTime.year().month().day().attributed)        dateString.transformingAttributes(\.dateField) { dateField in            switch dateField.value {            case .month:                dateString[dateField.range].foregroundColor = .red            case .day:                dateString[dateField.range].foregroundColor = .green            case .year:                dateString[dateField.range].foregroundColor = .blue            default:                break            }        }

image-20211007084630319

Markdown符号

从SwiftUI 3.0开始,Text已经对部分Markdown标签提供了支持。在本地化的属性字符串中,也提供了类似的功能,并且会在字符串中设置对应的属性。提供了更高的灵活性。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var markdownString = AttributedString(localized: "**Hello** ~world~ _!_")for (inlineIntent,range) in markdownString.runs[\.inlinePresentationIntent] {    guard let inlineIntent = inlineIntent else {continue}    switch inlineIntent{        case .stronglyEmphasized:            markdownString[range].foregroundColor = .red        case .emphasized:            markdownString[range].foregroundColor = .green        case .strikethrough:            markdownString[range].foregroundColor = .blue        default:            break    }}

image-20211007085859409

Markdown解析

AttributedString不仅可以在本地化字符串中支持部分的Markdown标签,并且提供了一个完整的Markdown解析器。

支持从String、Data或URL中解析Markdown文本内容。

比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let mdString = try! AttributedString(markdown: "# Title\n**hello**\n")print(mdString)// 解析结果Title {    NSPresentationIntent = [header 1 (id 1)]}hello {    NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 2)    NSPresentationIntent = [paragraph (id 2)]}

解析后会将文字风格和标签设置在inlinePresentationIntentpresentationIntent中。

•inlinePresentationIntent字符性质:比如粗体、斜体、代码、引用等•presentationIntent段落属性:比如段落、表格、列表等。一个Run中,presentationIntent可能会有多个内容,用component来获取。

README.md

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#  Hello ## Header2hello **world*** first* second> test `print("hello world")`| row1 | row2 || ---- | ---- || 34   | 135  |[新Formatter介绍](/posts/newFormatter/)

解析代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let url = Bundle.main.url(forResource: "README", withExtension: "md")!var markdownString = try! AttributedString(contentsOf: url,baseURL: URL(string: "https://www.fatbobman.com"))

解析后结果(节选):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Hello {    NSPresentationIntent = [header 1 (id 1)]}Header2 {    NSPresentationIntent = [header 2 (id 2)]}first {    NSPresentationIntent = [paragraph (id 6), listItem 1 (id 5), unorderedList (id 4)]}test  {    NSPresentationIntent = [paragraph (id 10), blockQuote (id 9)]}print("hello world") {    NSPresentationIntent = [paragraph (id 10), blockQuote (id 9)]    NSInlinePresentationIntent = NSInlinePresentationIntent(rawValue: 4)}row1 {    NSPresentationIntent = [tableCell 0 (id 13), tableHeaderRow (id 12), table [Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left), Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left)] (id 11)]}row2 {    NSPresentationIntent = [tableCell 1 (id 14), tableHeaderRow (id 12), table [Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left), Foundation.PresentationIntent.TableColumn(alignment: Foundation.PresentationIntent.TableColumn.Alignment.left)] (id 11)]}新Formatter介绍 {    NSPresentationIntent = [paragraph (id 18)]    NSLink = /posts/newFormatter/ -- https://www.fatbobman.com}

解析后的内容包括段落属性、标题号、表格列数、行数、对齐方式等。缩紧、标号等其他信息可以在代码中可以通过枚举关联值来处理。

大致的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for run in markdownString.runs {    if let inlinePresentationIntent = run.inlinePresentationIntent {        switch inlinePresentationIntent {        case .strikethrough:            print("删除线")        case .stronglyEmphasized:            print("粗体")        default:            break        }    }    if let presentationIntent = run.presentationIntent {        for component in presentationIntent.components {            switch component.kind{                case .codeBlock(let languageHint):                    print(languageHint)                case .header(let level):                    print(level)                case .paragraph:                    let paragraphID = component.identity                default:                    break            }        }    }}

SwiftUI并不支持presentationIntent附加信息的渲染。如果想获得理想的显示效果,请自行编写视觉风格设置代码。

自定义属性

使用自定义属性,不仅有利于开发者创建更符合自身要求的属性字符串,而且通过在Markdown文本中添加自定义属性信息,进一步降低信息和代码的耦合度,提高灵活度。

自定义属性的基本流程为:

•创建自定义AttributedStringKey为每个需要添加的属性创建一个符合Attributed协议的数据类型。•创建自定义AttributeScope并扩展AttributeScopes创建自己的Scope,并在其中添加所有的自定义属性。为了方便自定义属性集被用于需要指定Scope的场合,在自定义Scope中推荐嵌套入需要的系统框架Scope(swiftUI、uiKit、appKit)。并在AttributeScopes中添加上自定义的Scope。•扩展AttributeDynamicLookup(支持点语法)在AttributeDynamicLookup中创建符合自定义Scope的下标方法。为点语法、KeyPath提供动态支持。

实例1:创建id属性

本例中我们将创建一个名称为id的属性。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct MyIDKey:AttributedStringKey {    typealias Value = Int // 属性内容的类型。类型需要符合Hashable    static var name: String = "id" // 属性字符串内部保存的名称}extension AttributeScopes{    public struct MyScope:AttributeScope{        let id:MyIDKey  // 点语法调用的名称        let swiftUI:SwiftUIAttributes // 在我的Scope中将系统框架swiftUI也添加进来    }    var myScope:MyScope.Type{        MyScope.self    }}extension AttributeDynamicLookup{    subscript<T>(dynamicMember keyPath:KeyPath<AttributeScopes.MyScope,T>) -> T where T:AttributedStringKey {        self[T.self]    }}

调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var attribtedString = AttributedString("hello world")attribtedString.id = 34print(attribtedString)// Outputhello world {    id = 34}

实例2:创建枚举属性,并支持Markdown解析

如果我们希望自己创建的属性可以在Markdown文本中被解析,需要让自定义的属性符合CodeableAttributedStringKey以及MarkdownDecodableAttributedStringKye

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 自定义属性的数据类型不限,只要满足需要的协议即可enum PriorityKey:CodableAttributedStringKey,MarkdownDecodableAttributedStringKey{    public enum Priority:String,Codable{ //如需在Markdown中解析,需要将raw类型设置为String,并符合Codable        case low        case normal        case high    }    static var name: String = "priority"    typealias Value = Priority}extension AttributeScopes{    public struct MyScope:AttributeScope{        let id:MyIDKey        let priority:PriorityKey // 将新创建的Key也添加到自定义的Scope中        let swiftUI:SwiftUIAttributes    }    var myScope:MyScope.Type{        MyScope.self    }}

在Markdown中使用^[text](属性名称:属性值)来标记自定义属性

调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 在Markdown文本中解析自定义属性时,需指明Scope。var attributedString = AttributedString(localized: "^[hello world](priority:'low')",including: \.myScope)print(attributedString)// Outputhello world {    priority = low    NSLanguage = en}

实例3:创建多参数的属性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum SizeKey:CodableAttributedStringKey,MarkdownDecodableAttributedStringKey{    public struct Size:Codable,Hashable{        let width:Double        let height:Double    }    static var name: String = "size"    typealias Value = Size}// 在Scope中添加let size:SizeKey

调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 多参数在{}内添加let attributedString = AttributedString(localized: "^[hello world](size:{width:343.3,height:200.3},priority:'high')",including: \.myScope)print(attributedString)// Outputello world {    priority = high    size = Size(width: 343.3, height: 200.3)    NSLanguage = en}

在WWDC 2021新Formatter API[2]一文中,还有在Formatter中使用自定义属性的案例

总结

在AttributedString之前,多数开发者将属性字符串主要用于文本的显示样式描述,随着可以在Markdown文本中添加自定义属性,相信很快就会有开发者扩展AttributedString的用途,将其应用到更多的场景中。

希望本文能够对你有所帮助。

引用链接

[1] WWDC 2021新Formatter API:新老比较及如何自定义: https://www.fatbobman.com/posts/newFormatter/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 肘子的Swift记事本 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
用户增长|QQ情侣设计探索
近几年随着经济增速放缓,人口红利消退,人口、流量、资金红利逐渐萎缩,互联网时代逐渐进入“增量拉新”和“存量活跃”的双轮时代。产品用户增长和营收增长得到更多重视,QQ情侣也顺应时代变化,从全局出发,分阶段更系统地探索情侣用户增长和营收增长,取得了不错的成绩,并在这个过程中逐渐沉淀出了一套产品增长体系化的新方法。 项目背景 19年之前在QQ体系内,情侣产品以情侣空间为代表。随着人口红利的消退,从18年开始整个情侣空间的活跃/收入数据首次出现了年同比下跌,在这样的背景下,我们开始思考如何来打破活跃/收入同比
腾讯ISUX
2021/01/05
1.5K0
破局DevOps|8大北极星指标指引研发效能方向
放弃那些动辄就上百个的研发度量指标吧,8大北极星指标指引你的研发效能方向,1个北极星指标公式让你清晰了解公司研发效能现状。
laofo
2023/09/10
5470
破局DevOps|8大北极星指标指引研发效能方向
利用好数据,0基础也能做好产品运营。
作者:pikarzhan TEG鲁班工作室产品策划  导语 | 产品数据通常用于衡量产品健康度、帮助定位和解决问题、对用户进行分层运营、衡量产品收益以及挖掘产品指标提升关键点等。本文是对产品数据体系的初步介绍,希望帮助新手产品、交互、体验设计师们掌握产品数据规划,用数据驱动产品迭代,形成体系化认知。 数据指标基础概念 1. 数据指标是什么 定义:对于一个数据的量化,一般通过对字段进行某种计算得到(比如求和、平均)在原始数据的 基础上,通过统计汇总,加工处理形成的用于表征业务活动好坏优劣的数据 数
腾讯大讲堂
2021/08/12
5820
【深度】20场企业内训后,我总结出落地增长最常见的4个坑
我在2018年出版了《硅谷增长黑客实战笔记》后,不少国内的企业邀请我去做培训。这其中既包括腾讯,阿里,新浪这样的互联网大厂,又包括爱奇艺,陌陌这样的垂直头部公司,还包括脉脉,新世相这样的创业新秀,以及招行等传统行业的大牛。
用户1756920
2019/10/29
5600
【深度】20场企业内训后,我总结出落地增长最常见的4个坑
一文理解增长黑客方法论
| 导语 网上讲增长黑客方法论的文章很多,但大多只是碎片化的知识点或者雷同内容的复制粘贴,无法形成系统性的知识框架。 我读了增长黑客的3本书,浏览了100多篇文章,结合营销的专业知识梳理出增长黑客最核心的方法论,实战用到哪个部分的内容可以继续用关键词检索学习。 鹅厂越来越多的业务组建了增长团队,同时也利用增长黑客的方法低成本高效率地促进用户增长。数据化运营、数据驱动决策、精益数据分析等相关概念也非常火热。那么,你了解增长黑客吗? 本文将梳理增长黑客理论发展至今的核心方法论,与大家一起学习和讨论。
腾讯大讲堂
2019/08/20
2.4K0
一文理解增长黑客方法论
《增长黑客》 测验解析
1.下面对增长黑客团队的工作流程的描述中,哪项是正确的( D) A.收集与分析数据——做出假设——确定增长对象(核心指标) ——确定试验 的优先级排序——执行试验并优化——系统化推广 B.做出假设——收集与分析数据——确定增长对象(核心指标) ——确定试验 的优先级排序——执行试验并优化——系统化推广 C.确定增长对象(核心指标) ——做出假设——收集与分析数据——确定试验 的优先级排序——执行试验并优化——系统化推广 D.确定增长对象(核心指标) ——收集与分析数据——做出假设——确定试验 的优先级排序——执行试验并优化——系统化推广 解析: 此题略
葆宁
2020/03/05
5730
分析入门:优秀数据分析师的访谈1
Froc寄语:数据分析师(或者时髦一些的说法是数据科学家),是公司不可或缺的重要组成人员,一家缺失数据分析师的公司,至少说明这家公司缺少数据驱动的意识,在未来竞争中,一定处于被动。一直以来,我致力于推进数据化运营,而数据化运营需要解决几个核心问题:
用户1756920
2020/05/12
1.3K0
设计驱动商业 | PUPU读书改版
腾讯ISUX isux.tencent.com 社交用户体验设计 从2018年底到2019年初,阅读的商业模式正在发生翻天覆地的变化。从付费阅读到免费阅读的战场争夺中,各大免费APP如雨后春笋般涌现,正冲击着付费的城墙。 网文江湖,风云再起。PUPU读书存在产品框架不合理,优质的内容无法得到有效曝光和外显,加之视觉风格陈旧,品牌设计缺失等一系列体验问题。 对设计师而言,我们面临着更多挑战,不能只停留在优化用户体验本身,需要更多地从商业本质出发,以实现商业目标来优化产品功能和用户体验。201
腾讯ISUX
2020/02/24
7860
设计驱动商业 | PUPU读书改版
数据指标设计的奥妙
就像人走路的时候需要看到前方的道路,产品和运营在做决策前也需要睁开“双眼”。左眼,是数据;右眼,是用研。(哎,别问我为什么不是左眼用研,右眼数据……)
Spark学习技巧
2023/03/21
6220
数据指标设计的奥妙
一文了解增长黑客
作者:funcolin 腾讯TEG鲁班工作室应用开发工程师  导语|  A/B Test 或 “病毒营销” 就是增长黑客?增长黑客是完全是产品同学的工作?如果你也这么认为,别等了!赶紧进来!咱们深入交流一下!来自一名尝试理解增长黑客的小开发。 前言 作为开发同学,你或许已经有过或即将遇到类似的困惑:增长黑客就是病毒营销?开发参与增长过程就是搞搞报表;跑跑数据;且毫无挑战?增长是产品同学的事情,对开发同学而言就是继续以往 需求 > 实现 > 交付的常规流程,没有其他变化? 如果你也遇到了这些困惑,或者
腾讯大讲堂
2021/09/03
4600
PRD设计的实施方法论
北极星指标的标准:核心价值体现、反应用户活跃度、反应经营状况、易理解易沟通。具备目标指导性
chimchim
2022/11/13
3950
得物千人规模敏捷迭代实践分享
这种根深蒂固的误解,就像,你说你是学计算机的,别人以为你是修电脑的。如果你是这么想的,那这篇文章应该会重新认识项目管理,以及PMO这个角色。
得物技术
2024/04/09
2970
得物千人规模敏捷迭代实践分享
产品如何进行数据运营?
“ 好产品是1,而运营是在后面不断加0 ,是变成10、100,还是1亿,都取决于运营做的好不好。”
产品言语
2022/06/02
6770
产品如何进行数据运营?
[译] 如何避免拍脑袋想出的产品优先策略
原文地址:How to avoid opinion-based product prioritization 原文作者:Tamzin Taylor 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:Yuze 校对者:Yuhanlolo, geniusq1981 在看老板的脸色之外,其实我们还可以利用数据来做出更好的决策 产品决策是一件很难的事情。在大部分的公司里,做决定有时候还需要考虑到很多其他的因素。有时候会有组织内部竞争者的意见,团队领导或是老板个人的看法,
Android 开发者
2018/07/02
8210
敏捷交付的工程效能治理
在敏捷交付中,大家可能会遇到各种各样的问题,从而会影响最终的交付效果,甚至可能导致交付的失败。因此,如何在交付过程中进行有效的治理,提高交付效能,对于交付的最终效果会起到至关重要的作用。不可否认,交付效能和治理涵盖的范围很广,具体的实施还是需要根据实际情况进行细化,落地,跟踪, 反馈和改进。 框架的引入 在引入框架之前,很重要的一件事情是,让大家思考为什么要进行交付项目的工程效能的治理和改进。建议通过下面两个问题来作为和大家对话的开始: 在交付项目中,你区别于其他竞争对手的价值体现在什么地方? 你需要做什么
ThoughtWorks
2022/08/26
4490
敏捷交付的工程效能治理
产品后台数据已经很丰富,为什么还要做满意度调研?看文-->
引言 满意度研究是一种常用的研究方法,起源于上世纪60年代的线下服务消费行业,在无法长期跟踪和收集用户数据的前提下,用于量化阶段性的服务效果。然而对于互联网产品,做满意度研究是否具备相应的价值和差异性?本文总结了一次满意度研究的项目经验,并分享提升研究价值和效率的方法。 满意是一种心理感受,指用户对一项服务的质量的主观评价。“满意度”影响用户使用产品的愉悦度和满足感,进而影响粘性和忠诚度。满意度研究的目的是量化用户预期和使用感受的匹配度,从而检验服务关键指标的实现程度,以此辅助找到产品和服务的提升方向
腾讯大讲堂
2020/06/03
1.4K0
如何从1到99做好产品 | 得物技术
如果说推动一款产品从0到1的上线是产品成功的第一步,那么如何实现增长是我们还需要探索努力的99步,本文来聊聊对增长的一些看法。
得物技术
2023/04/20
4420
如何从1到99做好产品 | 得物技术
数据赋能:产品数据化运营四步法
产品经理做了很多从0-1的事情,而产品运营则是在这个1后面不断地加0,从1变成10、100、十亿。相信每个产品经理都希望自己的产品可以从1到无穷大,本文主要从数据运营的角度,分享数据在产品运营过程能够起到的作用,数据如何赋能运营。
数据干饭人
2022/07/01
5470
数据赋能:产品数据化运营四步法
从0到1 搭建B端数据指标体系
本文作者:adelitayang,腾讯TEG产品运营 一、B端与C端运营的区别 每次说到B端,大家都会不自觉地想要问,B端和C端究竟有什么不一样的地方呢?B端能不能直接复制C端的经验呢?刚好毕业之后先是在老牌企业服务公司从事B端的运营,再做了快2年的全栈C端运营,最后又回到B端运营的怀抱。 在我看来,无论是C端运营还是B端运营,在数据运营上,共通之处都是通过已有的数据去发现问题、分析原因以及预测趋势,都是为了驱动业务的增长,如用户增长、营收增长等。 不同的是,在业务层面上,B端业务比C端更为复杂,C端往往
腾讯大讲堂
2022/01/26
1.9K0
如何实现智能化用户增长策略部署与自动化运营
截至2020年12月31日,京东LTM活跃购买用户数达到4.719亿,全年净增了近1.1亿活跃用户,在这样海量规模的用户运营场景下,传统的人工运营方式已经很难实现对成本和收益的精细化控制。事实上,当今互联网行业的头部企业都面临着后流量红利时代的增量用户精准运营的难题。近1年来,京东零售用户增长与运营部积极探索先进的用户运营方法论,并创造了用户增长超1亿的行业壮举。本文结合零售用增团队的业务实践经验,从数智化运营角度介绍目前线上规模化运行的用户增长“机器”的基本原理。
京东技术
2021/05/11
1.9K0
推荐阅读
相关推荐
用户增长|QQ情侣设计探索
更多 >
LV.1
哈曼汽车智联Media
目录
  • AttributedString——不仅仅让文字更漂亮
    • 初步印象
    • AttributedString vs NSAttributedString
      • 类型
      • 安全性
      • 本地化支持
      • Formatter支持
      • SwiftUI集成
      • 支持的文件格式
      • 转换
    • 基础
      • AttributedStringKey
      • AttributeContainer
      • AttributeScope
      • 视图
      • Range
    • 本地化
      • 创建本地化属性字符串
      • replacementIndex
      • 使用locale设定字符串插值中的Formatter
      • 用Formatter生成属性字符串
      • Markdown符号
    • Markdown解析
    • 自定义属性
      • 实例1:创建id属性
      • 实例2:创建枚举属性,并支持Markdown解析
      • 实例3:创建多参数的属性
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档