schnabler
schnabler

Reputation: 715

Subtract UIView from another UIView in Swift

I'm sure this is a very simple thing to do, but I can't seem to wrap my head around the logic.

I have two UIViews. One black, semi-transparent and "full-screen" ("overlayView"), another one on top, smaller and resizeable ("cropView"). It's pretty much a crop-view setup, where I want to "dim" out the areas of an underlying image that are not being cropped.

My question is: How do I go about this? I'm sure my approach should be with CALayers and masks, but no matter what I try, I can't get behind the logic.

This is what I have right now:

This is what it looks like

This is what I would want it to look like:

This is what I want it to look like

How do I achieve this result in Swift?

Upvotes: 4

Views: 2192

Answers (2)

BelfDev
BelfDev

Reputation: 316

Although you won't find a method such as subtract(...), you can easily build a screen with an overlay and a transparent cut with the following code:

Swift 4.2

private func addOverlayView() {
    let overlayView = UIView(frame: self.bounds)
    let targetMaskLayer = CAShapeLayer()

    let squareSide = frame.width / 1.6
    let squareSize = CGSize(width: squareSide, height: squareSide)
    let squareOrigin = CGPoint(x: CGFloat(center.x) - (squareSide / 2),
                               y: CGFloat(center.y) - (squareSide / 2))
    let square = UIBezierPath(roundedRect: CGRect(origin: squareOrigin, size: squareSize), cornerRadius: 16)

    let path = UIBezierPath(rect: self.bounds)
    path.append(square)

    targetMaskLayer.path = path.cgPath
    // Exclude intersected paths
    targetMaskLayer.fillRule = CAShapeLayerFillRule.evenOdd

    overlayView.layer.mask = targetMaskLayer
    overlayView.clipsToBounds = true
    overlayView.alpha = 0.6
    overlayView.backgroundColor = UIColor.black

    addSubview(overlayView)
}

Just call this method inside your custom view's constructor or inside your ViewController's viewDidLoad().

Walkthrough

First I create a raw overlayView, then a CAShapeLayer which I called "targetMaskLayer". The ultimate goal is to draw a square with the help of UIBezierPath inside that overlayView. After defining the square's dimensions, I set its cgPath as the targetMaskLayer's path.

Now comes an important part:

targetMaskLayer.fillRule = CAShapeLayerFillRule.evenOdd

Here I basically configure the fill rule to exclude the intersection.

Finally, I provide some styling to the overlayView and add it as a subview.

ps.: don't forget to import UIKit

Upvotes: 5

Christian
Christian

Reputation: 4641

There might be another drawing solution but basically you have 4 areas that need to be handled. Take the square area above and below the space with full width and add the right and left side between them with constraints to eachother.

Upvotes: 1

Related Questions