简体   繁体   中英

Performance issue when creating a list of 1000 elements in SwiftUI

I have a horizontal scroll view with lists. When scrolling horizontally, how to make the lists to snap to the edges.

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

When I give the value of ListView(n: 1000) , the view is crashing. The app launches and a white screen is shown for some time and then I get a black screen.

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

How to fix this? My assumption is that it would be using something like dequeue cells like UITableView , but not sure why it's crashing.

There are a couple of issues with the code provided. The most important is you are not using a List just a ForEach nested in a ScrollView , which is like equivalent of placing 1000 UIViews in a UIStack - not very efficient. There is also a lot of hardcoded dimensions and quite a few of them are duplicates but nevertheless add a significant burden when the views are calculated.

I have simplified quite a lot and it runs with n = 10000 without crashing:

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 don't reuse anything. But List do.

so change this:

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

to this:

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

SwiftUI 2.0

You can use Lazy stacks like the LazyVStack and the LazyHStack . So even if you use them with ScrollView , It will be smooth and performant.

I've created SwiftUI horizontal list which loads views only for visible objects + extra elements as a buffer. Moreover, it exposes the offset parameter as binding so you can follow it or modify it from outside.

You can access source code hereHList

Give it a go. This example is prepared in the swift playground.

Example use case

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

To see content being reused in action you could do something like this

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

If you do remove .clipped() at the end you will see how the extra component is reused while scrolling when it moves out of the frame.

Update

While the above solution is still valid, however, it is a custom component. With WWDC2020 SwiftUI introduces lazy components such as LazyHStack but keep in mind that:

The stack is “lazy,” in that the stack view doesn't create items until it needs to render them onscreen.

Meaning, elements are loaded lazy but after that, they are kept in the memory.

My customHList only refers to visible components. Not the one which already has appeared.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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