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

