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

时间:2020-07-18 17:25:13

标签: swiftui watchkit swiftui-navigationlink

我有一个简单的 watchOS 6.2.8 SwiftUI应用程序,其中向用户显示消息列表。

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

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

class Message: Identifiable {
    let identifier: String
    let date: Date
    let title: String
    let body: String
    let category: String
    
    var id: String {
        identifier
    }

    init(identifier: String, date: Date, title: String, body: String, category: String) {
        self.identifier = identifier
        self.date = date
        self.title = title
        self.body = body
        self.category = category
    }
}

class Category: ObservableObject, Identifiable {
    let name: String
    @Published var messages: [Message] = []
    
    var id: String {
        name
    }

    init(name: String, messages: [Message] = []) {
        self.name = name
        self.messages = messages
    }
}

类别本身是@ObservableObject并发布messages,因此,当类别更新时(例如在后台),显示类别消息列表的视图也将更新。 (效果很好)

要存储这些消息,我有一个简单的MessageStore,它是一个@ObservableObject,看起来像这样:

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上正确传递的问题)

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

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

  • 所有消息 NavigationLink,该视图会显示所有消息
  • 对于每个类别,我都会显示一个NavigationLink,该视图会显示仅属于该特定类别的消息。

enter image description here

这一切都令人惊奇。但是,我不了解发生了一件非常奇怪的事情。 (第一个SwiftUI项目)

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

首先,我发现该类别已从列表中正确删除。

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

最后一部分让我疯狂..我不明白为什么会这样。从数据角度看,一切看起来都很好-消息已被删除,类别也已被删除。在自动导航之后,最终的UI状态也看起来不错-“所有消息”的消息计数正确,并且列表中现在不再显示具有零个消息的类别。

以下是主要ContentViewAllMessagesView的代码。如果有帮助,我当然可以在此处发布完整的代码。

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))
    }
}

抱歉,我知道这是很多代码,但是我觉得我需要提供足够的上下文和可见性以显示此处的情况。

位于https://github.com/st3fan/LearningSwiftUI/tree/master/MasterDetail的完整项目-我认为没有更多相关的代码,但是如果有,请告诉我,我将其移至问题中。

1 个答案:

答案 0 :(得分:0)

问题出在更新的ForEach中,这导致重新创建List并因此中断了链接。这看起来像SwiftUI的缺陷,因此值得向Apple提交反馈。

经过测试的解决方法是将“所有消息”导航链接移出列表(看起来有些不同,但可能合适)。经过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