A SwitfUI view transition that varies depending on its cause

What works so far

In a game I am displaying a number to the user with some code like this:

Text("\(model.product)")

I want Text to have an interesting transition happen each time model.product changes. To arrange this I've used the id hack:

Text("\(model.product)")
    .id(model.product)
    // Transition here is included at the end of the post.
    .transition(.explodeUp)

So now, in effect, each time the model.product changes, any existing Text showing the old product is removed (with a transition) and a new Text view is inserted to show the new product (with a transition).

So far, this works nicely, I've got my Transition all tweaked up, and it's a pretty nice effect [attached at the end]. Cool bananas!

What I want though

While this is nice as it is, not all model.product changes are alike…

The existing code uses the same transition regardless of how product.model has changed. What I want is for the transition used to depend on how product.model has changed.

FWIW, I actually want to vary the transition depending on why product.model has changed, but I can accomplish this in the model I think, so it seems unimportant to the SwiftUI side.

The current transition is great for when the number increases. I want to use something else if the number decreases (probably called AnyTransition.dropDown or something).

I'd like to be able to look at the old and new values of model.product and choose between two different transitions depending on whether the value has increased or decreased.

Looking at the Transition protocol, and Transaction, this seems like it probably ought to be possible to do this.

I think I'd certainly want to stop using the id hack. Instead perhaps I could have an extension on View which would read like this at the usage site…

Text(model.product)
    .replacingWithTransitionOnChanges(of: model.product) { oldValue, newValue in
        oldValue < newValue ? .explodeUp : .dropDown
    }

And the extension declaration would perhaps look like this:

extension View {
    func replacingWithTransitionOnChanges<Value: Hashable>(
        of value: Value, 
        _ transitionSource: (Value, Value) -> some Transition
    ) {
        // What in the name of fish cakes goes in here though? :-/
    }
}

But as you see, I've got not a lot of an idea how I'd build the function. Yet?

Any ideas for that? Or else – perhaps there are really easy ways to get this effect that I'm not thinking of?

Thanks!


The .explodeUp transition

My transition declaration, if anyone is interested…

extension AnyTransition {
    static var explodeUp: AnyTransition {
        .asymmetric(
            insertion: .scale(scale: 0.5)
                .combined(with: .opacity)
                .animation(.easeInOut(duration: 0.2)),
            removal: .scale(scale: 1.8)
                .combined(with: .opacity)
                .combined(with: .rotate(angle: .degrees(Double.random(in: -20..<20))))
                .animation(.easeInOut(duration: 0.4))
        )
    }

    static func rotate(angle: Angle) -> AnyTransition {
        .modifier(active: RotateModifier(angle: angle), identity: RotateModifier(angle: .zero))
    }
}

There's a specific SwiftUI type ContentTransition link which seems designed for this use case.

However:

  • The current library doesn't seem to have a way to ensure that the transition used will depend on how a property changes.
  • The currently library doesn't seem to provide for any custom creation of a ContentTransition (although the stock ones are really cool).

There's also a SwiftUI type PlaceholderContentView link which looks like it might be used to implement SwiftUI features such as transitions, so it could perhaps be used to implement the replacingWithTransitionOnChanges function I suggested above – however, it doesn't seem to be publicly accessible as far as I can tell.

A possible approach – I think it could be possible to nest several views inside each other with each of the views having a specific transition. Playing with id would then let a given view be replaced, which should trigger only its transition (because transitions only happen on the outermost view that is added or removed – as far as I know). This might work, but it isn't very extensible, at all reusable, or clear at the point of use what's going on.

A SwitfUI view transition that varies depending on its cause
 
 
Q