Updated for Xcode 12.5
Although SwiftUI comes with a selection of transitions built in, it’s also possible to write entirely custom transitions if we want to.
The process takes three steps:
ViewModifier
that represents your transition at any of its states.AnyTransition
extension that uses your view modifier for active and identity states.transition()
modifier.For example, we could write a shape and view modifier combination that lets us mimic the Iris animation in Keynote – it causes a new slide to appear in a circle that grows upwards, a bit like the old Looney Tunes intro sequence.
To demonstrate this in action, I’m going to show you a complete code example that does several things:
ScaledCircle
shape that creates a circle inside a rectangle that is scaled according to some animatable data.ViewModifier
struct to apply any shape (in our case, the scaled circle) as a clip shape for another view.AnyTransition
extension to wrap that modifier in a transition for easier access.Here’s the code, with added comments to explain what’s going on:
struct ScaledCircle: Shape {
// This controls the size of the circle inside the
// drawing rectangle. When it's 0 the circle is
// invisible, and when it’s 1 the circle fills
// the rectangle.
var animatableData: CGFloat
func path(in rect: CGRect) -> Path {
let maximumCircleRadius = sqrt(rect.width * rect.width + rect.height * rect.height)
let circleRadius = maximumCircleRadius * animatableData
let x = rect.midX - circleRadius / 2
let y = rect.midY - circleRadius / 2
let circleRect = CGRect(x: x, y: y, width: circleRadius, height: circleRadius)
return Circle().path(in: circleRect)
}
}
// A general modifier that can clip any view using a any shape.
struct ClipShapeModifier<T: Shape>: ViewModifier {
let shape: T
func body(content: Content) -> some View {
content.clipShape(shape)
}
}
// A custom transition combining ScaledCircle and ClipShapeModifier.
extension AnyTransition {
static var iris: AnyTransition {
.modifier(
active: ClipShapeModifier(shape: ScaledCircle(animatableData: 0)),
identity: ClipShapeModifier(shape: ScaledCircle(animatableData: 1))
)
}
}
// An example view move showing and hiding a red
// rectangle using our transition.
struct ContentView: View {
@State private var isShowingRed = false
var body: some View {
ZStack {
Color.blue
.frame(width: 200, height: 200)
if isShowingRed {
Color.red
.frame(width: 200, height: 200)
.transition(.iris)
.zIndex(1)
}
}
.padding(50)
.onTapGesture {
withAnimation(.easeInOut) {
isShowingRed.toggle()
}
}
}
}
SPONSORED ViRE offers discoverable way of working with regex. It provides really readable regex experience, code complete & cheat sheet, unit tests, powerful replace system, step-by-step search & replace, regex visual scheme, regex history & playground. ViRE is available on Mac & iPad.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.