SwiftUI TextField 进阶 —— 事件、焦点、键盘

(更新于 )

2025 年 12 月更新注: 虽然核心逻辑依然有效,但部分 API(如 onChange 和 Introspect)已发生变化,文章已针对现代开发环境进行了调整。

本文将探讨涉及 SwiftUI TextField 的事件、焦点切换、键盘设置等相关的经验、技巧和注意事项。

事件

onEditingChanged

当 TextField 获得焦点时(进入可编辑状态),onEditingChanged将调用给定的方法并传递true值;当 TextField 失去焦点时,再次调用方法并传递false

Swift
struct OnEditingChangedDemo:View{
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text:$name,onEditingChanged: getFocus)
        }
    }

    func getFocus(focused:Bool) {
        print("get focus:\(focused ? "true" : "false")")
    }
}

该参数的名称容易让使用者产生歧义,不要使用onEditingChanged判断用户是否输入了内容

需要注意的是,使用 ParseableFormatStyle 的构造方法(例如 TextField(value:format:))通常不提供该参数,因此对于使用新 Formatter 的 TextField 需要使用 FocusState 等其他手段来判断是否获得或失去焦点。

onCommit

当用户在输入过程中按下(或点击)return键时触发 onCommit(无法通过代码模拟触发)。如果用户没有点击return键(比如直接切换至其他的 TextField),将不会触发 onCommit。触发 onCommit 的同时,TextField 也将失去焦点。

Swift
struct OnCommitDemo:View{
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text: $name,onCommit: {print("commit")})
        }
    }
}

如果你需要在用户输入后对用户的录入内容进行判断,最好结合 onCommit 和 onEdtingChanged 一起来处理。如果想实时的对用户的录入数据进行处理,请参阅 SwiftUI TextField 进阶——格式与校验(2025)

onCommit 对 SecureField 同样适用。

在现代 SwiftUI 开发中,建议使用功能更强大的 onSubmit 来替代 onCommit

onSubmit

onSubmit 是现代 SwiftUI 中处理提交事件的首选方式。onCommit 和 onEditingChanged 是每个 TextField 对自身状态的描述,onSubmit 则可以从更高的角度对视图中多个 TextField 进行统一管理和调度。

Swift
// onSubmit 的定义
extension View {
    public func onSubmit(of triggers: SubmitTriggers = .text, _ action: @escaping (() -> Void)) -> some View
}

下面的代码将实现同上面 onCommit 一样的行为:

Swift
struct OnSubmitDemo:View{
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text: $name)
                .onSubmit {
                    print("commit")
                }
        }
    }
}

onSubmit 的触发条件同 onCommit 一致,需要用户主动点击return

onSubmit 同样适用于 SecureField。

作用域及嵌套

onSubmit 背后的是通过设置环境值TriggerSubmitActio(尚未对开发者开放)来实现的,因此 onSubmit 是有作用域范围的(可在视图树向上传递),且可嵌套。

Swift
struct OnSubmitDemo: View {
    @State var text1 = ""
    @State var text2 = ""
    @State var text3 = ""
    var body: some View {
        Form {
            Group {
                TextField("text1", text: $text1)
                    .onSubmit { print("text1 commit") }
                TextField("text2", text: $text2)
                    .onSubmit { print("text2 commit") }
            }
            .onSubmit { print("textfield in group commit") }
            TextField("text3", text: $text3)
                .onSubmit { print("text3 commit") }
        }
        .onSubmit { print("textfield in form commit1") }
        .onSubmit { print("textfield in form commit2") }
    }
}

当 TextField(text1) commit 时,控制台输出为

Shell
textfield in form commit2
textfield in form commit1
textfield in group commit
text1 commit

请注意,调用的顺序是从外层向内的

限定作用域

可以使用submitScope阻断作用域(限制在视图树上进一步传递)。比如,上面的代码中,在 Group 后面添加submitScope

Swift
Group {
    TextField("text1", text: $text1)
       .onSubmit { print("text1 commit") }
    TextField("text2", text: $text2)
        .onSubmit { print("text2 commit") }
    }
    .submitScope()  // 阻断作用域
    .onSubmit { print("textfield in group commit") }

当 TextField1 commit 时,控制台输出为

Shell
text1 commit

此时 onSubmit 的作用域将被限定在 Group 之内。

当视图中有多个 TextField 时,通过 onSubmit 和 FocusState(下文介绍)的结合,可以给用户带来非常好的使用体验。

对 searchable 的支持

