iOS: 无法检测到 CALayer 边界外的触摸
iOS: Unable to detect touch outside the bounds of CALayer
我正在创建一个可重用的多状态开关,该开关从 UIControl 扩展而来,它看起来类似于默认的 UISwitch,具有两个以上的状态。实现就像为每个状态添加 CALayers 和一个额外的 Layer 以在 UIView 上突出显示所选状态。
UI 看起来像这样
问题是,当用户点击图片中所示的方形边框外时,我无法识别触摸状态。有一个简单的便捷方法 (getIndexForLocation),我已将给定触摸点添加到 return 选定状态的索引。使用 returned 选定索引,我移动突出显示指示器的位置以移动到选定状态的中心。
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
}
上述方法是从UIControl 的endTracking(touch:for event) 方法调用的最后一个触摸点。
如何更改此方法,以便即使用户触摸了图像,我也可以获得 touched/selected 状态的索引。触控区域可以近似为状态图中心上方的高亮圆圈区域。
self.controlLayer为容器Layer,其子层为所有状态和高亮指示器。
下面提供了用选中的索引做位置动画的方法
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")
}
如有任何帮助,我们将不胜感激。
你说 if sLayers![i].frame.contains(point)
是在测试。因此,最简单的解决方案是将每个 "button" 的框架做得更大——大到足以包含您希望像 "button" 这样可触摸的整个区域。请记住,即使 frame 很大,drawing 也可以很小。
另外,顺便说一句,您的代码很愚蠢,因为您基本上是在执行命中测试。层的命中测试是内置的,所以一旦你的框架正确,你所要做的就是在超级层上调用 hitTest(_:)
。这会告诉你哪一层被点击了。
(当然,使用子视图而不是图层甚至会更好。视图可以检测触摸;图层不能。)
我正在创建一个可重用的多状态开关,该开关从 UIControl 扩展而来,它看起来类似于默认的 UISwitch,具有两个以上的状态。实现就像为每个状态添加 CALayers 和一个额外的 Layer 以在 UIView 上突出显示所选状态。
UI 看起来像这样
问题是,当用户点击图片中所示的方形边框外时,我无法识别触摸状态。有一个简单的便捷方法 (getIndexForLocation),我已将给定触摸点添加到 return 选定状态的索引。使用 returned 选定索引,我移动突出显示指示器的位置以移动到选定状态的中心。
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
}
上述方法是从UIControl 的endTracking(touch:for event) 方法调用的最后一个触摸点。
如何更改此方法,以便即使用户触摸了图像,我也可以获得 touched/selected 状态的索引。触控区域可以近似为状态图中心上方的高亮圆圈区域。
self.controlLayer为容器Layer,其子层为所有状态和高亮指示器。
下面提供了用选中的索引做位置动画的方法
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")
}
如有任何帮助,我们将不胜感激。
你说 if sLayers![i].frame.contains(point)
是在测试。因此,最简单的解决方案是将每个 "button" 的框架做得更大——大到足以包含您希望像 "button" 这样可触摸的整个区域。请记住,即使 frame 很大,drawing 也可以很小。
另外,顺便说一句,您的代码很愚蠢,因为您基本上是在执行命中测试。层的命中测试是内置的,所以一旦你的框架正确,你所要做的就是在超级层上调用 hitTest(_:)
。这会告诉你哪一层被点击了。
(当然,使用子视图而不是图层甚至会更好。视图可以检测触摸;图层不能。)