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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:57:33