Xavi Moll
Xavi Moll

Reputation: 287

SwiftUI List is extremely slow at showing actions (leading/trailing, contextMenu) when the dataset is big

I'm facing performance problems using a SwiftUI List with lots of data. I've created a demo app just to showcase the problem with 500_000 Strings and to show a trailing action for one of them, the CPU hits 100% for a few seconds, which is totally unusable. I also went ahead and wrapped UITableView to use it on SwiftUI using the same dataset (the same half a million Strings) and the trailing action is displayed instantly.

Is there any way to speed things up on the SwiftUI List or is this just a limitation of the framework?

I've made it easy to test both implementations by just changing the var named listKind, here's the sample code:

import SwiftUI

@main
struct LargeListPerformanceProblemApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView().navigationBarTitleDisplayMode(.inline)
            }
        }
    }
}

enum ListKind {
    case slow
    case fast
}

struct ContentView: View {
    
    var listKind = ListKind.slow
    var items: [String]
    
    init() {
        self.items = (0...500_000).map { "Item \($0)" }
    }
    
    var body: some View {
        switch listKind {
        case .slow:
            List {
                ForEach(items, id: \.self) { item in
                    Text(item).swipeActions(edge: .trailing, allowsFullSwipe: true) {
                        Button("Print") {
                            let _ = print("Tapped")
                        }
                    }
                }
            }.navigationTitle("Slow (SwiftUI List)")
        case .fast:
            FastList(items: self.items)
                .navigationTitle("Fast (UITableView Wrapper)")
        }
    }
}


// UITableView wrapper
struct FastList: UIViewRepresentable {
    
    let items: [String]
    
    init(items: [String]) {
        self.items = items
    }
    
    func makeUIView(context: Context) -> UITableView {
        let tableView = UITableView(frame: .zero, style: .insetGrouped)
        tableView.dataSource = context.coordinator
        tableView.delegate = context.coordinator
        return tableView
    }
    
    func updateUIView(_ uiView: UITableView, context: Context) {
        uiView.reloadData()
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(items: items)
    }
    
    class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
        
        var items: [String]
        
        init(items: [String]) {
            self.items = items
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            self.items.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
            cell.textLabel?.text = self.items[indexPath.row]
            return cell
        }
        
        func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
            let printAction = UIContextualAction(style: .normal, title: "Print") { _, _, block in
                print("Tapped")
                block(true)
            }
            return UISwipeActionsConfiguration(actions: [printAction])
        }
    }
}

Upvotes: 5

Views: 1752

Answers (1)

Eric Shieh
Eric Shieh

Reputation: 817

Profiling in instruments it appears that List creates meta data for every item in order to track changes (for things like insert/delete animations).

Instruments

So even though List is optimized to avoid creating invisible rows, it still has the meta data overhead that the direct UITableView implementation doesn't incur.

One other demonstration of the overhead of List is to instead use a ScrollView/LazyVStack combination. I don't recommend this as an alternative (in addition to the visual differences, it will blow up as you scroll down the list), but because it doesn't do change tracking, it too will have a fairly quick initial display.

Upvotes: 3

Related Questions