UltraThinMaterial flicker on rotation3DEffect or having shadow - SwiftUI

59 Views Asked by At

Problem: when using rotation3DEffect over a ZStack that has modifiers: .background(.ultraThinMaterial) and .shadow, ultraThinMaterial texture dissapears.

  • If shadow modifier is removed from ZStack(check ViewWithNoShadow in the following code) the glitch does not happen.
  • If we use rotationEffect instead of rotation3DEffect and keep the shadow(check ViewWithShadow in the following code), the glitch does not happen.

CODE:

ZStack with shadow modifier and ultraThinMaterial as background

struct ViewWithShadow: View {
    private let shadowValues: (radius: CGFloat, x: CGFloat, y: CGFloat) = (1,2,2)
    
    var body: some View {
        ZStack {
            
        }
        .frame(maxHeight: .infinity)
        .frame(maxWidth: .infinity)
        .background(.ultraThinMaterial)
        .cornerRadius(20)
        .shadow(color: .primary.opacity(0.3), radius: shadowValues.radius, x: shadowValues.x, y: shadowValues.y)
    }
}

ZStack with no shadow modifier and ultraThinMaterial as background

struct ViewWithNoShadow: View {
    private let shadowValues: (radius: CGFloat, x: CGFloat, y: CGFloat) = (1,2,2)
    
    var body: some View {
        ZStack {
            
        }
        .frame(maxHeight: .infinity)
        .frame(maxWidth: .infinity)
        .background(.ultraThinMaterial)
        .cornerRadius(20)
    }
}

ContentView is a simple container that decides which view to show between ViewWithShadow and ViewWithNoShadow

struct ContentView: View {
    let hasShadow: Bool
    
    var body: some View {
        ZStack {
            Color.teal
            VStack(spacing: 10) {
                RoundedRectangle(cornerRadius: 10)
                    .frame(width: 200, height: 100)
                
                RoundedRectangle(cornerRadius: 10)
                    .fill(Color.red)
                    .frame(width: 200, height: 100)
            }
            .padding()
            
            if hasShadow {
                ViewWithShadow()
                    .frame(height: 300)
                    .padding()
            } else {
                ViewWithNoShadow()
                    .frame(height: 300)
                    .padding()
            }
          
        }
    }
}

AppMainContainer shows an ScrollView with the three use cases mentioned above

struct AppMainContainer: View {
    @State var isContentAAside: Bool = false
    @State var isContentBAside: Bool = false
    @State var isContentCAside: Bool = false
    
    var body: some View {
        ScrollView(showsIndicators: false) {
            VStack {
                GroupBox("rotation3DEffect, ViewWithShadow") {
                    button(text: "Click A") {
                        isContentAAside.toggle()
                    }
                    ContentView(hasShadow: true)
                        .scaleEffect(isContentAAside ? 0.92 : 1)
                        .rotation3DEffect(isContentAAside ? .init(degrees: -20) : .init(degrees: 0), axis: isContentAAside ? (x: 0, y: -2, z: -0.3) : (x: 0, y: 0, z: 0))
                        .offset(x: isContentAAside ? UIScreen.main.bounds.width - 150 : 0, y: isContentAAside ? 20 : 0)
                        .animation(.easeInOut, value: isContentAAside)
                }
                
                GroupBox("rotationEffect, ViewWithShadow") {
                    button(text: "Click B") {
                        isContentBAside.toggle()
                    }
                    ContentView(hasShadow: true)
                        .scaleEffect(isContentBAside ? 0.92 : 1)
                        .rotationEffect(isContentBAside ? .init(degrees: -20) : .init(degrees: 0))
                        .offset(x: isContentBAside ? UIScreen.main.bounds.width - 220 : 0, y: isContentBAside ? 10 : 0)
                        .animation(.easeInOut, value: isContentBAside)
                }
                
                GroupBox("rotation3DEffect, ViewWithNoShadow") {
                    button(text: "Click C") {
                        isContentCAside.toggle()
                    }
                    ContentView(hasShadow: false)
                        .scaleEffect(isContentCAside ? 0.92 : 1)
                        .rotation3DEffect(isContentCAside ? .init(degrees: -20) : .init(degrees: 0), axis: isContentCAside ? (x: 0, y: -2, z: -0.3) : (x: 0, y: 0, z: 0))
                        .offset(x: isContentCAside ? UIScreen.main.bounds.width - 220 : 0, y: isContentCAside ? 10 : 0)
                        .animation(.easeInOut, value: isContentCAside)
                }
            }
        }
    }
    
    func button(text: String, action: @escaping () -> Void) -> some View {
        Button {
            action()
        } label: {
            Text(text)
        }
    }
}

App class, entry point

import SwiftUI

@main
struct GlitchApp: App {
    var body: some Scene {
        WindowGroup {
            AppMainContainer()
        }
    }
}

With the code above you can reproduce the following glitch:

enter image description here

Expected Result: Use rotation3DEffect on ViewWithShadow without having any weird behaviour.

Any idea of why does this happen and how to solve it?. Thanks in advance!

0

There are 0 best solutions below