iosswiftanimationswiftuimobile

How to hide background of matchedTransitionSource?


How can I hide the background of an animated NavigationLink while using the swipe-to-go-back gesture? Here's what I currently have:
animation #1
and what I want it to look like:
animation #2
similar to the iOS Photos app. I'm okay with covering the background using a solid color or a heavy blur, but I don't want it to be visible during the transition.

struct ContentView: View {
    @Namespace private var transitionNamespace
    var body: some View {
        NavigationStack {
            VStack {
                Text("Should not be visible while swiping")

                let value = "details"
                NavigationLink(value: value) {
                    Text("Link")
                        .frame(width: 200, height: 50)
                        .background(Color.green.opacity(0.5))
                        .clipShape(RoundedRectangle(cornerRadius: 32))
                        .matchedTransitionSource(id: value, in: transitionNamespace)
                }
            }
            .navigationDestination(for: String.self) { value in
                ZStack {
                    Color.yellow.ignoresSafeArea(.all)
                    Text(value)
                }
                .navigationTransition(.zoom(sourceID: value, in: transitionNamespace))
                .navigationBarBackButtonHidden(true)
            }
        }
    }
}

Another small related issue why this white border come while gesture in progress? How can I fix its color white borders

struct ContentView: View {
    @Namespace private var transitionNamespace
    var body: some View {
        NavigationStack {
            ZStack {
                Color.purple.ignoresSafeArea(.all)
//                    .padding(-200) // this is a solution?
                VStack {
                    Text("Should not be visible while swiping")

                    let value = "details"
                    NavigationLink(value: value) {
                        Text("Link")
                            .frame(width: 200, height: 50)
                            .background(Color.green.opacity(0.5))
                            .clipShape(RoundedRectangle(cornerRadius: 32))
                            .matchedTransitionSource(id: value, in: transitionNamespace)
                    }
                }
            }
            .navigationDestination(for: String.self) { value in
                ZStack {
                    Color.yellow.ignoresSafeArea(.all)
                    Text(value)
                }
                .navigationTransition(.zoom(sourceID: value, in: transitionNamespace))
                .navigationBarBackButtonHidden(true)
            }
        }
    }
}

Solution

  • You could try using a state variable to record whether to apply a blur or not.

    The blur can be applied to the full page (the VStack in your case), or just a portion of the page, perhaps excluding the link itself. Excluding the link itself ensures that the link is always unblurred during the transition back.

    @State private var isBlurred = false
    @State private var path = NavigationPath()
    
    NavigationStack(path: $path) { // 👈 modified
        VStack {
            Text("Should not be visible while swiping")
                .blur(radius: isBlurred ? 10 : 0) // 👈 added
    
            let value = "details"
            NavigationLink(value: value) {
                Text("Link")
                    .frame(width: 200, height: 50)
                    .background(Color.green.opacity(0.5))
                    .clipShape(RoundedRectangle(cornerRadius: 32))
                    .matchedTransitionSource(id: value, in: transitionNamespace)
            }
            .onDisappear { isBlurred = true } // 👈 added
        }
        .navigationDestination(for: String.self) { value in
            ZStack {
                Color.yellow.ignoresSafeArea(.all)
                Text(value)
            }
            .navigationTransition(.zoom(sourceID: value, in: transitionNamespace))
            .navigationBarBackButtonHidden(true)
        }
    }
    .task(id: path) { // 👈 added
        if isBlurred && path.isEmpty {
            isBlurred = false
        }
    }
    

    Animation