首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >只有当列表没有滚动到顶部时,NavigationLink才会在列表更新时弹出

只有当列表没有滚动到顶部时,NavigationLink才会在列表更新时弹出
EN

Stack Overflow用户
提问于 2022-01-11 21:03:03
回答 1查看 60关注 0票数 1

如果标题令人困惑,请表示歉意。因此,我正在实现一个聊天应用程序,其中有一个ChatRow列表,单击该列表后,将进入一个MessageView。当用户发送消息时,ChatRow的列表可能会重新排序,因为我对它们的排序方式是将包含最新消息的消息放在顶部。

代码大致如下(如果需要更多细节,请告诉我):

代码语言:javascript
复制
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懒散地加载元素,所以这可能是问题的原因。但我想不出解决这个问题的方法。

再现问题的代码

只需将以下内容复制到一个文件中并运行即可。

当你点击第一次聊天,点击第一次聊天,点击最后一次聊天,点击按钮时,观察事情是如何变化的。

代码语言:javascript
复制
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())
    }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-01-11 23:26:34

这是正确的,这与List懒洋洋地加载元素有关--一旦NavigationLink离开屏幕,如果Chat元素发生变化,View就会弹出堆栈。

对此的标准解决方案是将隐藏的NavigationLink添加到您的层次结构中,该层次结构具有一个isActive属性,该属性控制它是否处于活动状态。不幸的是,与SWIFT5.5中引入的方便列表元素绑定相比,它需要更多的样板代码。

您的代码可能如下所示:

代码语言:javascript
复制
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,即使它看起来只是演示代码中的单向连接,但假设在真正的代码中,您需要双向通信。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70673422

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档