iOS 15 新增的搜索框在点击return时同样会触发 onSubmit,不过需要将 triggers 设置为 search:

Swift
struct OnSubmitForSearchableDemo:View{
    @State var name = ""
    @State var searchText = ""
    var body: some View{
        NavigationView{
            Form{
                TextField("name:",text:$name)
                    .onSubmit {print("textField commit")}
            }
            .searchable(text: $searchText)
            .onSubmit(of: .search) { // 
                print("searchField commit")
            }
        }
    }
}

需要注意的是,SubmitTriggers 为 OptionSet 类型,onSubmit 对于SubmitTriggers内包含的值会通过环境在视图树中持续传递。当接受到的SubmitTriggers值不包含在 onSubmit 设置的SubmitTriggers时,传递将终止。简单的说,onSubmit(of:.search)将阻断 TextFiled 产生的 commit 状态。反之亦然。

例如,上面的代码,如果我们在 searchable 后面再添加一个onSubmt(of:.text), 将无法对 TextField 的 commit 事件进行响应。

Swift
.searchable(text: $searchText)
.onSubmit(of: .search) {
    print("searchField commit1")
}
.onSubmit {print("textField commit")} // 无法触发,被 search 阻断 

因此当同时对 TextFiled 以及搜索框做处理时,需要特别注意它们之间的调用顺序。

可以通过如下代码在一个 onSubmit 中同时支持 TextField 和搜索框:

Swift
.onSubmit(of: [.text, .search]) {
  print("Something has been submitted")
}

下面代码由于onSubmit(of:search)被放置在searchable之前也一样不会触发。

Swift
NavigationStack{
    Form{
        TextField("name:",text:$name)
            .onSubmit {print("textField commit")}
    }
    .onSubmit(of: .search) { // 不会触发
        print("searchField commit1")
    }
    .searchable(text: $searchText)
}

焦点

在 iOS 15 / macOS Moterey 之前,SwiftUI 没有为 TextField 提供获得焦点的方法(例如:becomeFirstResponder),因此在相当长的时间里,开发者只能通过非 SwiftUI 的方式来实现类似的功能。

在现代 SwiftUI 中,苹果为开发者提供了一个远好于预期的解决方案,同 onSubmit 类似,可以从更高的视图层次来统一对视图中的 TextField 进行焦点的判断和管理。

基础用法

SwiftUI 提供了一个新的 FocusState 属性包装器,用来帮助我们判断该视图内的 TextField 是否获得焦点。通过focusedFocusState与特定的 TextField 关联起来。

Swift
struct OnFocusDemo:View{
    @FocusState var isNameFocused:Bool
    @State var name = ""
    var body: some View{
        List{
            TextField("name:",text:$name)
                .focused($isNameFocused)
        }
        .onChange(of: isNameFocused) { _, value in
            print(value)
        }
    }
}

上面的代码将在 TextField 获得焦点时将isNameFocused设置为true,失去焦点时设置为false

对于同一个视图中的多个 TextField,你可以创建多个 FocusState 来分别关联对应的 TextField,例如:

Swift
struct OnFocusDemo: View {
    @FocusState var isNameFocused: Bool
    @FocusState var isPasswordFocused: Bool
    @State var name = ""
    @State var password = ""
    var body: some View {
        List {
            TextField("name:", text: $name)
                .focused($isNameFocused)
            SecureField("password:", text: $password)
                .focused($isPasswordFocused)
        }
        .onChange(of: isNameFocused) { _, value in
            print(value)
        }
        .onChange(of: isPasswordFocused) { _, value in
            print(value)
        }
    }
}

上述方法当视图中拥有更多的 TextField 时将变得很麻烦,而且不利于统一管理。好在,FocusState 不仅支持布尔值,还支持任何哈希类型。我们可以使用符合 Hashable 协议的枚举来统一管理视图中多个 TextField 的焦点。下面的代码将实现同上面一样的功能:

Swift
struct OnFocusDemo: View {
    @FocusState var focus: FocusedField?
    @State var name = ""
    @State var password = ""
    var body: some View {
        List {
            TextField("name:", text: $name)
                .focused($focus, equals: .name)
            SecureField("password:", text: $password)
                .focused($focus, equals: .password)
        }
        .onChange(of: focus) { _, value in
             print(value ?? "nil") 
        }
    }

    enum FocusedField: Hashable {
        case name, password
    }
}

显示视图后立刻让指定 TextField 获得焦点

通过 FocusState,可以方便的实现在视图显示后,立刻让指定的 TextField 获得焦点并弹出键盘:

