You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何用@FocusState保持键盘打开且避免焦点切换弹跳?

解决SwiftUI TextField焦点切换时的弹跳问题

问题根源

这种轻微弹跳通常是因为焦点变化时TextField的内置样式(如roundedBorder)会调整边框宽度、内部padding或阴影,导致布局尺寸发生微小变化,触发SwiftUI的默认布局动画,从而产生跳动效果。

解决方案

1. 固定TextField尺寸

给TextField添加固定宽高,避免焦点变化时尺寸波动:

struct NameInputView: View {
    @State private var firstName = ""
    @State private var lastName = ""
    @FocusState private var focusedField: Field?
    
    enum Field {
        case firstName, lastName
    }
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("First Name", text: $firstName)
                .focused($focusedField, equals: .firstName)
                .textFieldStyle(.roundedBorder)
                .frame(height: 44) // 固定高度
                .onSubmit {
                    focusedField = .lastName
                }
            
            TextField("Last Name", text: $lastName)
                .focused($focusedField, equals: .lastName)
                .textFieldStyle(.roundedBorder)
                .frame(height: 44) // 固定高度
                .onSubmit {
                    focusedField = nil
                }
            
            Button("Done") {
                focusedField = nil
            }
        }
        .padding()
    }
}

2. 使用自定义TextField样式(彻底避免布局变化)

自定义样式让焦点仅改变颜色而非布局尺寸,从根源消除跳动:

struct CustomTextFieldStyle: TextFieldStyle {
    let isFocused: Bool
    
    func _body(configuration: TextField<Self._Label>) -> some View {
        configuration
            .padding(.horizontal, 12)
            .padding(.vertical, 8)
            .background(Color(.systemGray6))
            .cornerRadius(8)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(isFocused ? Color.accentColor : Color.clear, lineWidth: 1)
            )
    }
}

struct NameInputView: View {
    @State private var firstName = ""
    @State private var lastName = ""
    @FocusState private var focusedField: Field?
    
    enum Field {
        case firstName, lastName
    }
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("First Name", text: $firstName)
                .focused($focusedField, equals: .firstName)
                .textFieldStyle(CustomTextFieldStyle(isFocused: focusedField == .firstName))
                .onSubmit {
                    focusedField = .lastName
                }
            
            TextField("Last Name", text: $lastName)
                .focused($focusedField, equals: .lastName)
                .textFieldStyle(CustomTextFieldStyle(isFocused: focusedField == .lastName))
                .onSubmit {
                    focusedField = nil
                }
            
            Button("Done") {
                focusedField = nil
            }
        }
        .padding()
    }
}

3. 禁用焦点切换时的动画

如果不需要焦点变化的过渡动画,可以直接禁用:

// 在切换焦点时包裹无动画逻辑
.onSubmit {
    withAnimation(.none) {
        focusedField = .lastName
    }
}

// 或者给TextField添加针对焦点变化的无动画修饰符
TextField("First Name", text: $firstName)
    .focused($focusedField, equals: .firstName)
    .textFieldStyle(.roundedBorder)
    .animation(.none, value: focusedField)

效果验证

应用上述任意方案后,焦点切换时布局尺寸不再发生变化,弹跳效果会完全消失。

内容的提问来源于stack exchange,提问作者agmcoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 03:23:35