在 UIControl/rotatable 视图中获取 UIButton 的点击事件
Get tap event for UIButton in UIControl/rotatable view
已编辑
有关最新项目,请参阅 Nathan 的评论部分。只剩下一个问题:获得正确的按钮。
已编辑
我想要一个用户可以旋转的 UIView。该 UIView 应包含一些可以单击的 UIButton。我很难过,因为我正在使用 UIControl subclass 来制作旋转视图,在那个 subclass 中,我必须禁用 UIControl 中子视图上的用户交互(使其旋转)这可能会导致 UIButtons 不可点击。如何使用户可以旋转 和 的 UIView 包含可点击的 UIButtons? This 对我的项目来说是一个 link,它让您领先一步:它包含 UIButtons 和一个可旋转的 UIView。但是我无法点击 UIButtons。
有更多细节的老问题
我正在使用这个 pod:https://github.com/joshdhenry/SpinWheelControl 我想对按钮点击做出反应。我可以添加按钮,但是我无法在按钮中接收点击事件。我正在使用 hitTests 但它们从未被执行过。用户应该旋转轮子并能够单击其中一个饼图中的按钮。
在此处获取项目:https://github.com/Jasperav/SpinningWheelWithTappableButtons
查看下面我在 pod 文件中添加的代码:
我在 SpinWheelWedge.swift 中添加了这个变量:
let button = SpinWheelWedgeButton()
我添加了这个class:
class SpinWheelWedgeButton: TornadoButton {
public func configureWedgeButton(index: UInt, width: CGFloat, position: CGPoint, radiansPerWedge: Radians) {
self.frame = CGRect(x: 0, y: 0, width: width, height: 30)
self.layer.anchorPoint = CGPoint(x: 1.1, y: 0.5)
self.layer.position = position
self.transform = CGAffineTransform(rotationAngle: radiansPerWedge * CGFloat(index) + CGFloat.pi + (radiansPerWedge / 2))
self.backgroundColor = .green
self.addTarget(self, action: #selector(pressed(_:)), for: .touchUpInside)
}
@IBAction func pressed(_ sender: TornadoButton){
print("hi")
}
}
这是 class TornadoButton:
class TornadoButton: UIButton{
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
if (pres.hitTest(suppt)) != nil{
return self
}
return super.hitTest(prespt, with: event)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
return (pres.hitTest(suppt)) != nil
}
}
我在循环 "for wedgeNumber in"
中将此添加到 SpinWheelControl.swift
wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 2, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
wedge.addSubview(wedge.button)
这是我认为可以检索按钮的地方,在 SpinWheelControl.swift:
override open func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let p = touch.location(in: touch.view)
let v = touch.view?.hitTest(p, with: nil)
print(v)
}
只有 'v' 始终是旋转轮本身,而不是按钮。我也没有看到打印的按钮,并且永远不会执行 hittest。这段代码有什么问题,为什么 hitTest 不执行?我宁愿有一个普通的 UIBUtton,但我认为我需要为此进行命中测试。
一般的解决方案是使用 UIView
并将所有 UIButton
放在它们应该在的位置,然后使用 UIPanGestureRecognizer
旋转视图,计算速度和方向矢量并旋转您的视图。为了旋转你的视图,我建议使用 transform
因为它是可动画的,而且你的子视图也会被旋转。 (补充:如果你想设置你的UIButtons
的方向总是向下,只需将它们反向旋转,它就会使它们总是向下看)
破解
有些人也使用 UIScrollView
而不是 UIPanGestureRecognizer
。将描述的视图放在 UIScrollView
中,并使用 UIScrollView
的委托方法计算速度和方向,然后按照描述将这些值应用于您的 UIView
。这个hack的原因是因为 UIScrollView
自动减速并提供更好的体验。 (使用此技术,您应该将 contentSize
设置为非常大的值,并定期将 UIScrollView
的 contentOffset
重新定位到 .zero
。
但我强烈建议第一种方法。
至于我的意见,你可以使用你自己的视图,只需要很少的子层和你需要的所有其他东西。
在这种情况下,您将获得充分的灵活性,但您还应该编写更多代码。
如果您喜欢这个选项,您可以在下面的 gif 上获得类似的东西(您可以根据需要自定义它 - 添加文本、图像、动画等):
在这里,我向您展示了 2 次连续平移和一次点击紫色部分 - 当检测到点击时 6 bg 颜色变为绿色
为了检测点击,我使用了 touchesBegan
,如下所示。
要使用此代码,您可以将下面的代码复制并粘贴到 playground 中并根据需要进行修改
//: A UIKit based Playground for presenting user interface
import UIKit import PlaygroundSupport
class RoundView : UIView {
var sampleArcLayer:CAShapeLayer = CAShapeLayer()
func performRotation( power: Float) {
let maxDuration:Float = 2
let maxRotationCount:Float = 5
let currentDuration = maxDuration * power
let currrentRotationCount = (Double)(maxRotationCount * power)
let fromValue:Double = Double(atan2f(Float(transform.b), Float(transform.a)))
let toValue = Double.pi * currrentRotationCount + fromValue
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = CFTimeInterval(currentDuration)
rotateAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
rotateAnimation.isRemovedOnCompletion = true
layer.add(rotateAnimation, forKey: nil)
layer.transform = CATransform3DMakeRotation(CGFloat(toValue), 0, 0, 1)
}
override func layoutSubviews() {
super.layoutSubviews()
drawLayers()
}
private func drawLayers()
{
sampleArcLayer.removeFromSuperlayer()
sampleArcLayer.frame = bounds
sampleArcLayer.fillColor = UIColor.purple.cgColor
let proportion = CGFloat(20)
let centre = CGPoint (x: frame.size.width / 2, y: frame.size.height / 2)
let radius = frame.size.width / 2
let arc = CGFloat.pi * 2 * proportion / 100 // i.e. the proportion of a full circle
let startAngle:CGFloat = 45
let cPath = UIBezierPath()
cPath.move(to: centre)
cPath.addLine(to: CGPoint(x: centre.x + radius * cos(startAngle), y: centre.y + radius * sin(startAngle)))
cPath.addArc(withCenter: centre, radius: radius, startAngle: startAngle, endAngle: arc + startAngle, clockwise: true)
cPath.addLine(to: CGPoint(x: centre.x, y: centre.y))
sampleArcLayer.path = cPath.cgPath
// you can add CATExtLayer and any other stuff you need
layer.addSublayer(sampleArcLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let point = touches.first?.location(in: self) {
if let layerArray = layer.sublayers {
for sublayer in layerArray {
if sublayer.contains(point) {
if sublayer == sampleArcLayer {
if sampleArcLayer.path?.contains(point) == true {
backgroundColor = UIColor.green
}
}
}
}
}
}
}
}
class MyViewController : UIViewController {
private var lastTouchPoint:CGPoint = CGPoint.zero
private var initialTouchPoint:CGPoint = CGPoint.zero
private let testView:RoundView = RoundView(frame:CGRect(x: 40, y: 40, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
testView.layer.cornerRadius = testView.frame.height / 2
testView.layer.masksToBounds = true
testView.backgroundColor = UIColor.red
view.addSubview(testView)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(MyViewController.didDetectPan(_:)))
testView.addGestureRecognizer(panGesture)
}
@objc func didDetectPan(_ gesture:UIPanGestureRecognizer) {
let touchPoint = gesture.location(in: testView)
switch gesture.state {
case .began:
initialTouchPoint = touchPoint
break
case .changed:
lastTouchPoint = touchPoint
break
case .ended, .cancelled:
let delta = initialTouchPoint.y - lastTouchPoint.y
let powerPercentage = max(abs(delta) / testView.frame.height, 1)
performActionOnView(scrollPower: Float(powerPercentage))
initialTouchPoint = CGPoint.zero
break
default:
break
}
}
private func performActionOnView(scrollPower:Float) {
testView.performRotation(power: scrollPower)
} } // Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
这是针对您的特定项目的解决方案:
步骤 1
在 SpinWheelControl.swift
的 drawWheel
函数中,启用 spinWheelView
上的用户交互。为此,请删除以下行:
self.spinWheelView.isUserInteractionEnabled = false
步骤 2
再次在 drawWheel
函数中,使按钮成为 spinWheelView
的子视图,而不是 wedge
。在楔形之后添加按钮作为子视图,因此它会出现在楔形图层的顶部。
旧:
wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 0.45, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
wedge.addSubview(wedge.button)
spinWheelView.addSubview(wedge)
新:
wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 0.45, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
spinWheelView.addSubview(wedge)
spinWheelView.addSubview(wedge.button)
步骤 3
创建一个新的 UIView
子类,将触摸传递到其子视图。
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if !subview.isHidden && subview.alpha > 0 && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
return true
}
}
return false
}
}
步骤 4
在 drawWheel
函数的最开始,将 spinWheelView
声明为 PassThroughView
类型。这将允许按钮接收触摸事件。
spinWheelView = PassThroughView(frame: self.bounds)
通过这些少量更改,您应该会得到以下行为:
(按下任何按钮时,消息将打印到控制台。)
限制
此解决方案允许用户像往常一样旋转滚轮,以及点击任何按钮。但是,这可能不是满足您需求的完美解决方案,因为存在一些限制:
- 如果用户在任何按钮的范围内开始触摸,则无法旋转滚轮。
- 可以在滚轮移动时按下按钮。
根据您的需要,您可以考虑构建自己的 Spinner,而不是依赖第三方 pod。这个 pod 的困难在于它使用 beginTracking(_ touch: UITouch, with event: UIEvent?)
和相关函数而不是手势识别器。如果您使用手势识别器,那么使用所有 UIButton
功能会更容易。
或者,如果您只想识别楔形范围内的触地事件,您可以进一步追求您的 hitTest
想法。
编辑:确定按下了哪个按钮。
如果我们知道滚轮的selectedIndex
和起始selectedIndex
,我们就可以计算出按下了哪个按钮。
目前起始selectedIndex
为0,按钮标签顺时针递增。点击所选按钮(tag = 0),打印 7,这意味着按钮处于 "rotated" 起始状态的 7 个位置。如果轮子从不同的位置开始,这个值就会不同。
这是一个使用两条信息确定点击按钮标签的快速函数:滚轮的 selectedIndex
和当前 point(inside point: CGPoint, with event: UIEvent?)
实现中的 subview.tag
PassThroughView
.
func determineButtonTag(selectedIndex: Int, subviewTag: Int) -> Int {
return subviewTag + (selectedIndex - 7)
}
同样,这绝对是一个技巧,但它确实有效。如果您打算继续向该微调器控件添加功能,我强烈建议您创建自己的控件,这样您就可以从一开始就设计它以满足您的需要。
我能够对项目进行修补,我想我可以解决你的问题。
- 在您的
SpinWheelControl
class 中,您将 spinWheelView
的 userInteractionEnabled
属性 设置为 false
。请注意,这不是您真正想要的,因为您仍然对点击 spinWheelView
内的按钮感兴趣。但是,如果您不关闭用户交互,则轮子不会转动,因为子视图会弄乱触摸!
- 为了解决这个问题,我们可以关闭子视图的用户交互,只手动触发我们感兴趣的事件——这基本上是最里面的按钮
touchUpInside
。
- 最简单的方法是使用
SpinWheelControl
的 endTracking
方法。当 endTracking
方法被调用时,我们手动遍历所有按钮并为它们调用 endTracking
。
- 现在关于按下哪个按钮的问题仍然存在,因为我们刚刚向所有按钮发送了
endTracking
。解决方案是重写按钮的 endTracking
方法,仅当该特定按钮的触摸 hitTest
为真时才手动触发 .touchUpInside
方法。
代码:
TornadoButton Class:(自定义 hitTest
和 pointInside
不再需要,因为我们不再对做通常的事情感兴趣命中测试;我们直接调用 endTracking
)
class TornadoButton: UIButton{
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
if let t = touch {
if self.hitTest(t.location(in: self), with: event) != nil {
print("Tornado button with tag \(self.tag) ended tracking")
self.sendActions(for: [.touchUpInside])
}
}
}
}
SpinWheelControlClass:endTracking
方法:
override open func endTracking(_ touch: UITouch?, with event: UIEvent?) {
for sv in self.spinWheelView.subviews {
if let wedge = sv as? SpinWheelWedge {
wedge.button.endTracking(touch, with: event)
}
}
...
}
此外,要测试是否调用了正确的按钮,只需在创建按钮时将按钮的标签设置为 wedgeNumber
。使用此方法,您将不需要像@nathan 那样使用自定义偏移量,因为右键将响应 endTracking
并且您可以通过 sender.tag
.
获取其标签
已编辑
有关最新项目,请参阅 Nathan 的评论部分。只剩下一个问题:获得正确的按钮。
已编辑
我想要一个用户可以旋转的 UIView。该 UIView 应包含一些可以单击的 UIButton。我很难过,因为我正在使用 UIControl subclass 来制作旋转视图,在那个 subclass 中,我必须禁用 UIControl 中子视图上的用户交互(使其旋转)这可能会导致 UIButtons 不可点击。如何使用户可以旋转 和 的 UIView 包含可点击的 UIButtons? This 对我的项目来说是一个 link,它让您领先一步:它包含 UIButtons 和一个可旋转的 UIView。但是我无法点击 UIButtons。
有更多细节的老问题
我正在使用这个 pod:https://github.com/joshdhenry/SpinWheelControl 我想对按钮点击做出反应。我可以添加按钮,但是我无法在按钮中接收点击事件。我正在使用 hitTests 但它们从未被执行过。用户应该旋转轮子并能够单击其中一个饼图中的按钮。
在此处获取项目:https://github.com/Jasperav/SpinningWheelWithTappableButtons
查看下面我在 pod 文件中添加的代码:
我在 SpinWheelWedge.swift 中添加了这个变量:
let button = SpinWheelWedgeButton()
我添加了这个class:
class SpinWheelWedgeButton: TornadoButton {
public func configureWedgeButton(index: UInt, width: CGFloat, position: CGPoint, radiansPerWedge: Radians) {
self.frame = CGRect(x: 0, y: 0, width: width, height: 30)
self.layer.anchorPoint = CGPoint(x: 1.1, y: 0.5)
self.layer.position = position
self.transform = CGAffineTransform(rotationAngle: radiansPerWedge * CGFloat(index) + CGFloat.pi + (radiansPerWedge / 2))
self.backgroundColor = .green
self.addTarget(self, action: #selector(pressed(_:)), for: .touchUpInside)
}
@IBAction func pressed(_ sender: TornadoButton){
print("hi")
}
}
这是 class TornadoButton:
class TornadoButton: UIButton{
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
if (pres.hitTest(suppt)) != nil{
return self
}
return super.hitTest(prespt, with: event)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
return (pres.hitTest(suppt)) != nil
}
}
我在循环 "for wedgeNumber in"
中将此添加到 SpinWheelControl.swiftwedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 2, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
wedge.addSubview(wedge.button)
这是我认为可以检索按钮的地方,在 SpinWheelControl.swift:
override open func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
let p = touch.location(in: touch.view)
let v = touch.view?.hitTest(p, with: nil)
print(v)
}
只有 'v' 始终是旋转轮本身,而不是按钮。我也没有看到打印的按钮,并且永远不会执行 hittest。这段代码有什么问题,为什么 hitTest 不执行?我宁愿有一个普通的 UIBUtton,但我认为我需要为此进行命中测试。
一般的解决方案是使用 UIView
并将所有 UIButton
放在它们应该在的位置,然后使用 UIPanGestureRecognizer
旋转视图,计算速度和方向矢量并旋转您的视图。为了旋转你的视图,我建议使用 transform
因为它是可动画的,而且你的子视图也会被旋转。 (补充:如果你想设置你的UIButtons
的方向总是向下,只需将它们反向旋转,它就会使它们总是向下看)
破解
有些人也使用 UIScrollView
而不是 UIPanGestureRecognizer
。将描述的视图放在 UIScrollView
中,并使用 UIScrollView
的委托方法计算速度和方向,然后按照描述将这些值应用于您的 UIView
。这个hack的原因是因为 UIScrollView
自动减速并提供更好的体验。 (使用此技术,您应该将 contentSize
设置为非常大的值,并定期将 UIScrollView
的 contentOffset
重新定位到 .zero
。
但我强烈建议第一种方法。
至于我的意见,你可以使用你自己的视图,只需要很少的子层和你需要的所有其他东西。 在这种情况下,您将获得充分的灵活性,但您还应该编写更多代码。
如果您喜欢这个选项,您可以在下面的 gif 上获得类似的东西(您可以根据需要自定义它 - 添加文本、图像、动画等):
在这里,我向您展示了 2 次连续平移和一次点击紫色部分 - 当检测到点击时 6 bg 颜色变为绿色
为了检测点击,我使用了 touchesBegan
,如下所示。
要使用此代码,您可以将下面的代码复制并粘贴到 playground 中并根据需要进行修改
//: A UIKit based Playground for presenting user interface
import UIKit import PlaygroundSupport
class RoundView : UIView {
var sampleArcLayer:CAShapeLayer = CAShapeLayer()
func performRotation( power: Float) {
let maxDuration:Float = 2
let maxRotationCount:Float = 5
let currentDuration = maxDuration * power
let currrentRotationCount = (Double)(maxRotationCount * power)
let fromValue:Double = Double(atan2f(Float(transform.b), Float(transform.a)))
let toValue = Double.pi * currrentRotationCount + fromValue
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = CFTimeInterval(currentDuration)
rotateAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
rotateAnimation.isRemovedOnCompletion = true
layer.add(rotateAnimation, forKey: nil)
layer.transform = CATransform3DMakeRotation(CGFloat(toValue), 0, 0, 1)
}
override func layoutSubviews() {
super.layoutSubviews()
drawLayers()
}
private func drawLayers()
{
sampleArcLayer.removeFromSuperlayer()
sampleArcLayer.frame = bounds
sampleArcLayer.fillColor = UIColor.purple.cgColor
let proportion = CGFloat(20)
let centre = CGPoint (x: frame.size.width / 2, y: frame.size.height / 2)
let radius = frame.size.width / 2
let arc = CGFloat.pi * 2 * proportion / 100 // i.e. the proportion of a full circle
let startAngle:CGFloat = 45
let cPath = UIBezierPath()
cPath.move(to: centre)
cPath.addLine(to: CGPoint(x: centre.x + radius * cos(startAngle), y: centre.y + radius * sin(startAngle)))
cPath.addArc(withCenter: centre, radius: radius, startAngle: startAngle, endAngle: arc + startAngle, clockwise: true)
cPath.addLine(to: CGPoint(x: centre.x, y: centre.y))
sampleArcLayer.path = cPath.cgPath
// you can add CATExtLayer and any other stuff you need
layer.addSublayer(sampleArcLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let point = touches.first?.location(in: self) {
if let layerArray = layer.sublayers {
for sublayer in layerArray {
if sublayer.contains(point) {
if sublayer == sampleArcLayer {
if sampleArcLayer.path?.contains(point) == true {
backgroundColor = UIColor.green
}
}
}
}
}
}
}
}
class MyViewController : UIViewController {
private var lastTouchPoint:CGPoint = CGPoint.zero
private var initialTouchPoint:CGPoint = CGPoint.zero
private let testView:RoundView = RoundView(frame:CGRect(x: 40, y: 40, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
testView.layer.cornerRadius = testView.frame.height / 2
testView.layer.masksToBounds = true
testView.backgroundColor = UIColor.red
view.addSubview(testView)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(MyViewController.didDetectPan(_:)))
testView.addGestureRecognizer(panGesture)
}
@objc func didDetectPan(_ gesture:UIPanGestureRecognizer) {
let touchPoint = gesture.location(in: testView)
switch gesture.state {
case .began:
initialTouchPoint = touchPoint
break
case .changed:
lastTouchPoint = touchPoint
break
case .ended, .cancelled:
let delta = initialTouchPoint.y - lastTouchPoint.y
let powerPercentage = max(abs(delta) / testView.frame.height, 1)
performActionOnView(scrollPower: Float(powerPercentage))
initialTouchPoint = CGPoint.zero
break
default:
break
}
}
private func performActionOnView(scrollPower:Float) {
testView.performRotation(power: scrollPower)
} } // Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
这是针对您的特定项目的解决方案:
步骤 1
在 SpinWheelControl.swift
的 drawWheel
函数中,启用 spinWheelView
上的用户交互。为此,请删除以下行:
self.spinWheelView.isUserInteractionEnabled = false
步骤 2
再次在 drawWheel
函数中,使按钮成为 spinWheelView
的子视图,而不是 wedge
。在楔形之后添加按钮作为子视图,因此它会出现在楔形图层的顶部。
旧:
wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 0.45, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
wedge.addSubview(wedge.button)
spinWheelView.addSubview(wedge)
新:
wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 0.45, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
spinWheelView.addSubview(wedge)
spinWheelView.addSubview(wedge.button)
步骤 3
创建一个新的 UIView
子类,将触摸传递到其子视图。
class PassThroughView: UIView {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
for subview in subviews {
if !subview.isHidden && subview.alpha > 0 && subview.isUserInteractionEnabled && subview.point(inside: convert(point, to: subview), with: event) {
return true
}
}
return false
}
}
步骤 4
在 drawWheel
函数的最开始,将 spinWheelView
声明为 PassThroughView
类型。这将允许按钮接收触摸事件。
spinWheelView = PassThroughView(frame: self.bounds)
通过这些少量更改,您应该会得到以下行为:
(按下任何按钮时,消息将打印到控制台。)
限制
此解决方案允许用户像往常一样旋转滚轮,以及点击任何按钮。但是,这可能不是满足您需求的完美解决方案,因为存在一些限制:
- 如果用户在任何按钮的范围内开始触摸,则无法旋转滚轮。
- 可以在滚轮移动时按下按钮。
根据您的需要,您可以考虑构建自己的 Spinner,而不是依赖第三方 pod。这个 pod 的困难在于它使用 beginTracking(_ touch: UITouch, with event: UIEvent?)
和相关函数而不是手势识别器。如果您使用手势识别器,那么使用所有 UIButton
功能会更容易。
或者,如果您只想识别楔形范围内的触地事件,您可以进一步追求您的 hitTest
想法。
编辑:确定按下了哪个按钮。
如果我们知道滚轮的selectedIndex
和起始selectedIndex
,我们就可以计算出按下了哪个按钮。
目前起始selectedIndex
为0,按钮标签顺时针递增。点击所选按钮(tag = 0),打印 7,这意味着按钮处于 "rotated" 起始状态的 7 个位置。如果轮子从不同的位置开始,这个值就会不同。
这是一个使用两条信息确定点击按钮标签的快速函数:滚轮的 selectedIndex
和当前 point(inside point: CGPoint, with event: UIEvent?)
实现中的 subview.tag
PassThroughView
.
func determineButtonTag(selectedIndex: Int, subviewTag: Int) -> Int {
return subviewTag + (selectedIndex - 7)
}
同样,这绝对是一个技巧,但它确实有效。如果您打算继续向该微调器控件添加功能,我强烈建议您创建自己的控件,这样您就可以从一开始就设计它以满足您的需要。
我能够对项目进行修补,我想我可以解决你的问题。
- 在您的
SpinWheelControl
class 中,您将spinWheelView
的userInteractionEnabled
属性 设置为false
。请注意,这不是您真正想要的,因为您仍然对点击spinWheelView
内的按钮感兴趣。但是,如果您不关闭用户交互,则轮子不会转动,因为子视图会弄乱触摸! - 为了解决这个问题,我们可以关闭子视图的用户交互,只手动触发我们感兴趣的事件——这基本上是最里面的按钮
touchUpInside
。 - 最简单的方法是使用
SpinWheelControl
的endTracking
方法。当endTracking
方法被调用时,我们手动遍历所有按钮并为它们调用endTracking
。 - 现在关于按下哪个按钮的问题仍然存在,因为我们刚刚向所有按钮发送了
endTracking
。解决方案是重写按钮的endTracking
方法,仅当该特定按钮的触摸hitTest
为真时才手动触发.touchUpInside
方法。
代码:
TornadoButton Class:(自定义 hitTest
和 pointInside
不再需要,因为我们不再对做通常的事情感兴趣命中测试;我们直接调用 endTracking
)
class TornadoButton: UIButton{
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
if let t = touch {
if self.hitTest(t.location(in: self), with: event) != nil {
print("Tornado button with tag \(self.tag) ended tracking")
self.sendActions(for: [.touchUpInside])
}
}
}
}
SpinWheelControlClass:endTracking
方法:
override open func endTracking(_ touch: UITouch?, with event: UIEvent?) {
for sv in self.spinWheelView.subviews {
if let wedge = sv as? SpinWheelWedge {
wedge.button.endTracking(touch, with: event)
}
}
...
}
此外,要测试是否调用了正确的按钮,只需在创建按钮时将按钮的标签设置为 wedgeNumber
。使用此方法,您将不需要像@nathan 那样使用自定义偏移量,因为右键将响应 endTracking
并且您可以通过 sender.tag
.