@FocusState变量变更监听失效,.onChange仅首次触发如何解决?
问题根因
- 代码存在绑定错误:两个TextField的
.focused(_:equals:)修饰器都绑定了.nameLast枚举值,点击手机号输入框时focusedField不会切换为.phoneNumber,自然不会触发后续的变更回调。 - 旧版
onChange(of:perform:)的取值逻辑坑:iOS17以下的旧版onChange闭包中,直接读取被监听的focusedField属性拿到的是变更前的旧值,只有闭包传入的参数才是变更后的新值,你原有代码打印的是全局focusedField,会误以为变更没有触发。 - 不需要额外维护
prevFocusedField状态,iOS17+的新版onChange已经提供了新旧值两个入参,旧版本也可以通过入参与属性值的差值拿到旧焦点。
修复后可运行实现
struct FocusView: View { @State private var nameLast = "Ispum" @State private var phoneNumber = "+41 79 888 88 88" enum Field { case nameLast case phoneNumber } @FocusState private var focusedField: Field? var body: some View { VStack { TextField("Last Name", text: $nameLast) .focused($focusedField, equals: .nameLast) TextField("Phone Number", text: $phoneNumber, prompt: Text("")) // 修正绑定的枚举值 .focused($focusedField, equals: .phoneNumber) } // iOS17+ 新版onChange,直接获取新旧焦点 .onChange(of: focusedField) { oldFocus, newFocus in // oldFocus即为刚刚失去焦点的输入框,直接对应做校验即可 switch oldFocus { case .nameLast: // 执行姓氏校验逻辑 print("校验姓氏:\(nameLast)") case .phoneNumber: // 执行手机号校验逻辑 print("校验手机号:\(phoneNumber)") case nil: // 首次加载无旧焦点,无需处理 break } } // 如果需要兼容iOS15/iOS16,用旧版onChange写法 /* .onChange(of: focusedField) { newFocus in // 此时focusedField是旧值,newFocus是新值 switch focusedField { case .nameLast: print("校验姓氏:\(nameLast)") case .phoneNumber: print("校验手机号:\(phoneNumber)") case nil: break } } */ .textFieldStyle(.roundedBorder) .padding() } // 校验逻辑实现示例 private func validateLastName(_ value: String) { // 你的校验逻辑 } private func validatePhoneNumber(_ value: String) { // 你的校验逻辑 } }
更优实现建议
如果校验逻辑和输入框强绑定,也可以单独给每个TextField加失去焦点监听,避免所有校验逻辑堆在同一个onChange里:
- 对每个输入框单独监听
focusedField == 当前枚举值的变化,当值从true变为false时触发校验即可。 - 如果需要支持用户点击回车提交校验,可额外给TextField加
.onSubmit修饰器触发对应校验。
内容的提问来源于stack exchange,提问作者appfrosch
相关产品推荐
相关产品推荐

