在UIControl /可旋转视图中获取UIButton的点击事件

时间:2017-10-04 21:09:25

标签: ios swift hittest uicontrol

被修改

与Nathan一起查看最新项目的评论部分。只剩下问题:获得正确的按钮。

被修改

我想要一个用户可以旋转的UIView。 UIView应包含一些可以单击的UIButton。我很难,因为我使用UIControl子类来制作旋转视图,在该子类中,我必须禁用UIControl中子视图的用户交互(使其旋转),这可能导致UIButton无法点击。如何创建用户可以旋转包含可点击的UIButtons的UIView? This是我项目的链接,它为您提供了一个良好的开端:它包含UIButtons和可旋转的UIView。但是我可以不点击UIButtons。

包含更多详情的旧问题

我正在使用这个pod:https://github.com/joshdhenry/SpinWheelControl,我想对按钮点击做出反应。我可以添加按钮,但是我无法在按钮中接收点击事件。我正在使用hitTests,但它们永远不会被执行。用户应旋转滚轮并能够单击其中一个饼图中的按钮。

在此处获取项目:https://github.com/Jasperav/SpinningWheelWithTappableButtons

请参阅我在pod文件中添加的代码:

我在SpinWheelWedge.swift中添加了这个变量:

let button = SpinWheelWedgeButton()

我添加了这个课程:

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")
    }
}

这是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
    }
}

我将此添加到SpinWheelControl.swift中,在循环中#34; for wedgeNumber in"

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,但我认为我需要进行测试。

4 个答案:

答案 0 :(得分:4)

我能够对项目进行修改,我认为我能解决您的问题。

  • SpinWheelControl课程中,您要将userInteractionEnabled的{​​{1}}属性设置为spinWheelView。请注意,这不是您想要的,因为您仍然有兴趣点击false内的按钮。但是,如果你没有关闭用户互动,那么轮子就不会转动,因为孩子们的观点搞得一团糟!
  • 要解决此问题,我们可以关闭子视图的用户交互,并手动触发我们感兴趣的事件 - 对于最里面的按钮,基本上是spinWheelView
  • 最简单的方法是使用touchUpInside的{​​{1}}方法。调用endTracking方法后,我们会手动遍历所有按钮并为其调用SpinWheelControl
  • 现在关于按下哪个按钮的问题仍然存在,因为我们刚刚向所有按钮发送了endTracking。解决方法是覆盖按钮的endTracking方法,并仅在该特定按钮的触摸endTracking为真时手动触发endTracking方法。

<强>代码:

TornadoButton 类:(不再需要自定义.touchUpInsidehitTest,因为我们不再对通常的点击测试感兴趣;我们只是直接调用{{ 1}})

hitTest

SpinWheelControl 类: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]) } } } } 即可。使用此方法,您不需要像@nathan那样使用自定义偏移量,因为右侧按钮将响应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) } } ... } 获取其标记。

答案 1 :(得分:3)

以下是针对特定项目的解决方案:

第1步

drawWheel的{​​{1}}功能中,在SpinWheelControl.swift上启用用户互动。为此,请删除以下行:

spinWheelView

第2步

再次在self.spinWheelView.isUserInteractionEnabled = false 函数中,将按钮设为drawWheel的子视图,而不是spinWheelView。在楔形后添加按钮作为子视图,因此它将出现在楔形图层的顶部。

旧:

wedge

新:

wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 0.45, position: spinWheelCenter, radiansPerWedge: radiansPerWedge)
wedge.addSubview(wedge.button)
spinWheelView.addSubview(wedge)

第3步

创建一个新的wedge.button.configureWedgeButton(index: wedgeNumber, width: radius * 0.45, position: spinWheelCenter, radiansPerWedge: radiansPerWedge) spinWheelView.addSubview(wedge) spinWheelView.addSubview(wedge.button) 子类,将触摸传递到其子视图。

UIView

第4步

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 } } 函数的最开始,声明drawWheelspinWheelView类型。这将允许按钮接收触摸事件。

PassThroughView

通过这些改动,您应该得到以下行为:

gif of working spinner with buttons

(按下任何按钮时,信息将打印到控制台。)

限制

此解决方案允许用户像往常一样旋转滚轮,以及点击任何按钮。但是,这可能不是满足您需求的完美解决方案,因为存在一些限制:

  • 如果用户在任何按钮的范围内触地,则无法旋转滚轮。
  • 在车轮运动时可以按下按钮。

根据您的需要,您可以考虑构建自己的微调器,而不是依赖第三方pod。这个pod的难点在于它使用spinWheelView = PassThroughView(frame: self.bounds) 和相关函数而不是手势识别器。如果您使用了手势识别器,则可以更轻松地使用所有beginTracking(_ touch: UITouch, with event: UIEvent?)功能。

或者,如果您只想识别楔形范围内的降落事件,则可以进一步追求UIButton想法。

编辑:确定按下了哪个按钮。

如果我们知道方向盘的hitTest和起始selectedIndex,我们就可以计算出按下了哪个按钮。

目前,起始selectedIndex为0,按钮标签顺时针增加。点击所选按钮(标签= 0),打印7,这意味着按钮是&#34;旋转&#34;处于起始状态的7个位置。如果车轮在不同位置开始,则该值会有所不同。

这是一个快速功能,用于确定使用两条信息点按的按钮标记:来自当前selectedIndex的方向盘selectedIndexsubview.tag实施point(inside point: CGPoint, with event: UIEvent?)

PassThroughView

同样,这绝对是一个黑客攻击,但它确实有效。如果您打算继续为此微调器控件添加功能,我强烈建议您创建自己的控件,以便您可以从头开始设计它以满足您的需求。

答案 2 :(得分:1)

一般解决方案是使用public class FibonacciSequence { /** * @param args */ public int[] fibonacci(int numArray[]) { for (int i = 1; i < numArray.length; i++) { System.out.println(numArray[i] + numArray[i - 1]); } return numArray; } public static void main(String[] args) { int a[]=new int []{1,2,3,4,5}; System.out.println(""+Arrays.toString(new FibonacciSequence().fibonacci(a))); } } 并将所有UIView放置在应有的位置,然后使用UIButton旋转视图,计算速度和方向向量旋转你的视图。为了旋转您的视图,我建议使用UIPanGestureRecognizer,因为它可以设置动画,并且您的子视图也会旋转。 (额外:如果你想将transform的方向始终向下设置,只需将它们反向旋转,这将使它们始终向下看)

哈克

有些人还使用UIButtons代替UIScrollView。将描述的视图放在UIPanGestureRecognizer内并使用UIScrollView的委托方法计算速度和方向,然后按照描述将这些值应用于UIScrollView。这种黑客攻击的原因是UIView自动降低速度并提供更好的体验。 (使用此技术,您应该将UIScrollView设置为非常大的内容,并定期将contentSize contentOffset重新定位到UIScrollView

但我强烈建议采用第一种方法。

答案 3 :(得分:1)

至于我的观点,你可以使用你自己的视图,只需要几个子层和你需要的所有其他东西。 在这种情况下,你将获得充分的灵活性,但你也应该多写一些代码。

如果您喜欢这个选项,您可以在下面的gif上获得类似的内容(您可以根据需要自定义它 - 添加文本,图像,动画等):

enter image description here

在这里,我向您展示2个连续平移和紫色部分上的一个水龙头 - 当检测到水龙头时6 bg颜色变为绿色

要检测我使用touchesBegan的点按,如下所示。

要使用此代码,您可以将下面的代码复制粘贴到游乐场并根据需要进行修改

//: 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()