I have a requirement to create a view that has a top and bottom view merged where the top view needs to have a curve at the bottom and both the top and bottom view performs different actions on tap. I was able to create this view as below
This is the screenshot of my storyboard constraints
This is my curve view class
class CurveView: UIView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
self.createBottomCurve()
}
private func createBottomCurve() {
let offset: CGFloat = self.frame.width / 1.2
let bounds: CGRect = self.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width, height: bounds.size.height / 2)
let rectPath: UIBezierPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
And this is the implementation in viewcontroller class
class ViewController: UIViewController, UIGestureRecognizerDelegate {
@IBOutlet weak var greenCurveView: CurveView!
@IBOutlet weak var yellowView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let greenViewTap = UITapGestureRecognizer(target: self, action: #selector(handleGreenViewTap(_:)))
greenViewTap.delegate = self
greenCurveView.addGestureRecognizer(greenViewTap)
let yellowViewTap = UITapGestureRecognizer(target: self, action: #selector(handleYellowTap(_:)))
yellowViewTap.delegate = self
yellowView.addGestureRecognizer(yellowViewTap)
}
@objc func handleGreenViewTap(_ pan: UITapGestureRecognizer ){
print("Green View is tapped")
}
@objc func handleYellowTap(_ pan: UITapGestureRecognizer ){
print("Yellow View is tapped")
}
}
This works fine except in the scenario where we tap yellow view in the highlighted rectangle area in red in the below image. In the below image, if I click on the yellow area in rectangle it takes the tap of green view which makes sense since the yellow view is behind green view. So, how do I determine the yellow area tap in this region? Any help is appreciated
What you need is to instruct CurveView
where it can be tapped, in this case within its own path:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return rectPath.contains(point)
}
So overall your CurveView
could look like this:
class CurveView: UIView {
var rectPath = UIBezierPath()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
self.createBottomCurve()
}
private func createBottomCurve() {
let offset: CGFloat = self.frame.width / 1.2
let bounds: CGRect = self.bounds
let rectBounds: CGRect = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.size.width, height: bounds.size.height / 2)
rectPath = UIBezierPath(rect: rectBounds)
let ovalBounds: CGRect = CGRect(x: bounds.origin.x - offset / 2, y: bounds.origin.y, width: bounds.size.width + offset, height: bounds.size.height)
let ovalPath: UIBezierPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer: CAShapeLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return rectPath.contains(point)
}
}
Output: