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

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代码控制视图和键盘?


问题原因

  1. SwiftUI默认会自动调整布局避免键盘遮挡输入框,你的自定义导航栏容器CustomNavBarContainerPreview是一个独立的VStack,导致你之前加的.ignoresSafeArea(.keyboard)没有作用到整个视图层级,无法阻止自动上移;
  2. 你用的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:57:01