How do I efficiently filter a long list in SwiftUI?

swiftui list
swiftui search list
swiftui list datasource
swiftui list dynamic height
swiftui multiple toggles
swiftui list of toggles
swiftui custom list
swiftui slow

I've been writing my first SwiftUI application, which manages a book collection. It has a List of around 3,000 items, which loads and scrolls pretty efficiently. If use a toggle control to filter the list to show only the books I don't have the UI freezes for twenty to thirty seconds before updating, presumably because the UI thread is busy deciding whether to show each of the 3,000 cells or not.

Is there a good way to do handle updates to big lists like this in SwiftUI?

var body: some View {
        NavigationView {
            List {
                Toggle(isOn: $userData.showWantsOnly) {
                    Text("Show wants")
                }

                ForEach(userData.bookList) { book in
                    if !self.userData.showWantsOnly || !book.own {
                        NavigationLink(destination: BookDetail(book: book)) {
                            BookRow(book: book)
                        }
                    }
                }
            }
        }.navigationBarTitle(Text("Books"))
    }

Have you tried passing a filtered array to the ForEach. Something like this:

ForEach(userData.bookList.filter {  return !$0.own }) { book in
    NavigationLink(destination: BookDetail(book: book)) { BookRow(book: book) }
}
Update

As it turns out, it is indeed an ugly, ugly bug:

Instead of filtering the array, I just remove the ForEach all together when the switch is flipped, and replace it by a simple Text("Nothing") view. The result is the same, it takes 30 secs to do so!

struct SwiftUIView: View {
    @EnvironmentObject var userData: UserData
    @State private var show = false

    var body: some View {
        NavigationView {

            List {
                Toggle(isOn: $userData.showWantsOnly) {
                    Text("Show wants")
                }

                if self.userData.showWantsOnly {
                   Text("Nothing")
                } else {
                    ForEach(userData.bookList) { book in
                        NavigationLink(destination: BookDetail(book: book)) {
                            BookRow(book: book)
                        }
                    }
                }
            }
        }.navigationBarTitle(Text("Books"))
    }
}
Workaround

I did find a workaround that works fast, but it requires some code refactoring. The "magic" happens by encapsulation. The workaround forces SwiftUI to discard the List completely, instead of removing one row at a time. It does so by using two separate lists in two separate encapsualted views: Filtered and NotFiltered. Below is a full demo with 3000 rows.

import SwiftUI

class UserData: ObservableObject {
    @Published var showWantsOnly = false
    @Published var bookList: [Book] = []

    init() {
        for _ in 0..<3001 {
            bookList.append(Book())
        }
    }
}

struct SwiftUIView: View {
    @EnvironmentObject var userData: UserData
    @State private var show = false

    var body: some View {
        NavigationView {

            VStack {
                Toggle(isOn: $userData.showWantsOnly) {
                    Text("Show wants")
                }

                if userData.showWantsOnly {
                    Filtered()
                } else {
                    NotFiltered()
                }
            }

        }.navigationBarTitle(Text("Books"))
    }
}

struct Filtered: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        List(userData.bookList.filter { $0.own }) { book in
            NavigationLink(destination: BookDetail(book: book)) {
                BookRow(book: book)
            }
        }
    }
}

struct NotFiltered: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        List(userData.bookList) { book in
            NavigationLink(destination: BookDetail(book: book)) {
                BookRow(book: book)
            }
        }
    }
}

struct Book: Identifiable {
    let id = UUID()
    let own = Bool.random()
}

struct BookRow: View {
    let book: Book

    var body: some View {
        Text("\(String(book.own)) \(book.id)")
    }
}

struct BookDetail: View {
    let book: Book

    var body: some View {
        Text("Detail for \(book.id)")
    }
}

SwiftUI List performane, My guess is your problem is that you are trying to filter the list directly or the array you pass to the list. Which leads to to whole thing being a big performance hog. SwiftUI's List is similar to UITableView in which you can show static or dynamic TableView cells as per your app need. However, it is simple to use, we don't need to create prototype cells in storyboards, or register them in code. We don't need to tell how many rows in a cell are there and we don't need to dequeue and configure cells.

I think we have to wait until SwiftUI List performance improves in subsequent beta releases. I’ve experienced the same lag when lists are filtered from a very large array (500+) down to very small ones. I created a simple test app to time the layout for a simple array with integer IDs and strings with Buttons to simply change which array is being rendered - same lag.

