- Когда появляется клавиатура, весь вид (включая заголовок) поднимается < /li>
Поле ввода иногда скрывается за клавиатурой < /li>
Область прокрутки контента не регулируется правильно < /li>
< /ul>
code < /h2>
Вот моя текущая реализация для Мой чат -раз
(у меня также есть SideMenuview, который, кажется, тоже подталкивается) < /p>
import SwiftUI
struct ChatView: View {
@State private var isMenuOpen = false
@State private var showSettings = false
@State private var showModelSelection = false
@State private var dragOffset: CGFloat = 0
@StateObject private var modelSelectionVM = ModelSelectionViewModel()
@StateObject private var chatHistoryVM = ChatHistoryViewModel()
@StateObject private var chatVM: ChatMessageViewModel
@FocusState private var isTextFieldFocused: Bool
private let menuWidth: CGFloat = UIScreen.main.bounds.width * 0.75
init() {
let modelVM = ModelSelectionViewModel()
let historyVM = ChatHistoryViewModel()
_modelSelectionVM = StateObject(wrappedValue: modelVM)
_chatHistoryVM = StateObject(wrappedValue: historyVM)
_chatVM = StateObject(wrappedValue: ChatMessageViewModel(modelSelectionVM: modelVM, chatHistoryVM: historyVM))
}
var body: some View {
ZStack(alignment: .leading) {
Color(.systemBackground)
.ignoresSafeArea()
// Dim background when menu is open
if isMenuOpen {
Color.black.opacity(0.4)
.ignoresSafeArea()
.onTapGesture { closeMenu() }
}
// Side Menu
SideMenuView(isMenuOpen: $isMenuOpen,
showSettings: $showSettings,
dragOffset: $dragOffset,
chatHistoryVM: chatHistoryVM,
chatVM: chatVM)
.offset(x: isMenuOpen ? 0 : -menuWidth)
VStack(spacing: 0) {
// **Fixed Header**
HStack {
Button(action: toggleMenu) {
Image(systemName: "line.horizontal.3")
.font(.title)
.foregroundColor(.blue)
}
.frame(width: 44, height: 44)
Spacer()
Button(action: { showModelSelection = true }) {
Text(modelSelectionVM.selectedModel?.name ?? "Select Model")
.foregroundColor(modelSelectionVM.selectedModel == nil ? .red : .primary)
}
Spacer()
Button(action: {
if chatVM.messages.isEmpty {
chatVM.isTemporaryChat.toggle()
} else {
chatHistoryVM.selectedChatId = nil
chatVM.clearMessages()
}
}) {
Image(systemName: chatVM.messages.isEmpty ?
(chatVM.isTemporaryChat ? "timer.circle.fill" : "timer.circle") :
"plus.circle.fill")
.font(.title)
.foregroundColor(chatVM.isTemporaryChat ? .orange : .blue)
}
.frame(width: 44, height: 44)
}
.padding(.horizontal)
.padding(.top, 50)
.frame(height: 50) // Fix header height
.background(Color(.systemBackground))
.zIndex(1) // Ensure header stays fixed
// **Scrollable Messages**
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 8) {
if chatVM.isTemporaryChat && chatVM.messages.isEmpty {
Text("This chat won't appear in history, use or create memories, or be used to train our models. For safety purposes, we may keep a copy of this chat for up to 30 days.")
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
.padding()
}
ForEach(chatVM.messages) { message in
ChatMessageView(message: message)
.id(message.id)
}
}
.padding(.bottom, 8)
}
.onChange(of: chatVM.messages.count) { _ in
scrollToBottom(proxy: proxy)
}
}
.padding(.bottom, 10)
// **Input Field**
VStack(spacing: 0) {
Divider()
HStack(spacing: 16) {
TextField("Type a message...", text: $chatVM.currentInput)
.padding(.horizontal, 16)
.padding(.vertical, 12)
.background(Color(.systemGray6))
.cornerRadius(20)
.focused($isTextFieldFocused)
.submitLabel(.send)
.onSubmit {
chatVM.sendMessage()
}
Button(action: {
chatVM.sendMessage()
}) {
Image(systemName: "arrow.up.circle.fill")
.font(.system(size: 30))
.foregroundColor(chatVM.currentInput.isEmpty || chatVM.isTyping ? .gray : .blue)
}
.disabled(chatVM.currentInput.isEmpty || chatVM.isTyping)
}
.padding(.horizontal)
.padding(.vertical, 8)
}
.background(Color(.systemBackground))
.ignoresSafeArea(.keyboard, edges: .bottom)
}
.frame(width: UIScreen.main.bounds.width)
.background(Color(.systemBackground))
.offset(x: dragOffset + (isMenuOpen ? menuWidth : 0))
}
.gesture(
DragGesture()
.onChanged { gesture in
let translation = gesture.translation.width
if isMenuOpen {
dragOffset = Swift.min(0, translation)
} else {
dragOffset = Swift.max(0, translation)
}
}
.onEnded { gesture in
let translation = gesture.translation.width
let velocity = gesture.velocity.width
withAnimation(.spring(response: 0.3, dampingFraction: 0.9)) {
if isMenuOpen {
isMenuOpen = translation > -menuWidth / 2 || velocity > 500
} else {
isMenuOpen = translation > menuWidth / 2 || velocity > 500
}
dragOffset = 0
}
}
)
.overlay {
if showSettings {
SettingsView(isPresented: $showSettings)
.transition(.move(edge: .bottom))
}
if showModelSelection {
ModelSelectionView(isPresented: $showModelSelection, viewModel: modelSelectionVM)
.transition(.move(edge: .bottom))
}
}
}
< /code>
ожидаемое поведение < /h2>
Заголовок должен оставаться фиксированным в верхней части < /li>
Чат Область содержания должна настроить его размер и прокрутка, когда появляется клавиатура < /li>
Поле ввода должно оставаться видимым и перемещаться с клавиатурой < /li>
Аналогично тому, как imessage обрабатывает клавиатуру Внешний вид
< /ul>
что я попробовал < /h2>- Использование .IgnoressaFearea (.keyboard, края: .bottom) < / Code>
- Добавление наблюдателей за клавиатурой и вручную настраивать позиции просмотра
- Использование GeometryReader для управления макетом
- Различные комбинации ZSTACK и VSTACK
- Различные подходы с SafeAreainsets
- ios 17+< /li>
swiftui < /li>
xcode 15.2
Как я могу правильно реализовать обработку клавиатуры в этом интерфейсе чата:
[*] Держите заголовок закрепленным в верхней части < /li>
Разрешить содержание чата регулировать и прокручивать надлежащим образом < /li>
Держите поле ввода видимым Выше клавиатуры
Поддерживайте плавную анимацию во время внешнего вида клавиатуры/исчезновения !
Подробнее здесь: https://stackoverflow.com/questions/794 ... ng-content