Search code examples
iosswiftswiftui

Filling only a part of SwiftUI with background color


I am trying to design a video trimmer view in SwiftUI as follows. This result is this. My question is whether I can have the middle part (i.e. everything except 20 points on the left and right and 5 points up and down) transparent so that I can show thumbnails behind them (perhaps by having a custom clip shape)?

enter image description here

  var body: some View {
    HStack(spacing: 10) {
        Image(systemName: "chevron.compact.left")
            .frame(height:70)
        
        Spacer()
        
        Image(systemName: "chevron.compact.right")
    }
    .foregroundColor(.black)
    .font(.title3.weight(.semibold))
    .padding(.horizontal, 7)
    .padding(.vertical, 3)
    .background(.yellow)
    .clipShape(RoundedRectangle(cornerRadius: 7))
    .frame(width: 300)
    .onGeometryChange(for: CGFloat.self) { proxy in
        proxy.size.width
    } action: { width in
        print("width = \(width)")
    }

}

Solution

  • Instead of background and then clipShape, use background(_:in:fillStyle:). This allows you to specify a Shape in which you want the background color to fill.

    You can write a Shape like this:

    struct VideoTrimmerBackgroundShape: Shape {
        func path(in rect: CGRect) -> Path {
            RoundedRectangle(cornerRadius: 7)
                .path(in: rect)
                .subtracting(
                    RoundedRectangle(cornerRadius: 7)
                        .path(in: rect.insetBy(dx: 20, dy: 5))
                )
        }
    }
    

    I am simply subtracting the path created by an insetted rounded rectangle from a non-insetted one.

    Then

    .background(.yellow, in: VideoTrimmerBackgroundShape())
    // you don't need clipShape anymore!
    

    subtracting is available since iOS 17. If you need to support a lower version, you can just add the smaller rounded rectangle as a subpath, but use even-odd filling.

    struct VideoTrimmerBackgroundShape: Shape {
        func path(in rect: CGRect) -> Path {
            var p = RoundedRectangle(cornerRadius: 7)
                .path(in: rect)
            p.addPath(
                RoundedRectangle(cornerRadius: 7)
                    .path(in: rect.insetBy(dx: 20, dy: 5))
            )
            return p
        }
    }
    
    .background(.yellow, in: VideoTrimmerBackgroundShape(), fillStyle: .init(eoFill: true))