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

SwiftUI返回导航栈后键盘未正确收起,屏幕区域响应异常

SwiftUI导航返回后键盘异常修复方案

问题概述

在一级页面唤起搜索键盘后,进入二级页面再返回,会出现键盘消失但屏幕下半区无响应、底部safeAreaInset移位的异常。测试环境为Xcode 14.1、iOS 16.1,需兼容iOS 15.0。尝试调用resignFirstResponder或dismissSearch时,要么问题未解决,要么触发如下报错:

pushViewController:animated: called on <_TtGC7SwiftUI41StyleContextSplitViewNavigationControllerVS_19SidebarStyleContext_ 0x10f01c600> while an existing transition or presentation is occurring; the navigation stack will not be updated.
中文翻译:在已有转场/展示操作执行期间调用了pushViewController:animated:,导航栈将无法更新。

该问题源于SwiftUI导航转场与键盘状态同步的冲突,转场时的动画周期与键盘收起操作重叠,导致系统误判键盘仍处于弹出状态。

修复方案

1. 延迟收起键盘避开转场周期

在一级页面的onDisappear回调中,延迟执行焦点重置,避开导航转场的动画时间:

struct FirstLevelView: View {
    @State private var searchQuery = ""
    @FocusState private var isSearchFocused: Bool
    @State private var showSecondView = false

    var body: some View {
        NavigationStack {
            VStack {
                TextField("搜索内容", text: $searchQuery)
                    .focused($isSearchFocused)
                    .textFieldStyle(.roundedBorder)
                    .padding()
                
                List(0..<20) { index in
                    Text("列表项 \(index)")
                }
            }
            .safeAreaInset(edge: .bottom) {
                Text("底部固定视图")
                    .frame(maxWidth: .infinity)
                    .padding()
                    .background(Color.gray.opacity(0.1))
            }
            .navigationTitle("首页")
            .navigationDestination(isPresented: $showSecondView) {
                SecondLevelView()
            }
            .onDisappear {
                // 延迟0.1秒执行,避开导航转场的动画周期
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                    isSearchFocused = false
                }
            }
        }
    }
}

2. 利用导航路径变化同步焦点状态

针对iOS 16+的NavigationStack,通过监听导航路径的变化,在返回一级页面时主动重置焦点:

struct FirstLevelView: View {
    @State private var navPath = NavigationPath()
    @FocusState private var isSearchFocused: Bool

    var body: some View {
        NavigationStack(path: $navPath) {
            VStack {
                TextField("搜索内容", text: .constant(""))
                    .focused($isSearchFocused)
                    .textFieldStyle(.roundedBorder)
                    .padding()
                
                Button("进入二级页面") {
                    navPath.append("second")
                }
            }
            .navigationDestination(for: String.self) { _ in
                SecondLevelView()
            }
            .onChange(of: navPath) { oldPath, newPath in
                // 当路径长度缩短时(从二级页面返回),重置焦点
                if newPath.count < oldPath.count {
                    DispatchQueue.main.async {
                        isSearchFocused = false
                    }
                }
            }
        }
    }
}

3. UIKit协调器兼容iOS 15+

如果使用UISearchController,通过UIViewControllerRepresentable的协调器监听键盘状态,确保转场时键盘正确收起:

struct CustomSearchBar: UIViewControllerRepresentable {
    @Binding var searchText: String
    
    func makeUIViewController(context: Context) -> UISearchController {
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchBar.delegate = context.coordinator
        searchController.obscuresBackgroundDuringPresentation = false
        return searchController
    }
    
    func updateUIViewController(_ uiViewController: UISearchController, context: Context) {
        uiViewController.searchBar.text = searchText
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, UISearchBarDelegate {
        let parent: CustomSearchBar
        
        init(_ parent: CustomSearchBar) {
            self.parent = parent
        }
        
        func searchBarTextDidChange(_ searchBar: UISearchBar) {
            parent.searchText = searchBar.text ?? ""
        }
        
        // 在键盘即将结束编辑时延迟收起,避免与导航转场冲突
        func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                searchBar.resignFirstResponder()
            }
            return true
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:21:08