当键盘出现时,SwiftUI ScrollView 不会滚动到底部

问题描述 投票:0回答:1

我正在 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> = []
}

关于在这里做什么有什么想法吗?我必须想象这是一个相当常见的实现,但我正在努力在网上找到一个可行的解决方案。

蒂亚!

swift swiftui keyboard
1个回答
0
投票

据我了解,这个例子可能会有帮助。请尝试一下。

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)
                        }
                    }
                }
           }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.