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