简体   繁体   English

SwiftUI 列出 watchOS 上 NavigationLinks 性能低下的问题

[英]SwiftUI List slow performance with NavigationLinks on watchOS

I have a small List with around 20 elements in it.我有一个小列表,其中包含大约 20 个元素。 Each row in the list is a simple custom view with a few labels inside embedded in a NavigationLink.列表中的每一行都是一个简单的自定义视图,其中有一些标签嵌入在 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"))
    }
}

Running this simple list on an Apple Watch using significant amounts of CPU when scrolling causing it to drop frames.在 Apple Watch 上运行这个简单的列表会在滚动时占用大量 CPU,导致丢帧。

The performance seems to be significantly worse when each list item has a NavigationLink as the root view.Removing the navigation link reduces CPU usage by upto 50% and vastly improves performance on an Apple Watch Series 2 but we need the list rows to be clickable.当每个列表项都有一个 NavigationLink 作为根视图时,性能似乎明显更差。删除导航链接可将 CPU 使用率降低多达 50%,并大大提高 Apple Watch Series 2 的性能,但我们需要列表行可点击。

The app I am building is very similar in layout to PopQuiz demo app produced by Apple https://developer.apple.com/documentation/watchkit/creating_a_watchos_app_with_swiftui我正在构建的应用程序在布局上与 Apple https 生产的 PopQuiz 演示应用程序非常相似://developer.apple.com/documentation/watchkit/creating_a_watchos_app_with_swiftui

Running the above sample code also exhibits the same issues.运行上面的示例代码也会出现同样的问题。

I have profiled it in instruments and the bulk of the time seems to be in layout related code.我已经在仪器中对它进行了分析,大部分时间似乎都在与布局相关的代码中。

在此处输入图像描述

I appreciate the Apple Watch 2 is fairly old now but surely a basic list such as the above should be able to run performant.我很欣赏 Apple Watch 2 现在已经相当老了,但可以肯定的是,像上面这样的基本列表应该能够正常运行。 Other system apps on the device run well although it is unlikely they will be using swiftUI.设备上的其他系统应用程序运行良好,尽管它们不太可能使用 swiftUI。

Are tips or gotchas I should be aware of?是我应该注意的提示或问题吗?

What about using one NavigationLink that gets activated and its information from RunCell TapGesture?使用一个被激活的 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"))
    }
}

Some ideas,一些想法,

  1. Avoid unnecessary redrawing of RunCell .避免不必要的重绘RunCell Make it conform to Equatable if it's not already.如果还没有,请使其符合Equatable
struct RunCell: View, Equatable {

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

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

  1. If all RunCell views look roughly the same in terms of properties, put the List in a compositingGroup .如果所有 RunCell 视图在属性方面看起来大致相同,请将 List 放入compositingGroup中。

There's probably something that can be done with the NavigationLink also, but not sure what. NavigationLink 也可能可以做一些事情,但不确定是什么。 There's a SwiftUI component for Insturments, but I'm not sure it will give more insight than TimeProfiler here. Insturments 有一个 SwiftUI 组件,但我不确定它会比 TimeProfiler 在这里提供更多的洞察力。

It might depend on how heavy is RunOverview.init because all those navigation link views are constructed during this ForEach iteration (even thought not yet activated这可能取决于RunOverview.init的重量,因为所有这些导航链接视图都是在此 ForEach 迭代期间构建的(即使认为尚未激活

You can try DeferView from this solution to defer real destination construction to the moment when corresponding link activated您可以尝试从此解决方案中的DeferView将真正的目标构建推迟到相应链接激活的那一刻

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM