TextField 是一个水平扩张型视图,垂直方向不扩张。
// 第1个参数:是输入框的默认占位字符,会以灰色字样式显示在输入框内
TextField("这里就是输入占位符", text: $textFieldData)
为了获取或设置 TextField 中的文本,需要将其绑定到一个变量上。这个变量被传递到 TextField 的初始化器中。然后需要做的就是改变这个绑定变量的文本来改变 TextField 中的内容,或者读取这个绑定变量的值来查看当前 TextField 中的文字。
//1. 先要设置字符串类型的状态变量
@State private var textFieldData = ""
//2. 用$符号绑定变量
TextField("", text: $textFieldData)
// 3. 根据属性的类型不同,需要区别对待,否则会报错
// 如果属性类型是【字符串】,则用 text
TextField("Amount", text: $checkAmount)
// 如果属性类型是【浮点数】等,则用 value
TextField("Amount", value: $checkAmount)
// 显示数字格式(带科学计数法的)
TextField("Enter your score", value: $score, format: .number)
// 显示百分比格式
TextField("Enter your score", value: $score, format: .percent)
// 显示货币格式
TextField("Amount", value: $checkAmount, format: .currency(code: "USD"))
TextField("Amount", value: $checkAmount, format: .currency(code: "CNY"))
// 显示货币格式(下面是设成 .currency ,后面的链式调用&nil合并是设置货币单位,如果无法获取当地货币,则用USD)
TextField("Amount", value: $checkAmount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))
<aside>
💡 Locale 是内置的一个巨大结构,负责存储用户的所有区域设置——他们使用什么日历、他们如何分隔数字中的千位数字、他们是否使用公制等等。在我们的例子中,我们询问用户是否有首选的货币代码,如果他们没有,我们将回退到“美元”,所以至少我们有一些东西。
</aside>
创建 TextField 时,可以提供一个它可以沿其生长的轴。即 TextField 一开始是单行文本,随着用户键入它可以扩大变成多行输入框。
struct ContentView: View {
@AppStorage("notes") private var notes = ""
var body: some View {
NavigationStack {
TextField("Enter your text", text: $notes, axis: .vertical)
// 可以通过添加 lineLimit() 修饰符来控制 TextField 可以增长的大小
// 例如,我们可能想说它应该以单行开始,但允许最多增长五行:
.lineLimit(5)
.textFieldStyle(.roundedBorder)
}
}
}
// 传入范围,代表:始终至少有两行高,但最多可达五行
.lineLimit(2...5)
// 使用 reservesSpace 参数,以便视图自动为其可以拥有的最大大小分配足够的空间
// 例如,这将创建一个 TextField ,它保留足够的布局空间来容纳最多五行文本:
.lineLimit(5, reservesSpace: true)
SecureField 是一个水平扩张型视图,垂直方向不扩张。
为了获取或设置 SecureField 中的文本,需要将其绑定到一个变量上。这个变量传递到 SecureField 的初始化器中。然后需要做的就是改变这个绑定变量的文本来改变 SecureField 中的内容。或者读取绑定变量的值来查看 SecureField 中当前的文本。SecureField 的工作原理与常规 TextField 几乎相同,不同之处在于,出于保护隐私的目的,这些字符被屏蔽掉了。
struct ContentView: View {
@State private var password: String = ""
var body: some View {
VStack {
SecureField("Enter a password", text: $password)
Text("You entered: \\(password)")
}
}
}
当输入简短文本时,TextField 非常有用;但对较长文本,可能需要切换到 TextEditor 视图,它能更好地为用户提供大量的工作空间。
TextEditor 实际上比 TextField 更容易,我们不能调整其样式或添加占位符文本,能做的只是将它绑定到一个字符串属性TextEditor 时需要确保它不会超出安全区域,否则打字会很棘手;要将其嵌入 NavigationStack 、 Form 或类似内容中Form 内部改变事物的外观,因此请确保在 Form 内部和外部尝试它们,看看它们有何变化。// 例如:将 TextEditor 与 @AppStorage 组合来创建世界上最简单的笔记应用程序
struct ContentView: View {
@AppStorage("notes") private var notes = ""
var body: some View {
NavigationStack {
TextEditor(text: $notes)
.navigationTitle("Notes")
}
}
}
从 iOS 26、macOS 26 及相关平台开始, TextEditor 获得了对 AttributedString 的一流支持。通过此更改,您可以从编辑纯文本过渡到创建完全格式化的富文本,包括 Markdown、链接、属性转换等。
要启用富文本格式,请将绑定到 TextEditor 变量的类型更改为 AttributedString 。通过这项小小的改动,编辑器将自动支持样式设置。用户可以直接在编辑器中添加粗体、斜体和下划线样式,您的应用会将这些属性作为 AttributedString 的一部分捕获。
struct RichTextEditor: View {
@State private var richText: AttributedString = "This is rich text"
var body: some View {
TextEditor(text: $richText)
}
}
// 除了直接编辑,我们还可以通过组合多个实例,以编程方式构建富文本
var a = AttributedString("Hello, ")
var b = AttributedString("world")
b.font = .body.bold()
a.append(b)
// 通过使用运算符 += 构建
a += AttributedString("!")
// 获取指定位置后,在该位置插入
if let index = a.firstIndex(of: ",") {
a.insert(AttributedString(" dear"), at: a.index(after: index))
}
AttributedString 也内置了 Markdown 支持。这意味着您可以轻松地从 Markdown 内容创建富文本,无论内容来自用户输入还是服务器响应。
// Markdown 解析尊重粗体、斜体、链接甚至内联代码格式等属性
do {
let thankYou = try AttributedString(
markdown: "**Thank you!** Visit our [website](<https://www.createwithswift.com>)"
)
print(thankYou)
} catch {
print("Markdown parsing failed: \\(error.localizedDescription)")
}
struct RichSelectTextEditor: View {
@State private var text: AttributedString = "This is rich text"
@State private var selection = AttributedTextSelection()
var body: some View {
TextEditor(text: $text, selection: $selection)
}
}
使用 AttributedTextSelection ,您不仅可以访问文本范围,还可以访问这些范围内应用的属性。
像 Selections 选择不仅仅提供文本范围。它们还提供 typingAttributes ,将应用于当前光标位置新插入文本的属性。
let typingAttributes = selection.typingAttributes(in: text)
使用输入属性,你可以将 UI 与编辑器状态同步。例如,你可以根据光标当前是否位于粗体文本内来启用或禁用“粗体”切换。如果您想要应用格式更改,请使用此方法:
attributedText.transformAttributes(in: &selection) { attributes in
...
}
该方法主要通过以下三个重要方式发挥作用:
&selection 作为输入输出参数:选择内容通过 & 传递,因为 SwiftUI 在应用属性时可能需要对其进行调整。例如,如果相邻的运行在编辑后合并,则选择内容仍然有效。attributes 作为 inout AttributeContainer :在闭包内部,你会收到一个 AttributeContainer ,它代表一个文本属性字典。修改它会更新整个选择的属性。transformAttributes 确保在可能的情况下合并属性运行,从而使属性文本保持规范化并避免不必要的碎片。使用此方法可以安全地转换许多不同的属性,通过组合这些属性,您可以构建超越粗体和斜体的编辑功能,包括链接到自定义颜色、排版调整等等。
attributedText.transformAttributes(in: &selection) { attributes in
// Font styling
attributes.font = (attributes.font ?? .default).bold(true)
attributes.font = (attributes.font ?? .default).italic(true)
attributes.font = .system(size: 18, weight: .semibold, design: .rounded)
// Underline and strikethrough
attributes.underlineStyle = .single
attributes.strikethroughStyle = .single
attributes.strikethroughColor = .red
// Colors
attributes.foregroundColor = .blue
attributes.backgroundColor = .yellow
// Links
attributes.link = URL(string: "<https://www.createwithswift.com>")
// Typography adjustments
attributes.baselineOffset = 2
attributes.kern = 1.5
}
以下是如何将简单的开关连接到编辑器的当前选择。此模式适用于粗体、斜体、下划线、颜色和其他属性。只需几个控件,您就可以构建一个功能齐全的文本格式工具栏。
其中 fontResolutionContext 环境值可根据用户上下文解析 typingAttributes 将其设置为环境值,是因为 SwiftUI 中的字体是自适应资源 ,而非绝对值。通过在环境中解析字体,您可以确保编辑器中的格式逻辑始终与用户实际看到的内容匹配,包括动态类型、可访问性和特定于平台的排版。
@Environment(\\.fontResolutionContext) private var fontResolutionContext
Toggle(
"Toggle Bold",
systemImage: "bold",
isOn: Binding(
get: {
let font = attributedTextSelection.typingAttributes(in: attributedText).font
let resolved = (font ?? .default).resolve(
in: fontResolutionContext
)
return resolved.isBold
},
set: { isBold in
attributedText.transformAttributes(in: &attributedTextSelection) {
$0.font = ($0.font ?? .default).bold(isBold)
}
}
)
)
对于复杂的输入框需求,我们需要通过使用 UIViewRepresentable 包装 UIKit 的 UITextView 类来创建文本视图。例如在纯 SwiftUI 的 TextEditor 视图中,无法直接通过官方 API 获取输入光标的位置并在该位置插入文本。TextEditor 只能绑定一个 String,但 无法感知光标的 selectedRange,也没有像 UITextView 那样的 selectedRange 属性。
关于在 SwiftUI 中使用 UIKit 视图,可以参见这里:桥接 UIKit 视图
https://www.appcoda.com/swiftui-textview-uiviewrepresentable/
首先要为 UITextView 创建自定义包装器,可以编写如下代码:
import SwiftUI
struct TextView: UIViewRepresentable {
// 接受两个绑定:一个用于文本输入保存,另一个用于字体样式
@Binding var text: String
@Binding var textStyle: UIFont.TextStyle
// 在 makeUIView 方法中,我们用 textStyle 文本样式初始化文本视图,返回标准的 UITextView
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
return textView
}
// 每当 SwiftUI 中的状态发生变化时,框架都会自动调用 updateUIView 方法来更新视图的配置
func updateUIView(_ uiView: UITextView, context: Context) {
// 1.当在文本视图中键入内容时,我们在这里更新 UITextView 的文本
uiView.text = text
// 2.如果调用者对文本样式进行了任何更改,则 UITextView 可以刷新为新样式
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
}
现在切换到引用 TextView 的主视图,例如 ContentView.swift
// 1.声明两个状态变量来保存文本输入和文本样式
@State private var message = ""
@State private var textStyle = UIFont.TextStyle.body
// 2.显示文本视图,在 body 中插入以下代码
TextView(text: $message, textStyle: $textStyle)
.padding(.horizontal)
<aside> 💡
</aside>