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