skip to Main Content

Cover image

Mockup image

I have a SwiftUI view where I display a background image with a mockup of an iPhone over it. The mockup has a transparent area to show the background image. I want to add a Color.black.opacity(0.2) overlay to darken the background image for better readability of the content. However, I want the dark overlay to exclude the transparent area of the mockup.

I tried the following approach:

Color.black.opacity(0.2)
    .ignoresSafeArea()
    .overlay(
        Image(.mockup)
            .resizable()
            .scaledToFit()
            .blendMode(.destinationOut)
)

This approach doesn’t seem to work as expected—the dark overlay is still visible over the mockup’s transparent area.

Here’s the full code for context:

import SwiftUI

struct ContentView: View {
    @State var name: String = ""
    
    var body: some View {
        ZStack {
            Image(.cover)
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()
            
            VStack {
                Image(.mockup)
                
                Spacer()
                
                Text("Lucas & Lavínia")
                    .font(.title)
                    .fontWeight(.bold)
                
                Spacer().frame(height: 24)
                
                TextField(
                    "",
                    text: $name,
                    prompt: Text("Nome").foreground(.white)
                )
                .padding(15)
                .background(.white.opacity(0))
                .overlay(
                    RoundedRectangle(
                        cornerRadius: 8
                    )
                    .stroke(
                        .white,
                        lineWidth: 1
                    )
                )
                .clipShape(.rect(cornerRadius: 8))
                .foregroundStyle(.white)
                
                Spacer().frame(height: 24)
                
                Button {
                    
                } label: {
                    HStack {
                        Text("Selecionar fotos da galeria")
                    }
                    .frame(maxWidth: .infinity)
                    .foregroundColor(.white)
                    .padding(.vertical, 15)
                    .background(.red)
                    .cornerRadius(40)
                }
                .frame(maxWidth: .infinity)
                
                Spacer().frame(height: 24)
                
                Button {
                    
                } label: {
                    Text("Capturar foto agora")
                }
                
                Spacer().frame(height: 35)
            }
            .frame(maxWidth: .infinity)
            .foregroundStyle(.white)
            .padding(.all)
            .padding(.top, 25)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
    }
}

#Preview {
    ContentView()
}

How can I apply the black overlay only to the parts of the background image that are not covered by the mockup? Is there a way to mask or exclude the area inside the transparent section of the mockup? Or would I need a completely different approach to achieve this effect?

Any guidance or code examples would be greatly appreciated!

2

Answers


  1. The code adds a dark overlay to the background image but keeps the mockup’s transparent area clear. Here’s how:

    1. Overlay and Masking: The Color.black.opacity(0.2) overlay darkens the background, but we mask it so the mockup’s transparent area isn’t darkened.
    2. Blend Mode: The mockup uses .blendMode(.destinationOut) to "cut out" the dark overlay in its transparent parts.
    3. Compositing Group: Combines everything for proper rendering.
      This keeps the mockup clear while making the rest of the background darker for better readability. Just ensure the mockup image is a transparent PNG!

    You can replace rectangle ("// Exclude mockup area") with your image. Try below code:

    ZStack {
            // Background Image with Overlay
            Image(".cover")
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()
                .overlay(
                    Color.black.opacity(0.2) // Dark overlay
                        .mask(
                            ZStack {
                                Rectangle() // Apply overlay to the entire screen
                                    .fill(Color.black)
                                Rectangle() // Exclude mockup area
                                    .frame(width: 200, height: 300)
                                    .scaledToFit()
                                    .blendMode(.destinationOut) // Cut out the transparent area
                            }
                            .compositingGroup()
                        )
                )
    ...........
    

    enter image description here

    Login or Signup to reply.
  2. First of all, I would suggest showing the base image in the background of the ZStack, instead of as the first layer of the ZStack. This way, the overflow from scaling-to-fill will not cause the ZStack to extend off-screen.

    Then, a masking layer can be added as the first layer of the ZStack, which is seen above the background image.

    • The mask is formed using semi-transparent Color.black.

    • A RoundedRectangle is overlayed over the semi-transparent background. The corner radius should approximately match the shape of the mockup.

    • The rounded rectangle is applied using .blendMode(.destinationOut). This causes the shape to be cut out from the underlying semi-transparent black layer.

    • The modifier .compositingGroup() is applied, to prevent blend mode from burning deeper into lower layers.

    To match the size and position of the cut-out with the mockup image in the VStack, .matchedGeometryEffect can be used. This requires a namespace:

    @Namespace private var ns
    

    Here is the example with updates applied:

    ZStack {
        Color.black
            .opacity(0.5)
            .ignoresSafeArea()
            .overlay {
                RoundedRectangle(cornerRadius: 40)
                    .inset(by: 4)
                    .fill(.black)
                    .matchedGeometryEffect(id: "mockup", in: ns, isSource: false)
                    .blendMode(.destinationOut)
            }
            .compositingGroup()
    
        VStack {
            Image(.mockup)
                .resizable()
                .scaledToFit()
                .matchedGeometryEffect(id: "mockup", in: ns)
    
            // ... other content as before
        }
        // ... modifiers as before
    }
    .background {
        Image(.cover)
            .resizable()
            .scaledToFill()
            .ignoresSafeArea()
    }
    

    Screenshot

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search