SwiftUI Tutorials (Animation Views and Transitions)

Section 4

Compose Animations for Complex Effects


If I add .transition(.slide) and .animation(.ripple(index: index)) as the tutorial says,

the graph data won't update when I click on different buttons(Elevation, Heart Rate, Pace).


var body: some View {
        let data = hike.observations
        let overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: self.path] })
        let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!
        let heightRatio = (1 - CGFloat(maxMagnitude / magnitude(of: overallRange))) / 2

        return GeometryReader { proxy in
            HStack(alignment: .bottom, spacing: proxy.size.width / 120) {
                ForEach(data.indices) { index in
                    GraphCapsule(
                        index: index,
                        height: proxy.size.height,
                        range: data[index][keyPath: self.path],
                        overallRange: overallRange)
                    .colorMultiply(self.color)
                    .transition(.slide)
                    .animation(.ripple())
                }
                .offset(x: 0, y: proxy.size.height * heightRatio)
            }
        }
    }


Can someone explain to me please?

Replies

I'm experiencing the same problem. If I remove the .transition(.slide) row it kinda works. But it looks like adding the transition breaks the link between the data and the GraphCapsule instances. As a Swift newbie I cannot figure out why this happens. 😕

Any luck with this? I have the same issue — it seems that transition is applied when you toggle the showDetail in HikeView.swift, but then it blocks GraphCapsule from being redrawn — I've added print to the GraphCapsule body and it's not triggered when there is a transition on GraphCapsule initialisation.
That doesn't make any sense and makes me frustrating about my understanding of transitions and animations in SwiftUI.
The problem is due to a crucial bug in the tutorial. I'm surprise Apple did not correct it. Back when the tutorial was release, during WWDC2019 the tutorial worked fine, because the ForEach/List views had a specific behavior. However, by iOS13.0 beta 5, the behavior of those views changed and the tutorial stopped working properly. It seems they never updated it.

The change of the List and ForEach views was disclosed in the iOS 13.0 beta 5 release notes.

Basically, if you use a ForEach with static data or a range, the ForEach will read the data once. If it later gets modified, the ForEach won't update. This is what is happening in the tutorial. But before I go into the details of the tutorial, let me explain with a minimal example:

Code Block swift
struct ExampleView: View {
    @State private var array = ["apple", "peach", "banana"]
    var body: some View {
        VStack {
            Button("Add Row") {
                self.array.append("orange")
            }
            ForEach(array.indices) { idx in
Text(array[idx])
}
        }
    }
}


In the above example, indices is a range so taping on the "Add Row" button updates the array, but the view will not update. Indeed, that is what happens. To make it work, the code can be modified like this:

Code Block swift
struct LandmarkList: View {
    @State private var array = ["apple", "peach", "banana"]
    var body: some View {
        VStack {
            Button("Add Row") {
                self.array.append("orange")
            }
            ForEach(array, id:\.self) { fruit in
                Text(fruit)
            }
        }
    }
}


Now back to the tutorial problem. By introducing the change below, the problem is solved. Using firstIndex is not very elegant, but to demonstrate where the problem is and what makes it work, it is elegant enough. ;-)

Code Block swift
ForEach(data, id: \.self) { v in
    let index = data.firstIndex(where: { v == $0 })!
    GraphCapsule(
        index: index,
        height: proxy.size.height,
        range: data[index][keyPath: self.path],
        overallRange: overallRange)
    .colorMultiply(self.color)
    .transition(.slide)
    .animation(.ripple(index: index))
}


Maybe you could create a bug report with Apple and tell them their tutorial has a bug. It is an ugly bug for a tutorial aim at SwiftUI beginners.
Thank you for the response!
But your final solution doesn't seem to work. It first requires to add return before GraphCapsule and then it says Generic parameter 'Content' could not be inferred.

Anyway, the solution appeared to be that simple:
Code Block swift
ForEach(data.indices, id: \.self) { index in
GraphCapsule(
index: index,
height: proxy.size.height,
range: data[index][keyPath: self.path],
overallRange: overallRange)
.colorMultiply(self.color)
.transition(.slide)
.animation(.ripple(index: index))
}

This one works fine for me (Xcode 11.5, btw).

Anyway, still not clear why the issue appears only after .transition is added and works fine w/o it.
You can change
Code Block
ForEach(data.indices)

to
Code Block
ForEach(data.indices, id:\.self)
, it worked for me.