I have an overlay transition that uses matchedgeometry effect, as shown in this video: Matched Geometry Effect with same sized text
I want to enlarge the transitioned text after the animation, but it seems that the text frame isn't being scaled in time, causing the text to be shortened in transition. Matched Geometry Effect with different sized text
Is there a way to overcome this?
The original text:
Text(info.name)
.font(.system(size: 22, weight: .bold))
.matchedGeometryEffect(id: info.name, in: namespace)
The transitioned text:
Text(currentCard.name)
.font(.title.bold())
.matchedGeometryEffect(id: currentCard.name, in: namespace)
Thanks
Finally figured it out!
Add a separate font size @State var
that you trigger inside your matchedGeometryEffect
's if
statement .onAppear
.
Combine this with the AnimatableCustomFontModifier
and the font size will animate just fine!
I know. It sounds gross. You wouldn't believe how long this took. Here's a working example.
struct ContentView: View {
@Namespace var namespace
@State var hero = false
@State var heroFontLarge = false
var body: some View {
VStack {
if hero {
Text("hello")
.animatableFont(name: "San Francisco", size: heroFontLarge ? 64 : 16)
.matchedGeometryEffect(id: "title", in: namespace)
// Start the font size animation. Only gets called once the transition starts
.onAppear {
withAnimation(.linear(duration: 1)) {
heroFontLarge = true // Start animating a larger font
}
}
.transition(.scale(scale: 1)) // Stops a fading out bug
} else {
Text("hello")
.animatableFont(name: "San Francisco", size: heroFontLarge ? 64 : 16)
.matchedGeometryEffect(id: "title", in: namespace)
// Start the font size animation. Only gets called once the transition starts
.onAppear {
withAnimation(.linear(duration: 1)) {
heroFontLarge = false // Start animating a smaller font
}
}
.transition(.scale(scale: 1)) // Stops a fading out bug
}
Button("Toggle") {
withAnimation(.linear(duration: 1)) {
hero.toggle()
}
}
}
}
}
// A modifier that animates a font through various sizes.
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-animate-the-size-of-text
struct AnimatableCustomFontModifier: ViewModifier, Animatable {
var name: String
var size: Double
var animatableData: Double {
get { size }
set { size = newValue }
}
func body(content: Content) -> some View {
content
.font(.custom(name, size: size))
}
}
// To make that easier to use, I recommend wrapping
// it in a `View` extension, like this:
extension View {
func animatableFont(name: String, size: Double) -> some View {
self.modifier(AnimatableCustomFontModifier(name: name, size: size))
}
}
Unfortunately, it's a little buggy if you try to reverse the animation before it's finished. Should't be a problem if you can delay interaction until the animation is finished.