SwiftUI List 在 watchOS 上使用 NavigationLinks 性能低下

SwiftUI List slow performance with NavigationLinks on watchOS

我有一个小列表,其中包含大约 20 个元素。列表中的每一行都是一个简单的自定义视图,其中有一些标签嵌入在 NavigationLink 中。

struct RunList: View {
    var model: RunModel
    var body: some View {
        List {
            ForEach(model.runs) { run in
//                NavigationLink(destination: RunOverview(run: run)) {. ** seems to have biggest impact on performance. 
                    RunCell(run: run).frame(height: 100)
//                }
            }
        }
        .listStyle(CarouselListStyle())
        .navigationBarTitle(Text("Demo App"))
    }
}

运行 Apple Watch 上的这个简单列表在滚动时使用大量 CPU 导致丢帧。

当每个列表项都有一个 NavigationLink 作为根时,性能似乎明显更差 view.Removing 导航 link 减少了 CPU 高达 50% 的使用,并大大提高了性能Apple Watch Series 2,但我们需要列表行是可点击的。

我正在构建的应用程序在布局上与 Apple 生产的 PopQuiz 演示应用程序非常相似 https://developer.apple.com/documentation/watchkit/creating_a_watchos_app_with_swiftui

运行上面的示例代码也存在同样的问题。

我已经在 instruments 中对其进行了分析,大部分时间似乎都在与布局相关的代码中。

我很欣赏 Apple Watch 2 现在已经相当老了,但肯定的是,像上面这样的基本列表应该能够 运行 性能。设备上的其他系统应用 运行 很好,尽管它们不太可能使用 swiftUI。

我应该注意哪些提示或问题?

这可能取决于 RunOverview.init 的重量,因为所有这些导航 link 视图都是在此 ForEach 迭代期间构建的(甚至认为尚未激活

您可以尝试 DeferView 将真正的目标构建推迟到相应 link 激活的那一刻

ForEach(model.runs) { run in
    NavigationLink(destination: DeferView { RunOverview(run: run) }) {
        RunCell(run: run).frame(height: 100)
    }
}

使用一个被激活的 NavigationLink 及其信息来自 RunCell TapGesture?

struct RunList: View {
    var model: RunModel
    @State var activeRun: Run?
    @State var runIsActive = false
    var body: some View {
        List {
            if activeRun != nil {
                NavigationLink(destination: RunOverView(run: activeRun!, isActive: $runIsActive, label: {EmptyView()})
            }
            ForEach(model.runs) { run in
                RunCell(run: run)
                    .frame(height: 100)
                    .onTapGesture {
                        self.activeRun = run
                        self.runIsActive = true
                }
            }
        }
        .listStyle(CarouselListStyle())
        .navigationBarTitle(Text("Demo App"))
    }
}

一些想法,

  1. 避免不必要的重绘 RunCell。使其符合 Equatable(如果尚未符合)。
struct RunCell: View, Equatable {

    static func == (lhs: RunCell, rhs: RunCell) -> Bool {
       lhs.run == rhs.run // or whatever is equal
    }
...
  1. 也许修复列表元素的提供大小

RunCell(run: run).fixedSize(vertical: true).frame(height: 100)

  1. 如果所有 RunCell 视图在属性方面看起来大致相同,请将列表放在 compositingGroup 中。

NavigationLink 可能也可以做一些事情,但不确定是什么。有一个用于 Insturments 的 SwiftUI 组件,但我不确定它会比 TimeProfiler 在这里提供更多的洞察力。