Swift
struct OnFocusDemo: View {
    @FocusState var focus: FocusedField?
    @State var name = ""
    @State var password = ""
    var body: some View {
        List {
            TextField("name:", text: $name)
                .focused($focus, equals: .name)
            SecureField("password:", text: $password)
                .focused($focus, equals: .password)
        }
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // 仅低版本需要延时
                focus = .name
            }
        }
    }

    enum FocusedField: Hashable {
        case name, password
    }
}

在低版本 SwiftUI 中,onAppear 要有一定延时才能让 TextField 焦点

在多个的 TextFiled 之间切换焦点

通过使用 focused 和 onSubmit 的结合,我们可以实现当用户在一个 TextField 中输入完成后(点击return),自动让焦点切换到下一个 TextField 的效果。

Swift
struct OnFocusDemo: View {
    @FocusState var focus: FocusedField?
    @State var name = ""
    @State var email = ""
    @State var phoneNumber = ""
    var body: some View {
        List {
            TextField("Name:", text: $name)
                .focused($focus, equals: .name)
                .onSubmit {
                    focus = .email
                }
            TextField("Email:", text: $email)
                .focused($focus, equals: .email)
                .onSubmit {
                    focus = .phone
                }
            TextField("PhoneNumber:", text: $phoneNumber)
                .focused($focus, equals: .phone)
                .onSubmit {
                    if !name.isEmpty && !email.isEmpty && !phoneNumber.isEmpty {
                        submit()
                    }
                }
        }
    }

    private func submit() {
        // submit all infos
        print("submit")
    }

    enum FocusedField: Hashable {
        case name, email, phone
    }
}

上述代码也可以利用 onSubmit 的传递特性变成如下的模样:

Swift
List {
    TextField("Name:", text: $name)
        .focused($focus, equals: .name)
    TextField("Email:", text: $email)
        .focused($focus, equals: .email)
    TextField("PhoneNumber:", text: $phoneNumber)
        .focused($focus, equals: .phone)
}
.onSubmit {
    switch focus {
        case .name:
            focus = .email
        case .email:
            focus = .phone
        case .phone:
            if !name.isEmpty, !email.isEmpty, !phoneNumber.isEmpty {
                submit()
            }
        default:
            break
    }
}

结合设定的屏幕按钮(例如辅助键盘视图)或者快捷键,我们也可以让焦点向前改变或者跳转到其他特定的 TextField 上。

使用快捷键获得焦点

当一个视图中有多个 TextField(包括 SecureField)时,我们可以直接使用 Tab 键按顺序在 TextField 中切换焦点,但 SwiftUI 并没有直接提供使用快捷键让某个 TextField 获得焦点的功能。通过结合 FocusStatekeyboardShortcut 可以在 iPadOS 和 macOS 下获得这种能力。

创建支持快捷键绑定的 focused

Swift
public extension View {
    func focused(_ condition: FocusState<Bool>.Binding, key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View {
        focused(condition)
            .background(Button("") {
                condition.wrappedValue = true
            }
            .keyboardShortcut(key, modifiers: modifiers)
            .hidden()
            )
    }

    func focused<Value>(_ binding: FocusState<Value>.Binding, equals value: Value, key: KeyEquivalent, modifiers: EventModifiers = .command) -> some View where Value: Hashable {
        focused(binding, equals: value)
            .background(Button("") {
                binding.wrappedValue = value
            }
            .keyboardShortcut(key, modifiers: modifiers)
            .hidden()
            )
    }
}

调用代码:

Swift
struct ShortcutFocusDemo: View {
    @FocusState var focus: FouceField?
    @State private var email = ""
    @State private var address = ""
    var body: some View {
        Form {
            TextField("email", text: $email)
                .focused($focus, equals: .email, key: "t")
            TextField("address", text: $address)
                .focused($focus, equals: .address, key: "a", modifiers: [.command, .shift, .option])
        }
    }

    enum FouceField: Hashable {
        case email
        case address
    }
}

当用户输入 ⌘ + T 时,负责 email 的 TextField 将获得焦点,用户输入⌘ + ⌥ + ⇧ + A 时,负责 address 的 TextField 获得焦点。

上述代码在 iPad 模拟器上运行效果不佳(有时无法激活),请使用真机测试。

创建自己的 onEditingChanged

判断单个 TextField 的焦点状态最佳选择仍是使用onEditingChanged,但对于某些无法使用 onEditingChanged 的场合(比如新的 Formatter),我们可以利用 FocusState 来实现类似的效果。

