skip to Main Content

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)")
    }

}

2

Answers


  1. 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))
    
    Login or Signup to reply.
  2. Another way to cut out part of the background is to use an overlay with .blendMode(.destinationOut). It is also necessary to apply .compositingGroup() to the result, to prevent the blend mode burning into deeper layers.

    Replace these modifiers:

    .background(.yellow)
    .clipShape(RoundedRectangle(cornerRadius: 7))
    

    with:

    .background {
        RoundedRectangle(cornerRadius: 7)
            .fill(.yellow)
            .overlay {
                Rectangle()
                    .padding(.vertical, 5)
                    .padding(.horizontal, 20)
                    .blendMode(.destinationOut)
            }
            .compositingGroup()
    }
    

    Screenshot


    You mentioned in a comment to the answer you already accepted that you would like the chevrons to look centered. Since you are applying padding of 7 at the sides, this means applying padding (or spacing) of 7 on the inner side too.

    One way to achieve this is would be to move the chevrons into the overlay. Then in fact the whole view can be simplified:

    var body: some View {
        RoundedRectangle(cornerRadius: 7)
            .fill(.yellow)
            .frame(width: 300, height: 76)
            .overlay {
                HStack(spacing: 7) {
                    Image(systemName: "chevron.compact.left")
                    Rectangle()
                        .padding(.vertical, 5)
                        .blendMode(.destinationOut)
                    Image(systemName: "chevron.compact.right")
                }
                .padding(.horizontal, 7)
                .font(.title3.weight(.semibold))
                .foregroundStyle(.black)
            }
            .compositingGroup()
    }
    

    Screenshot

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