-1

I want to cut off the upper and lower portion of a container view in my table view cell using UIBezierPath() & CAShapeLayer(). The code is as follows:

func cutView() {
        let containerViewHeight: CGFloat = containerView.frame.height
        let headerCut: CGFloat = 50
        let newHeight = containerViewHeight - headerCut/2
        let newHeaderUpperLayer = CAShapeLayer()
        let newHeaderLowerLayer = CAShapeLayer()

        newHeaderUpperLayer.fillColor = UIColor.black.cgColor
        newHeaderLowerLayer.fillColor = UIColor.black.cgColor
        containerView.layer.mask = newHeaderUpperLayer
        containerView.layer.mask = newHeaderLowerLayer

        let getViewFrame = CGRect(x: 0, y: -newHeight, width: containerView.bounds.width, height: containerViewHeight)
        let cutDirectionUpper = UIBezierPath()
        let cutDirectionLower = UIBezierPath()

        cutDirectionUpper.move(to: CGPoint(x: 0, y: 0))
        cutDirectionUpper.addLine(to: CGPoint(x: getViewFrame.width, y: headerCut))
        cutDirectionUpper.addLine(to: CGPoint(x: getViewFrame.width, y: getViewFrame.height))
        cutDirectionUpper.addLine(to: CGPoint(x: 0, y: getViewFrame.height))

        cutDirectionLower.move(to: CGPoint(x: 0, y: 0))
        cutDirectionLower.addLine(to: CGPoint(x: getViewFrame.width, y: 0))
        cutDirectionLower.addLine(to: CGPoint(x: getViewFrame.width, y: getViewFrame.height))
        cutDirectionLower.addLine(to: CGPoint(x: 0, y: getViewFrame.height - headerCut))

        newHeaderUpperLayer.path = cutDirectionUpper.cgPath
        newHeaderLowerLayer.path = cutDirectionLower.cgPath
    }

It's working separately but not together. What I am missing here? Any suggestions would be highly appreciated. Thanks in advance.

If I separately run it, It works like this:

enter image description here enter image description here

But what I want is this:

enter image description here

Using combined UIBezierPath():

func cutView() {
        let containerViewHeight: CGFloat = containerView.frame.height
        let headerCut: CGFloat = 50
        let newHeight = containerViewHeight - headerCut/2
        let newHeaderLayer = CAShapeLayer()

        newHeaderLayer.fillColor = UIColor.black.cgColor
        containerView.layer.mask = newHeaderLayer

        let getViewFrame = CGRect(x: 0, y: -newHeight, width: containerView.bounds.width, height: containerViewHeight)
        let cutDirectionLower = UIBezierPath()
        let cutDirectionUpper = UIBezierPath()

        cutDirectionUpper.move(to: CGPoint(x: 0, y: 0))
        cutDirectionUpper.addLine(to: CGPoint(x: getViewFrame.width, y: headerCut))
        cutDirectionUpper.addLine(to: CGPoint(x: getViewFrame.width, y: getViewFrame.height))
        cutDirectionUpper.addLine(to: CGPoint(x: 0, y: getViewFrame.height))

        cutDirectionLower.move(to: CGPoint(x: 0, y: 0))
        cutDirectionLower.addLine(to: CGPoint(x: getViewFrame.width, y: 0))
        cutDirectionLower.addLine(to: CGPoint(x: getViewFrame.width, y: getViewFrame.height))
        cutDirectionLower.addLine(to: CGPoint(x: 0, y: getViewFrame.height - headerCut))

        cutDirectionUpper.append(cutDirectionLower)
        newHeaderLayer.path = cutDirectionUpper.cgPath
    }

Using combined CGMutablePath():

func cutView() {
        let containerViewHeight: CGFloat = containerView.frame.height
        let headerCut: CGFloat = 50
        let newHeight = containerViewHeight - headerCut/2
        let newHeaderLayer = CAShapeLayer()
        let combinedPath = CGMutablePath()

        newHeaderLayer.fillColor = UIColor.black.cgColor
        containerView.layer.mask = newHeaderLayer

        let getViewFrame = CGRect(x: 0, y: -newHeight, width: containerView.bounds.width, height: containerViewHeight)
        let cutDirectionLower = UIBezierPath()
        let cutDirectionUpper = UIBezierPath()

        cutDirectionUpper.move(to: CGPoint(x: 0, y: 0))
        cutDirectionUpper.addLine(to: CGPoint(x: getViewFrame.width, y: headerCut))
        cutDirectionUpper.addLine(to: CGPoint(x: getViewFrame.width, y: getViewFrame.height))
        cutDirectionUpper.addLine(to: CGPoint(x: 0, y: getViewFrame.height))

        cutDirectionLower.move(to: CGPoint(x: 0, y: 0))
        cutDirectionLower.addLine(to: CGPoint(x: getViewFrame.width, y: 0))
        cutDirectionLower.addLine(to: CGPoint(x: getViewFrame.width, y: getViewFrame.height))
        cutDirectionLower.addLine(to: CGPoint(x: 0, y: getViewFrame.height - headerCut))

        combinedPath.addPath(cutDirectionUpper.cgPath)
        combinedPath.addPath(cutDirectionLower.cgPath)

        newHeaderLayer.path = combinedPath
    }

