iosswiftswiftui

How to use a scrollable view as a mask?


I have a red rectangle that takes up the entire screen. I wish to mask this rectangle with a ScrollView consisting of a series of rectangular shapes, such that I am able to only see the below red rectangle through the frames of the rectangles in the ScrollView.

My approach, below, makes scrolling on the ScrollView not work.

Please note that instead of a red rectangle, I wish to have a custom background image, hence any suggestion to simply give a static red background to each of the ScrollView items would not be satisfactiory.

Thank you.

            Rectangle()
                .fill(Color.red)
                .mask {
                    ScrollView{
                        LazyVStack(spacing: 10) {
                            Rectangle().frame(width: .infinity, height: 500)
                            Rectangle().frame(width: .infinity, height: 500)
                            Rectangle().frame(width: .infinity, height: 500)
                        }
                    }
                }

Solution

  • It seems that a mask, like a clip shape, cannot be scrolled.

    As a workaround, you could consider using .blendMode. In particular, blend mode .destionationOut can be used to cut-out a form from an underlying layer.

    If you apply the blend mode over a layer of solid background color then this gives you a layer of background color with "holes" in it. When this is applied as an overlay to the base content, it gives the same effect as a mask.

    The "scope" of the blend operation needs to be constrained by applying .compositingGroup, otherwise it goes on burning through to lower layers.

    Here is an updated version of your example to show it working. You said you are planning to use an image as the base content, so this is illustrated too:

    Image(systemName: "ladybug")
        .resizable()
        .scaledToFit()
        .padding(30)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.red)
        .overlay {
            ScrollView{
                LazyVStack(spacing: 10) {
                    ForEach(0..<5) { _ in
                        Rectangle()
                            .frame(height: 200)
                    }
                }
            }
            .blendMode(.destinationOut)
            .background(.background)
            .compositingGroup()
        }
    

    Animation