Skip to main content

How to create a custom transition

About 3 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to create a custom transition 관련

SwiftUI by Example

Back to Home

How to create a custom transition | SwiftUI by Example

How to create a custom transition

Updated for Xcode 15

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:

  1. Create a ViewModifier that represents your transition at any of its states.
  2. Create an AnyTransition extension that uses your view modifier for active and identity states.
  3. Apply that transition to your views using the 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:

  1. Defines a ScaledCircle shape that creates a circle inside a rectangle that is scaled according to some animatable data.
  2. Create a custom ViewModifier struct to apply any shape (in our case, the scaled circle) as a clip shape for another view.
  3. Wraps that in an AnyTransition extension to wrap that modifier in a transition for easier access.
  4. Creates a SwiftUI view to demonstrate our transition in action.

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: Double

    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()
            }
        }
    }
}

Download this as an Xcode projectopen in new window

Similar solutions…
How to make views scroll with a custom transition | SwiftUI by Example

How to make views scroll with a custom transition
How to add and remove views with a transition | SwiftUI by Example

How to add and remove views with a transition
How to create and compose custom views | SwiftUI by Example

How to create and compose custom views
How to create custom animated drawings with TimelineView and Canvas | SwiftUI by Example

How to create custom animated drawings with TimelineView and Canvas
How to create custom bindings | SwiftUI by Example

How to create custom bindings

이찬희 (MarkiiimarK)
Never Stop Learning.