  • 对单个 TextField 进行判断
Swift
public extension View {
    func focused(_ condition: FocusState<Bool>.Binding, onFocus: @escaping (Bool) -> Void) -> some View {
        focused(condition)
            .onChange(of: condition.wrappedValue) { _, value in
                onFocus(value == true)
            }
    }
}

调用:

Swift
struct onEditingChangedFocusVersion: View {
    @FocusState var focus: Bool
    @State var price = 0
    var body: some View {
        Form {
            TextField("Price:", value: $price, format: .number)
                .focused($focus) { focused in
                    print(focused)
                }
        }
    }
}
  • 对多个 TextField 进行判断

为了避免在 TextField 失去焦点后出现多次调用的情况,我们需要在视图层次保存上次获得焦点的 TextField 的 FocusState 值。

Swift
public extension View {
    func storeLastFocus<Value: Hashable>(current: FocusState<Value?>.Binding, last: Binding<Value?>) -> some View {
        onChange(of: current.wrappedValue) { _, newValue in
            if newValue != last.wrappedValue {
                last.wrappedValue = newValue
            }
        }
    }

    func focused<Value>(_ binding: FocusState<Value>.Binding, equals value: Value, last: Value?, onFocus: @escaping (Bool) -> Void) -> some View where Value: Hashable {
        return focused(binding, equals: value)
            .onChange(of: binding.wrappedValue) { _, focusValue in
                if focusValue == value {
                    onFocus(true)
                } else if last == value { // 只触发一次
                    onFocus(false)
                }
            }
    }
}

调用:

Swift
struct OnFocusView: View {
    @FocusState private var focused: Focus?
    @State private var lastFocused: Focus?
    @State private var name = ""
    @State private var email = ""
    @State private var address = ""
    var body: some View {
        List {
            TextField("Name:", text: $name)
                .focused($focused, equals: .name, last: lastFocused) {
                    print("name:", $0)
                }
            TextField("Email:", text: $email)
                .focused($focused, equals: .email, last: lastFocused) {
                    print("email:", $0)
                }
            TextField("Address:", text: $address)
                .focused($focused, equals: .address, last: lastFocused) {
                    print("address:", $0)
                }
        }
        .storeLastFocus(current: $focused, last: $lastFocused) // 保存上次的 focsed 值
    }

    enum Focus {
        case name, email, address
    }
}

键盘

使用 TextField 不可避免的需要同软键盘打交道,本节将介绍几个同键盘有关例子。

键盘类型

在 iPhone 中,我们可以通过keyboardType来设定软键盘类型,方便用户的录入或限制录入字符范围。

比如:

Swift
struct KeyboardTypeDemo: View {
    @State var price: Double = 0
    var body: some View {
        Form {
            TextField("Price:", value: $price, format: .number.precision(.fractionLength(2)))
                .keyboardType(.decimalPad) // 支持小数点的数字键盘
        }
    }
}

image-20211020184520202

目前支持的键盘类型共有 11 种,分别为:

  • asciiCapable

    ASCII 字符键盘

  • numbersAndPunctuation

    数字及标点符号

  • URL

    便于输入 URL,包含字符和./.com

  • numberPad

    使用区域设置的数字键盘(0-9、۰-۹、०-९ 等)。适用于正整数或 PIN

  • phonePad

    数字及其他电话中使用的符号,如*#+

  • namePhonePad

    方便录入文字及电话号码。字符状态同 asciiCapable 类似,数字状态同 numberPad 类似

  • emailAddress

    便于录入@.的 assiiCapable 键盘

  • decimalPad

    包含小数点的 numberPad,具体见上图

  • twitter

    包含@#的 asciiCapable 键盘

  • webSearch

    包含.的 asciiCapable 键盘,return键标记为go

  • asciiCapableNumberPad

