Text 文本

// 基本调用方法:Text 后面跟的参数必须是 String 类型
Text("Stay Hungry. Stay Foolish.")

// 字符串插值:如果要展示的信息不是 String 类型,需要用 \\() 进行插值
Text("\\(price)")

// Text也可以用于展示 Image:
Text(Image(systemName: "person"))
	.font(.system(.largeTitle, weight: .bold))

使用第三方字体

<aside> 💡 关于 iOS 设备默认的可用字体可以看这: http://iosfonts.com/ 。如果没有合适的字体,你还可以使用自己下载的合法的第三方字体。

</aside>

  1. 首先下载字体文件,并拖入项目文件夹里。

    将字体文件拖到 Xcode 的项目导航器中。可以一次拖动包含 TrueType (.ttf) 或 OpenType (.otf) 文件的整个文件夹。接受默认设置;您确实想要复制项目、创建组并添加到当前目标。

    您可以通过在项目导航器中选择字体文件,然后使用 Command-Option-1 打开 Xcode 的文件检查器窗格来检查字体文件是否已正确添加到当前目标。在项目导航器中突出显示每个字体文件,并确保其目标成员资格显示您应用程序的目标。

  2. 制作修改器 ViewModifier (其中字体名不需要加后缀)

    struct CustomFontModifier : ViewModifier {
        var size : CGFloat = 28
        func body(content: Content) -> some View {
            content
                .font(.custom("Oswald-Regular", size: size))
        }
    }
    
  3. 修改 Info.plist 属性列表,新增 【Fonts provided by application】 字段

    点击最左边导航中的项目根图标,找到Targets,点击项目名,在右边找到 info 子菜单。在Custom iOS Target Properties中,新增 Fonts provided by application 。做完后,左侧的项目导航目录里会多一项 info 文件。在 Fonts provided by application 里新增 item,后面填上字体的名称即可,有几个字体就新增几项。

  4. 在字段里逐个填入 item,即字体文件名,如:Oswald-Bold.ttf

使用 + 号连接文本

// 可以用 + 号连接多个文本(默认基线会对齐)
Text("Here is another ")
+ Text("example").foregroundColor(.red).underline()
+ Text("Notice").foregroundColor(.purple).bold()
+ Text("as a whole.").bold().italic()

// 可以用 + 号连接多个文本(设置基线偏移,解决基线不对齐问题,负数代表往下)
Text("100").bold()
	+ Text(" SWIFTUI ")
		.baselineOffset(-12)
	+ Text ("VIEWS").bold()
// 使用 + 号连接的带修饰符的 Text,修饰符只影响其所属 Text
Text("Hello Developer!") + Text("How are you?")
	.font(.title)
	.italic()

// 如果希望影响全部 Text,可以用括号把所有 Text 包括起来
(Text("Hello Developer!") + Text(" How are you?"))
	.font(.title)
	.italic()

<aside> 💡

【文本连接】和【字符串插值】两种方式都可以实现文本的连接,但它们之间对本地化翻译有一些不同的影响。总之,文本连接对于简单的样式场景非常有效,但最佳做法是始终优先使用文本插值,以确保语法正确且翻译自然。具体查看:

https://nilcoalescing.com/blog/TextConcatenationVsTextInterpolationInSwiftUI/?utm_source=substack&utm_medium=email

</aside>

使用 redacted 显示占位符

// 1. 将文本显示为占位符
// SwiftUI 允许将文本标记为视图中的占位符,这意味着它会被渲染,但会被灰色遮盖以表明它不是最终内容。
// 这是通过 redacted(reason:) 修饰符以及可用于根据需要覆盖密文的 unredacted() 修饰符提供的。
Text("This is placeholder text").redacted(reason: .placeholder)

// 还可以加到父元素上
VStack(alignment: .leading) {
    Text("This is placeholder text This is placeholder telder tex")
    Text("And so is this")
}.redacted(reason: .placeholder)

// 2. 还可以查询从环境传入的任何编辑原因
struct ContentView: View {
    @Environment(\\.redactionReasons) var redactionReasons
    let bio = "The rain in Spain falls mainly on the Spaniards"
    var body: some View {
        if redactionReasons == .placeholder {
            Text("Loading…")
        } else {
            Text(bio)
                .redacted(reason: redactionReasons)
        }
    }
}

// 3. 还允许将视图的某些部分标记为包含敏感信息,这使我们可以使用密文更轻松地隐藏或显示它
// 要使用此功能请将 privacySensitive() 修饰符添加到应隐藏的任何视图
// 然后在更高的视图层次结构中应用 .redacted(reason: .privacy) 修饰符
VStack {
    Text("Card number")
        .font(.headline)

    if redactionReasons.contains(.privacy) {
        Text("[HIDDEN]").privacySensitive()
    } else {
        Text("1234 5678 9012 3456").privacySensitive()
    }
}
.redacted(reason: .privacy)

// 4.默认情况下,隐私敏感上下文被灰色框屏蔽,但您也可以通过从环境中读取密文原因来提供自定义布局:
struct ContentView: View {
    @Environment(\\.redactionReasons) var redactionReasons

