WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SwiftUI List performance with row count > 2000

Forums > SwiftUI

Good afternoon

For a online shop backend I want to move away functionality from the Datables web app onto macOS... few things are just to complex to handle with a Datatables only solution (o;

Especially for editing shop items with Datatables around 3800 rows are returned as JSON and displayed in the browser, while loading of the JSON to /dev/null takes itself around 300msecs....

How could SwiftUI lists performance be enhanced when dealing with row count between 2000 and 20000?

Tried with a fix number of 2000 rows...and I see that the data is loaded as fast as with "wget -O /dev/null server.com/json.php"...but until the rows are displayed on macOS it takes almost 2 seconds....and for 5000 rows almost 5 seconds....so 1000 rows/second....

Is there a built-in functionality like Datatables has to load parts on demand when it knows the total number of rows in advance? Not sure how searching for specific rows would be affected as this is rather fast with Datatables on Safari.

thanks in advance richard

   

Maybe looking at the concurrency methods in last year's HWS Live 2021 might give you some ideas on how to approach this.

As you have HWS+ you should be able to access this.

   

I not sure the figures of speed you gave is it retrive from the server or loading into the UI on device? If latter you might look at using a Set or you might want to look at Combine Mastery in SwiftUI or Working with Data in SwiftUI by Mark MoeyKens of Big Mountain Studio.

   

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

   

Another solution which works on macOS: replace List with ScrollView and LazyVStack:

    ScrollView {
        LazyVStack {
            ForEach(Array(1...10000), id: \.self, content: Row.init)
        }
    }

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.