    包含数字的 asciiCapable 键盘

尽管苹果预置了不少键盘模式可以选择,不过在某些情况下仍无法满足使用的需要。

比如:numberPad、decimalPad 没有 -return。在现代 SwiftUI 中,我们可以通过 Toolbar 设置键盘辅助视图(下文具体介绍)来轻松解决这一问题。

通过 TextContentType 获得建议

在使用某些 iOS app 时,在录入文字时会在软键盘上方自动提示我们需要输入的内容,比如电话、邮件、验证码等等。这些都是使用 textContentType 得到的效果。

通过给 TextField 设定 UITextContentType,系统在输入时智能地推断出可能想要录入的内容,并显示提示。

下面的代码在录入密码时,将允许使用钥匙串:

Swift
struct KeyboardTypeDemo: View {
    @State var password = ""
    var body: some View {
        Form {
            SecureField("", text: $password)
                .textContentType(.password)
        }
    }
}

image-20211020192033318

下面的代码在录入邮箱地址时,将从你的通讯录和邮件中查找相似的地址予以提示:

Swift
struct KeyboardTypeDemo: View {
    @State var email = ""
    var body: some View {
        Form {
            TextField("", text: $email)
                .textContentType(.emailAddress)
        }
    }
}

image-20211020193117256

可以设定的 UITextContentType 种类有很多,其中使用的比较多的有:

  • password
  • 姓名的选项,如:name、givenName、middleName 等等
  • 地址选项,如 addressCity、fullStreetAddress、postalCode 等等
  • telephoneNumber
  • emailAddress
  • oneTimeCode(验证码)

测试 textContentType最好在真机上进行,模拟器不支持某些项目或者没有足够的信息提供。

取消键盘

有些情况下,在用户输入完毕后,我们需要取消软键盘的显示,以便留出更大的显示空间。

使用 scrollDismissesKeyboard (iOS 16+)

在现代 iOS 开发中,处理列表滚动时键盘收起的最佳实践是使用 scrollDismissesKeyboard 修饰符。这是 iOS 16 引入的原生功能,无需任何 Hack 手段。

Swift
ScrollView {
    TextField("Text", text: $text)
}
.scrollDismissesKeyboard(.immediately) // 滚动时立即收起键盘

使用 FocusState 取消键盘

如果为 TextField 设置了对应的 FocusState,通过将该值设置为falsenil即可取消键盘

Swift
struct HideKeyboardView: View {
    @State private var name = ""
    @FocusState private var nameIsFocused: Bool

    var body: some View {
        Form {
            TextField("Enter your name", text: $name)
                .focused($nameIsFocused)

            Button("dismiss Keyboard") {
                nameIsFocused = false
            }
        }
    }
}

使用 UIKit 方法(旧方法)

更多的情况下,我们可以直接通过 UIkit 提供的方法来取消键盘:

Swift
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

例如下面的代码将在用户对视图进行拖拽时取消键盘:

Swift
struct ResignKeyboardOnDragGesture: ViewModifier {
    var gesture = DragGesture().onChanged { _ in
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }

    func body(content: Content) -> some View {
        content.gesture(gesture)
    }
}

extension View {
    func dismissKeyboard() -> some View {
        return modifier(ResignKeyboardOnDragGesture())
    }
}

struct HideKeyboardView: View {
    @State private var name = ""
    var body: some View {
        Form {
            TextField("Enter your name", text: $name)
        }
        .dismissKeyboard()
    }
}

键盘辅助视图

通过 toolbar 创建

在 SwiftUI 中,我们可以通过 ToolbarItem(placement: .keyboard, content: View) 来自创建键盘的辅助视图(inputAccessoryView)。

通过输入辅助视图,可以解决很多之前难以应对的问题,并为交互提供更多的手段。

下面的代码将为输入浮点数时添加正负转换以及确认按钮:

Swift
struct ToolbarKeyboardDemo: View {
    @State var price = ""
    var body: some View {
        Form {
            TextField("Price:", text: $price)
                .keyboardType(.decimalPad)
                .toolbar {
                    ToolbarItem(placement: .keyboard) {
                        HStack {
                            Button("-/+") {
                                if price.hasPrefix("-") {
                                    price.removeFirst()
                                } else {
                                    price = "-" + price
                                }
                            }
                            .buttonStyle(.bordered)
                            Spacer()
                            Button("Finish") {
                                UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                                // do something
                            }
                            .buttonStyle(.bordered)
                        }
                        .padding(.horizontal, 30)
                    }
                }
        }
    }
}

image-20211020202404796

遗憾的时,通过 ToolbarItem 设置输入辅助视图目前还有以下不足:

  • 显示内容受限

    高度固定,且无法利用辅助视图的完整显示区域。同其他类型的 Toolbar 类似,SwiftUI 会干预内容的排版。

  • 无法对同一视图中多个 TextField 分别设定辅助视图

