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

如何在SwiftUI中添加键盘收起时仍常驻显示的键盘工具栏

SwiftUI 实现常驻底部 inputAccessoryView 的方案

原生 SwiftUI 提供的 .toolbar(placement: .keyboard) API 仅会在键盘弹出时渲染视图,没有公开参数支持键盘收起后常驻显示,你可以通过桥接 UIKit 的 inputAccessoryView 能力实现需求,既保留系统级的键盘跟随动画效果,又能实现工具栏常驻。

具体实现

1. 封装 UIKit 桥接层

import SwiftUI
import UIKit

struct AccessoryViewHost<Content: View>: UIViewControllerRepresentable {
    let content: Content
    @Binding var isFirstResponder: Bool
    
    init(isFirstResponder: Binding<Bool>, @ViewBuilder content: () -> Content) {
        self._isFirstResponder = isFirstResponder
        self.content = content()
    }
    
    func makeUIViewController(context: Context) -> AccessoryViewController<Content> {
        let vc = AccessoryViewController<Content>()
        vc.accessoryContent = content
        return vc
    }
    
    func updateUIViewController(_ uiViewController: AccessoryViewController<Content>, context: Context) {
        uiViewController.accessoryContent = content
        if isFirstResponder {
            uiViewController.becomeFirstResponder()
        } else {
            uiViewController.resignFirstResponder()
        }
    }
}

class AccessoryViewController<Content: View>: UIViewController {
    var accessoryContent: Content? {
        didSet {
            hostController.rootView = accessoryContent
            hostController.view.sizeToFit()
        }
    }
    
    private lazy var hostController = UIHostingController(rootView: accessoryContent)
    
    override var canBecomeFirstResponder: Bool { true }
    
    override var inputAccessoryView: UIView? {
        hostController.view
    }
}

2. SwiftUI 层调用示例

struct ChatView: View {
    // 保持为true即可让工具栏常驻,设为false可临时隐藏工具栏
    @State private var isAccessoryActive = true
    @State private var inputText = ""
    
    var body: some View {
        VStack(spacing: 0) {
            // 聊天消息列表
            List(0..<100) { index in
                Text("聊天消息 \(index)")
            }
            .listStyle(.plain)
            
            // 输入框区域
            HStack {
                TextField("输入内容", text: $inputText)
                    .textFieldStyle(.roundedBorder)
                Button("发送") {
                    // 发送逻辑
                }
            }
            .padding()
        }
        .background {
            // 挂载常驻工具栏
            AccessoryViewHost(isFirstResponder: $isAccessoryActive) {
                HStack(spacing: 24) {
                    Button("表情") { /* 表情逻辑 */ }
                    Button("图片") { /* 选图逻辑 */ }
                    Button("语音") { /* 语音逻辑 */ }
                    Spacer()
                    Button("更多") { /* 拓展功能逻辑 */ }
                }
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
                .background(Color(.systemGray5))
            }
        }
        .onTapGesture {
            // 点击空白收起键盘,不会隐藏工具栏
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }
    }
}

注意事项

  • 该方案完全复用 UIKit 原生 inputAccessoryView 能力,工具栏动画和系统键盘完全同步,不会出现自定义视图常见的动画错位、掉帧问题
  • 该方案最低支持 iOS 14,如需兼容更早版本,可将桥接层中的 UIHostingController 替换为纯 UIKit 实现的工具栏视图
  • 输入框不需要放在工具栏内,可以正常放在页面的层级中,不会影响动画效果

内容的提问来源于stack exchange,提问作者alionthego

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 05:57:01