SwiftUI实现类Slack输入附件栏:绑定键盘与拖拽展开
SwiftUI 实现类似Slack的绑定键盘可拖拽展开输入视图
结合UIKit的键盘管理是实现这个需求的最优方案,以下是具体实现思路和代码示例:
核心实现思路
- 键盘状态全局管理:用
ObservableObject封装键盘通知监听,统一同步键盘的显示状态和高度到SwiftUI视图。 - 可拖拽输入容器:通过
UIViewRepresentable封装UIKit视图,添加拖拽手势实现高度调整,同时支持点击唤起键盘。 - 视图布局联动:将输入容器固定在页面底部,通过键盘高度的占位视图保证列表内容不被键盘遮挡,拖拽时动态调整输入容器高度。
完整代码实现
1. 键盘状态管理器
import SwiftUI import UIKit import Combine class KeyboardManager: ObservableObject { @Published var keyboardHeight: CGFloat = 0 @Published var isKeyboardVisible: Bool = false private var cancellables = Set<AnyCancellable>() init() { // 监听键盘弹出通知 NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) .compactMap { $0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect } .map { $0.height } .sink { [weak self] height in self?.keyboardHeight = height self?.isKeyboardVisible = true } .store(in: &cancellables) // 监听键盘收起通知 NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) .sink { [weak self] _ in self?.keyboardHeight = 0 self?.isKeyboardVisible = false } .store(in: &cancellables) } }
2. 可拖拽输入容器封装
struct DraggableInputContainer: UIViewRepresentable { @Binding var inputHeight: CGFloat @Binding var isKeyboardVisible: Bool let content: () -> AnyView func makeUIView(context: Context) -> UIView { let containerView = UIView() containerView.backgroundColor = .systemBackground // 嵌入SwiftUI内容 let hostingController = UIHostingController(rootView: content()) hostingController.view.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(hostingController.view) // 设置约束 NSLayoutConstraint.activate([ hostingController.view.topAnchor.constraint(equalTo: containerView.topAnchor), hostingController.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), hostingController.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), hostingController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 60) // 输入框最小高度 ]) // 添加拖拽手势 let panGesture = UIPanGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handlePan(_:))) containerView.addGestureRecognizer(panGesture) // 添加点击唤起键盘手势 let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:))) containerView.addGestureRecognizer(tapGesture) return containerView } func updateUIView(_ uiView: UIView, context: Context) { // 键盘收起时重置输入框高度 if !isKeyboardVisible { inputHeight = 60 } } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject { let parent: DraggableInputContainer init(_ parent: DraggableInputContainer) { self.parent = parent } @objc func handlePan(_ gesture: UIPanGestureRecognizer) { guard let view = gesture.view else { return } let translation = gesture.translation(in: view.superview) switch gesture.state { case .changed: // 计算新高度,限制在最小60和最大屏幕高度-导航栏-键盘高度之间 let newHeight = max(60, parent.inputHeight - translation.y) let maxAvailableHeight = UIScreen.main.bounds.height - 44 - parent.keyboardHeight parent.inputHeight = min(newHeight, maxAvailableHeight) gesture.setTranslation(.zero, in: view.superview) default: break } } @objc func handleTap(_ gesture: UITapGestureRecognizer) { // 主动唤起键盘 UIApplication.shared.sendAction(#selector(UIResponder.becomeFirstResponder), to: nil, from: nil, for: nil) } } } // 扩展View方便转换为AnyView extension View { func anyView() -> AnyView { AnyView(self) } }
3. 主视图使用示例
struct ChatView: View { @State private var messageText = "" @State private var inputHeight: CGFloat = 60 @StateObject private var keyboardManager = KeyboardManager() var body: some View { NavigationView { List { // 模拟聊天内容列表 ForEach(0..<20) { index in Text("消息 \(index)") .padding(.vertical, 8) } } .navigationTitle("Slack风格聊天") .safeAreaInset(edge: .bottom) { VStack(spacing: 0) { // 可拖拽输入容器 DraggableInputContainer(inputHeight: $inputHeight, isKeyboardVisible: $keyboardManager.isKeyboardVisible) { HStack(spacing: 12) { Button(action: { /* 附件按钮逻辑 */ }) { Image(systemName: "plus.circle") .font(.title) .foregroundColor(.blue) } TextField("输入消息...", text: $messageText) .textFieldStyle(.roundedBorder) Button(action: { // 发送消息逻辑 messageText = "" }) { Text("发送") .foregroundColor(.blue) } } .padding(.horizontal) .frame(height: inputHeight) .background(Color.systemBackground) .anyView() } // 键盘占位视图,避免列表被键盘遮挡 if keyboardManager.isKeyboardVisible { Color.clear.frame(height: keyboardManager.keyboardHeight) } } .background(Color.systemBackground) .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: -2) } .onTapGesture { // 点击空白处收起键盘 UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } } } }
关键细节说明
- 键盘状态同步:通过
KeyboardManager统一处理键盘通知,确保SwiftUI视图能实时响应键盘的显示/收起和高度变化。 - 拖拽逻辑:利用
UIPanGestureRecognizer监听拖拽动作,动态调整输入容器高度,同时限制高度范围保证交互合理性。 - 唤起键盘:点击输入容器时,通过
UIApplication的全局方法让输入框成为第一响应者,实现点击唤起键盘的效果。 - 布局适配:使用
safeAreaInset将输入容器固定在底部,配合键盘高度的占位视图,保证列表内容始终处于可见区域。
内容的提问来源于stack exchange,提问作者Vignesh
相关产品推荐
相关产品推荐

