I want my clipping shape to expand with the frame of the view, as shown in the sample below.
]1
The code for achieving this, is the following:
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()
}
However, now I am explicitly stating the size with the custom function .scale(identity:active)
, which I want to avoid. The current frame size of the view is what I am after in func path(in rect: CGRect) -> Path
. I thought it would give me that with the rect
property, but that will just immediately give me the end frame size and nothing in between.
Changing the code to:
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))
}
}
Will give me:
Now I could play around with animatableData making it interpolate between 0 and 1, but as you might expect that will only scale the circle from zero to what the frame should become.
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)
)
}
}
Will give me:
So it's nearly there, but it should scale from the origin size, not from zero.
I am out of options I can try, does anyone have an idea?