I am creating this structured through masks:
Each hexagon should be clickable. This is the code I used:
// To create one masked hexagun
let hex = UIImage(named: "hexum")
let mask = CALayer()
mask.contents = hexum!.CGImage
mask.frame = CGRectMake(0, 0, hex!.size.width, hex!.size.height)
let img = UIImageView(image: UIImage(named: "img"))
img.layer.mask = mask
img.layer.masksToBounds = true
// Gesture Recognizer
let singleTap = UITapGestureRecognizer(target: self, action: "tapDetected")
singleTap.numberOfTapsRequired = 1
img.addGestureRecognizer(singleTap)
img.userInteractionEnabled = true
func tapDetected() {
print("Clicked!")
}
The problem is that the click region is larger than the mask, which will cause the inconvenience of a region overlapping each other. Something like this:
The yellow border shows the clickable region (not actually visible)
I am a beginner, it may be a trivial problem, but can you help me solve? Thank you.
You can adopt the
UIGestureRecognizerDelegate
protocol, and implement thegestureRecognizer(_:shouldReceiveTouch:)
method to further constrain whether or not a gesture should fire. The link suggested by @tnylee would be a good place to start in terms of figuring out how to do such hit testing.Here is a Swift 3 HexagonImageView, tappable just within the hexagon:
First create a UIBezier path:
}
Then create a Hexagon UIImageView:
}
Using this Extension:
}
Finally, you can use it like this in ViewController ViewDidLoad:
@Benjamin Mayo gave great options to resolve the issue. I ended up choosing the simplest, yet, efficient one: hit-testing each shape as a circle.
I'm putting the code that can help someone else:
Result:
If you want to do this perfectly, use the
UIGestureRecognizerDelegate
methodgestureRecognizer(gesture, shouldReceiveTouch: touch) -> Bool
. You will need to map the given gesture recogniser to a particular hexagon and then do pixel precise hit-testing on the image for that hexagon. This latter part is achieved by rendering the mask image to a graphics context and finding the pixel at the point corresponding to the touch location.However, this is likely overkill. You can simplify the problem by hit-testing each shape as a circle, not a hexagon. The circle shape roughly approximates the hexagon so it will work almost the same for a user and avoids messy pixel-level alpha equality. The inaccuracy of touch input will cover up the inaccurate regions.
Another option is to rework your views to be based on
CAShapeLayer
masks.CAShapeLayer
includes apath
property. Bezier paths in UIKit include their own rolled versions of path-contains-point methods so you can just use that for this purpose.I'm not sure it's the simplest and the rightest way but I'd check the location of user's tap and override
touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
.