如果标题令人困惑,请表示歉意。因此,我正在实现一个聊天应用程序,其中有一个ChatRow列表,单击该列表后,将进入一个MessageView。当用户发送消息时,ChatRow的列表可能会重新排序,因为我对它们的排序方式是将包含最新消息的消息放在顶部。
代码大致如下(如果需要更多细节,请告诉我):
struct ContentView: View {
@EnvironmentObject var chatsManager: ChatsManager
@EnvironmentObject var messagesManager: MessagesManager
var body: some View {
NavigationView{
VStack{
// Some Views
VStack{
if chatsManager.chats.isEmpty{
Text("you have no chats for now").frame(maxHeight:.infinity, alignment: .top)
}
else {
List() {
ForEach($chatsManager.chats, id: \.id){ $chat in
NavigationLink (destination:
MessageView(chat: chat)
.onAppear{messagesManager.fetchMessages(from: chat.id)}
){ ChatRow(chat: $chat) }
}
}.listStyle(.plain)
}
}
}.navigationBarTitle("").navigationBarHidden(true)
}.navigationViewStyle(.stack)
}
}一个非常奇怪的事情是,当List被滚动到顶部时,如果我点击视口内的聊天,一切都会完美地工作(没有自动弹出回滚,当手动弹出返回时,List会被正确更新)。
但是,如果我向下滚动列表,当顶部的几个ChatRow从屏幕上滚动时,如果我发送任何消息,就会弹出。
我从搜索web中学到的是,List懒散地加载元素,所以这可能是问题的原因。但我想不出解决这个问题的方法。
再现问题的代码
只需将以下内容复制到一个文件中并运行即可。
当你点击第一次聊天,点击第一次聊天,点击最后一次聊天,点击按钮时,观察事情是如何变化的。
import SwiftUI
import Combine
struct DebugView: View {
@StateObject var chatsManager = ChatsManager()
var body: some View {
NavigationView{
VStack{
HStack {
Text("Chats")
}.padding()
VStack{
List() {
ForEach($chatsManager.chats, id: \.id){ $chat in
NavigationLink (destination:
ChatDetailView(chat: chat)
){ DemoChatRow(chat: $chat) }}
}.listStyle(.plain)
}
}.navigationBarTitle("").navigationBarHidden(true)
}.navigationViewStyle(.stack)
.environmentObject(chatsManager)
}
}
struct DemoChatRow: View {
@Binding var chat: Chat
var body: some View {
VStack{
Text(chat.name)
Text(chat.lastMessageTimeStamp, style: .time)
}
.frame(height: 50)
}
}
struct ChatDetailView: View {
var chat: Chat
@EnvironmentObject var chatsManager: ChatsManager
var body: some View {
Button(action: {
chatsManager.updateDate(for: chat.id)
} ) {
Text("Click to update the current chat to now")
}
}
}
class ChatsManager: ObservableObject {
@Published var chats = [
Chat(id: "GroupChat 1", name: "GroupChat 1", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 2", name: "GroupChat 2", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 3", name: "GroupChat 3", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 4", name: "GroupChat 4", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 5", name: "GroupChat 5", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 6", name: "GroupChat 6", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 7", name: "GroupChat 7", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 8", name: "GroupChat 8", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 9", name: "GroupChat 9", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat 10", name: "GroupChat 10", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat2 5", name: "GroupChat2 5", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat2 6", name: "GroupChat2 6", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat2 7", name: "GroupChat2 7", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat2 8", name: "GroupChat2 8", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat2 9", name: "GroupChat2 9", lastMessageTimeStamp: Date()),
Chat(id: "GroupChat2 10", name: "GroupChat2 10", lastMessageTimeStamp: Date())].sorted(by: {$0.lastMessageTimeStamp.compare($1.lastMessageTimeStamp) == .orderedDescending})
func updateDate(for chatID: String) {
if let idx = chats.firstIndex(where: {$0.id == chatID}) {
self.chats[idx] = Chat(id: chatID, name: self.chats[idx].name, lastMessageTimeStamp: Date())
}
self.chats.sort(by: {$0.lastMessageTimeStamp.compare($1.lastMessageTimeStamp) == .orderedDescending})
}
}
struct Chat: Identifiable, Hashable {
var id: String
var name: String
var lastMessageTimeStamp: Date
static func == (lhs: Chat, rhs: Chat) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
struct DebugView_Previews: PreviewProvider {
static var previews: some View {
DebugView().environmentObject(ChatsManager())
}
}发布于 2022-01-11 23:26:34
这是正确的,这与List懒洋洋地加载元素有关--一旦NavigationLink离开屏幕,如果Chat元素发生变化,View就会弹出堆栈。
对此的标准解决方案是将隐藏的NavigationLink添加到您的层次结构中,该层次结构具有一个isActive属性,该属性控制它是否处于活动状态。不幸的是,与SWIFT5.5中引入的方便列表元素绑定相比,它需要更多的样板代码。
您的代码可能如下所示:
struct DebugView: View {
@StateObject var chatsManager = ChatsManager()
@State private var activeChat : String?
private func activeChatBinding(id: String?) -> Binding<Bool> {
.init {
activeChat != nil && activeChat == id
} set: { newValue in
activeChat = newValue ? id : nil
}
}
private func bindingForChat(id: String) -> Binding<Chat> {
.init {
chatsManager.chats.first { $0.id == id }!
} set: { newValue in
chatsManager.chats = chatsManager.chats.map { $0.id == id ? newValue : $0 }
}
}
var body: some View {
NavigationView{
VStack{
HStack {
Text("Chats")
}.padding()
VStack{
List() {
ForEach($chatsManager.chats, id: \.id) { $chat in
Button(action: {
activeChat = chat.id
}) {
DemoChatRow(chat: $chat)
}
}
}.listStyle(.plain)
}
.background {
NavigationLink("", isActive: activeChatBinding(id: activeChat)) {
if let activeChat = activeChat {
ChatDetailView(chat: bindingForChat(id: activeChat).wrappedValue)
} else {
EmptyView()
}
}
}
}.navigationBarTitle("").navigationBarHidden(true)
}.navigationViewStyle(.stack)
.environmentObject(chatsManager)
}
}注意:我保留了Binding,即使它看起来只是演示代码中的单向连接,但假设在真正的代码中,您需要双向通信。
https://stackoverflow.com/questions/70673422
复制相似问题