Dynamically filtering a SwiftUI List, SwiftUI's List view likes to work with arrays of objects that conform to the Identifiable protocol Duration: 3:49 Posted: 18 Apr 2020 Fully updated for Xcode 11.5. SwiftUI’s List view is similar to UITableView in that it can show static or dynamic table view cells based on your needs. However, it is significantly simpler to use: we don’t need to create prototype cells in storyboards, or register them in code; we don’t need to tell it how many rows there are; we don’t need to dequeue and configure cells by hand, and more.

Instead of a complicated workaround, just empty the List array and then set the new filters array. It may be necessary to introduce a delay so that emptying the listArray won't be omitted by the followed write.

List(listArray){item in
  ...
}
self.listArray = []
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
  self.listArray = newList
}

How to fix slow List updates in SwiftUI – Hacking with Swift, If you have a SwiftUI list with lots of rows, you might find it's really slow to update when you sort or filter those rows – code Worse, if we had used 10,000 rows the code would effectively never finish; it would just take too long. Building a dynamic list of items in SwiftUI is easy. Using a List container, rows of data can be presented just like an UITableView in UIKit. When using a list, you don’t need to worry about reusing views that have been scrolled. The OS will automatically clean up the views inside the list and reuse them.

Check this article https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui

In short the solution proposed in this article is to add .id(UUID()) to the list:

List(items, id: \.self) {
    Text("Item \($0)")
}
.id(UUID())

"Now, there is a downside to using id() like this: you won't get your update animated. Remember, we're effectively telling SwiftUI the old list has gone away and there's a new list now, which means it won't try to move rows around in an animated way."

Fix update List slow in SwiftUI, If you have a list of SwiftUI with lots of rows, you may find updating very slow when sorting or filtering those rows – s code may take a second or two If we wait a little longer, the updated lists are constantly changing. Remember, we're effectively telling SwiftUI the old list is gone and now there's a new list,  I am trying to learn SwiftUI and creating a movie search app with The movie Database API. I would like to fetch new data once the scroll goes at the end of the List. I found a possible solution on SO using ForEach inside the List and checking when the list reaches the last item and then performing the onAppear() call.

This code will work correctly provided that you initialize your class in the 'SceneDelegate' file as follows:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?
var userData = UserData()


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Create the SwiftUI view that provides the window contents.
    let contentView = ContentView()

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView:
            contentView
            .environmentObject(userData)
        )
        self.window = window
        window.makeKeyAndVisible()
    }
}

Swiftui search filter, I had just built a custom range slider component that would be the perfect candidate for SwiftUI. Efficiently Filter a Long List, SwiftUI. Cannot convert value of type  I've never done a post on this before, so hopefully this is set up correctly. I'm new to swift, and I want to create a button that adds a new item to a list that contains a navigation link to a f

Let's stop filtering for a second · Swift in Depth, Let's go on the magical journey of filtering in Swift and start building your collection vocabulary. We call shuffle() on the mutable array to shuffle the array in place. When you want to bring out the big guns, consider reduce ! Again, I want to reiterate that SwiftUI is absolutely going to be the future of development for Apple’s platforms, but it will take a long time to gain adoption at the level of UIKit. In the meantime, SwiftUI is the perfect candidate for personal apps, hobby apps, prototype apps, or general experimentation.

Swiftui search filter, Finding An Item In An Array With “firstIndex(of:)” The easiest approach to find an item in an array is with the firstIndex(of:) Efficiently Filter a Long List, SwiftUI. Fucking SwiftUI is a curated list of questions and answers about SwiftUI. You can track change in Changelog All the answers you found here don't mean to be complete or detail, the purpose here is to act as a cheat sheet or a place that you can pick up keywords you can use to search for more detail.

SwiftUI - Handling User Input, SwiftUI - Handling User Input - Section 2: Filter the List View (Xcode 11). One Minute Swift Duration: 1:00 Posted: 18 Jun 2019 Fully updated for Xcode 11.5. SwiftUI’s list view has built-in support for sections and section headers, just like UITableView in UIKit. To add a section around some cells, start by placing a Section around it, optionally also adding a header and footer.

Comments
  • I've tried that now. It renders the initial list quickly, but if I tie !$0.own to the state of the toggle I have the same performance issue.
  • Updated the answer with a workaround.
  • Believe or not, The reason of this bugs relates to that Android like de/initialization of the list I mentioned to you before. Don't be shocked again :)
  • Yes, thanks, I remembered! That's actually how I came up with the idea for the workaround. By destroying the List entirely, instead of letting it cache the rows for later use.
  • The workaround works beautifully. Thank you.
  • I think you're right. I'm sure that this problem will get addressed at some point soon.
  • where to you place the code snippet to empty the list?
  • I tried this solution, but I got crashes every time I navigated BACK from a view where I have List with .id(UUID()) solution.