Unwanted Crossfade Effect in SwiftUI's Matched Geometry Effect

152 Views Asked by At

In SwiftUI, I'm working with two screens, Screen A and Screen B. I'm attempting to create a transition effect using SwiftUI's matched geometry feature. The transition involves two rounded rectangles: one on Screen A, which appears as a circle due to its corner radius and size, and another on Screen B with a zero corner radius, serving as the background.

Expected Behavior:

I anticipated the animation would display as a seamless morphing of a single object from one configuration to another, specifically altering the corner radius, height, and width.

Actual Behavior:

However, while at first glance that is what appears to happen, if you look closer, you see the transition seems to be more of a crossfade. As it fades out, the source shape does interpolate its corner radius towards the target configuration, but the target shape, as it fades in does not interpolate from the source radius.

This screenshot of the top right section of the animation at approx 50% elapsed illustrates the undesired behavior:

enter image description here

Its seems unlikely the cross fade can simply be "turned off" ... I suppose a cross fade may be essential to matchedGeometry effect, as it seems it would be the only way to interpolate between certain properties like a different fill... but even if this is true, it wouldn't be apparent if only the target shape was also interpolating its shape... which it is not in this case.

I would like any answer not to change the animation mechanism -- this is just a simplified version of a larger project. The animation must be triggered by withAnimation{} and act upon the navController.screen.

Minimal Example Code:

Main Screen:

enum Screen {
    case main, target
}

@Observable
class Nav {
    var screen:Screen = .main
}

struct ContentView: View {
    
    @Environment(Nav.self) var navController
    
    // matched geometry
    @Namespace var namespace
    
    var body: some View {
        
        switch navController.screen {
        case .main:
            ZStack{
                RoundedRectangle(cornerRadius: 15)
                    .fill(Color.green)
                    .matchedGeometryEffect(id: "circleToBackground", in: namespace)
                    .frame(width: 30, height: 30)
                    .onTapGesture {
                        withAnimation(.easeInOut(duration: 4.0)){
                            navController.screen = .target
                        }
                    }
            }
            
        case .target:
            TargetScreen(namespace: namespace)
        }
        
    }
}

Target Screen:

struct TargetScreen: View {
    
    let namespace: Namespace.ID
    @Environment(Nav.self) var navController
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 0)
                .fill(LinearGradient(colors: [Color.green, Color.white], startPoint: UnitPoint(x:0.5,y:0.0), endPoint: UnitPoint(x:0.5,y:1.0)))
                .matchedGeometryEffect(id: "circleToBackground", in: namespace)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            
            Text("BACK").onTapGesture {
                withAnimation {
                    navController.screen = .main
                }
            }
        }
    }
    
}
0

There are 0 best solutions below