Home > Software design >  Why does matched geometry effect break when the matched views are in separate variables?
Why does matched geometry effect break when the matched views are in separate variables?

Time:07-09

The matched geometry effect works properly when my code looks like this:

    @State var isZoomed: Bool = false
    @State var showText: Bool = false
    @Namespace var namespace
    
    var user: UserModel = exampleUsers[0]
    
    
    var body: some View {
        VStack {
            if !isZoomed {
                VStack {
                    Image(user.localProfileUrl!)
                            .resizable().aspectRatio(contentMode: .fit)
                            .matchedGeometryEffect(id: "rec", in: namespace)
                            .mask(RoundedRectangle(cornerRadius: 10)
                                .matchedGeometryEffect(id: "rec", in: namespace))
                        .frame(height: 100)
                }
                .overlay {
                    GeometryReader { geo in
                        VStack(alignment: .leading) {
                            Text(user.artistName!.uppercased())
                                .font(.title)
                                .bold()
                                .matchedGeometryEffect(id: "artistName", in: namespace)
                            Text(user.occupation)
                                .matchedGeometryEffect(id: "occupation", in: namespace)
                            Text("\(user.followers) Followers")
                                .matchedGeometryEffect(id: "followerCount", in: namespace)                        }
                        .frame(maxWidth: .infinity, maxHeight: 100, alignment: .bottomLeading)
                        .opacity(showText ? 1 : 0)
                        .padding()
                    }
                }
                .onTapGesture {
                    withAnimation() {
                        isZoomed.toggle()
                        showText.toggle()
                    }
                }
            }
            
            if isZoomed {
                VStack {
                    Image(user.localProfileUrl!)
                            .resizable().aspectRatio(contentMode: .fit)
                            .matchedGeometryEffect(id: "rec", in: namespace)
                            .mask(RoundedRectangle(cornerRadius: 20, style: .continuous)
                                .matchedGeometryEffect(id: "rec", in: namespace))
                            .frame(height: 500)
                }
                .overlay {
                    GeometryReader { geo in
                        VStack(alignment: .leading) {
                            Text(user.artistName!.uppercased())
                                .font(.title)
                                .bold()
                                .matchedGeometryEffect(id: "artistName", in: namespace)
                            Text(user.occupation)
                                .matchedGeometryEffect(id: "occupation", in: namespace)
                            Text("\(user.followers) Followers")
                                .matchedGeometryEffect(id: "followerCount", in: namespace)
                        }
                        .frame(maxWidth: .infinity, maxHeight: 500, alignment: .bottomLeading)
                        .opacity(showText ? 1 : 0)
                        .padding()
                    }
                }
                .onTapGesture {
                    withAnimation() {
                        isZoomed.toggle()
                        showText.toggle()
                    }
                }
                .offset(y: -100)
            }
        }

The effect smoothly transitions from a small frame to a large frame and back to a small frame (as intended). However, if I refactor the code to split the two views into separate vars like so:

    var body: some View {
        VStack {
            if !isZoomed {
                nonZoom
            }
            
            if isZoomed {
                fullZoom
            }
        }
    }

essentially putting the two Vstacks into separate vars, the geometry effect breaks:

enter image description here

So, why is such a simple tweak (which is quite a necessary one considering we need to separate views into files later on as the project gets more and more complicated) break the effect? And how do we fix it?

CodePudding user response:

Actually it runs fine with me, also in vars.
Additionally I think you don't need showText as it toggles together with isZoomed. Also as your two views just differ by frame size, you can even reduce it to one var view and use .frame on that:

enter image description here

struct ContentView: View {
    
    @State var isZoomed: Bool = false
    @Namespace var namespace
    
    var body: some View {
        VStack {
            if !isZoomed {
                userImage
                    .frame(height: 100)
            } else {
                userImage
                    .frame(height: 500)
            }
        }
    }
    
    var userImage: some View {
        VStack {
            Image("yoga")
                .resizable().aspectRatio(contentMode: .fit)
                .matchedGeometryEffect(id: "rec", in: namespace)
                .mask(RoundedRectangle(cornerRadius: 10)
                    .matchedGeometryEffect(id: "rec", in: namespace))
        }
        .overlay {
                VStack(alignment: .leading) {
                    Text("ETHAN BECKER")
                        .font(.title)
                        .bold()
                        .matchedGeometryEffect(id: "artistName", in: namespace)
                    Text("Animation Artist")
                        .matchedGeometryEffect(id: "occupation", in: namespace)
                    Text("3.000 Followers")
                    .matchedGeometryEffect(id: "followerCount", in: namespace)                        }
                .frame(maxWidth: .infinity, maxHeight: 500, alignment: .bottomLeading)
                .opacity(isZoomed ? 1 : 0)
                .padding()
        }
        .onTapGesture {
            withAnimation() {
                isZoomed.toggle()
            }
        }
    }
}
  • Related