如果标题令人困惑,我很抱歉。因此,我正在实现一个聊天应用程序,其中有一个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
得到滚动离开屏幕,我会弹出回来,如果我发送任何消息。
我在网上搜索发现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())
}
}
2条答案
按热度按时间exdqitrt1#
这与
List
延迟加载元素的事实有关--一旦NavigationLink
离开屏幕,如果Chat
元素发生变化,View
最终将从堆栈中弹出。标准的解决方案是在层次结构中添加一个隐藏的
NavigationLink
,该NavigationLink
具有isActive
属性,该属性控制NavigationLink
是否处于活动状态。不幸的是,与Swift 5.5中引入的方便的列表元素绑定相比,它需要更多的样板代码。您的代码可能如下所示:
Binding
保留为DemoChatRow
,尽管在演示代码中它看起来只是一个单向连接,假设在您的真实的代码中,您需要双向通信 *4xrmg8kj2#
尽管@jnpdc的答案有效,但是您失去了所有
NavigationLink
特性,比如选定状态等。解决这个问题的一个简单方法是将你的
List
Package 在ScrollViewReader
中,并保持滚动位置在背景中。只需观察活动对象,当它改变时,滚动到对象的id
。P.S.这个问题在任何情况下都可以通过iOS 16 API解决。