SwiftUI: Transparent part of the View and text

Viewed 40

Spending a few days playing around with different modifications and stacks I got stuck in achieve transparent part of the View and text. Goal is made views from code example with .purple color is transparent and they should be background gradient color like in expected result.

Code example:

struct CircleView: View {
    var body: some View {
        ZStack {
            LinearGradient(colors: [.mint, .cyan], startPoint: .topLeading, endPoint: .bottomTrailing)
            HStack(spacing: -20) {
                ZStack() {
                    Circle()
                        .frame(width: 120, height: 120)
                        .foregroundColor(.white)
                    Text("")
                        .font(.system(size: 60))
                }
                ZStack() {
                    Circle()
                        .strokeBorder(.purple, lineWidth: 5)
                        .background(Circle().fill(.white))
                        .frame(width: 75, height: 75)
                    VStack() {
                        Text("108")
                        Text("Days")
                    }
                    .foregroundColor(.purple)
                }
            }
        }
    }
}

Expected result:

enter image description here

2 Answers

I personally would do this with the following extension:

public extension View {
    @inlinable
    func reverseMask<Mask: View>(
        alignment: Alignment = .center,
        @ViewBuilder _ mask: () -> Mask
    ) -> some View {
        self.mask {
            Rectangle()
                .overlay(alignment: alignment) {
                    mask()
                        .blendMode(.destinationOut)
                }
        }
    }
}

You can use this on any view to subtract from it. In your example the right circle would look like this:

Circle()
    .fill(Color.white)
    .frame(width: 75, height: 75)
    .reverseMask {
        VStack() {
            Text("108")
            Text("Days")
        }
    }

Cameron has a great answer and it should be accepted. Based on it here's how you can also make a "transparent" circle around the small circle

struct CircleView: View {
    var body: some View {
        let bigCircleSize: CGFloat = 120
        let smallCircleSize: CGFloat = 75
        let borderWidth: CGFloat = 5
        let maskCircleSize = smallCircleSize + borderWidth
        let spacing: CGFloat = -20

        ZStack {
            LinearGradient(colors: [.mint, .cyan], startPoint: .topLeading, endPoint: .bottomTrailing)
            HStack(spacing: spacing) {
                ZStack {
                    Circle()
                        .frame(width: bigCircleSize, height: bigCircleSize)
                        .foregroundColor(.white)

                    Text("")
                        .font(.system(size: 60))
                }
                .reverseMask {
                    Circle()
                        .frame(width: maskCircleSize, height: maskCircleSize)
                        .offset(x: bigCircleSize / 2 + spacing + smallCircleSize / 2)
                }

                ZStack {
                    Circle()
                        .foregroundColor(.white)
                        .background(Circle().fill(.white))
                        .frame(width: smallCircleSize, height: smallCircleSize)
                        .reverseMask {
                            VStack() {
                                Text("108")
                                Text("Days")
                            }
                        }
                }
            }
        }
    }
}

public extension View {
    @inlinable
    func reverseMask<Mask: View>(
        alignment: Alignment = .center,
        @ViewBuilder _ mask: () -> Mask
    ) -> some View {
        self.mask {
            Rectangle()
                .overlay(alignment: alignment) {
                    mask()
                        .blendMode(.destinationOut)
                }
        }
    }
}

The idea is to make another mask on the big circle. Calculate the x-offset and you're good to go

Related