    var body: some View {
        VStack {
            Text("Card number")
                .font(.headline)

            if redactionReasons.contains(.privacy) {
                Text("[HIDDEN]")
            } else {
                Text("1234 5678 9012 3456")
            }
        }
    }
}

呈现 Markdown 内容

Text 视图提供了两种使用 Markdown 设置文本样式的方法:直接在 Text 视图中和使用 AttributedString 。

直接在 Text 视图中使用 Markdown 对于静态文本来说很方便,但对于动态字符串或当您想要将样式应用于字符串的不同部分时,您需要使用 AttributedString 以编程方式。

// SwiftUI 内置了对 Markdown 渲染的支持,包括粗体、斜体、链接等
// 它实际上内置于 SwiftUI 的 Text 视图中,因此您可以编写如下代码:
VStack {
    Text("This is regular text.")
    Text("* This is **bold** text, this is *italic* text, and this is ***bold, italic*** text.")
    Text("~~A strikethrough example~~")
    Text("`Monospaced works too`")
    Text("Visit Apple: [click here](<https://apple.com>)")

    // 该链接是可自动点击的。默认情况下,Markdown 链接将使用应用程序的强调色,但可以使用 tint() 修饰符更改它:
    Text("Visit Apple: [click here](<https://apple.com>)")
        .tint(.indigo)

    // 注意:不支持图像

}

// 支持自动 Markdown 转换是因为 SwiftUI 将这些字符串解释为 LocalizedStringKey 实例(即可以由应用程序本地化的字符串)
// 这意味着如果您想从属性或变量创建 Markdown 文本,您应该将其显式标记为 LocalizedStringKey 以获得 Markdown 渲染:

struct ContentView: View {
    let markdownText: LocalizedStringKey = "* This is **bold** text, this is *italic* text, and this is ***bold, italic*** text."
    var body: some View {
        Text(markdownText)
    }
}

// 如果希望原始文本保持不变(即将原始的、未格式化的 Markdown 符号保留在原处),只需删除 LocalizedStringKey 注释即可
// 或者,您可以使用 Text(verbatim:) 初始值设定项完全禁用 Markdown 和本地化。

处理英文复数

https://nilcoalescing.com/blog/HandlePluralsInSwiftUITextViewsWithInflection/

https://samwize.com/2025/04/11/plurals-with-swiftui/?utm_source=substack&utm_medium=email

解决英文复数的展示问题,一般有以下做法:

1. 最懒的办法

最偷懒的做法是写成固定文本的 cup(s)

2. 使用三元运算符

第二种做法是使用三元运算符来纠正这个问题,如下所示:

Text(
    question.questionNotes.count == 1 ?
    NSLocalizedString("one_note", comment: "") :
    String(format: NSLocalizedString("multiple_notes", comment: ""), question.questionNotes.count)
)

// 这种做法要注意本地化语言时,要添加两种状态的文案
// 英文 Localizable.strings (English)
one_note = "1 note";
multiple_notes = "%d notes";

3. 使用特殊 Markdown 写法

Foundation 框架有一个称为自动语法协议的功能,它可以确保文本遵循复数和性别等语法规则。它与 SwiftUI 无缝协作,允许我们直接在文本视图中处理复数,而无需任何额外的手动逻辑。要使文本自动调整为复数值,我们可以指定它使用 inflection rule,并定义其范围:

Text("You read ^[\\(bookCount) book](inflect: true) this year!")

Stepper("^[\\(coffeeAmount) cup](inflect: true)", value: $coffeeAmount, in: 1...20)

GI_OGwIb0AAGFnP.jpg

SwiftUI 将此语法识别为自定义 Markdown 属性,当使用字符串文本创建文本视图时,SwiftUI 会将字符串视为 LocalizedStringKey 并解析它包含的 Markdown。它识别字符串中的变形属性,并使用 Foundation 的自动语法一致性功能在渲染过程中应用必要的调整。

这种集成使 SwiftUI 中的复数处理变得简单而高效。自动语法一致性功能和 inflection 属性是在 iOS 15 中引入的,最初支持英语和西班牙语。多年来,语法引擎已扩展到包含其他语言,从 iOS 18 开始,它还支持德语、法语、意大利语、葡萄牙语、印地语和韩语,使其在多语言应用程序中更加通用。

<aside> 💡

当使用方案 3 时有一个问题,例如: .accessibilityHint("^[\\(filter.activeIssuesCount) issue](inflect: true)")

这时如果启用 VoiceOver 将看到错误:ERROR: ^[%lld issue] (inflect: true) not found in table Localizable of bundle

因为 [\\(filter.activeIssuesCount) issue] 的其中一部分是特殊 Markdown 语法,另一部分 %lld 是整数的 Localizable.strings 格式,这是我们的字符串插值中要替换的格式。虽然可以将此文本添加到 Localizable.strings 文件中,但这不是一个长久的解决方案,因为自动语法协议不支持其他语言,例如匈牙利语!

