Swift用户界面详细信息删除

Swift用户界面详细信息删除,swift,swiftui,swiftui-list,swiftui-navigationlink,Swift,Swiftui,Swiftui List,Swiftui Navigationlink,我有一个SwiftUI列表,当点击单元格时,该列表显示详细视图/推送至导航: import SwiftUI struct DevicesInRangeList: View { @ObservedObject var central = Central() var body: some View { NavigationView { List(central.peripheralsInRange) { peripheral in

我有一个SwiftUI列表,当点击单元格时,该列表显示详细视图/推送至导航:

import SwiftUI

struct DevicesInRangeList: View {
    @ObservedObject var central = Central()

    var body: some View {
        NavigationView {
            List(central.peripheralsInRange) { peripheral in
                NavigationLink(destination: DeviceView(peripheral: peripheral).onAppear {
                    self.central.connect(peripheral: peripheral)
                }.onDisappear {
                    self.central.disconnect(peripheral: peripheral)
                }) {
                    DeviceRow(deviceID: peripheral.deviceID, name: peripheral.name)
                }
            }.onAppear {
                self.central.scanning = true
            }.onDisappear {
                self.central.scanning = false
            }.navigationBarTitle("Devices in range")
        }
    }
}
如果我点击一行,将显示详细信息。如果外围设备断开连接,它将从外围设备量程阵列中删除,并且该行将被删除–但仍会显示详细信息。删除关联行时,如何删除详细信息


编辑: 在Asperi的回答之后,我有以下几点仍然不起作用:

struct DevicesInRangeList: View {
    @ObservedObject var central = Central()

    @State private var localPeripherals: [Peripheral] = []

    @State private var activeDetails = false
    var body: some View {
        NavigationView {
            List(localPeripherals, id: \.self) { peripheral in
                NavigationLink(destination:
                    DeviceView(peripheral: peripheral)
                        .onReceive(self.central.$peripheralsInRange) { peripherals in
                            if !peripherals.contains(peripheral) {
                                self.activeDetails = false
                            }
                        }
                        .onAppear {
                            self.central.connect(peripheral: peripheral)
                        }
                        .onDisappear {
                            self.central.disconnect(peripheral: peripheral)
                        }
                , isActive: self.$activeDetails) {
                    DeviceRow(deviceID: peripheral.deviceID, name: peripheral.name)
                }
            }.onReceive(central.$peripheralsInRange) { peripherals in
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    self.localPeripherals = peripherals
                }
            }.onAppear {
                self.central.scanning = true
                self.localPeripherals = self.central.peripheralsInRange
            }.onDisappear {
                self.central.scanning = false
            }.navigationBarTitle("Devices in range")
        }
    }
}

嗯。。。会有点长,但值得。。。我在简化模型上复制了缺陷行为。。。这就是问题的原因

2020-01-22 19:53:41.008064+0200测试[5539:983123][TableView]警告 仅此一次:UITableView被告知布局其可见单元格和其他单元格 不在视图层次结构中的内容(表视图或 其超级视图尚未添加到窗口)。这可能会导致以下错误: 强制表视图中的视图加载并执行布局而不显示 准确信息(例如表视图边界、特征集合、布局 页边距、安全区域插图等),也会导致不必要的 由于额外的布局过程而导致的性能开销。做一个象征 要捕获的UITableViewAlertForLayoutOutsideViewHierarchy处的断点 请在调试器中执行此操作,并查看是什么导致了这种情况,以便 如果可能的话,完全避免此操作,或者将其推迟到表中 视图已添加到窗口中。表视图:

此异常会中断导航堆栈,因此详细信息视图不会自行关闭,也不会被
isActive
状态强制关闭

下面是重现问题的初始代码(一旦开始,只需浏览任意一行并等待20秒)

这里有一个解决方案。。。这个想法在列表内容的时间更新和决策时刻是分开的,需要关闭细节

struct TestNavigationLinkDestruction_Fixed: View {
    @ObservedObject var model = TestedModel()

    @State private var selected: Int? = nil
    @State private var localStorage: [Int] = []


    var body: some View {
        NavigationView {
            // List locally stored items
            List(localStorage, id: \.self) { item in
                NavigationLink("Item \(item)", destination:
                    DetachedDetailView(item: item)
                        .onReceive(self.model.$originalRange) { items  in
                            if !items.contains(item) {
                                self.selected = nil // !!! unwind at once
                            }
                        }
                , tag:item, selection: self.$selected)
            }
            .onReceive(self.model.$originalRange) { items  in
                DispatchQueue.main.async {
                    self.localStorage = items // !!! postpone local data update
                }
            }
        }
        .onAppear {
            self.localStorage = self.model.originalRange // ! initial load from model

            // >>> simulate async data update
            DispatchQueue.main.asyncAfter(deadline: .now() + 20) {
                self.model.originalRange = [10, 20, 30, 40, 50, 60, 70, 80, 90]
            }
        }
    }
}


所以。。您只需将上述内容应用到代码中,我相信这是可行的。

最好的方法是在显示数据之前检查是否存在od数据。我采用了苹果的master/demo来展示如何做到这一点。在这个模板应用程序中,他们使用@State var作为记录源,但想法是一样的。检查详细视图中是否存在“记录”

import SwiftUI

private let dateFormatter: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .medium
    dateFormatter.timeStyle = .medium
    return dateFormatter
}()

struct ContentView: View {
    @State private var dates = [Date]()

    var body: some View {
        NavigationView {
            MasterView(dates: $dates)
                .navigationBarTitle(Text("Master"))
                .navigationBarItems(
                    leading: EditButton(),
                    trailing: Button(
                        action: {
                            withAnimation { self.dates.insert(Date(), at: 0) }
                        }
                    ) {
                        Image(systemName: "plus")
                    }
                )
            DetailView(dates: $dates).navigationBarTitle(Text("Detail"))
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

struct MasterView: View {
    @Binding var dates: [Date]

    var body: some View {
        List {
            ForEach(dates, id: \.self) { date in
                NavigationLink(
                    destination: DetailView(dates: self._dates, selectedDate: date).navigationBarTitle(Text("Detail"))
                ) {
                    Text("\(date, formatter: dateFormatter)")
                }
            }.onDelete { indices in
                indices.forEach { self.dates.remove(at: $0) }
            }
        }
    }
}

struct DetailView: View {
    @Binding var dates: [Date]
    var selectedDate: Date?

    var body: some View {
            if let selectedDate = selectedDate, dates.contains(selectedDate) {
                return Text("\(selectedDate, formatter: dateFormatter)")
            } else {
                return Text("Detail view content goes here")
            }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

你能告诉我什么是
外围设备吗?它是不是
可观察对象
?它知道自己消失了吗?没有,但是可以观察到central.peripheralsInRange,它正在正确更新列表。我不知道我需要在外设上发布什么属性来指示是否应该显示它,以及在SwiftUI中如何观察它?我找到了原因和修复,这是一次有趣的冒险。请参阅更新的答案。您的假设是正确的
self.activeDetails
在断开连接时设置为false,但详细信息视图不会消失。实际上,尽管设置了该视图,
self.activeDetails
的值不会更改。也许它是固定到导航链接的状态,所以不可能更改?我尝试了这个,但仍然不起作用。activeDetails在更新localPeripherals之前设置为false,但它不会忽略该细节。在asyncAfter块中检查activeDetails时仍然正确。@Nick,在我更新的代码中没有
activeDetails
,这种方法是新的,因为它只包含存根模型,所以经过测试并与Xcode 11.2/iOS 13.2一起工作。但实际上,我不明白有什么不同。activeDetails的设置与所选属性的设置相同。确定是!我把iPad的快照放在这里,以便于查看结果:-)你可以在横向iPhone上尝试它(在纵向上,你看不到细节,直到选中,所以它必须存在。但是在iPhone上,如果你正在查看细节(没有主控显示)并且日期被删除(通过外部事件)我不认为这会跳回主列表?这是可以做到的,为什么不?但想想,这是个好主意吗?最好告诉用户“设备已断开连接”或者诸如此类。如果你忽略细节视图,你必须在iPad、iPhone X、iPhone SE上使用不同的方法,这取决于设备方向……在这种情况下,为了提问和理解,我想回到主视图/删除细节。你想知道如何以编程方式忽略你的细节吗看法
import SwiftUI

private let dateFormatter: DateFormatter = {
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .medium
    dateFormatter.timeStyle = .medium
    return dateFormatter
}()

struct ContentView: View {
    @State private var dates = [Date]()

    var body: some View {
        NavigationView {
            MasterView(dates: $dates)
                .navigationBarTitle(Text("Master"))
                .navigationBarItems(
                    leading: EditButton(),
                    trailing: Button(
                        action: {
                            withAnimation { self.dates.insert(Date(), at: 0) }
                        }
                    ) {
                        Image(systemName: "plus")
                    }
                )
            DetailView(dates: $dates).navigationBarTitle(Text("Detail"))
        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
    }
}

struct MasterView: View {
    @Binding var dates: [Date]

    var body: some View {
        List {
            ForEach(dates, id: \.self) { date in
                NavigationLink(
                    destination: DetailView(dates: self._dates, selectedDate: date).navigationBarTitle(Text("Detail"))
                ) {
                    Text("\(date, formatter: dateFormatter)")
                }
            }.onDelete { indices in
                indices.forEach { self.dates.remove(at: $0) }
            }
        }
    }
}

struct DetailView: View {
    @Binding var dates: [Date]
    var selectedDate: Date?

    var body: some View {
            if let selectedDate = selectedDate, dates.contains(selectedDate) {
                return Text("\(selectedDate, formatter: dateFormatter)")
            } else {
                return Text("Detail view content goes here")
            }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}