在 SwiftUI 中创建包含 1000 个元素的列表时出现性能问题



我有一个带有列表的水平滚动视图。水平滚动时,如何使列表与边缘对齐。

struct RowView: View {
var post: Post
var body: some View {
GeometryReader { geometry in
VStack {
Text(self.post.title)
Text(self.post.description)
}.frame(width: geometry.size.width, height: 200)
//.border(Color(#colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1)))
.background(Color(#colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)))
.cornerRadius(10, antialiased: true)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
struct ListView: View {
var n: Int
@State var posts = [Post(id: UUID(), title: "1", description: "11"),
Post(id: UUID(), title: "2", description: "22"),
Post(id: UUID(), title: "3", description: "33")]
var body: some View {
GeometryReader { geometry in
ScrollView {
ForEach(0..<self.n) { n in
RowView(post: self.posts[0])
//.border(Color(#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)))
.frame(width: geometry.size.width, height: 200)
}
}
}
}
}
struct ContentView: View {
init() {
initGlobalStyles()
}
func initGlobalStyles() {
UITableView.appearance().separatorColor = .clear
}
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView(.horizontal) {
HStack {
ForEach(0..<3) { _ in
ListView(n: 1000)  // crashes
.frame(width: geometry.size.width - 60)
}
}.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0))
}
}
}
}
}

当我给出ListView(n: 1000)的值时,视图崩溃了。应用程序启动并显示白屏一段时间,然后我得到黑屏。

2019-10-06 15:52:57.644766+0530 MyApp[12366:732544] [Render] CoreAnimation: Message::send_message() returned 0x1000000e

如何解决这个问题?我的假设是它会使用像UITableView这样的取消排队单元格之类的东西,但不确定为什么它会崩溃。

提供的代码存在一些问题。最重要的是,您不是在List使用,而是嵌套在ScrollView中的ForEach,这相当于在UIStack中放置了1000个UIViews - 效率不高。还有许多硬编码维度,其中相当多的维度是重复的,但在计算视图时增加了很大的负担。

我已经简化了很多,它以n = 10000运行而不会崩溃:

struct ContentView: View {
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView(.horizontal) {
HStack {
ForEach(0..<3) { _ in
ListView(n: 10000)
.frame(width: geometry.size.width - 60)
}
}   .padding([.leading], 10)
}
}
}
}
}
struct ListView: View {
var n: Int
@State var posts = [Post(id: UUID(), title: "1", description: "11"),
Post(id: UUID(), title: "2", description: "22"),
Post(id: UUID(), title: "3", description: "33")]
var body: some View {
List(0..<self.n) { n in
RowView(post: self.posts[0])
.frame(height: 200)
}
}
}
struct RowView: View {
var post: Post
var body: some View {
HStack {
Spacer()
VStack {
Spacer()
Text(self.post.title)
Text(self.post.description)
Spacer()
}
Spacer()
}   .background(RoundedRectangle(cornerRadius: 10)
.fill(Color(#colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1))))
}
}

>ScrollView不要重用任何东西。但List做到了。

所以改变这个:

ScrollView {
ForEach(0..<self.n) { n in
,,,
}
}

对此:

List(0..<self.n) { n in
,,,
}

SwiftUI 2.0

您可以使用惰性堆栈,例如LazyVStackLazyHStack。所以即使你用ScrollView它们,也会流畅和高性能。

我创建了 SwiftUI 水平列表,它只加载可见对象的视图 + 额外元素作为缓冲区。此外,它将偏移量参数公开为绑定,以便您可以跟踪它或从外部修改它。

您可以在此处访问源代码 HList

试一试!这个例子是在快速的操场上准备的。

示例用例

struct ContentView: View {
@State public var offset: CGFloat = 0

var body: some View {
HList(offset: self.$offset, numberOfItems: 10000, itemWidth: 80) { index in
Text("(index)")
}
}
}

要查看内容在运行中被重复使用,您可以执行以下操作

struct ContentView: View {
@State public var offset: CGFloat = 0

var body: some View {
HList(offset: self.$offset, numberOfItems: 10000, itemWidth: 80) { index in
Text("(index)")
}
.frame(width: 200, height: 60)
.border(Color.black, width: 2)
.clipped()
}
}

如果您在最后删除.clipped(),您将看到额外的组件在滚动时如何重复使用。

更新

虽然上述解决方案仍然有效,但它是一个自定义组件。 在WWDC2020 SwiftUI 中引入了惰性组件,例如 LazyHStack,但请记住:

堆栈是"惰性的",因为堆栈视图在需要在屏幕上呈现项目之前不会创建项目。

这意味着,元素被加载lazy但之后,它们被保存在内存中。

我的自定义 HList 仅引用可见组件。不是已经出现的那个。

最新更新