skip to Main Content

I am trying to overlap two circles in SwiftUI and have a margin between them. I am presently using this method:

ZStack {
    Circle()
        .frame(width: 60, height: 60)
        .foregroundColor(Color.blue)
        .shadow(color: .black.opacity(0.5), radius: 4, x: 2, y: 2)
    ZStack {
        Circle()
            .frame(width: 26, height: 26)
            .foregroundColor(Color(.systemGray5))
        Circle()
            .frame(width: 22, height: 22)
            .foregroundColor(.blue)
    }
    .offset(x: 26, y: 17)
}

enter image description here

The problem is that because of the shadow on the big circle I will never be able to perfectly match the background on the smaller circle’s border circle (the one that is systemGray5. So although it looks okay, I only want the margin to appear between the circles. Not all the way around the smaller circle.

In illustrator or other ways I would clip the big image with my 26 size circle and it would look like a bite taken out of it. Then I can achieve this effect perfectly.

Is there anyway to clip the bottom of my large circle in SwiftUI?

2

Answers


  1. Here is a demo of possible approach with inverted mask (it is simplified but the idea should be clear – removing hardcoding and "bite" position calculations is on you).

    Tested with Xcode 13.2 / iOS 15.2

    demo

    struct DemoView: View {
        struct BiteCircle: Shape {
            func path(in rect: CGRect) -> Path {
                let offset = rect.maxX - 26
                let crect = CGRect(origin: .zero, size: CGSize(width: 26, height: 26)).offsetBy(dx: offset, dy: offset)
    
                var path = Rectangle().path(in: rect)
                path.addPath(Circle().path(in: crect))
                return path
            }
        }
        var body: some View {
            ZStack {
                Circle()
                    .frame(width: 60, height: 60)
                    .foregroundColor(Color.blue)
                    .mask(BiteCircle().fill(style: .init(eoFill: true)))     // << here !!
                    .shadow(color: .black.opacity(0.5), radius: 4, x: 2, y: 2)
    
                Circle()
                    .frame(width: 22, height: 22)
                    .foregroundColor(.blue)
                    .offset(x: 18, y: 18)
            }
    
        }
    }
    
    Login or Signup to reply.
  2. I think this would be helpfull

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

    }

    You can use it so

    ZStack {
            Circle().frame(width: 60, height: 60)
                .reverseMask { 
                    Circle().frame(width: 25, height: 25)
                        .offset(x: 20, y: 20)
                }
            Circle().frame(width: 20, height: 20)
                .offset(x: 20, y: 20)
        }
        .foregroundColor(.blue)
        .shadow(color: .black, radius: 4)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search