Вопрос или проблема
В данный момент я сталкиваюсь с проблемами в ChatView моего приложения. Это пример кода того, чего я хочу добиться. Он имеет функциональность, такую как загрузка более старых сообщений, когда пользователь почти достиг верха, и загрузка более новых сообщений, когда он внизу.
Проблема в том, что когда пользователь загружает более новые сообщения, scrollview постоянно прокручивается вниз, и это происходит рекурсивно. Это была та же проблема при загрузке более старых сообщений (он постоянно прокручивался вверх), но я исправил это с помощью модификатора flipped(). Однако проблема с нижней частью все еще остается.
struct ChatMessage: View {
let text: String
var body: some View {
HStack {
Text(text)
.foregroundStyle(.white)
.padding()
.background(.blue)
.clipShape(
RoundedRectangle(cornerRadius: 16)
)
.overlay(alignment: .bottomLeading) {
Image(systemName: "arrowtriangle.down.fill")
.font(.title)
.rotationEffect(.degrees(45))
.offset(x: -10, y: 10)
.foregroundStyle(.blue)
}
Spacer()
}
.padding(.horizontal)
}
}
struct Message : Identifiable,Equatable {
var id: Int
var text: String
}
struct GoodChatView: View {
@State var messages: [Message] = []
@State private var scrollViewProxy: ScrollViewProxy? // Хранит прокси
@State var messageId: Int?
var body: some View {
ScrollViewReader { scrollView in
ScrollView {
LazyVStack {
ForEach(messages, id: \.id) { message in
ChatMessage(text: "\(message.text)")
.flippedUpsideDown()
.onAppear {
if message == messages.last {
print("старые данные")
loadMoreData()
}
if message == messages.first {
print("новые данные")
loadNewData()
}
}
}
}
.scrollTargetLayout()
}
.flippedUpsideDown()
.scrollPosition(id: $messageId)
.onAppear {
for i in 1...20 {
let message = Message(id: i, text: "\(i)")
messages.append(message)
}
messageId = messages.first?.id
}
}
}
func loadMoreData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let count = messages.count
var newMessages: [Message] = []
for i in count+1...count+20 {
let message = Message(id: i, text: "\(i)")
newMessages.append(message)
}
messages.append(contentsOf: newMessages)
}
}
func loadNewData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let count = messages.count
var newMessages: [Message] = []
for i in count+1...count+20 {
let message = Message(id: i, text: "\(i)")
newMessages.append(message)
}
newMessages = newMessages.reversed()
messages.insert(contentsOf: newMessages, at: 0)
}
}
}
struct FlippedUpsideDown: ViewModifier {
func body(content: Content) -> some View {
content
.rotationEffect(.radians(Double.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
}
}
extension View {
func flippedUpsideDown() -> some View {
modifier(FlippedUpsideDown())
}
}
Буду признателен за любую помощь. Если есть другие способы достичь этого, дайте знать 🙂
Ответ или решение
Чтобы сохранить положение прокрутки в компоненте ScrollView при добавлении новых сообщений в вашем приложении чата, необходимо применять несколько принципов и подходов. В вашем коде, необходимо учесть несколько моментов, чтобы избежать бесконечной прокрутки вверх и вниз и обеспечить правильное отображение новых сообщений, а также удобство пользователя.
1. Понимание проблемы
Ваш код использует ScrollView
и LazyVStack
для отображения сообщений в чате. При добавлении новых сообщений, ваш интерфейс прокручивает содержимое автоматически. Это становится проблемой, когда вы загружаете новые сообщения, так как прокрутка ведёт к некорректному поведению. Это происходит из-за того, что при добавлении сообщений интерфейс продолжает обновляться, вызывая дополнительные действия прокрутки.
2. Сохранение позиции прокрутки
Чтобы избежать проблемы с непрерывной прокруткой, необходимо сохранить позицию прокрутки перед добавлением новых сообщений и восстанавливать её после. Один из способов – использовать ScrollViewReader
совместно со свойством scrollTo
.
3. Корректировка кода
struct GoodChatView: View {
@State var messages: [Message] = []
@State private var scrollViewProxy: ScrollViewProxy? // Храните прокси
@State var messageId: Int?
var body: some View {
ScrollViewReader { scrollView in
ScrollView {
LazyVStack {
ForEach(messages, id: \.id) { message in
ChatMessage(text: "\(message.text)")
.flippedUpsideDown()
.onAppear {
if message == messages.last {
loadMoreData()
} else if message == messages.first {
loadNewData()
}
}
}
}
.scrollTargetLayout()
}
.flippedUpsideDown()
.onAppear {
for i in 1...20 {
let message = Message(id: i, text: "\(i)")
messages.append(message)
}
messageId = messages.first?.id
}
}
}
func loadMoreData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let count = messages.count
var newMessages = (count + 1...count + 20).map { Message(id: $0, text: "\($0)") }
messages.append(contentsOf: newMessages)
scrollToBottom() // Вернуться к нижней части
}
}
func loadNewData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let count = messages.count
var newMessages = (count + 1...count + 20).map { Message(id: $0, text: "\($0)") }
messages.insert(contentsOf: newMessages.reversed(), at: 0)
scrollToTop() // Вернуться к верхней части
}
}
private func scrollToBottom() {
DispatchQueue.main.async {
if let lastId = messages.last?.id {
messageId = lastId
}
}
}
private func scrollToTop() {
DispatchQueue.main.async {
if let firstId = messages.first?.id {
messageId = firstId
}
}
}
}
struct FlippedUpsideDown: ViewModifier {
func body(content: Content) -> some View {
content
.rotationEffect(.radians(Double.pi))
.scaleEffect(x: -1, y: 1, anchor: .center)
}
}
extension View {
func flippedUpsideDown() -> some View {
modifier(FlippedUpsideDown())
}
}
4. Оптимизация
В приведенном коде были добавлены функции scrollToBottom
и scrollToTop
, которые устанавливают текущую позицию прокрутки. Это помогает избегать проблем с непрерывной прокруткой при обновлении списка сообщений.
Заключение
Данный подход позволит вам сохранить положение прокрутки при загрузке новых сообщений и предотвратить нежелательное поведение интерфейса. Вы сможете создать более удобный и отзывчивый чат, сохраняя при этом целостность пользовательского опыта. Пользуйтесь данными рекомендациями, чтобы обеспечить качественное взаимодействие с вашим приложением.