Using Transitions in SwiftUI

cropped.gif

Views in SwiftUI can be added to and removed from other views easily by checking a state property. When views are added or removed using just a normal bool check they are automatically given a fade in/out transition by default. That’s great for a lot of cases, but what if you want to do something different, like show a message coming in from the top?

Setting up

To show how to set a different transition to animate a view in and out from the top of the screen, I am going to use a message view I threw together. Since this tutorial isn’t about building custom views I’m not going to dive into detail on how this one is built, but you can check out my tutorial on building custom list rows to learn how to do something similar.

struct Message: View {
    enum MessageStyle {
        case error
        case message
        case success
        case warning

        var backgroundColor: Color {
            switch self {
            case .error: return Message.errorRed
            case .message: return .white
            case .success: return Message.successGreen
            case .warning: return Message.warningYellow
            }
        }

        var textColor: Color {
            switch self {
            case .error: return .white
            case .message: return .black
            case .success: return .white
            case .warning: return .black
            }
        }
    }

    let title: String
    let message: String
    let borderColor: Color
    let borderWidth: CGFloat
    let style: MessageStyle

    private let cornerRadius: CGFloat = 8

    init(title: String, message: String, borderColor: Color = .black, borderWidth: CGFloat = 0, style: MessageStyle = .message) {
        self.title = title
        self.message = message
        self.borderColor = borderColor
        self.borderWidth = borderWidth
        self.style = style
    }

    var body: some View {
        VStack {
            ZStack(alignment: .leading) {
                style.backgroundColor
                    .cornerRadius(cornerRadius)
                    .shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 2)
                VStack(alignment: .leading, spacing: 4) {
                    Text(title)
                        .font(.headline)
                        .foregroundColor(style.textColor)
                    Text(message)
                        .font(.subheadline)
                        .foregroundColor(style.textColor)
                }
                .padding()
            }
            .fixedSize(horizontal: false, vertical: true)
            .overlay(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .stroke(borderColor, lineWidth: borderWidth)
            )
            .padding()
            Spacer()
        }
        .transition(.move(edge: .top)) // 1
        .animation(.spring()) // 2
    }
}

extension Message {
    static let errorRed = Color(red: 236/255, green: 32/255, blue: 32/255)
    static let successGreen = Color(red: 19/255, green: 127/255, blue: 13/255)
    static let warningYellow = Color(red: 236/255, green: 187/255, blue: 32/255)
}

Setting a transition

In the code for the Message view you’ll notice a couple of lines near the end with numbered comments. Let’s take a look at those lines a little closer:

  1. This is where the transition is being set using the .transition modifier. For this example I have chosen to use the .move transition, telling it to move using the top edge of the view. You could use a different edge if you’d like, such as .leading if you want the view to animate in and out from the left side. You could also use a different transition like scale or slide. For a full list of out-of-the-box transitions take a look at Apple’s documentation here.

  2. Along with setting the transition I’m setting the animation curve to use for the Message view. I chose to use a spring animation to give it a little playful bounciness when it comes into view.

Running the transition animation

OK so now that we’ve got a Message view set up and have given it an animation and a transition all that’s left to do is drop it into another view. To do that we’ll use a button that toggles a bool state property, then check that property to conditionally add the new Message view.

struct ContentView: View {
    @State private var showMessage = false

    var body: some View {
        ZStack {
            Button(action: {
                withAnimation {
                    self.showMessage.toggle()
                }
            }) {
                Text("Show Message")
            }

            if showMessage {
                Message(title: "Success!", message: "You successfully presented your first message using transitions.", style: .success)
            }
        }
    }
}

In the button’s action make sure you’re wrapping your toggle() in withAnimation so that the transition animates. If you don’t then your new Message view will just appear and disappear when tapping on the button!

Check out some other recent tutorials

Previous
Previous

Rotating Views Along Any Axis in SwiftUI

Next
Next

Showing an Alert with SwiftUI