先把表单状态和校验规则分开
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)
暂无评论,快来抢沙发吧!