SwiftUI中如何阻止键盘上推视图?常规方法无效求助
SwiftUI 键盘弹出时视图上移的解决方法
问题概述
我是编程新手,在用SwiftUI开发第一个应用时碰到了头疼的键盘问题:点击TextField后整个视图会自动向上移动,我需要让视图完全保持不动。
我的ContentView包含自定义导航栏和多个子视图,相关代码如下:
ContentView 代码
struct ContentView: View { var body: some View { CustomNavBarContainerPreview { ZStack { Color(.purple) .opacity(0.4) .ignoresSafeArea(.all) VStack(alignment: .center) { // 包含TextField的子视图 PullView() RandomView() LanguageView() Button { // 按钮操作 } label: { ZStack { RoundedRectangle(cornerRadius: 15) .foregroundColor(.cyan) .frame(minWidth: 300, idealWidth: 340, maxWidth: 340, minHeight: 70, idealHeight: 85, maxHeight: 85, alignment: .center) Text("Accept Settings") .font(Font.custom("Ubuntu-Regular", size: 24)) .foregroundColor(.white) } .padding(.vertical, 10) } } .frame(maxWidth: .infinity, maxHeight: .infinity) // 尝试过这个,但没用 .ignoresSafeArea(.keyboard, edges: .bottom) } // 这个也没效果 .ignoresSafeArea(.keyboard) .onTapGesture { dismissKeyboard() } } } func dismissKeyboard() { UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.endEditing(true) } }
子视图代码
struct PullView: View { @State var num1: String = "" @State var num2: String = "" var body: some View { ZStack(alignment: .topLeading) { Rectangle() .frame(minWidth: 342, idealWidth: 342, maxWidth: 342, minHeight: 150, idealHeight: 178, maxHeight: 178, alignment: .center) .foregroundColor(.white) VStack { HStack { Text("Title One") .bold() .multilineTextAlignment(.leading) .foregroundColor(.black) .padding(.leading, 15) Spacer() Image(systemName: "info.circle.fill") .scaledToFill() .frame(alignment: .trailing) .padding(.trailing, 15) } .frame(maxWidth: 342, maxHeight: 50) .padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0)) .background(Rectangle() .foregroundColor(Color(red: 230/255, green: 234/255, blue: 242/255)) .opacity(0.5)) HStack(alignment: .center) { Text("field1") .foregroundColor(.black) TextField("", value: $num1, formatter: NumberFormatter()) .textFieldStyle(DefaultTextFieldStyle()) .foregroundColor(.black) .multilineTextAlignment(.center) .frame(width: 90, height: 48) .keyboardType(.numberPad) .background(.green) Text("field2") .foregroundColor(.black) TextField("", value: $num2, formatter: NumberFormatter()) .textFieldStyle(DefaultTextFieldStyle()) .foregroundColor(.black) .multilineTextAlignment(.center) .frame(width: 90, height: 48) .keyboardType(.numberPad) .background(.green) } .padding(.all, 10) } } } } struct RandomView: View { var body: some View { ZStack(alignment: .topLeading) { Rectangle() .frame(minWidth: 342, idealWidth: 342, maxWidth: 342, minHeight: 150, idealHeight: 178, maxHeight: 178, alignment: .center) .foregroundColor(.white) VStack { HStack { Text("Title Two") .bold() .multilineTextAlignment(.leading) .foregroundColor(.black) .padding(.leading, 15) Spacer() Image(systemName: "info.circle.fill") .scaledToFill() .frame(alignment: .trailing) .padding(.trailing, 15) } .frame(maxWidth: 342, maxHeight: 50) .padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0)) .background(Rectangle() .foregroundColor(Color(red: 230/255, green: 234/255, blue: 242/255)) .opacity(0.5)) Text("Random settings") Text("Subview test") } } } } struct LanguageView: View { var body: some View { ZStack(alignment: .topLeading) { Rectangle() .frame(minWidth: 342, idealWidth: 342, maxWidth: 342, minHeight: 150, idealHeight: 178, maxHeight: 178, alignment: .center) .foregroundColor(.white) VStack { HStack { Text("Title Three") .bold() .multilineTextAlignment(.leading) .foregroundColor(.black) .padding(.leading, 15) Spacer() Image(systemName: "info.circle.fill") .scaledToFill() .frame(alignment: .trailing) .padding(.trailing, 15) } .frame(maxWidth: 342, maxHeight: 50) .padding(EdgeInsets.init(top: 0, leading: 0, bottom: 0, trailing: 0)) .background(Rectangle() .foregroundColor(Color(red: 230/255, green: 234/255, blue: 242/255)) .opacity(0.5)) Text("Language settings") Text("Subview test") } } } }
自定义导航栏代码
struct CustomNavBar: View { var body: some View { ZStack { Rectangle() .foregroundColor(.clear) .frame(width: .infinity, height: 70) .background(.mint) HStack { Button("Back") { } Spacer() Text("Heading") .bold() .foregroundColor(.white) Spacer() Button("Next") { } } .padding(.horizontal) } .navigationBarHidden(true) } } struct CustomNavBarContainerPreview<Content: View>: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { VStack(spacing: 0) { CustomNavBar() content } } }
我试过在代码各处加.ignoresSafeArea(.keyboard)、用Spacers()、ScrollView方案都没用,想知道问题出在哪,怎么修复,或者能不能用UIKit代码控制视图和键盘?
问题原因
- SwiftUI默认会自动调整布局避免键盘遮挡输入框,你的自定义导航栏容器
CustomNavBarContainerPreview是一个独立的VStack,导致你之前加的.ignoresSafeArea(.keyboard)没有作用到整个视图层级,无法阻止自动上移; - 你用的
UIApplication.shared.windows写法在iOS 13+已经过时,可能影响键盘收起的逻辑。
修复方案
方案1:用UIKit键盘通知手动控制(彻底解决)
通过监听键盘弹出/收起的通知,手动抵消视图的偏移,完全禁用自动上移:
步骤1:创建键盘管理器类
import UIKit import Combine class KeyboardManager: ObservableObject { @Published var keyboardHeight: CGFloat = 0 private var cancellables = Set<AnyCancellable>() init() { // 监听键盘弹出通知 NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) .compactMap { $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect } .map { $0.height } .assign(to: \.keyboardHeight, on: self) .store(in: &cancellables) // 监听键盘收起通知 NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) .map { _ in CGFloat(0) } .assign(to: \.keyboardHeight, on: self) .store(in: &cancellables) } }
步骤2:在ContentView中使用管理器
struct ContentView: View { // 初始化键盘管理器 @StateObject private var keyboardManager = KeyboardManager() var body: some View { CustomNavBarContainerPreview { ZStack { Color(.purple) .opacity(0.4) .ignoresSafeArea(.all) VStack(alignment: .center) { PullView() RandomView() LanguageView() Button { dismissKeyboard() } label: { ZStack { RoundedRectangle(cornerRadius: 15) .foregroundColor(.cyan) .frame(minWidth: 300, idealWidth: 340, maxWidth: 340, minHeight: 70, idealHeight: 85, maxHeight: 85) Text("Accept Settings") .font(Font.custom("Ubuntu-Regular", size: 24)) .foregroundColor(.white) } .padding(.vertical, 10) } } .frame(maxWidth: .infinity, maxHeight: .infinity) } // 用offset抵消键盘带来的视图上移 .offset(y: -keyboardManager.keyboardHeight) .onTapGesture { dismissKeyboard() } } .ignoresSafeArea(.keyboard) } // 更新键盘收起方法,适配iOS 13+ func dismissKeyboard() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
方案2:调整视图层级让.ignoresSafeArea(.keyboard)生效
把.ignoresSafeArea(.keyboard)移到最外层的容器CustomNavBarContainerPreview中,让它作用于整个视图:
struct CustomNavBarContainerPreview<Content: View>: View { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } var body: some View { VStack(spacing: 0) { CustomNavBar() content } // 把忽略键盘安全区域的设置移到这里 .ignoresSafeArea(.keyboard) } }
然后移除ContentView中所有的.ignoresSafeArea(.keyboard)调用,这样整个视图就会忽略键盘的安全区域变化,不会自动上移。
方案说明
- 方案1完全手动控制键盘偏移,适合需要自定义键盘交互的场景,兼容性更好;
- 方案2更简洁,利用SwiftUI的原生设置解决问题;
- 替换了过时的键盘收起方法,确保在新版本iOS上稳定运行。
内容的提问来源于stack exchange,提问作者Maria Kravchenko
相关产品推荐
相关产品推荐

