Matched Geometry Effects v.2

Рассмотрим еще один пример анимации, который имитирует раскрытие обложки проигрываемой композиции в Apple Music:

Для этого нам понадобиться свойство, отвечающее за размер изображения. При этом размер будет меняться в зависимости от значения логического свойства isZoomed. В том случае, если оно будет принимать значение true, то размер изображение будет 300 на 300 поинтов, а иначе по 44 поинта с каждой стороны:

struct ContentView: View {
    
    @Namespace private var animation
    @State private var isZoomed = false
    
    var imageFrame: CGFloat {
        isZoomed ? 300 : 44
    }
    
    var body: some View {
        Text("Hello, World!")
    }
}

Теперь выполним расстановку элементов в зависимости от значения логического свойства:

var body: some View {
    VStack {
        Spacer()
        
        VStack {
            HStack {
                RoundedRectangle(cornerRadius: 10)
                    .fill(Color(.red))
                    .frame(width: imageFrame, height: imageFrame)
                    .padding(.top, isZoomed ? 20 : 0)
                
                if !isZoomed {
                    Text("Artist - Song")
                        .font(.headline)
                        .matchedGeometryEffect(id: "TrackTitle", in: animation)
                    Spacer()
                }
                
            }
		}
	}
}

В качестве изображения у нас служит квадрат со скругленными углами, который мы поместили в горизонтальный стек вместе с названием музыкальной композиции. При этом текст будет отображаться только в том случае, если обложка композиции не раскрыта. Сам горизонтальный стек помещен в вертикальный стек, который в свою очередь находится так в вертикальном стеке.

Сразу после горизонтального стека создаем условие, при котором текст будет отображаться в вертикальном стеке, если обложка раскрыта:

if isZoomed {
    Text("Artist - Song")
        .font(.headline)
        .matchedGeometryEffect(id: "TrackTitle", in: animation)
        .padding(.bottom, 60)
}

Теперь нам надо проработать логику раскрытия музыкальной композиции по тапу. Для этого вызовем модификатор .onTapGestureу внутреннего вертикального стека:

.onTapGesture {
    withAnimation(.spring()) {
        self.isZoomed.toggle()
    }
}

И еще несколько модификаторов, для настройки внешнего вида вертикального стека:

.padding()
.frame(maxWidth: .infinity)
.frame(height: isZoomed ? 400 : 60)
.background(Color(white: 0.9))

В итоге тело свойства body выглядит так:

var body: some View {
    VStack {
        Spacer()
        
        VStack {
            HStack {
                RoundedRectangle(cornerRadius: 10)
                    .fill(Color(.red))
                    .frame(width: imageFrame, height: imageFrame)
                    .padding(.top, isZoomed ? 20 : 0)
                
                if !isZoomed {
                    Text("Artist - Song")
                        .font(.headline)
                        .matchedGeometryEffect(id: "TrackTitle", in: animation)
                    Spacer()
                }
            }
            
            if isZoomed {
                Text("Artist - Song")
                    .font(.headline)
                    .matchedGeometryEffect(id: "TrackTitle", in: animation)
                    .padding(.bottom, 60)
            }
        }
        .onTapGesture {
            withAnimation(.spring()) {
                self.isZoomed.toggle()
            }
        }
        .padding()
        .frame(maxWidth: .infinity)
        .frame(height: isZoomed ? 400 : 60)
        .background(Color(white: 0.9))
    }
}

Первоисточник

Курсы по языку Swift, доступные каждому: LearnMeToo