swiftuishadow

Can I prevent shadows to overlay other views?


I have views with shadows with a big shadow blur. I want the shadow to go behind the other views. Tried some stuff with zIndexing, but nothing seems to work. This is what is happening:

bleeding shawdows

Simplified code:

import SwiftUI

struct TestBackgroundView: View {
    var body: some View {
        VStack {
            Color.white.frame(height: 80)
                .shadow(color: .red, radius: 40)            

            Color.white.frame(height: 80)
                .shadow(color: .red, radius: 40)
        }
    }
}

#Preview {
    TestBackgroundView()
}

In my own code, the views are buttons, where the button have a background shadow, that also changes based on the pressed state.

I've tried to overlay 2 VStacks in a ZStack, where de background is only on the bottom vstack. But then I cannot have a clear color to place the shadow on, so my animation of the button looks off, as the background of the first VStack is still visible.

Zstack option

struct TestBackgroundView: View {
    var body: some View {
        ZStack {
            VStack {
                Color.white.frame(height: 80)
                    .shadow(color: .red, radius: 40)

                Color.white.frame(height: 80) // Color.clear will not show shadow
                    .shadow(color: .red, radius: 40)
            }
            VStack {
                Color.white.frame(height: 80)

                Color.black.opacity(0.8) // Simulating pressed state
                    .padding()
                    .frame(height: 80)

            }
        }
    }
}

Any other solutions possible?


Solution

  • By default, a shadow will only cover views that came earlier in the layout. Views that follow in the layout will cover the shadow of an earlier view. So the problem only exists for the shadow of the second view in the VStack.

    If you know the spacing of the VStack then you could use a .mask to stop the shadow from going over the preceding view:

    VStack(spacing: 10) {
        Color.white
            .frame(height: 80)
            .shadow(color: .red, radius: 40)
    
        Color.white
            .frame(height: 80)
            .shadow(color: .red, radius: 40)
            .mask(alignment: .top) {
                Rectangle()
                    .padding(.top, -10) // VStack spacing
                    .frame(height: 200, alignment: .top)
            }
    }
    

    Screenshot

    If you didn't know the height of the view concerned then you could use a GeometryReader to measure it:

    // (second case)
    Color.white
        .frame(height: 80)
        .shadow(color: .red, radius: 40)
        .mask(alignment: .top) {
            GeometryReader { proxy in
                Rectangle()
                    .padding(.top, -10) // VStack spacing
                    .frame(height: proxy.size.height + 100, alignment: .top)
            }
        }