TLDR: Surround any Swift UI updates in a:
@State private var myStateVar: [MyState] = []
DispatchQueue.main.async {
// ... do updates to myStateVar here
}
This was my problem, but Apple didn't help by changing SwiftUI, not telling us about it, not providing a stack trace, and not even providing a code lint in Xcode for state updates. Every other modern framework provides a stack trace back to our own code where we can figure out what we did wrong. It seems only Apple requires you to do some esoteric internal debug dump, send it to Apple, hold your breath, and hope to get a response.
I was updating a SwiftUI Table view, and suddenly, a new version of SwiftUI on macOS (but probably doesn't matter which Apple OS), had this totally unhelpful crash:
ForEach<Binding<Array<Replacement>>, UUID, TableRow<Binding<Replacement>>>: the ID 02A438DA-C928-4FCB-BA5F-518B2C476A6C occurs multiple times within the collection, this will give undefined results!
Swift/ContiguousArrayBuffer.swift:675: Fatal error: Index out of range
Totally unhelpful, right? And it's wrong. That ID does NOT appear more than once in the data collection itself. It's the way SwiftUI is handling the updates internally.
My code worked for like 2 years until Apple did an update to SwiftUI that caused this crash.
Here's my code. I am filtering a Table with a search field:
struct ReplacementsView: View {
var body: some View {
VStack {
searchField
replacementsTable
actionButtons
}
...
@State private var searchQuery = ""
@State private var filteredReplacements: [Replacement] = []
private func filterReplacements() {
/// THE PROBLEM IS HERE. Doing a UI @State update synchronously causes a crash here.
if searchQuery.isEmpty {
filteredReplacements = replacementsManager.replacements
return
}
filteredReplacements = replacementsManager.replacements.filter { replacement in
replacement.pattern.lowercased().contains(searchQuery.lowercased()) ||
replacement.replacement.lowercased().contains(searchQuery.lowercased())
}
}
private var searchField: some View {
TextField("Search...", text: $searchQuery)
.textFieldStyle(.roundedBorder)
.padding([.top, .leading, .trailing])
.onChange(of: searchQuery) {
filterReplacements()
}
}
private var replacementsTable: some View {
ScrollViewReader { proxy in
Table($filteredReplacements, selection: $selection) {
...
TableColumn("Replacement") { $row in
TextField("", text: $row.replacement, onEditingChanged: { editing in
if !editing {
update(row)
}
})
.accessibilityTextContentType(.sourceCode)
.onSubmit {
update(row)
}
}
...
Now, Poor ChatGPT can't even figure out SwiftUI because the documentation is horrible. The wealthiest company in the world should be able to hire tech writers!!! I wonder if Apple has more than 2 or 3 tech writers to cover all the developer documentation. And even then, 2 or 3 people full time should be able to do better than this.
So finally, after much frustration, it helped me realize I needed to do this async:
private func filterReplacements() {
DispatchQueue.main.async {
if searchQuery.isEmpty {
filteredReplacements = replacementsManager.replacements
return
}
filteredReplacements = replacementsManager.replacements.filter { replacement in
replacement.pattern.lowercased().contains(searchQuery.lowercased()) ||
replacement.replacement.lowercased().contains(searchQuery.lowercased())
}
}
}
That's it. That's all it was. Surrounded the state update with a DispatchQueue.main.async { ... } and ... voila! No crash.
Now, if this is Apple best practice, why doesn't Apple's new AI in Xcode 16 lint this and yell at me that I should not be updating this synchronously?
Why am I so angry? I am a visually impaired software engineer. It's hard enough for me to read gobs and gobs and gobs of posts and generated non-answers to get to the solution of a very simple problem. And I have to do this a hundred times a day. And this app I wrote is to fix accessibility on my Mac, because Apple's built-in text-to-speech is so very limited. I am angry at Apple because the average user has a hard time finding out how to use their stuff. They go on and on about how great they are on accessibility, yet no, they aren't. Accessibility is Apple's very last concern. Profits before people, always. I've been a Mac user since 2005, and grew up with an Apple //c. Steve Jobs understood people. Apple's current leadership doesn't.
This is why Flutter is a million times better than SwiftUI. Flutter throws an exception and tells you not to update state while the build context is being rendered. Duh. And it runs on everything, not just Apple devices, which makes it that much more valuable. Now, I work in like 3 different languages/frameworks here, not solely on Apple stuff (probably makes me a black sheep here, not to mention being visually impaired and calling out Apple for its lack of true concern about accessibility), and Apple could be doing a lot more to help us, instead of making us spend hours and hours figuring out what Apple could have just told us in the documentation and in its own editor, Xcode, which is solely focused on Apple's ecosystem... and is the least accessible editor on the market, to boot. So why is Xcode so very, very horrible at helping us with this most basic issue?
With the resources available to you, Apple, this treatment of your customers and users is not acceptable.