聊天应用场景下SwiftUI List如何在键盘弹出且列表在底部时自动上移
解决方案
实现思路
- 监听系统键盘的弹出/收起通知,获取键盘高度和对应动画参数,保证过渡效果和系统键盘完全同步
- 动态修改List的底部内容内边距,让List可视区域随键盘位置同步调整
- 可选搭配ScrollViewReader实现键盘弹出时自动滚动到列表最底部,符合聊天场景交互需求
完整实现代码
import SwiftUI import Combine struct ChatView: View { @State var text: String = "" @FocusState private var keyboardVisible: Bool @State private var keyboardHeight: CGFloat = 0 @State private var cancellables = Set<AnyCancellable>() var body: some View { NavigationView { VStack(spacing: 0) { ScrollViewReader { proxy in List { ForEach(1...100, id: \.self) { item in Text("\(item)") .id(item) } } .listStyle(.plain) .contentInsets(.init(top: 0, leading: 0, bottom: keyboardHeight, trailing: 0)) // 不需要自动滚到底部可以删除下面的onChange逻辑 .onChange(of: keyboardHeight) { newValue in guard newValue > 0 else { return } withAnimation { proxy.scrollTo(100, anchor: .bottom) } } .navigationTitle("Conversations") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { keyboardVisible = false } label: { Text("Hide Keyboard") } } } } ZStack { Color.red .frame(height: 44) TextEditor(text: $text) .frame(height: 32) .focused($keyboardVisible) } } .onAppear { // 监听键盘弹出 NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification) .sink { notification in guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect, let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } // 减去底部安全区高度避免多余内边距 let safeAreaBottom = UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .compactMap { $0 as? UIWindowScene } .first?.windows.first?.safeAreaInsets.bottom ?? 0 let height = keyboardFrame.height - safeAreaBottom withAnimation(.easeInOut(duration: duration)) { keyboardHeight = height } } .store(in: &cancellables) // 监听键盘收起 NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification) .sink { notification in guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } withAnimation(.easeInOut(duration: duration)) { keyboardHeight = 0 } } .store(in: &cancellables) } } } }
关键说明
- 监听通知时同时获取键盘动画时长,保证List内边距调整的动画和键盘弹出动画完全同步,不会出现割裂感
- 计算键盘高度时减去了底部安全区高度,避免出现多余的底部空白
- iOS15及以上支持.contentInsets修饰符,如果要兼容更低版本,可以替换为
.padding(.bottom, keyboardHeight)
内容的提问来源于stack exchange,提问作者alionthego
相关产品推荐
相关产品推荐

