ios – How to recreate the Photos grid transition animation from Months to Years in SwiftUI?


In the stock Photos app, there’s a transition animation when switching the view from Month to Year. Initially each month is represented as a single cell but when switching to year, the cells combine into a single new cell. A similar animation style can be seen here as well.

I’m attempting my hand at recreating using a combination of LazyVGrid and matchedGeometryEffect but the cells don’t combine smoothly like in the video. Any help would be greatly appreciated!

import SwiftUI

struct TestV: View {
    @Namespace private var namespace
    @State private var isExpanded = false

    var body: some View {
        VStack {
            ZStack {
                if !isExpanded {
                    SubviewOne(namespace: namespace, isExpanded: $isExpanded)
                        .transition(.offset())
                } else {
                    SubviewTwo(namespace: namespace, isExpanded: $isExpanded)
                        .transition(.offset())
                }

            }
            Spacer()
            Toggle(isOn: $isExpanded.animation(.easeInOut(duration: 2)), label: { Text("Matched") }).frame(width: 140)
        }
    }
}

struct SubviewOne: View {
    
    var namespace: Namespace.ID
    @Binding var isExpanded: Bool
    let colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple]
    let groups = ["one", "two", "two"]
    var gridItemLayout = [GridItem(.flexible())]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridItemLayout, spacing: 0) {
                ForEach((0...2), id: \.self) { num in
                    ZStack {
                        Rectangle().fill(colors[num % colors.count])
                        Text("\(num)")
                            .font(.system(size: 30))
                    }
                    .matchedGeometryEffect(id: isExpanded ? groups[num]:"", in: namespace, isSource: false)
                        .frame(height: 150)
                        .frame(maxWidth: .infinity)
                }
            }
        }
    }
}

struct SubviewTwo: View {
    var namespace: Namespace.ID
    @Binding var isExpanded: Bool
    let colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple]
    let groups = ["one", "two"]
    var gridItemLayout = [GridItem(.flexible())]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: gridItemLayout, spacing: 20) {
                ForEach((0...1), id: \.self) { num in
                    ZStack {
                        Rectangle().fill(colors[num % colors.count])
                        Text("\(num)")
                            .font(.system(size: 30))
                    }
                    .matchedGeometryEffect(id:groups[num], in: namespace)
                    .frame(width: 350, height: 250)
                }
            }
        }
    }
}

#Preview {
    TestV()
}

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img