在watchOS上使用SwiftUI进行意外(自动)导航

在watchOS上使用SwiftUI进行意外(自动)导航,swiftui,watchkit,swiftui-navigationlink,Swiftui,Watchkit,Swiftui Navigationlink,我有一个简单的watchOS 6.2.8 SwiftUI应用程序,其中我向用户提供了一个消息列表 这些消息被建模为类,并具有标题、正文和类别名称。我还有一个Category对象,它是这些消息的视图,只显示特定的类别名称 我特别提到watchOS 6.2.8,因为它似乎与其他平台上的SwiftUI行为有所不同 类别本身是一个@observeObject并发布消息,因此当类别更新时(如在后台),显示类别消息列表的视图也将更新。(这很有效) 为了存储这些消息,我有一个简单的MessageStore,它

我有一个简单的watchOS 6.2.8 SwiftUI应用程序,其中我向用户提供了一个消息列表

这些消息被建模为类,并具有标题、正文和类别名称。我还有一个Category对象,它是这些消息的视图,只显示特定的类别名称

我特别提到watchOS 6.2.8,因为它似乎与其他平台上的SwiftUI行为有所不同

类别本身是一个
@observeObject
并发布
消息
,因此当类别更新时(如在后台),显示类别消息列表的视图也将更新。(这很有效)

为了存储这些消息,我有一个简单的
MessageStore
,它是一个
@observeobject
,如下所示:

class MessageStore: ObservableObject {
    @Published var messages: [Message] = []
    @Published var categories: [Category] = []

    static let sharedInstance = MessageStore()

    func insert(message: Message) throws { ... mutage messages and categories ... }
    func delete(message: Message) throws { ... mutage messages and categories ... }
}
(为了简单起见,我使用单例,因为在watchOS上没有正确传递环境对象会出现问题)

这个故事使
消息
类别
保持同步。添加设置了类别名称的新邮件时,它还将在
类别
列表中创建或更新
类别
对象

在我的主要观点中,我提出了两件事:

  • 所有消息
    导航链接
    ,转到视图以显示所有消息
  • 对于每个类别,我都会显示一个
    NavigationLink
    ,该链接会转到一个视图,以仅显示该特定类别中的消息

令人惊讶的是,这一切都起了作用。但有一件很奇怪的事情发生了,我不明白。(第一个SwiftUI项目)

当我进入“所有消息”列表并删除包含特定类别的所有消息时,当我导航回主视图时,会发生意外情况

首先,我注意到该类别已从列表中正确删除

但是,主视图会自动快速导航到所有消息列表,然后返回。

最后一部分是驱使我。。疯子我不明白为什么会这样。从数据的角度来看,一切看起来都很好——邮件已经被删除,类别也被删除了。在自动导航之后,最终的UI状态看起来也不错——所有消息的消息计数都是正确的,现在没有消息的类别不再显示在列表中

以下是主
ContentView
AllMessagesView
的代码。如果有帮助,我当然可以在这里发布完整的代码

struct AllMessagesView: View {
    @ObservedObject var messageStore = MessageStore.sharedInstance

    @ViewBuilder
    var body: some View {
        if messageStore.messages.count == 0 {
            Text("No messages").multilineTextAlignment(.center)
                .navigationBarTitle("All Messages")
        } else {
            List {
                ForEach(messageStore.messages) { message in
                    MessageCellView(message: message)
                }.onDelete(perform: deleteMessages)
            }
            .navigationBarTitle("All Messages")
        }
    }

    func deleteMessages(at offsets: IndexSet) {
        for index in offsets {
            do {
                try messageStore.delete(message: messageStore.messages[index])
            } catch {
                NSLog("Failed to delete message: \(error.localizedDescription)")
            }
        }
    }
}

//

struct CategoryMessagesView: View {
    @ObservedObject var messageStore = MessageStore.sharedInstance

    @ObservedObject var category: Category

    var body: some View {
        Group {
            if category.messages.count == 0 {
                Text("No messages in category “\(category.name)”").multilineTextAlignment(.center)
            } else {
                List {
                    ForEach(category.messages) { message in
                        MessageCellView(message: message)
                    }.onDelete(perform: deleteMessages)
                }
            }
        }.navigationBarTitle(category.name)
    }

    func deleteMessages(at offsets: IndexSet) {
        for index in offsets {
            do {
                try messageStore.delete(message: category.messages[index])
            } catch {
                NSLog("Cannot delete message: \(error.localizedDescription)")
            }
        }
    }
}

