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?
If I separately run it, it works like this:
But what I want is this:
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:
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:
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
}
}