swiftuirenderingswiftui-animationswiftui-transition

SwiftUI opacity transition fades each views and subviews separately


TLDR: how to render a whole subview before fading it in?

Consider this code snippet: a simple white view and a button that toggles an overlaying FrontView. The FrontView is a superposition of 2 black views, a fullscreen one and a smaller one.

struct ContentView: View {
    @State private var frontViewIsOpen = false
    
    var body: some View {
        ZStack {
            Color.white
                .zIndex(0)
            if frontViewIsOpen {
                FrontView()
                    .transition(.opacity)
                    .zIndex(1)
            }
            VStack {
                Spacer()
                Button("Toggle") {
                    frontViewIsOpen.toggle()
                }
            }
            .padding(.bottom, 50)
            .zIndex(2)
        }
        .animation(.linear(duration: 1), value: frontViewIsOpen)
        .ignoresSafeArea()
    }
}

struct FrontView: View {
    var body: some View {
        Color.black
            .overlay {
                Color.black.frame(width: 100, height: 100)
            }
    }
}

On toggle, the FrontView fades in during 1 second. Here are the screenshots at t=0, t=0.5 and t=1:

swiftui fade animation steps

During the whole animation, the inner 100pt black view is clearly visible. I guess SwiftUI not only fades-in the FrontView, but also all subviews in the tree. At t=0.5, the FrontView alpha is 0.5, the inner view is also 0.5, so with transparency, the intersection of both should be 0.75.

I recreated this test on UIKit. While fading-in/out a view, UIKit does not fade subviews automatically, resulting with a transition rendering like:

uikit fade animation steps

I'm trying to reproduce the UIKit transition behavior in SwiftUI. I tried to remove the inner views transaction animation with .transaction { $0.animation = nil }, or to set to the inner view transition to .identity, without success.

I there a way to render the whole subview before transitionning ?


Solution

  • Just add .compositingGroup to FrontView:

    var body: some View {
        Color.black
            .overlay {
                Color.black.frame(width: 100, height: 100)
            }
            .compositingGroup() // <- HERE
    }