SwiftUI: animate<>(:::) called way too many times due to ForEach

Hey folks,

Having some trouble getting Animations to behave properly. I'll try to explain what I am doing, but maybe it is easier to run the project, which you can clone from github here. The key is in commenting out line 32/33 and uncommenting lines 34/35 in GaugeView.swift

Otherwise: here is the high-level goal I'm aiming for: I'd like to see a horizontal gauge, akin to a tape measure (ignore the colors in this example image ), but (initially) centered on zero, and extending both to negative and positive measures.

It will represent some value and that value can go up to a positive maximum number, and a minimum negative number. On overflow on either side, the tape measure should wrap-around. For this, I would like to create the illusion of an infinite tape measure, that keeps moving as the values change. There will be NO user interaction at all, the values are coming in from some device, and simply need to be displayed.

Now, the basic logic seems to work: There is a tapemeasure view, which shows zero in the center. As a new value comes in, the view's offset gets animated to have the new value be in the center. At the end of the animation. The view's offset is reset to zero (without animation), and the view's content is updated to reflect the new center value.

In the current project there is only a single view, but the idea it that to the left and right of the visible view, there will be two other views to fill in the other parts. However, that is not relevant at the moment.

To support changes mid-animation, I've implemented a basic custom animation myself. This one allows me to measure how often animate<>(:::) gets called for now, but will be needed to make sure animations remain smooth when merging them.

The issue I run into is that that view with the actual contant contains a ForEach. And because of that, animate<>(:::) seems to be called many more times than I would expect. That number seems to be linearly related to the number of items in the ForEach. So I watched the WWDC session on view identy (Demystify SwiftUI), watched all animation-related sessions and tried to find some documatation on animations. Couldn't find what I was looking for. When I replace the content view with a Rectangle(), the issue disappears.

So my question is: how to prevent a subview's changes to call animate(:::) so many times when a ForEach is present? Or maybe just block all internal animations of a subview? In essence I never need the content of the subview to be animated, only it's offset in the superview.

Replies

I can't update the original post, so I'll provide more context here: Turns out, the ForEach is not the problem. The problem is simply that each subview will add 60 calls each per second of animation duration. Interestingly, this happens even when the subview has no dependencies and all values are hardcoded.

Could the problem be with the custom animation implementation?