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("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>

参数: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()
设置 TextField 对齐方式 .multilineTextAlignment()
设置 TextField 背景色 .background(.yellow) 直接设置即可
设置 TextEditor 背景色 .scrollContentBackground(.hidden) 直接设置 background 还不行,需要禁用滚动内容背景
输入字符的大写控制 .textInputAutocapitalization() word、sentence、character、never、none
强制大/小写输入 .textCase(.uppercase) lowercase、uppercase
禁用自动纠正 .disableAutocorrection()
禁用自动纠正 .autocorrectionDisabled()
输入框语义 .textContentType(.name) emailAddress、URL、telephoneNumber 等等
获取键盘焦点 .focused($@FocusState) 设置隐藏/显示输入键盘,绑定 @FocusState 参数
输入键盘类型 .keyboardType(.phonePad) 仅在 iOS 下有用,注意用 #if os(iOS) 进行判断
自定义键盘提交按钮 .submitLabel(.continue) done、go、send、join、return、continue
设置提交动作 .onSubmit { … } 点击提交或 return 后执行的动作,‣
禁用交互 .disabled() 适用于所有视图,不只是 TextField 输入框

自定义输入框样式

// 有背景、有边框的输入框
// 通过 Overlay 画了一个底色以及边框
TextField("Placeholder Text", text: $withOutline)                              
	.overlay(
			RoundedRectangle(cornerRadius: 8)                        
			.stroke(Color.orange, lineWidth: 2)            
	)

// 有背景的圆角输入框
TextField("Type something...", text: $text)
	.frame(width: 320, height: 44) 
	.background(Color.gray.opacity(0.1))
	.multilineTextAlignment(.center)
	.clipShape{
		RoundedRectangle(cornerRadius: 10, style: .continuous)
	}
	

// 有背景、有占位符的胶囊型输入框
TextField("", text: $text, prompt:
		(
		Text(Image(systemName: "lock")) + 
		Text("Enter Password")
		)
		.foregroundStyle(.purple.opacity(0.7))
		.fontWeight(.light)
)
.padding(.leading)
.frame(width: 320, height: 50)
.background(Color.purple.opacity(0.1))
.foregroundStyle(.purple)
.clipShape(
	Capsule()
)

自定义输入框占位符

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