Как масштабировать форму с помощью 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)
)
}
}
Даст мне:
Итак, он почти готов, но он должен масштабироваться от исходного размера, а не от нуля.
У меня нет вариантов, которые я могу попробовать, есть у кого-нибудь идеи?