'How to apply shadow to interior views in SwiftUI?

I have added a shadow around the VStack that holds my two text fields and a submit button. However, the shadow is also being applied to the two text fields inside the VStack.

Is there something I am missing here that is causing this to happen? I tried adding shadow(radius: 0) on the text fields, but it doesn't change anything. If I remove the padding and background from the text fields, then the shadow goes away.

var body: some View {
    VStack() {
        Spacer()

        VStack() {
            TextField($email, placeholder: Text("email"))
                .padding()
                .background(Color(red: 242 / 255, green: 242 / 255, blue: 242 / 255))

            SecureField($password, placeholder: Text("password"))
                .padding()
                .background(Color(red: 242 / 255, green: 242 / 255, blue: 242 / 255))

            Button(action: { self.login() }, label: { Text("Login").foregroundColor(Color.white) })
                .padding()
                .background(Color(red: 0, green: 116 / 255, blue: 217 / 255))
        }
        .padding()
        .background(Color.white)
        .shadow(radius: 10)

        Spacer()
    }
    .padding()
    .background(Color(red: 0, green: 116 / 255, blue: 217 / 255))
    .edgesIgnoringSafeArea(.all)
}


Solution 1:[1]

You can use clipped() here to fix this

VStack() {
    Text("Text")
        .background(Color.red)
        .padding()
        .padding()

    Text("Text")
        .background(Color.purple)
        .padding()
}
.padding()
.background(Color.white)

.clipped()
.shadow(color: Color.red, radius: 10, x: 0, y: 0)

Output:

enter image description here

Hope it is helpful :)

Solution 2:[2]

For me it also worked to apply the shadow to the background instead of the Stack, e.g:

VStack {
  // ...
}.background(Rectangle().fill(Color.white).shadow(radius: 8))

Solution 3:[3]

There is a dedicated modifier for this - compositingGroup.

From documentation:

/// Wraps this view in a compositing group.
///
/// A compositing group makes compositing effects in this view's ancestor
/// views, such as opacity and the blend mode, take effect before this view
/// is rendered.
/// [...] 
/// This limits the scope of [...] change to the outermost view.
VStack {
    Text("Text")
        .background(Color.red)
        .padding()
        .padding()
    Text("Text")
        .background(Color.purple)
        .padding()
}
.padding()
.background(Color.white)
.compositingGroup()
.shadow(color: Color.red, radius: 10, x: 0, y: 0)

Solution 4:[4]

For the user's who are struggling to have this work for RoundedRectangle, use:

HStack {
 // Your nested views
}
.overlay(
  RoundedRectangle(cornerRadius: 10)
   .stroke(Color.black.opacity(0.2), lineWidth: 1)
)
.background(
   RoundedRectangle(
     cornerRadius: 10
   )
   .foregroundColor(Color.white)
   .shadow(
     color: Color.black,
     radius: 5,
     x: 0,
     y: 0
   )
)

The shadow is applied in the .background, RoundedRectagle element shadow.

While quite verbose, it's easy to read and the same principle can be applied to and from any other shape.

Solution 5:[5]

You can work around the issue by using a ZStack to put a Rectangle,Capsule,Circle or other shape behind the VStack. Then apply modifiers to the shape instead of the VStack itself.

ZStack() {
    Capsule()
        .frame(width: 50, height: 50)
        .background(Color.white)
        .shadow(radius: 10)

    VStack() {
        //...
    }
}

Seems like a pretty annoying feature to me, there are many times when you would want a shadow on a container without it being applied to all its elements.

If anyone knows a better way please share, thanks!

Solution 6:[6]

Hello i'm using only this at the moment but it serves it's purpose - allows me to have inner shadow.

With some modification you can have what you want.

private struct InnerShadow: ViewModifier {

    func body(content: Content) -> some View {
        HStack(spacing: .zero) {
            verticalLine().offset(x: -1)
            VStack(spacing: .zero) {
                horizontalLine().offset(y: -1)
                content
                horizontalLine().offset(y: 1)
            }
            verticalLine().offset(x: 1)
        }

    }

    private func verticalLine() -> some View {
        Rectangle()
            .foregroundColor(.white)
            .frame(maxWidth: 1, maxHeight: .infinity)
            .shadow(radius: 4)

    }

    private func horizontalLine() -> some View {
        Rectangle()
            .foregroundColor(.white)
            .frame(maxWidth: .infinity, maxHeight: 1)
            .shadow(radius: 4)
    }

}

Solution 7:[7]

Using .clipped() (as some other answers suggest) can lead to some undesirable side-effects in some situations. Drawing an extra view using .background() and giving that a shadow is inefficient (in terms of compute) and tedious.

The best way to do this is to call .compositingGroup() on your VStack. That will look like this:

VStack {
    // Some inner views
}
.compositingGroup()

Per this page from the Apple Developer Documentation:

A compositing group makes compositing effects in this view’s ancestor views, such as opacity and the blend mode, take effect before this view is rendered.

Because the view and its children get composited as a single View, any shadows applied to it don't get propagated to the children.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 KYDronePilot
Solution 2 Fabian Streitel
Solution 3 pawello2222
Solution 4
Solution 5 Declan Maybury
Solution 6 Ignelio
Solution 7