animationswiftuimatchedgeometryeffect

SwiftUI move image between pages animation


I've been trying for days but I couldn't find a solution. My goal is to move the Image from one location to another. But I don't want the image to only move up and down while moving it. I want it to look like the image is sliding while the page is sliding. I'm sorry if I didn't explain it clearly. When you run the code on your computer, you will understand the animation I am trying to make. I ask for your help


struct SwiftUIView: View {
    @State private var showFirstView = true
    @Namespace var namespace
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                if showFirstView {
                    FirstView()
                        .transition(.move(edge: .trailing))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                } else {
                    SecondView()
                        .transition(.move(edge: .leading))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                }
            }
            .animation(.easeInOut(duration: 0.5), value: showFirstView)
        }
        .overlay(alignment: .bottom) {
            Button(action: {
                withAnimation {
                    showFirstView.toggle()
                }
            }) {
                Text("Switch View")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding()
        }
    }
    
    func FirstView() -> some View {
        ZStack{
            Color.red
                .ignoresSafeArea()
            cusview()
        }
    }
    
    func SecondView() -> some View {
        ZStack {
            Color.green
                .ignoresSafeArea()
            cusview()
                .offset(y: -150)
        }
    }
    
    func cusview() -> some View {
        Image(.image10)
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 140, height: 100)
            .matchedGeometryEffect(id: "one", in: namespace)
    }
}
 
#Preview {
    SwiftUIView()
}

Solution

  • When I tried your code in a simulator (iPhone 15 running iOS 17.5), the animation of the image was disconnected:

    You said in your post:

    I don't want the image to only move up and down

    so I was asking in a comment, how exactly you do want it to move? I would suggest, there would be two conceivable ways to animate the image, if the movement should not be vertical-only:

    1. The image in the appearing view could stay as you have it (diagonal movement), but the image in the disappearing view could also move diagonally, instead of only vertically.

      This would mean, when one image moves off to the right, the new image comes in from the left at the same height. This would still look disconnected, but at least the image would have a consistent direction of movement.

      I tried to find a way to implement it this way, but wasn't successful, sorry.

    2. The animation would be less disconnected if the image would move smoothly from one position to the other. If you don't want the animation to be vertical-only, then the image would need to move in some kind of curve instead.

    This second style of animation can be achieved by showing the image as another layer in the ZStack and have it move between placeholder positions in the two child views.

    A curved path can be achieved by using different animation durations for the transitions and the matchedGeometryEffect:

    The updated example below shows it working this way. Other changes and suggestions:

    struct SwiftUIView: View {
        @State private var showFirstView = true
        @Namespace var namespace
    
        var body: some View {
            ZStack {
                if showFirstView {
                    FirstView()
                        .transition(.move(edge: .trailing))
                } else {
                    SecondView()
                        .transition(.move(edge: .leading))
                }
            }
            .animation(.easeInOut(duration: 0.8), value: showFirstView)
            .overlay {
                cusview()
    
                    // Try adjusting the duration to get different movements
                    .animation(.easeInOut(duration: 0.7), value: showFirstView)
            }
            .overlay(alignment: .bottom) {
                Button {
                    showFirstView.toggle()
                } label: {
                    Text("Switch View")
                        .padding(.horizontal, 4)
                        .padding(.vertical, 9)
                }
                .buttonStyle(.borderedProminent)
                .padding()
            }
        }
    
        func FirstView() -> some View {
            ZStack{
                Color.red
                    .ignoresSafeArea()
                imagePlaceholder
            }
        }
    
        func SecondView() -> some View {
            ZStack {
                Color.green
                    .ignoresSafeArea()
                imagePlaceholder
                    .offset(y: -150)
            }
        }
    
        private var imagePlaceholder: some View {
            Color.clear
                .frame(width: 140, height: 100)
                .matchedGeometryEffect(id: "one", in: namespace, isSource: true)
        }
    
        func cusview() -> some View {
            Image(.image10)
                .resizable()
                .scaledToFill()
                .matchedGeometryEffect(id: "one", in: namespace, isSource: false)
        }
    }
    

    Animation