iOS15-16下UIHostingController嵌入SwiftUI时,如何在键盘显示时隐藏底部安全区?
在iOS15/16中实现UIHostingController嵌入SwiftUI视图时键盘隐藏底部安全区的方案
针对iOS17以下版本(15/16),可以通过以下几种替代方案实现和hostingController.view.keyboardLayoutGuide.usesBottomSafeArea = false相同的效果:
方案一:结合键盘通知动态调整底部约束
iOS15已引入keyboardLayoutGuide但缺少usesBottomSafeArea属性,我们可以通过监听键盘显示/隐藏事件,动态修改hostingController视图的底部约束,让其直接贴合键盘顶部,忽略底部安全区:
let chatMainView = ChatScreenView(viewModel: chatViewModel) let hostingController = UIHostingController(rootView: chatMainView) addChild(hostingController) self.view.addSubview(hostingController.view) hostingController.view.translatesAutoresizingMaskIntoConstraints = false // 定义可动态修改的底部约束 var bottomConstraint: NSLayoutConstraint! // 分版本处理约束初始化 if #available(iOS 17.0, *) { hostingController.view.keyboardLayoutGuide.usesBottomSafeArea = false bottomConstraint = hostingController.view.bottomAnchor.constraint(equalTo: self.view.keyboardLayoutGuide.topAnchor) } else { // iOS15/16默认绑定到父视图底部,后续通过键盘通知调整 bottomConstraint = hostingController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) } // 激活所有约束 NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), hostingController.view.topAnchor.constraint(equalTo: self.view.topAnchor), bottomConstraint ]) hostingController.didMove(toParent: self) // iOS15/16专属:监听键盘状态变化 if #unavailable(iOS 17.0) { // 键盘帧变化通知 NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main ) { [weak self] notification in guard let self = self, let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } // 计算键盘顶部在当前视图中的位置 let keyboardTopInView = self.view.convert(keyboardFrame.origin, from: nil).y // 获取底部安全区高度 let bottomSafeArea = self.view.safeAreaInsets.bottom // 调整约束:让hostingController底部贴到键盘顶部,抵消安全区高度 bottomConstraint.constant = keyboardTopInView - self.view.bounds.height + bottomSafeArea self.view.layoutIfNeeded() } // 键盘隐藏时恢复约束 NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main ) { [weak self] _ in guard let self = self else { return } bottomConstraint.constant = 0 self.view.layoutIfNeeded() } }
方案二:在SwiftUI视图内部忽略键盘安全区
直接在SwiftUI的ChatScreenView中设置忽略键盘带来的底部安全区,同时让UIHostingController的视图填满父视图(包括底部安全区):
SwiftUI视图代码:
struct ChatScreenView: View { var viewModel: ChatViewModel var body: some View { VStack { // 消息列表区域 List { // 消息内容 } // 输入框区域 HStack { TextField("输入消息...", text: .constant("")) Button("发送") {} } .padding() } .ignoresSafeArea(.keyboard, edges: .bottom) // 忽略键盘对应的底部安全区 } }
UIKit嵌入代码:
let chatMainView = ChatScreenView(viewModel: chatViewModel) let hostingController = UIHostingController(rootView: chatMainView) addChild(hostingController) self.view.addSubview(hostingController.view) hostingController.view.translatesAutoresizingMaskIntoConstraints = false // 让hostingController视图完全填满父视图(包含底部安全区) NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), hostingController.view.topAnchor.constraint(equalTo: self.view.topAnchor), hostingController.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor) ]) hostingController.didMove(toParent: self)
这种方式让SwiftUI自行处理键盘弹出时的布局,确保输入框等元素直接贴合键盘顶部,无需额外的UIKit约束调整。
方案三:封装兼容扩展简化代码
将版本兼容逻辑封装成UIView扩展,统一处理键盘布局和安全区:
extension UIView { func setupKeyboardLayoutGuideForHostingController(completion: @escaping (NSLayoutConstraint) -> Void) { if #available(iOS 17.0, *) { keyboardLayoutGuide.usesBottomSafeArea = false completion(bottomAnchor.constraint(equalTo: keyboardLayoutGuide.topAnchor)) } else { // 初始化约束到父视图底部 let constraint = bottomAnchor.constraint(equalTo: superview?.bottomAnchor ?? bottomAnchor) // 监听键盘变化 NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main ) { [weak self, weak constraint] notification in guard let self = self, let superview = self.superview, let constraint = constraint, let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } let keyboardTop = superview.convert(keyboardFrame.origin, from: nil).y let bottomSafeArea = superview.safeAreaInsets.bottom constraint.constant = keyboardTop - superview.bounds.height + bottomSafeArea superview.layoutIfNeeded() } // 键盘隐藏恢复约束 NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main ) { [weak constraint] _ in constraint?.constant = 0 constraint?.superview?.layoutIfNeeded() } completion(constraint) } } }
使用方式:
let chatMainView = ChatScreenView(viewModel: chatViewModel) let hostingController = UIHostingController(rootView: chatMainView) addChild(hostingController) self.view.addSubview(hostingController.view) hostingController.view.translatesAutoresizingMaskIntoConstraints = false var bottomConstraint: NSLayoutConstraint! hostingController.view.setupKeyboardLayoutGuideForHostingController { constraint in bottomConstraint = constraint } NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor), hostingController.view.topAnchor.constraint(equalTo: self.view.topAnchor), bottomConstraint ]) hostingController.didMove(toParent: self)
这样可以让主代码更简洁,同时自动适配不同iOS版本。
内容的提问来源于stack exchange,提问作者Kusal Rajapaksha
相关产品推荐
相关产品推荐

