Searchable list with binding causes indexOutOfRange crash on iOS 18 physical device

Hi folks,

Unsure if I've implemented some sort of anti-pattern here, but any help or feedback would be great.

I've created a minimal reproducible sample below that lets you filter a list of people and mark individuals as a favourite.

When invoking the search function on a physical device running iOS 18.3.1, it crashes with Swift/ContiguousArrayBuffer.swift:675: Fatal error: Index out of range.

It runs fine on iOS 17 (physical device) and also on the various simulators I've tried (iOS 18.0, iOS 18.2, iOS 18.3.1).

If I remove the toggle binding, the crash doesn't occur (but I also can't update the toggles in the view model).

I'm expecting to be able to filter the list without a crash occurring and retain the ability to have the toggle switches update the view model.

Sample code is below. Thanks for your time 🙏!

import SwiftUI

struct Person {
    let name: String
    var isFavorite = false
}

@MainActor
class ViewModel: ObservableObject {
    private let originalPeople: [Person] = [
        .init(name: "Holly"),
        .init(name: "Josh"),
        .init(name: "Rhonda"),
        .init(name: "Ted")
    ]

    @Published var filteredPeople: [Person] = []

    @Published var searchText: String = "" {
        didSet {
            if searchText.isEmpty {
                filteredPeople = originalPeople
            } else {
                filteredPeople =  originalPeople.filter { $0.name.lowercased().contains(searchText.lowercased()) }
            }

        }
    }

    init() {
        self.filteredPeople = originalPeople
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        NavigationStack {
            List {
                ForEach($viewModel.filteredPeople, id: \.name) { person in
                    VStack(alignment: .leading) {
                        Text(person.wrappedValue.name)
                        Toggle("Favorite", isOn: person.isFavorite)
                    }
                }
            }
            .navigationTitle("Contacts")
        }
        .searchable(text: $viewModel.searchText)
    }
}

#Preview {
    ContentView()
}```

It crashes also on simulator.

Crash comes from:

                ForEach($viewModel.filteredPeople, id: \.name) { person in

Reason is that when you type in search, because of didSet, you update the filtered but the forEach is still using the full array ans waits for a 4th person which does not exist any more.

Hi  @Claude31 - apologies, you're correct. This also fails on the iOS 18.3.1 simulator. It works on the other simulator versions I mentioned though - which is confusing.

Is there a best practice approach for this sort of problem?

As I mentioned, if I remove the binding in the Toggle and instead just refer to a .constant(), then the crash disappears (along with the toggle functionality!).

If I add a level of indirection and create a wrapper around Toggle that manages its own State, then the crash also disappears but the wrapper view doesn't update when changes to the published list are made.

I would expect the ForEach to only be attempting to iterate over the number of elements in viewModel.filteredPeople.

@CloudosaurusRex01 did you find a workaround? I ran into the same issue with iOS 18.3.1.

@Claude31 @CloudosaurusRex01 the Toggle appears to be the problem for me - I can delete items in my ForEach array just fine when I remove the Toggle. This issue appeared in 18.3. Please tag me if you find a workaround!

Searchable list with binding causes indexOutOfRange crash on iOS 18 physical device
 
 
Q