我正在 SwiftUI 中开发一个视图,该视图具有消息的 ScrollView 以及页面底部的文本输入字段。
按下输入字段时,键盘会正确出现,并且输入栏会按预期随键盘一起向上移动。但是,当键盘出现时,我无法让消息保持锚定在滚动视图的底部,这会导致 1-2 条消息被键盘和文本输入字段覆盖。
我理想中想要的是让消息与键盘动画一致向上滚动,就像 iOS 中的消息应用程序一样。
过去几天我尝试用在 Stack、Reddit、YouTube、ChatGPT 等上找到的几种不同方法来解决这个问题,但没有任何效果。
目前的实现如下:
@State private var keyboard = KeyboardResponder()
VStack{
ScrollViewReader { scrollProxy in
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 16) {
ForEach(Array(messages.enumerated()), id: \.offset) { index, chat in
HStack {
Spacer()
VStack(alignment: .trailing) {
if index == 0 || coreViewModel.activeChat!.messages[index - 1].role != "user" {
Text("User")
.font(Constants.Designs.Fonts.Main.BodyTwo)
.foregroundColor(Constants.Designs.Colors.Grayscale.Black)
.padding(.trailing, 12)
}
Text(chat.message)
.padding()
.font(Constants.Designs.Fonts.Main.BodyOne)
.background(Constants.Designs.Colors.Core.Primary)
.foregroundColor(Constants.Designs.Colors.Grayscale.White)
.cornerRadius(12)
}
}
}
}
}
.onChange(of: messages, initial: true) {
scrollProxy.scrollTo(messages.count - 1, anchor: .bottom)
}
.onChange(of: keyboard.currentHeight) {
scrollProxy.scrollTo(messages.count - 1, anchor: .bottom)
}
}
Constants.Designs.TextInputs.CoreTextInput(labelName: "Continue the conversation here", inputTextBinder: $inputText) { }
}
KeyboardResponder 在哪里
final class KeyboardResponder: ObservableObject {
@Published var currentHeight: CGFloat = 0
var keyboardWillShowNotification = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
var keyboardWillHideNotification = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
init() {
keyboardWillShowNotification.map { notification in
CGFloat((notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)!.height - 35)
}
.assign(to: \.currentHeight, on: self)
.store(in: &cancellableSet)
keyboardWillHideNotification.map { _ in
CGFloat(0)
}
.assign(to: \.currentHeight, on: self)
.store(in: &cancellableSet)
}
private var cancellableSet: Set<AnyCancellable> = []
}
关于在这里做什么有什么想法吗?我必须想象这是一个相当常见的实现,但我正在努力在网上找到一个可行的解决方案。
蒂亚!
据我了解,这个例子可能会有帮助。请尝试一下。
struct ContentView: View {
let messages = Message.dummyData
@State private var tappedMessage: Message?
@State private var newMessage = ""
@FocusState private var focus: Bool
var body: some View {
ScrollViewReader { scrollReader in
ScrollView {
LazyVStack(alignment: .leading, spacing: 24) {
ForEach(messages, id: \.id) { message in
MessageContainer(message: message)
.id(message.id)
.onTapGesture {
tappedMessage = message
focus = true
}
}
}
.padding(.horizontal, 16)
}
if let tappedMessage {
VStack {
TextEditor(text: $newMessage)
.frame(height: 80)
.padding()
.background(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.gray, lineWidth: 1)
)
.padding(.horizontal, 16)
.focused($focus)
Button("Send") { self.tappedMessage = nil }
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
withAnimation {
scrollReader.scrollTo(tappedMessage.id)
}
}
}
}
}
}
}