Home > front end >  SwiftUI ForEach and how does redrawing views work in this case
SwiftUI ForEach and how does redrawing views work in this case

Time:07-02

For some reason I am genuinely incapable of understanding how ForEach works for some reason... So first of all I think SwiftUI automatically redraws views on it's own when it sees that it's necessary right? But in this example say I have some array ["a","a","a"] and I wanna loop over the elements with ForEach

VStack
{
   ForEach(array, id:\.self) { item in
      Text(item)

   }

But I just don't understand where id's come in to be useful and I guess this stems in from me maybe not understanding how all this gets redrawn but let's say I decide to change this array into an array like ["b","b","b"] what causes this to be redrawn? Does ForEach just recognize that the array changed and it decides to redraw it or idk what exactly triggers the redraw?

So I don't really know what makes it get redrawn whether its ForEach realizing the array changed or what but this gets redrawn right? So it goes over the loop again and creates a view for each element again so why is the id neccessary? If I change the array to ["a","b","c"] SwiftUI also recognize this and redraws it and then I can swap a[0] and a[1] but SwiftUI also recognizes this and redraws it again so why are ID's necessary??

CodePudding user response:

it goes over the loop again and creates a view for each element again

Don't think of a view like a UIKit "UIView". A SwiftUI view is just a description. The foreach is creating a value struct that is compared against the previous value struct. The diffs, if any, are used by the SwiftUI runtime to work out how to mutate the on screen drawing objects, including, for example, animating the changes. SwiftUI automatically works out how it will make changes to what was drawn previously in order to get to what should be being drawn now.

The id is needed so that you can work out from the diff how exactly something changed, for example if you only know:

["A", "B", "C"]
->
["B", "C", "C"]

That could be:

(1) A changing to B, the B to C

or

(2) the A being removed, and then a C being added to the end.

By adding in an ID we can tell which thing is which:

[("A", 1), ("B", 2), ("C", 3)] 
-> 
[("B", 1), ("C", 2), ("C", 3)]
 = change A to B, change B to C
 = animate cells 1 and 2

or

[("A", 1), ("B", 2), ("C", 3)] 
-> 
[("B", 2), ("C", 3), ("C", 4)]
 = delete 1, append 4 = "C"
 = animate deletion of 1, shift cells left, animate new cell in from right

The magic of how SwiftUI view notices it needs to recompute a View body and work out if anything/what has changed, and potentially update its drawing state is based around the property wrappers of State, ObservedObject, StateObject etc (not ForEach). These propertyWrappers wrap the variables like your "array" such that when they are set (when their setter function is called), additional work is performed to call your view's body function to give a new value for the view's description and then the diffing, possible updating etc all happens.

CodePudding user response:

In your case where the array is ["a", "a", "a"] you're setting the id to \.self which is "a"(i.e self). Similarly, if you change that array to ["b", "b", "b"] the id changes to "b" causing a redraw.

Identifiable

We use the id in order to make the array act like the Identifiable protocol. Try this:

ForEach(0..<someArray.count) { index in
    ...
}.onAppear {
    DispatchQueue.main.asyncAfter(deadline: .now()   10) {
        self.someArray.append(someElement)
    }
}

When you run the above snippet, the ForEach doesn't get redrawn. That behavior is the result of not using Identifiable or assigning a uniques identifier, ForEach can't identify each unique element, hence it will give you a runtime warning(purple colored).

ForEach

  1. Only use ForEach(0..<array.count) { index in when your data is static.

  2. If it is dynamic, use the Identifiable protocol: ForEach(someIdentifiableArray) { element in

  3. Or using id: ForEach(someIdentifiableArray, id: \.uniqueProperty) { element in

Note: When an array contains duplicates, make sure to have an id property used for identification like UUID.

  • Related