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

SwiftUI中TextField聚焦触发跨栈视图平滑动画的实现方案

问题描述

使用matchedGeometryEffect关联两个视图的尺寸与位置后,通过TextField聚焦事件触发动画时,键盘弹出会导致动画执行不流畅,如何让元素从position1平滑移动到position2?

相关代码:

struct ContentView: View {
    @State private var text = ""
    @Namespace var ns
    @FocusState private var isFocused: Bool
    
    var body: some View {
        ZStack {
            Color.clear
                .contentShape(Rectangle())
                .onTapGesture {
                    isFocused = false
                }
            VStack {
                HStack {
                    TextField("placeholder", text: $text, axis: .vertical)
                        .focused($isFocused)
                        .frame(minHeight: 28)
                        .background(Color.red)
                    if !isFocused {
                        Text("xxxxxx") // position1
                            .background(Color.blue)
                            .matchedGeometryEffect(id: "xxxxxx", in: ns)
                    }
                }
                if isFocused {
                    HStack {
                        Spacer()
                        Text("xxxxxx") // position2
                            .background(Color.blue)
                            .matchedGeometryEffect(id: "xxxxxx", in: ns)
                    }
                }
            }
            .background(Color.yellow)
            .animation(.easeInOut(duration: 1), value: isFocused)
        }
        .padding()
    }
}

动画表现:元素从TextField右侧(position1)移动到下方靠右位置(position2)时,因键盘弹出导致卡顿,动画不连贯。

解决方案

1. 替换全局动画修饰符,用withAnimation精准控制触发时机

全局.animation会响应所有与isFocused相关的布局变化(包括键盘弹出带来的视图偏移),引发动画冲突。改用withAnimation包裹状态变更操作,仅在聚焦/失焦时触发目标动画:

  • 给TextField添加点击手势,触发聚焦时启动动画:
    TextField("placeholder", text: $text, axis: .vertical)
        .focused($isFocused)
        .frame(minHeight: 28)
        .background(Color.red)
        .onTapGesture {
            withAnimation(.easeInOut(duration: 1)) {
                isFocused = true
            }
        }
    
  • 修改外部点击失焦的逻辑,同样用withAnimation包裹:
    Color.clear
        .contentShape(Rectangle())
        .onTapGesture {
            withAnimation(.easeInOut(duration: 1)) {
                isFocused = false
            }
        }
    
  • 移除原VStack上的全局.animation(.easeInOut(duration: 1), value: isFocused)修饰符。

2. 优化matchedGeometryEffect参数

明确指定匹配的属性与锚点,减少不必要的动画计算,确保动画对齐精准:

// position1的Text
Text("xxxxxx")
    .background(Color.blue)
    .matchedGeometryEffect(id: "xxxxxx", in: ns, properties: .frame, anchor: .center)

// position2的Text
Text("xxxxxx")
    .background(Color.blue)
    .matchedGeometryEffect(id: "xxxxxx", in: ns, properties: .frame, anchor: .center)

3. 隔离键盘布局变化的影响

键盘弹出会导致父视图偏移,给VStack添加.ignoresSafeArea(.keyboard),让容器不随键盘移动,保证动画路径不受干扰:

VStack {
    // 原有内容
}
.background(Color.yellow)
.ignoresSafeArea(.keyboard)

完整修改后的代码

struct ContentView: View {
    @State private var text = ""
    @Namespace var ns
    @FocusState private var isFocused: Bool
    
    var body: some View {
        ZStack {
            Color.clear
                .contentShape(Rectangle())
                .onTapGesture {
                    withAnimation(.easeInOut(duration: 1)) {
                        isFocused = false
                    }
                }
            
            VStack {
                HStack {
                    TextField("placeholder", text: $text, axis: .vertical)
                        .focused($isFocused)
                        .frame(minHeight: 28)
                        .background(Color.red)
                        .onTapGesture {
                            withAnimation(.easeInOut(duration: 1)) {
                                isFocused = true
                            }
                        }
                    
                    if !isFocused {
                        Text("xxxxxx") // position1
                            .background(Color.blue)
                            .matchedGeometryEffect(id: "xxxxxx", in: ns, properties: .frame, anchor: .center)
                    }
                }
                
                if isFocused {
                    HStack {
                        Spacer()
                        Text("xxxxxx") // position2
                            .background(Color.blue)
                            .matchedGeometryEffect(id: "xxxxxx", in: ns, properties: .frame, anchor: .center)
                    }
                }
            }
            .background(Color.yellow)
            .ignoresSafeArea(.keyboard)
        }
        .padding()
    }
}

核心逻辑说明

  • withAnimation确保动画仅在聚焦状态变更时触发,避免键盘布局变化的干扰。
  • matchedGeometryEffect的properties: .frame限定只匹配尺寸与位置,减少动画计算量。
  • .ignoresSafeArea(.keyboard)让容器保持固定布局,保证元素移动路径的连贯性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:23:13