Close Menu
geekfence.comgeekfence.com
    What's Hot

    Open Cosmos launches first satellites for new LEO constellation

    January 25, 2026

    Achieving superior intent extraction through decomposition

    January 25, 2026

    How UX Research Reveals Hidden AI Orchestration Failures

    January 25, 2026
    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook Instagram
    geekfence.comgeekfence.com
    • Home
    • UK Tech News
    • AI
    • Big Data
    • Cyber Security
      • Cloud Computing
      • iOS Development
    • IoT
    • Mobile
    • Software
      • Software Development
      • Software Engineering
    • Technology
      • Green Technology
      • Nanotechnology
    • Telecom
    geekfence.comgeekfence.com
    Home»iOS Development»Transitions in SwiftUI · objc.io
    iOS Development

    Transitions in SwiftUI · objc.io

    AdminBy AdminNovember 10, 2025No Comments6 Mins Read0 Views
    Facebook Twitter Pinterest LinkedIn Telegram Tumblr Email
    Transitions in SwiftUI · objc.io
    Share
    Facebook Twitter LinkedIn Pinterest Email


    During our SwiftUI Workshop we often notice that very few people seem to know about transitions, even though they’re not very complicated and incredibly useful.

    Transitions happen when a view is removed from the view tree, or added to the view tree. However, if you’ve done some SwiftUI, you will have noticed that there is no actual way to add views to the view tree — there is no addSubview(_:). Instead, you can only add and remove views through the combination of a state change and using an if statement (or switch or ForEach). In other words, views are somehow added and removed for us automatically, yet transitions fire only once. Before we dive into the details of this, let’s consider a very simple transition:

    								struct ContentView: View {
        @State var visible = false
        var body: some View {
            VStack {
                Toggle("Visible", isOn: $visible)
                if visible {
                    Text("Hello, world!")
                }
            }
            .animation(.default, value: visible)
        }
    }
    
    							

    When we run the above code we can see the text fade in and out. This is the default transition (.opacity). When the view gets inserted into the view tree, it fades in, and once it gets removed it fades out. Note that if the body executes again, the view doesn’t fade in again unless the condition in the if statement changes.

    To build up a mental model of what’s happening, we can consider the SwiftUI view tree for the above view:

    SwiftUI views are ephemeral: the body of ContentView gets executed and from it a render tree is created. This render tree is persistent across view updates, and it represents the actual views on screen. Once the render tree is updated, the value for body then goes away. Here’s the render tree after the initial rendering:

    Once we tap the switch, a state change happens and the body of ContentView executes again. The existing render tree is then updated. In this case, SwiftUI noticed that the if condition changed from false to true, and it will insert our Text view into the render tree:

    The change in the render tree is what triggers the transition. Transitions only animate when the current transaction contains an animation. In the example above, the .animation call causes the transition to animate.

    The render tree does not actually exist with that name or form, but is simply a model for understanding how SwiftUI works. We’re not completely sure how these things are represented under the hood.

    When we change our view to have an if/else condition, things get a bit more interesting. Here’s the code:

    								struct ContentView: View {
        @State var visible = false
        var body: some View {
            VStack {
                Toggle("Visible", isOn: $visible)
                if visible {
                    Text("Hello, world!")
                } else {
                    Image(systemName: "hand.wave")
                }
            }
            .animation(.default, value: visible)
        }
    }
    
    							

    When we render the initial view tree, it will contain a VStack with a Toggle and a Text. Once the state changes from false to true, the text is replaced by an image. In the ephemeral view tree there is always either the Text or the Image, never both. In the render tree however, during the animation the tree will contain both views:

    Because we use the default transition, it looks like the text fades into the image and back. However, you can think of them as separate transitions: the text has a removal transition (fade out) and the image has an insertion transition (fade in).


    We are not limited to the default fade transition. For example, here is a transition that slides in from the leading edge when a view is inserted, and removes the view by scaling it down:

    								let transition = AnyTransition.asymmetric(insertion: .slide, removal: .scale)
    
    							

    We can then combine it with an .opacity (fade) transition. The .combined operator combines both transitions in parallel to get the following effect:

    								let transition = AnyTransition.asymmetric(insertion: .slide, removal: .scale).combined(with: .opacity)
    VStack {
        Toggle("Visible", isOn: $visible)
        if visible {
            Text("Hello, world!")
                .transition(transition)
        } else {
            Text("Hello world!")
                .transition(transition)
        }
    }
    .animation(.default.speed(0.5), value: visible)
    
    							

    Note that in the sample above, we used a visible value to switch between the two Texts, even though they are the same. We can simplify the code a bit by using id(_:). Whenever the value we pass to id changes, SwiftUI considers this to be a new view in the render tree. When we combine this with our knowledge of transitions, we can trigger a transition just by changing the id of a view. For example, we can rewrite the sample above:

    								let transition = AnyTransition.asymmetric(insertion: .slide, removal: .scale).combined(with: .opacity)
    VStack {
        Toggle("Visible", isOn: $visible)
        Text("Hello, world!")
            .id(visible)
            .transition(transition)
    }
    .animation(.default.speed(0.5), value: visible)
    
    							

    Before the animation, the text is present, and during the animation the newly inserted view (with id(false)) is transitioned in, and the old view (with id(true)) is transitioned out. In other words: both views are present during the animation:


    When the builtin transitions don’t cover your needs, you can also create custom transitions. There is the .modifier(active:identity) transition. When a view isn’t transitioning, the identity modifier is applied. When a view is removed, the animation interpolates in between the identity modifier and the active modifier before removing the view completely. Likewise, when a view is inserted it starts out with the active modifier at the start of the animation, and ends with the identity modifier at the end of the animation.

    Here’s an example of a favorite button with a custom transition. This isn’t a perfect implementation (we would not hardcode the offsets and width of the button) but it does show what’s possible:

    The full code is available as a gist.


    Sometimes when performing a transition you might see unexpected side-effects. In our case we were almost always able to resolve these by wrapping the view we’re transitioning inside a container (for example, a VStack or ZStack). This adds some “stability” to the view tree that can help prevent glitches.

    In essence, transitions aren’t very complicated. However, achieving the result you want can be a bit tricky sometimes. In order to effectively work with transitions you have to understand the difference between the view tree and the render tree. And when you want to have custom transitions, you also need to understand how animations work. We cover this in both our workshops and our book Thinking in SwiftUI.

    If your company is interested in a workshop on SwiftUI, do get in touch.



    Source link

    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email

    Related Posts

    A Deep Dive into SwiftData migrations – Donny Wals

    January 24, 2026

    AI, find me some work…

    January 23, 2026

    Swift adapter design pattern – The.Swift.Dev.

    January 22, 2026

    Text is not visible when the button is in disabled state

    January 21, 2026

    What’s New in SwiftUI for iOS 18

    January 19, 2026

    WWDC 2023: A Reflection on Apple’s “Spatial Computing” Journey

    January 17, 2026
    Top Posts

    Understanding U-Net Architecture in Deep Learning

    November 25, 202511 Views

    Hard-braking events as indicators of road segment crash risk

    January 14, 20269 Views

    Microsoft 365 Copilot now enables you to build apps and workflows

    October 29, 20258 Views
    Don't Miss

    Open Cosmos launches first satellites for new LEO constellation

    January 25, 2026

    Press Release Open Cosmos, the company building satellites to understand and connect the world, has…

    Achieving superior intent extraction through decomposition

    January 25, 2026

    How UX Research Reveals Hidden AI Orchestration Failures

    January 25, 2026

    ByteDance steps up its push into enterprise cloud services

    January 25, 2026
    Stay In Touch
    • Facebook
    • Instagram
    About Us

    At GeekFence, we are a team of tech-enthusiasts, industry watchers and content creators who believe that technology isn’t just about gadgets—it’s about how innovation transforms our lives, work and society. We’ve come together to build a place where readers, thinkers and industry insiders can converge to explore what’s next in tech.

    Our Picks

    Open Cosmos launches first satellites for new LEO constellation

    January 25, 2026

    Achieving superior intent extraction through decomposition

    January 25, 2026

    Subscribe to Updates

    Please enable JavaScript in your browser to complete this form.
    Loading
    • About Us
    • Contact Us
    • Disclaimer
    • Privacy Policy
    • Terms and Conditions
    © 2026 Geekfence.All Rigt Reserved.

    Type above and press Enter to search. Press Esc to cancel.