skip to Main Content

I am developing an iOS app using SwiftUI, and I’m experiencing graphical glitches when I use the indices of my state array in a ForEach loop instead of the elements themselves. Specifically, the UI elements in my list start to flicker and disappear when I attempt to reorder them.

List Graphical Glitch

The ‘Directions’ section is animated correctly, while the ‘Ingredients’ section appears glitchy.

Approximation of the ‘Add Recipe’ view

The only difference between these two sections is that one ForEach iterates over the arrays indices, while the other iterates over the elements.

struct TestView: View {
    @State private var testData = ["Test 1", "Test 2"]

    var body: some View {
        NavigationStack {
            Form {
                Section("Ingredients") {
                    ForEach(testData.indices, id: .self) {
                        Text(testData[$0])
                    }
                    .onMove {
                        testData.move(fromOffsets: $0, toOffset: $1)
                    }
                }
                
                Section("Directions") {
                    ForEach(testData, id: .self) {
                        Text($0)
                    }
                    .onMove {
                        testData.move(fromOffsets: $0, toOffset: $1)
                    }
                }
            }
        }
    }
}

What could be causing this issue and how can I fix it? Does anyone know the "why"? What are the SwiftUI mechanics behind this graphical hiccup?

2

Answers


  1. ForEach does not need your array’s indices for iteration. There is no reason to pass them into your ForEach loop, especially since you are using .self to identify the array items. The solution is clear from your question: Remove the .indices portion of your code and use $0 instead of testData[$0]. It will run just fine, and you won’t have the graphical glitch.

    Login or Signup to reply.
  2. SwiftUI determines what Views to redraw and animate based on their identity. Ultimately, it creates a representation of the current hierarchy with IDs, then applies transformations, then compares the new representation with the old representation. It uses the changes to determine what to animate.

    When you use indexes as the identity, you tie identity to current location, rather than to the value itself. So moving items around creates inconsistent situations.

    Consider the list identified by unique values: A B C D. Applying the operation "move item 2 to position 0". This should result in C A B D, and it does, and that is correct.

    But now consider the list identified by index: 0 1 2 3. Now apply "move item 2 to position 0." The new list is still 0 1 2 3, since the identity is the current location, and changes when the item is moved. This breaks the precondition that the end-state should be consistent with the operations, and can glitch the UI. Whether it actually glitches the UI depends on the both the specific setup and implementation details of SwiftUI. It’s undefined behavior, so all kinds of things can happen.

    You very rarely should use indexes as identifiers, and if you must, then you cannot reorder the list.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search