struct ContentView: View {
    @ObservedObject var messageStore = MessageStore.sharedInstance

    var body: some View {
        List {
            Section {
                NavigationLink(destination: AllMessagesView()) {
                    HStack {
                        Image(systemName: "tray.2")
                        Text("All Messages")
                        Spacer()
                        Text("\(messageStore.messages.count)")
                            .font(messageCountFont())
                            .bold()
                            .layoutPriority(1)
                            .foregroundColor(.green)
                    }
                }
            }
            
            Section {
                Group {
                    if messageStore.categories.count > 0 {
                        Section {
                            ForEach(messageStore.categories) { category in
                                NavigationLink(destination: CategoryMessagesView(category: category)) {
                                    HStack {
                                        Image(systemName: "tray") // .foregroundColor(.green)
                                        Text("\(category.name)").lineLimit(1).truncationMode(.tail)
                                        Spacer()
                                        Text("\(category.messages.count)")
                                            .font(self.messageCountFont())
                                            .bold()
                                            .layoutPriority(1)
                                            .foregroundColor(.green)
                                    }
                                }
                            }
                        }
                    } else {
                        EmptyView()
                    }
                }
            }
        }
    }
    
    // TODO This is pretty inefficient
    func messageCountFont() -> Font {
        let font = UIFont.preferredFont(forTextStyle: .caption1)
        return Font(font.withSize(font.pointSize * 0.75))
    }
}
抱歉,我知道这是很多代码,但我觉得我需要提供足够的上下文和可见性来显示这里发生了什么


完整项目在-我不认为更多的代码是相关的,但如果是,请让我知道,我将把它移到这里的问题。

问题在于更新的
ForEach
,这导致重新创建
列表,从而断开链接。这看起来像是SwiftUI缺陷,所以值得向苹果提交反馈

经过测试的解决方法是将所有消息导航链接移出列表(看起来有点不同,但可能是合适的)。使用Xcode 12/watchOS 7.0进行测试

struct ContentView: View {
    @ObservedObject var messageStore = MessageStore.sharedInstance

    var body: some View {
        VStack {
            NavigationLink(destination: AllMessagesView()) {
                HStack {
                    Image(systemName: "tray.2")
                    Text("All Messages")
                    Spacer()
                    Text("\(messageStore.messages.count)")
                        .font(messageCountFont())
                        .bold()
                        .layoutPriority(1)
                        .foregroundColor(.green)
                }
            }
            List {
                ForEach(messageStore.categories) { category in
                    NavigationLink(destination: CategoryMessagesView(category: category)) {
                        HStack {
                            Image(systemName: "tray") // .foregroundColor(.green)
                            Text("\(category.name)").lineLimit(1).truncationMode(.tail)
                            Spacer()
                            Text("\(category.messages.count)")
                                .font(self.messageCountFont())
                                .bold()
                                .layoutPriority(1)
                                .foregroundColor(.green)
                        }
                    }
                }
            }
        }
    }

    // ... other code

是否有理由
消息
是一个类?根据我的经验,SwiftUI数据对象作为结构更好地工作,除非您需要引用语义,而您似乎不需要引用语义。我自己也有类似的结果,我没有使用
列表
,而是使用
滚动视图
——那时UI有点不同,但奇怪的行为消失了。
struct ContentView: View {
    @ObservedObject var messageStore = MessageStore.sharedInstance

    var body: some View {
        VStack {
            NavigationLink(destination: AllMessagesView()) {
                HStack {
                    Image(systemName: "tray.2")
                    Text("All Messages")
                    Spacer()
                    Text("\(messageStore.messages.count)")
                        .font(messageCountFont())
                        .bold()
                        .layoutPriority(1)
                        .foregroundColor(.green)
                }
            }
            List {
                ForEach(messageStore.categories) { category in
                    NavigationLink(destination: CategoryMessagesView(category: category)) {
                        HStack {
                            Image(systemName: "tray") // .foregroundColor(.green)
                            Text("\(category.name)").lineLimit(1).truncationMode(.tail)
                            Spacer()
                            Text("\(category.messages.count)")
                                .font(self.messageCountFont())
                                .bold()
                                .layoutPriority(1)
                                .foregroundColor(.green)
                        }
                    }
                }
            }
        }
    }

    // ... other code