英语的复数形式不规则,即使涉及0个项目也使用复数形式。但也有更复杂的语言,例如,阿拉伯语有一种形式表示一个对象的零,另一种形式表示一个,另一个形式表示两个,另一个形式表示几个对象,另一种形式表示许多对象,还有一个形式表示所有其他计数。我们可以尝试用 Swift 对这些规则进行编码,但这比你想象的要困难得多。例如,俄语也有特殊的复数规则,当存在许多对象时,但俄语中“许多”的定义与阿拉伯语中的“许多”不同!

</aside>

4. 最好的办法

基于以上方案的语言本地化问题,因此需要一个更好的解决方案。终极保险的方法还是用 Localizable.stringsdict

具体参照这里:4. 解决英语复数的问题 (这样不管是展示复数,还是本地化,都没有问题)


AttributedString 高级字符串

Text 视图能够渲染 Foundation 的 AttributedString 结构,从而实现添加下划线、删除线、网页链接、背景颜色等效果。对于更高级或动态的样式, AttributedString 是首选工具。它允许您将不同的样式应用于字符串的不同部分。

创建方法

AttributedString 的创建方式,是先声明一个 AttributedString 对象,然后再用点语法设置各个属性。

struct ContentView: View {

		// 声明:先声明一个 AttributedString 对象,再设置其各种属性
		var message: AttributedString {
				var result = AttributedString("Hello, world!")
        result.font = .largeTitle
				return result
    }

		// 使用时:作为字符串传入到 Text
		var body:some View {
        Text(message)
    }
    
}

常见属性

遗憾的是, AttributedString 的 API 不大透明 ,所以这里展示一大堆示例来帮助入门。

// 字体大小
result.font = .largeTitle

// 前景色
result.foregroundColor = .white

// 背景色
result.backgroundColor = .red

// 下划线图案和颜色
result.underlineStyle = Text.LineStyle(pattern: .solid, color: .white)

// 使用 link 属性将可点击的网络链接附加到文本中
result.link = URL(string: "<https://www.hackingwithswift.com>")

// 还可以调整字符串各部分的基线偏移,强制将其放置在高于或低于默认值的位置:
struct ContentView: View {

		var message: AttributedString {
				let string = "The letters go up and down"
				var result = AttributedString()
				for (index, letter) in string.enumerated() {
						var letterString = AttributedString(String(letter))
            letterString.baselineOffset = sin(Double(index)) * 5
            result += letterString
        }
        result.font = .largeTitle
				return result
    }

		var body:some View {
        Text(message)
    }
}

使用 AttributedString 让 Text 展示多个样式:

该示例仅使用 Text 和常规 SwiftUI 修饰符来完成,但 AttributedString 的部分功能是自定义属于 String 而不是 Text 。这意味着背景颜色是 String 本身的一部分,因此如果需要,我们可以使用不同的背景颜色将多个 String 合并在一起:

struct ContentView: View {

		// 声明红色背景的字符串
		var message1: AttributedString {
				var result = AttributedString("Hello")
        result.font = .largeTitle
        result.foregroundColor = .white
        result.backgroundColor = .red
				return result
    }

		// 声明蓝色背景的字符串
		var message2: AttributedString {
				var result = AttributedString("World!")
        result.font = .largeTitle
        result.foregroundColor = .white
        result.backgroundColor = .blue
				return result
    }

		// 使用时:
		// 如果您尝试对 Text 使用 background() 修饰符,会发现它不起作用。
		var body:some View {
        Text(message1 + message2)
    }
    
}

<aside> 💡 您可以将 SwiftUI 修饰符与 AttributedString 属性混合使用。重要的是,通过 AttributedString 应用的属性优先于 SwiftUI 修饰符。

</aside>

使用 MarkDown

在此示例中, AttributedString 使用 Markdown 语法来制作一些斜体文本。当此 AttributedString 传递到 Text 视图时,这些样式将被保留。当将 font 和 foregroundColor 修饰符应用于 Text 视图时,这些样式将应用于整个文本,但它们不会覆盖粗体和由 AttributedString 指定的斜体样式。

这是因为 AttributedString 的样式优先于 SwiftUI 修饰符。

let quote = """
**"Be yourself;** everyone else is _already taken._"
- **Oscar Wilde**
"""
let attributedQuote = try! AttributedString(markdown: quote)

struct ContentView: View {
  var body: some View {
    Text(attributedQuote)
      .font(.system(size: 16, weight: .medium, design: .serif))
      .foregroundColor(.blue)
  }
}

使用元数据

AttributedString 真正强大的功能是它不会丢弃我们提供的有关字符串的所有元数据,这解锁了大量额外的功能。例如,出于可访问性原因,我们可以将字符串的一部分标记为需要拼写,以便在使用 VoiceOver 时正确读出密码等内容:

struct ContentView: View {
		var message: AttributedString {
				var password = AttributedString("abCayer-muQai")
        password.accessibilitySpeechSpellsOutCharacters = true
				return "Your password is: " + password
    }

		var body:some View {
        Text(message)
    }
}

更令人印象深刻的是它处理结构化信息的方式。