Как масштабировать форму с помощью MatchedGeometryEffect?

Я хочу, чтобы моя форма обрезки расширялась вместе с рамкой представления, как показано в примере ниже.

]1

Код для достижения этой цели следующий:

      import SwiftUI

struct ContentView: View {
    @Namespace private var namespace
    
    @State private var showRed: Bool = false
    
    var body: some View {
        ZStack {
            if !showRed {
                Color.blue
                    .matchedGeometryEffect(id: "card", in: namespace)
                    .frame(width: 200, height: 200)
                    .transition(.scale(identity: 200, active: 400))
            } else {
                Color.red
                    .matchedGeometryEffect(id: "card", in: namespace)
                    .frame(width: 400, height: 400)
                    .transition(.scale(identity: 400, active: 200))
            }
        }
        .onTapGesture {
            withAnimation(.easeInOut(duration: 3)) {
                showRed.toggle()
            }
        }
    }
}

struct ScaledCircle: Shape {
    var animatableData: Double

    func path(in rect: CGRect) -> Path {
        let width = animatableData
        let height = animatableData
        
        let x = rect.midX - (width / 2)
        let y = rect.midY - (height / 2)
        
        return Circle().path(in: CGRect(x: x, y: y, width: width, height: height))
    }
}

struct ClipShapeModifier: ViewModifier, Animatable {
    var animatableData: Double

    func body(content: Content) -> some View {
        content
            .clipShape(ScaledCircle(animatableData: animatableData))
    }
}

extension AnyTransition {
    static func scale(identity: Double, active: Double) -> AnyTransition {
        .modifier(
            active: ClipShapeModifier(animatableData: active),
            identity: ClipShapeModifier(animatableData: identity)
        )
    }
}

#Preview {
    ContentView()
}

Однако теперь я явно указываю размер с помощью пользовательской функции..scale(identity:active), чего я хочу избежать. Текущий размер кадра представления — это то, что мне нужно вfunc path(in rect: CGRect) -> Path. Я думал, что это даст мне это сrectсвойство, но это сразу же даст мне конечный размер кадра и ничего промежуточного.

Изменение кода на:

      struct ScaledCircle: Shape {
    var animatableData: Double

    func path(in rect: CGRect) -> Path {
        let width = rect.width
        let height = rect.height
        
        let x = rect.midX - (width / 2)
        let y = rect.midY - (height / 2)
        
        return Circle().path(in: CGRect(x: x, y: y, width: width, height: height))
    }
}

struct ClipShapeModifier: ViewModifier, Animatable {
    var animatableData: Double

    func body(content: Content) -> some View {
        content
            .background(.purple)
            .clipShape(ScaledCircle(animatableData: animatableData))
    }
}

Даст мне:

Теперь я мог бы поиграть с animatableData, заставив его интерполировать значения от 0 до 1, но, как и следовало ожидать, это приведет только к масштабированию круга от нуля до того, каким должен стать кадр.

      struct ScaledCircle: Shape {
    var animatableData: Double

    func path(in rect: CGRect) -> Path {
        let width = rect.width * animatableData
        let height = rect.height * animatableData
        
        let x = rect.midX - (width / 2)
        let y = rect.midY - (height / 2)
        
        return Circle().path(in: CGRect(x: x, y: y, width: width, height: height))
    }
}

extension AnyTransition {
    static func scale(identity: Double, active: Double) -> AnyTransition {
        .modifier(
            active: ClipShapeModifier(animatableData: 0),
            identity: ClipShapeModifier(animatableData: 1)
        )
    }
}

Даст мне:

Итак, он почти готов, но он должен масштабироваться от исходного размера, а не от нуля.

У меня нет вариантов, которые я могу попробовать, есть у кого-нибудь идеи?

0 ответов

Другие вопросы по тегам