I'm creating a reusable multi state switch extends from UIControl that looks similar to the default UISwitch, with more than two states. The implementation is like adding CALayers for each of the state and one additional Layer for highlighting the selected state on the UIView.
The UI looks like this
The problem is that I could not recognize touch of a state when the user tapped just outside the squared border as indicated in the image. There is a simple convenience method (getIndexForLocation) I have added to return the index of selected state, given the touch point. With the returned selected index I displace the highlight indicator's position to move on center of the selected state.
func getIndexForLocation(_ point: CGPoint) -> Int {
var index = selectedIndex
let sLayers = self.controlLayer.sublayers
for i in 0..<totalStates {
if sLayers![i].frame.contains(point) {
index = i
}
}
return index
}
The above method is called from endTracking(touch:for event) method of UIControl with the last touch point.
How can I change this method, so that I can get index of the touched/selected state, even If the user has touched around the image. The touch area can be approximately the area of highlight circle placed on top of the center of the state image.
self.controlLayer is the container Layer whose sublayers are all the states and the highlight indicator.
The method that adds does position animation with selected index is provided below
func performSwitch(to index: Int) -> () {
guard selectedIndex != index else {
return
}
let offsetDiff = CGFloat((index - selectedIndex) * (stateSize + 2))
let oldPosition = indicatorPosition
indicatorPosition.x += offsetDiff
let newPosition = indicatorPosition
let animation: CABasicAnimation = CABasicAnimation(keyPath: "position")
animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.785, 0.135, 0.15, 0.86)
animation.duration = 0.6
animation.fromValue = NSValue(cgPoint: oldPosition!)
animation.toValue = NSValue(cgPoint: newPosition!)
animation.autoreverses = false
animation.delegate = self
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
self.selectedIndex = index
self.stateIndicator.add(animation, forKey: "position")
}
Any help would be greatly appreciated.
You are testing by saying if sLayers![i].frame.contains(point)
. The simplest solution, therefore, is make the frame of each "button" much bigger — big enough to encompass the whole area you wish to be touchable as this "button". Remember, the drawing can be small even if the frame is big.
Also, just as an aside, your code is silly because you are basically performing hit-testing. Hit-testing for layers is built-in, so once you have your frames right, all you have to do is call hitTest(_:)
on the superlayer. That will tell you which layer was tapped.
(Of course, it would be even better to use subviews instead of layers. Views can detect touches; layers can't.)