TextField 单行输入框

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)

参数:format 格式化

// 显示数字格式(带科学计数法的)
TextField("Enter your score", value: $score, format: .number)

// 显示货币格式
TextField("Amount", value: $checkAmount, format: .currency(code: "USD"))

// 显示货币格式(下面是设成 .currency ,后面的链式调用&nil合并是设置货币单位,如果无法获取当地货币,则用USD)
TextField("Amount", value: $checkAmount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))

<aside> 💡 Locale 是内置的一个巨大结构,负责存储用户的所有区域设置——他们使用什么日历、他们如何分隔数字中的千位数字、他们是否使用公制等等。在我们的例子中,我们询问用户是否有首选的货币代码,如果他们没有,我们将回退到“美元”,所以至少我们有一些东西。

</aside>

参数:axis 自适应轴

创建 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)

TextEditor 多行输入框

当输入简短文本时,TextField 非常有用;但对于较长的文本,可能需要切换到 TextEditor 视图。它允许多行文本,能更好地为用户提供大量的工作空间。使用 TextEditor 实际上比使用 TextField 更容易,你不能调整它的样式或添加占位符文本,你只需绑定它到一个字符串。不过需要小心,要确保它不会超出安全区域,否则打字会很棘手;要将其嵌入 NavigationStackForm 或类似内容中。

//例如:将 TextEditor 与 @AppStorage 组合来创建世界上最简单的笔记应用程序
struct ContentView: View {
    @AppStorage("notes") private var notes = ""
    var body: some View {
        NavigationStack {
            TextEditor(text: $notes)
                .navigationTitle("Notes")
        }
    }
}

<aside> 💡 SwiftUI 通常会在 Form 内部改变事物的外观,因此请确保在 Form 内部和外部尝试它们,看看它们有何变化。

</aside>


SecureField 安全文本框

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)")
        }
    }
}

输入交互管理

如何在 SwiftUI 使用@FocusState, @FocusedValue and @FocusedObject

输入框焦点 @FocusState

想要隐藏弹出的键盘,需要给 SwiftUI 一些方法来确定复选框当前是否应该有焦点(是否应该接收来自用户的文本输入)。其次需要添加某种按钮来在用户需要时删除该焦点,这反过来又会导致键盘消失。整个过程是通过 .focused 修饰符和 @FocusState 状态参数实现的。

// 解决第1个问题需要属性包装器 @FocusState ,它与常规 @State 属性完全相同,只不过是专门为处理 UI 中的输入焦点而设计的
struct ContentView: View {

    @State private var name = ""
    
    //1. 定义焦点状态属性
    @FocusState private var nameIsFocused: Bool

    var body: some View {
        VStack {
            TextField("Enter your name", text: $name)
		            //2. 将此修饰符添加到 TextField 后,当文本字段聚焦时,nameIsFocused 是 true,否则为 false
                .focused($nameIsFocused)

						// 解决第二个问题,可以人为地增加一个按钮
            Button("Submit") {
                nameIsFocused = false
            }
        }
    }
}

对于更高级的用途,可以使用 @FocusState 来跟踪可选的枚举 case,以检查确定当前聚集的表单字段。例如,我们可能会显示三个文本字段,要求用户提供各种信息,然后在最终信息到达后提交表单:

struct ContentView: View {

    enum Field {
        case firstName
        case lastName
        case emailAddress
    }

    @State private var firstName = ""
    @State private var lastName = ""
    @State private var emailAddress = ""
    @FocusState private var focusedField: Field?

    var body: some View {
        VStack {
            TextField("Enter your first name", text: $firstName)
                .focused($focusedField, equals: .firstName)
                .textContentType(.givenName)
                .submitLabel(.next)

            TextField("Enter your last name", text: $lastName)
                .focused($focusedField, equals: .lastName)
                .textContentType(.familyName)
                .submitLabel(.next)

            TextField("Enter your email address", text: $emailAddress)
                .focused($focusedField, equals: .emailAddress)
                .textContentType(.emailAddress)
                .submitLabel(.join)
        }
        .onSubmit {
            switch focusedField {
            case .firstName:
                focusedField = .lastName
            case .lastName:
                focusedField = .emailAddress
            default:
                print("Creating account…")
            }
        }
    }
}

<aside> 💡 重要提示:您不应尝试对两个不同的表单字段使用相同的焦点绑定。

</aside>

获取默认焦点

macOS 上提供了一个 defaultFocus() 修饰符,让我们在视图显示后立即激活一个视图作为用户输入的第一响应者。遗憾的是,它在 iOS 上不存在,但我们可以使用 onAppear() 来解决这个问题。

// iOS 实现方案
struct ContentView: View {
    enum FocusedField {
        case firstName, lastName
    }
    @State private var firstName = ""
    @State private var lastName = ""
    @FocusState private var focusedField: FocusedField?

    var body: some View {
        Form {
            TextField("First name", text: $firstName)
                .focused($focusedField, equals: .firstName)

            TextField("Last name", text: $lastName)
                .focused($focusedField, equals: .lastName)
        }
        .onAppear {
            focusedField = .firstName
        }
    }
}
// macOS 实现方案
struct ContentView: View {
    enum FocusedField {
        case firstName, lastName
    }
    @State private var firstName = ""
    @State private var lastName = ""
    @FocusState private var focusedField: FocusedField?
    var body: some View {
        Form {
            TextField("First name", text: $firstName)
                .focused($focusedField, equals: .firstName)

            TextField("Last name", text: $lastName)
                .focused($focusedField, equals: .lastName)
        }
        .defaultFocus($focusedField, .firstName)
    }
}

修饰符索引

修饰符代码示例 说明
输入框样式 .textFieldStyle() squareBorder、automatic、plain、roundedBorder 对单行多行输入框都适用
输入框光标颜色 .tint() 以前是用 .accentColor()
输入框语义 .textContentType(.name) emailAddress、URL、telephoneNumber 等等
禁用首字母大写 .textInputAutocapitalization() word、sentence、character、never、none
禁用自动纠正 .disableAutocorrection()
强制大/小写输入 .textCase(.uppercase) lowercase、uppercase
获取键盘焦点 .focused($@FocusState) 设置隐藏/显示输入键盘,绑定 @FocusState 参数
输入键盘类型 .keyboardType(.phonePad) 仅在 iOS 下有用,注意用 #if os(iOS) 进行判断
自定义键盘提交按钮 .submitLabel(.continue) done、go、send、join、return、continue
禁用交互 .disabled() 适用于所有视图,不只是 TextField 输入框

自定义输入框样式

// 设置输入框背景以及边框样式:  
// 通过Overlay人为地画了一个底色,以及边框
TextField("Placeholder Text", text: $withOutline)                              
	.overlay(
			RoundedRectangle(cornerRadius: 8)                        
			.stroke(Color.orange, lineWidth: 2)            
	)

自定义输入框占位符

//可以通过ZStack把两个视图叠加在一起,制造占位符的假象
ZStack(alignment: .leading) {
		//判断如果输入框为空,则显示中间的Text内容
		if textFieldData.isEmpty {
				Text("Enter name here").bold()
					.foregroundColor(Color(.systemGray4))
		}
		//这是放置在下方的输入框
		TextField("", text: $textFieldData)
}