所属专题:移动开发专题:Swift、SwiftUI 与 iOS 实践

SwiftUI 表单与数据校验:从输入状态到可访问的提交流程

SwiftUI 表单与数据校验:从输入状态到可访问的提交流程 - 暂无配图,技术文章默认封面

先把表单状态和校验规则分开

SwiftUI 表单最容易失控的地方,是把输入、错误提示和提交副作用全部塞进视图。一个更容易维护的做法是让表单值保持可编辑,让校验函数只负责返回错误,再由提交动作决定是否调用网络接口。这样用户输入半截内容时不会立刻弹出一串错误,提交时也能一次性聚焦第一个问题。

下面的示例使用 iOS 17 可用的 SwiftUI API,不依赖第三方库。密码只是演示字段,真实项目还应把注册请求放到服务端,并通过 HTTPS 发送。

import SwiftUI

private struct SignUpForm {
    var email = ""
    var password = ""
    var confirmation = ""

    func errors() -> [String: String] {
        var result: [String: String] = [:]
        let trimmedEmail = email.trimmingCharacters(in: .whitespacesAndNewlines)
        if trimmedEmail.isEmpty {
            result["email"] = "请输入邮箱"
        } else if !trimmedEmail.contains("@") {
            result["email"] = "邮箱格式不正确"
        }
        if password.count < 8 {
            result["password"] = "密码至少需要 8 个字符"
        }
        if confirmation != password {
            result["confirmation"] = "两次输入的密码不一致"
        }
        return result
    }
}

struct SignUpView: View {
    private enum Field { case email, password, confirmation }
    @State private var form = SignUpForm()
    @State private var messages: [String: String] = [:]
    @State private var showSuccess = false
    @FocusState private var focusedField: Field?

    var body: some View {
        Form {
            Section("账户信息") {
                TextField("邮箱", text: $form.email)
                    .textInputAutocapitalization(.never)
                    .keyboardType(.emailAddress)
                    .focused($focusedField, equals: .email)
                errorText(for: "email")

                SecureField("密码", text: $form.password)
                    .focused($focusedField, equals: .password)
                errorText(for: "password")

                SecureField("确认密码", text: $form.confirmation)
                    .focused($focusedField, equals: .confirmation)
                errorText(for: "confirmation")
            }
            Section {
                Button("创建账户", action: submit)
            }
        }
        .navigationTitle("注册")
        .alert("提交成功", isPresented: $showSuccess) {
            Button("好的", role: .cancel) {}
        }
    }

    @ViewBuilder
    private func errorText(for key: String) -> some View {
        if let message = messages[key] {
            Text(message).font(.footnote).foregroundStyle(.red)
        }
    }

    private func submit() {
        messages = form.errors()
        guard messages.isEmpty else {
            if messages["email"] != nil { focusedField = .email }
            else if messages["password"] != nil { focusedField = .password }
            else { focusedField = .confirmation }
            return
        }
        // 这里替换为实际的 async 请求,并在请求中处理服务器错误。
        showSuccess = true
    }
}

什么时候校验

提交校验是最低要求。邮箱这类格式简单的字段,可以在失焦后显示错误;密码强度则适合在输入完成或提交时判断。不要在每一次按键时请求服务端验证,这既增加延迟,也会把临时的中间状态当成错误。服务端返回的用户名重复、验证码过期等业务错误,应该另存为表单级消息,而不是伪装成客户端格式错误。

如果表单有多个页面,保存的是用户已经确认的值,校验的是当前步骤需要的值。将 SignUpForm 做成纯值类型后,可以直接为它编写单元测试,不必启动 SwiftUI。校验函数也不应修改界面状态;界面只根据返回的错误字典渲染,从而避免递归更新。

让可访问性和键盘流程可用

每个输入框都需要明确的提示文本和合适的键盘类型。提交失败时把焦点移动到第一个错误字段,VoiceOver 用户也能沿着同一顺序完成修正。错误文字不要只用颜色区分,应该同时提供可读文本。较长表单可在键盘出现时滚动到焦点位置,提交按钮则应在请求进行时禁用,避免重复提交。

SwiftUI 的状态管理可以参考SwiftUI 与 SwiftData:@Observable 状态管理实践。需要处理 Foundation 字符串、日期和正则表达式时,可结合Swift 与 Foundation 框架携手开发:实战技巧与避坑指南。如果表单提交后要缓存草稿或上传附件,再阅读Foundation 框架文件处理全流程指南,按数据生命周期拆分职责。

连接真实接口前的检查

客户端校验只改善体验,不能替代服务端校验。服务端仍需验证邮箱、密码策略、授权和请求频率,并返回稳定的错误码。网络请求应使用 URLSession 的 async/await,把超时、取消、离线和非 2xx 响应转换成用户能理解的状态。日志中不要记录密码和完整令牌。

发布前至少覆盖空值、首尾空格、非 ASCII 邮箱、密码不一致、请求重复点击和服务器拒绝六类情况。用真实设备检查键盘、动态字体和深色模式,再确认错误文字不会把按钮推到屏幕外。移动专题入口移动开发专题汇总了本站的 Swift、SwiftUI 与 iOS 实践,可作为后续阅读路径。

分享这篇文章:

评论 (0)

请 登录 后发表评论, 还没有账户?立即注册

暂无评论,快来抢沙发吧!