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

SwiftUI中键盘弹出时ScrollViewReader未上移,消息被覆盖如何解决?

解决键盘弹出遮挡消息列表的问题

问题说明

点击输入框开始输入时,键盘带动画弹出,包含输入框和发送按钮的HStack会向上偏移,但此时键盘与上移后的HStack会遮挡底部的消息列表,需要让消息列表同步向上偏移,避免内容被遮挡。

解决步骤

  1. 添加键盘高度状态变量:存储当前键盘高度,用于动态调整布局
  2. 监听键盘通知:通过系统通知获取键盘显示/隐藏时的高度变化,配合动画更新状态
  3. 动态调整内边距:给包含消息列表和输入栏的容器设置动态底部内边距,值与键盘高度绑定,让整体内容随键盘弹出而上移
  4. 优化滚动行为:键盘高度变化时自动滚动到最新消息,保证用户能看到底部对话内容

修改后的完整代码

import SwiftUI

struct ContentView: View {
    @State var messages: [Message] = [
        Message(text: "Hello!", isMe: false),
        Message(text: "Hi there!", isMe: true),
        Message(text: "How are you?", isMe: false),
        Message(text: "I'm good, thanks. How about you?", isMe: true),
        Message(text: "I'm doing well too, thanks for asking.", isMe: false)
    ]
    @State var newMessage: String = ""
    @State private var keyboardHeight: CGFloat = 0 // 存储键盘高度

    var body: some View {
        VStack {
            ScrollViewReader { scrollView in
                ScrollView {
                    VStack(alignment: .leading, spacing: 10) {
                        ForEach(messages) { message in
                            MessageView(message: message)
                                .id(message.id)
                        }
                    }
                    .onChange(of: messages) { _ in
                        withAnimation {
                            scrollView.scrollTo(messages.last?.id)
                        }
                    }
                    .onChange(of: keyboardHeight) { _ in
                        // 键盘高度变化时滚动到最新消息
                        withAnimation {
                            scrollView.scrollTo(messages.last?.id)
                        }
                    }
                }
                .onAppear {
                    scrollView.scrollTo(messages.last?.id)
                    // 注册键盘显示/隐藏通知
                    NotificationCenter.default.addObserver(
                        forName: UIResponder.keyboardWillShowNotification,
                        object: nil,
                        queue: .main
                    ) { notification in
                        let frame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero
                        withAnimation {
                            keyboardHeight = frame.height
                        }
                    }

                    NotificationCenter.default.addObserver(
                        forName: UIResponder.keyboardWillHideNotification,
                        object: nil,
                        queue: .main
                    ) { _ in
                        withAnimation {
                            keyboardHeight = 0
                        }
                    }
                }
                .onDisappear {
                    // 移除通知监听,避免内存泄漏
                    NotificationCenter.default.removeObserver(self)
                }
                .gesture(
                    TapGesture()
                        .onEnded { _ in
                            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                        }
                )
            }

            HStack {
                TextField("Type a message...", text: $newMessage)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Button(action: sendMessage) {
                    Text("Send")
                }
            }
            .padding()
        }
        .padding(.bottom, keyboardHeight) // 动态绑定底部内边距
        .edgesIgnoringSafeArea(.bottom) // 忽略底部安全区,让输入栏紧贴键盘
    }

    func sendMessage() {
        guard !newMessage.trimmingCharacters(in: .whitespaces).isEmpty else { return }
        messages.append(Message(text: newMessage, isMe: true))
        newMessage = ""
        // 发送后自动收起键盘
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct Message: Identifiable, Equatable {
    let id = UUID()
    let text: String
    let isMe: Bool
}

struct MessageView: View {
    let message: Message

    var body: some View {
        HStack {
            if message.isMe {
                Spacer()
            }
            Text(message.text)
                .padding(10)
                .background(message.isMe ? Color.blue : Color.gray)
                .foregroundColor(.white)
                .cornerRadius(10)
            if !message.isMe {
                Spacer()
            }
        }
        .padding(message.isMe ? .leading : .trailing, 50)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

代码说明

  • 键盘监听逻辑:通过系统通知捕获键盘高度变化,用动画同步更新状态变量,保证界面过渡流畅
  • 动态布局调整:容器的底部内边距与键盘高度绑定,键盘弹出时自动增加内边距,推动整个内容区上移,避免遮挡
  • 滚动优化:在键盘状态变化和新消息添加时,自动滚动到最新消息,确保用户始终能看到对话底部
  • 输入体验优化:发送消息时过滤空内容,同时自动收起键盘,提升交互流畅度
  • 内存管理:视图消失时移除通知监听,防止内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 14:42:47