OK... after doing some prototyping I can confirm the effects you see: under macOS a List
containing 10'000 rows does have a noticable lag of 2 seconds on my M1 Max powered mac when appearing on screen, as you can see in my logs.
2022-05-03 17:58:54.145856+0200 PlainList[4082:156952] [ContentView] body start
2022-05-03 17:58:54.147857+0200 PlainList[4082:156952] [ContentView] body end
2022-05-03 17:58:56.142330+0200 PlainList[4082:156952] [Row] Row 10000 appeared
2022-05-03 17:58:56.154867+0200 PlainList[4082:156952] [Row] Row 1 appeared
2022-05-03 17:58:56.157445+0200 PlainList[4082:156952] [Row] Row 2 appeared
2022-05-03 17:58:56.159027+0200 PlainList[4082:156952] [Row] Row 3 appeared
2022-05-03 17:58:56.160320+0200 PlainList[4082:156952] [Row] Row 4 appeared
2022-05-03 17:58:56.162500+0200 PlainList[4082:156952] [Row] Row 5 appeared
What you see in above logs: SwiftUI is really creating all the 10'000 rows... Row 10000 is even appearing on screen in my case?!
My row contains an AsyncImage
and a Text
as you can see in the code below:
import SwiftUI
import os
struct Row: View {
static let logger = Logger(subsystem: "PlainList", category: "Row")
let nr: Int
var body: some View {
HStack(spacing: 20) {
AsyncImage(url: URL(string: "https://www.hackingwithswift.com/uploads/no-orange.png")!) { image in
image.resizable()
.scaledToFill()
} placeholder: {
ProgressView()
}
.frame(width: 100, height: 80)
.clipped()
Text("Row \(nr)")
.font(.largeTitle)
}
.onAppear {
Self.logger.log("Row \(nr) appeared")
}
.onDisappear() {
Self.logger.log("Row \(nr) disappeared")
}
}
}
struct ContentView: View {
static let logger = Logger(subsystem: "PlainList", category: "ContentView")
var body: some View {
let _ = Self.logger.log("body start")
List {
ForEach(Array(1...10000), id: \.self, content: Row.init)
}
let _ = Self.logger.log("body end")
}
}
If you add more debugging code to the Row.body
you will see that the body is evaluated for each row... 10'000 times!
So this means, SwiftUI is calculating the total height of the List by producing the body of each row atleast once.
To speed things up, you can do the following: Fix the size of the row. Yes, I agree, this ugly... but by adding the following line
.frame(width: 800, height: 80, alignment: .leading)
after the .onDisappear() {...}
block, the lag will mostly disappear (0.27s):
2022-05-03 18:08:02.197917+0200 PlainList[4414:172447] [ContentView] body start
2022-05-03 18:08:02.199928+0200 PlainList[4414:172447] [ContentView] body end
2022-05-03 18:08:02.469837+0200 PlainList[4414:172447] [Row] Row 10000 appeared
2022-05-03 18:08:02.481136+0200 PlainList[4414:172447] [Row] Row 1 appeared
2022-05-03 18:08:02.483785+0200 PlainList[4414:172447] [Row] Row 2 appeared
2022-05-03 18:08:02.485376+0200 PlainList[4414:172447] [Row] Row 3 appeared
2022-05-03 18:08:02.486693+0200 PlainList[4414:172447] [Row] Row 4 appeared
2022-05-03 18:08:02.487971+0200 PlainList[4414:172447] [Row] Row 5 appeared
I hope this solves your issue too.
(To be honest: I have no idea why the code without restricting the Row
to a fixed frame works nicely on iOS... UIKit just works differently than AppKit.)