Both of them remove the upper and lower cut. What I am missing here?
Output of using combined CGMutablePath() & combined UIBezierPath() separately is as follows:

enter image description here

Tulon
  • 4,011
  • 6
  • 36
  • 56
  • 1
    You are saying `containerView.layer.mask = newHeaderUpperLayer; containerView.layer.mask = newHeaderLowerLayer`. So you throw away the first mask layer entirely, replacing it with the second one. – matt Nov 21 '19 at 17:40
  • @matt Thanks for commenting. But I want to draw both of them on the same `containerView`. So what should I do? – Tulon Nov 21 '19 at 17:44
  • I have tried both combined `CGMutablePath()` & combined `UIBezierPath()`, but none of them are working, what am I missing here? – Tulon Nov 21 '19 at 18:35
  • Why don’t you just use your two shape layers as sublayers of a single mask layer? – matt Nov 21 '19 at 18:38
  • `containerView.layer.mask?.addSublayer(newHeaderUpperLayer)` & `containerView.layer.mask?.addSublayer(newHeaderLowerLayer)` same result. `newHeaderUpperLayer.addSublayer(newHeaderLowerLayer)` & `containerView.layer.mask?.addSublayer(newHeaderUpperLayer)` same result. Both of the cut has gone. :( – Tulon Nov 21 '19 at 19:05
  • Also tried `newHeaderUpperLayer.addSublayer(newHeaderLowerLayer)` & `containerView.layer.mask = newHeaderUpperLayer` but not working. – Tulon Nov 21 '19 at 19:08

2 Answers2

1

You are assigning two masks to the same layer. Obviously it would take the last assignment (as any other variable would).

Here is what you can do.

  1. Have one mutable path.
  2. Add the two paths and store them in this mutable path. (or append in the same mutable path).
  3. Have single mask based on this path.
Mohammad Sadiq
  • 5,070
  • 28
  • 29
  • Thanks for your suggestions. I am looking into it. – Tulon Nov 21 '19 at 17:58
  • I have tried both combined `CGMutablePath()` & combined `UIBezierPath()`, but none of them are working, what am I missing here? The question is updated with the codes. – Tulon Nov 21 '19 at 18:35
1

I think you'll find it much easier to work with a subclassed UIImageView that handles the masking for you.

Start with defining your shape like this:

enter image description here

You will move to pt1, addLine to pt2, addLine to pt3, addLine to pt4, and then close the path.

To get that to work automatically, update the path in our subclass in layoutSubviews() -- that way it will adjust its size whenever the view changes size.

Here is an example I used to create that image:

class CutImageView: UIImageView {

    let maskLayer = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    func commonInit() -> Void {
        layer.mask = maskLayer
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let headerCut: CGFloat = 50

        let pt1: CGPoint = CGPoint(x: bounds.minX, y: bounds.maxY - headerCut)
        let pt2: CGPoint = CGPoint(x: bounds.minX, y: bounds.minY)
        let pt3: CGPoint = CGPoint(x: bounds.maxX, y: headerCut)
        let pt4: CGPoint = CGPoint(x: bounds.maxX, y: bounds.maxY)

        let pth = UIBezierPath()

        pth.move(to: pt1)
        pth.addLine(to: pt2)
        pth.addLine(to: pt3)
        pth.addLine(to: pt4)
        pth.close()

        maskLayer.path = pth.cgPath

    }

}

class ViewController: UIViewController {

    let cutImageView: CutImageView = {
        let v = CutImageView(frame: CGRect.zero)
        v.translatesAutoresizingMaskIntoConstraints = false
        v.contentMode = .scaleToFill
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let bkgImage = UIImage(named: "background") else {
            fatalError("missing images")
        }

        view.backgroundColor = .systemGreen

        view.addSubview(cutImageView)

        let g = view.safeAreaLayoutGuide

        NSLayoutConstraint.activate([

            cutImageView.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
            cutImageView.centerXAnchor.constraint(equalTo: g.centerXAnchor, constant: 0.0),
            cutImageView.widthAnchor.constraint(equalToConstant: 300.0),
            cutImageView.heightAnchor.constraint(equalToConstant: 240.0),


        ])

        cutImageView.image = bkgImage

    }
}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Man, I don't know how to say but love you. :D Thanks a lot for sharing this awesome piece of code. O:) I was wasting time with goddamn `UIView`. – Tulon Nov 21 '19 at 19:45