    在 ToolbarItem 中无法使用稍微复杂一点的判断语法。如果分别对不同的 TextField 进行设定,SwiftUI 会将所有的内容合并起来显示。

目前 SwiftUI 对 toolbar 内容的干预和处理有些过头。初衷是好的,帮助开发者更轻松的组织按钮且自动针对不同平台优化并最佳显示效果。但 toolbar 及 ToolbarItem 的 ResultBuilder 的限制太多,无法在其中进行更复杂的逻辑判断。将键盘辅助视图集成到 toolbar 的逻辑中也有些令人令人费解。

通过 UIKit 创建

当前阶段,通过 UIKit 来创建键盘辅助视图仍是 SwiftUI 下的最优方案。不仅可以获得完全的视图显示控制能力,并且可以对同一视图下的多个 TextField 进行分别设置。

我们将使用 SwiftUI-Introspect 库来实现这一点。

Swift
// 需引入 SwiftUIIntrospect 库
import SwiftUIIntrospect

extension UIView {
    func constrainEdges(to other: UIView) {
        translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            leadingAnchor.constraint(equalTo: other.leadingAnchor),
            trailingAnchor.constraint(equalTo: other.trailingAnchor),
            topAnchor.constraint(equalTo: other.topAnchor),
            bottomAnchor.constraint(equalTo: other.bottomAnchor),
        ])
    }
}

extension View {
    func inputAccessoryView<Content: View>(@ViewBuilder content: @escaping () -> Content) -> some View {
        introspect(.textField, on: .iOS(.v15, .v16, .v17, .v18, .v26)) { td in
            let viewController = UIHostingController(rootView: content())
            viewController.view.constrainEdges(to: viewController.view)
            td.inputAccessoryView = viewController.view
        }
    }
    
    func inputAccessoryView<Content: View>(content: Content) -> some View {
        introspect(.textField, on: .iOS(.v15, .v16, .v17, .v18, .v26)) { td in
            let viewController = UIHostingController(rootView: content)
            viewController.view.constrainEdges(to: viewController.view)
            td.inputAccessoryView = viewController.view
        }
    }
}

调用:

Swift
struct OnFocusDemo: View {
    @FocusState var focus: FocusedField?
    @State var name = ""
    @State var email = ""
    @State var phoneNumber = ""
    var body: some View {
        Form {
            TextField("Name:", text: $name)
                .focused($focus, equals: .name)
                .inputAccessoryView(content: accessoryView(focus: .name))

            TextField("Email:", text: $email)
                .focused($focus, equals: .email)
                .inputAccessoryView(content: accessoryView(focus: .email))

            TextField("PhoneNumber:", text: $phoneNumber)
                .focused($focus, equals: .phone)
        }
        .onSubmit {
            switch focus {
            case .name:
                focus = .email
            case .email:
                focus = .phone
            case .phone:
                if !name.isEmpty, !email.isEmpty, !phoneNumber.isEmpty {}
            default:
                break
            }
        }
    }
}

struct accessoryView: View {
    let focus: FocusedField?
    var body: some View {
        switch focus {
        case .name:
            Button("name") {}.padding(.vertical, 10)
        case .email:
            Button("email") {}.padding(.vertical, 10)
        default:
            EmptyView()
        }
    }
}

自定义 SubmitLabel

默认情况下,TextField(SecureField)在键盘上对应的 submit 行为按钮为 return,通过使用 submitLabel 修饰器,我们可以将 return 按钮修改成更符合输入上下文的显示文字。

Swift
TextField("Username", text: $username)
    .submitLabel(.next)

image-20211021070740662

目前支持的种类有:

  • continue
  • done
  • go
  • join
  • next
  • return
  • route
  • search
  • send

例如之前的代码中,我们可以分别为nameemailphoneNumber设定不同的对应显示:

Swift
TextField("Name:", text: $name)
    .focused($focus, equals: .name)
    .submitLabel(.next)

TextField("Email:", text: $email)
    .focused($focus, equals: .email)
    .submitLabel(.next)

TextField("PhoneNumber:", text: $phoneNumber)
    .focused($focus, equals: .phone)
    .submitLabel(.return)

总结

从 SwiftUI 1.0 开始,苹果持续不断地完善 TextField 的功能。在现代版本中,SwiftUI 不仅提供了更多的原生修饰器,而且提供了 FocusState、onSubmit 此类的统合管理逻辑。SwiftUI 的主要控件的原生功能已经可以比肩对应的 UIKit 控件了。

关于如何对 TextField 的显示做更多的定制,之后会撰文探讨。

订阅 Fatbobman 周报

每周精选 Swift 与 SwiftUI 开发技巧,加入众多开发者的行列。

立即订阅