Search code examples
swiftuiios14xcode12

How to pass @namespace to multiple Views in SwiftUI?


I'm playing with the new Xcode 12 beta & SwiftUi 2.0. .matchedGeometryEffect() modifier is great to do Hero animations. There is a new property @Namespace is introduced in SwiftUI. Its super cool. working awesome.

I was just wondering if there is any possibility to pass a Namespace variable to multiple Views?

Here is an example I'm working on,

struct HomeView: View {
    @Namespace var namespace
    @State var isDisplay = true
    
    var body: some View {
        ZStack {
            if isDisplay {
                VStack {
                    Image("share sheet")
                        .resizable()
                        .frame(width: 150, height: 100)
                        .matchedGeometryEffect(id: "img", in: namespace)
                    Spacer()
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.blue)
                .onTapGesture {
                    withAnimation {
                        self.isDisplay.toggle()
                    }
                }
            } else {
                VStack {
                    Spacer()
                    Image("share sheet")
                        .resizable()
                        .frame(width: 300, height: 200)
                        .matchedGeometryEffect(id: "img", in: namespace)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color.red)
                .onTapGesture {
                    withAnimation {
                        self.isDisplay.toggle()
                    }
                }
            }
        }
    }
}

It is working fine.

But if I want to extract the Vstack as a SubView, Below picture shows that I have extracted the first VStack into a subview.

enter image description here

I'm getting a compliment Cannot find 'namespace' in scope

Is there a way to pass namespace across multiple Views?


Solution

  • The @Namespace is a wrapper for Namespace.ID, and you can pass Namespace.ID in argument to subviews.

    Here is a demo of possible solution. Tested with Xcode 12 / iOS 14

    struct HomeView: View {
        @Namespace var namespace
        @State var isDisplay = true
    
        var body: some View {
            ZStack {
                if isDisplay {
                    View1(namespace: namespace, isDisplay: $isDisplay)
                } else {
                    View2(namespace: namespace, isDisplay: $isDisplay)
                }
            }
        }
    }
    
    struct View1: View {
        let namespace: Namespace.ID
        @Binding var isDisplay: Bool
        var body: some View {
            VStack {
                Image("plant")
                    .resizable()
                    .frame(width: 150, height: 100)
                    .matchedGeometryEffect(id: "img", in: namespace)
                Spacer()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.blue)
            .onTapGesture {
                withAnimation {
                    self.isDisplay.toggle()
                }
            }
        }
    }
    
    struct View2: View {
        let namespace: Namespace.ID
        @Binding var isDisplay: Bool
        var body: some View {
            VStack {
                Spacer()
                Image("plant")
                    .resizable()
                    .frame(width: 300, height: 200)
                    .matchedGeometryEffect(id: "img", in: namespace)
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.red)
            .onTapGesture {
                withAnimation {
                    self.isDisplay.toggle()
                }
            }
        }
    }