1

I read everywhere that List is supposed to be Lazy on iOS but following snippet seems to contradict it

import SwiftUI

struct Item {
    var id: Int
}

let items = (1...30000)
    .map { v in
        Item(id: v)
    }

struct ItemRow:View{
    let item: Item
    init(item: Item){
        self.item = item
        print("init #\(item.id)")
    }
    var body: some View{
        Text(String(item.id))
    }
}

struct ContentView: View {
    var body: some View {
//        ScrollView {
//            LazyVStack {
//                ForEach(items, id: \.id) { item in
//                    ItemRow(item: item)
//                }
//            }
//        }
        List(items, id: \.id) { item in
            ItemRow(item: item)
        }
    }
}

This prints out 30000 times twice (new empty project). Also checked that ItemRow's body is also called for all 30k items immediately.

Am I missing something?

5
  • It's supposed to work, I cannot reproduce with List? Commented Nov 14, 2024 at 10:20
  • 1
    Looks like it becomes lazy when the ItemRow is wrapped in an HStack/VStack. Commented Nov 14, 2024 at 20:10
  • @Kevin Hmm, it does! At least in this extremely simple example. Any idea why? Commented Nov 15, 2024 at 7:52
  • 1
    @sonle You can reproduce it by opening new project and pasting the snippet as is Commented Nov 15, 2024 at 7:53
  • I'm not 100% sure, but from my testing it seems to require its direct contents to be of a type that it can determine the exact number of rows for. I'll post an answer explaining what I think/found. Commented Nov 15, 2024 at 16:12

2 Answers 2

1

TL;DR wrap all the rows in a VStack or similar container.

At a high level, laziness requires a List to know from its Content type that all iterations will result in the same number of cells, and how many that is. Evidently the algorithm to do this only crawls known concrete classes and does not check the View.Body associated type on custom views.

List will be lazy if and only if its body conforms to the above. If the List body contains one or more ForEach loops, either directly or nested in Section, the List itself will be greedily iterated but the ForEach(es) will be lazy iff their bodies meet the same requirements.

An extensive but probably not comprehensive list of acceptable views:

Container views, regardless of contents

  • HStack / VStack / ZStack
  • LazyHStack / LazyVStack
  • LazyHGrid / LazyVGrid
  • Grid
  • Table
  • ScrollView
  • Probably nested Lists, I didn't check.

Leaf/Control views

Certain transparent/helper views if and only if their content meets the requirements, e.g.

  • TupleView (implicit wrapper for multiple views)
  • Group (only groups for purpose of modifiers; is NOT a container.)
  • ScrollReader
  • GeometryReader
Sign up to request clarification or add additional context in comments.

Comments

0

It calls body to figure out how many Views there are per row. It needs to do that to calculate the size of the List. It is much faster if you have constant number of Views per row, i.e. avoid any any ifs. It isn't a big deal, View structs are just values like Ints, negligible performance-wise. As long as you don't do anything slow in body, e.g. accidentally init a heap object or a sort. This was actually recently covered at WWDC 2023 https://developer.apple.com/videos/play/wwdc2023/10160?time=806

To speed it up you can just do:

List(items) { item in
    ItemRow(itemID: item.id) // since you only need the id it wont call body if another property of item is changed.
}

or

List(items) { item in
    Text(item.id, format: .number)
    Text(item.text)
}

It's best to only pass in the data Views need to keep them small and fast. E.g. in the last example it avoids the pointless wrapper ItemRow.

3 Comments

Thank you for your reply. Naturally I dumbed down the example as possible but encountered this behaviour while working with a list that contains multiple types of rows each type having custom view composition based on data so in light of what you mentioned I don't think I can achieve laziness of List in my scenario.
Any idea why Kevin's comment turns the list into a lazy one (wrapping ItemRow in HStack or VStack)
Maybe because now HStack's body is called instead of your ItemRow. So no difference in terms of performance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.