如何将触摸事件传递给另一个视图?

时间:2020-04-04 18:50:52

标签: ios swift iphone xcode

我有一个带有两个UIPickerView子视图的UIView,每个子视图旋转60度,一个顺时针旋转,一个逆时针旋转。我想根据用户滑动的方向分别滚动每个pickerview。由于一个位于另一个顶部,因此只能滚动顶部的pickerview。因此,我希望能够在用户沿其方向滑动时滚动底部的pickerview。

Screenshot

我找到的最接近的答案是覆盖hitTest,但是后来我无法确定滑动方向。我想我必须以某种方式使用touchesBegan,touchesMoved和touchesEnded来确定滑动方向。

我的第一个想法是这样的,似乎没有用

var startPoint: CGPoint?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    startPoint = touches.first!.location(in: self)
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    guard let startPoint = startPoint else { return }

    let endPoint = touches.first!.location(in: self)

    //This condition is arbitrary
    //The actual condition will be more complex
    if startPoint.y > endPoint.y {
        pickerViewA.isUserInteractionEnabled = true
    } else {
        pickerViewB.isUserInteractionEnabled = true
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    pickerViewA.isUserInteractionEnabled = false
    pickerViewB.isUserInteractionEnabled = false
    startPoint = nil
}

3 个答案:

答案 0 :(得分:0)

此问题的可能解决方案之一是使用屏幕大小的透明视图(您的根ViewController):

transparentView3 = MyTransparentView(frame: view.bounds)
transparentView3?.backgroundColor = UIColor.clear
view.addSubview(transparentView3!)

您可以为此透明视图创建一个类,并在那里覆盖触摸事件:

class MyTransparentView: UIView {
    
    weak var delegate: MyTransparentViewDelegate?
    var swipePoint1: CGPoint?
    var swipeTime1: Double?
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        swipePoint1 = touches.first?.location(in: self)
        swipeTime1 = event?.timestamp
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        
        if let swipePoint2 = touches.first?.location(in: self),
            let swipeTime2 = event?.timestamp,
            let swipePoint1 = swipePoint1,
            let swipeTime1 = swipeTime1 {
            
            if swipePoint1.x > swipePoint2.x && swipeTime2-swipeTime1 < 0.5  {
                delegate?.importantSwipeOccured()
            }
        }
    }
}

与使用UIGestureRecognizers相比,这将提供更多的灵活性。

现在,包含两个选择器和一个透明视图的ViewController可以成为透明视图的委托,并将消息重定向到您的选择器:

protocol MyTransparentViewDelegate: AnyObject {
    func importantSwipeOccured()
}

func importantSwipeOccured() {
    let color2 = rotatedView2!.backgroundColor
    rotatedView2!.backgroundColor = rotatedView1!.backgroundColor
    rotatedView1!.backgroundColor = color2
}

完整的示例可以在这里找到,您可以运行项目或仅使用以下代码:https://github.com/gatamar/so61033169/blob/master/so61033169/ViewController.swift

答案 1 :(得分:0)

在重叠视图的区域上发生触摸序列时,手势之间实际上存在着争夺该触摸序列的竞争。

也许尝试一下?

我们在顶部放置了一个透明视图,并为其分配了自定义手势识别器。想法是使此手势充当一种中介,该中介确定哪个选择器视图获取触摸顺序;而不是他们彼此争夺,而头号总是赢。

每个选择器视图都有其自己的手势,我们将告诉两者都要求自定义手势失败。这意味着我们的自定义手势必须首先失败,然后这些手势才有机会获取触摸顺序。

在自定义手势的代码中,我们将始终将其状态设置为“失败”,从而允许将触摸序列传递给其他手势。但是在失败之前,我们将通过在两个选择器视图上相应地设置userInteractionEnabled来确定哪个选择器视图应获取此触摸顺序。当然,这是希望如果我们在视图上禁用userInteraction,则该视图将不会获得向下传递的触摸顺序。大声笑,在这种情况下,我们将提出其他建议。

以下是我们自定义手势的代码,可帮助您入门:

import UIKit.UIGestureRecognizerSubclass

class BrokerGesture: UIPanGestureRecognizer {

    private var startPoint: CGPoint!
    private var views: [UIView]

    override init(target: Any?, action: Selector?, views: [UIView]) {
        self.views = views
        super.init(target: target, action: action)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        self.startPoint = touches.first!.location(in: self.view!.superview)
        super.touchesBegan(touches, with: event)
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
        if self.state == .possible {
            let loc = touches.first!.location(in: self.view!.superview)
            let deltaX = abs(loc.x - self.startPoint.x)
            let deltaY = abs(loc.y - self.startPoint.y)

            // Your code here:
            // - Use deltaX and deltaY
            // - Set userInteraction on both views accordingly
            self.state = .failed

        } else {
            super.touchesMoved(touches, with: event)
        }

        // Don't call super here! It will just set the state to .began! and we don't want to do that.

    }
}

答案 2 :(得分:0)

尝试了许多不同的尝试后,我回过头来重新阅读了Matt Neuburg在iOS上的书中的“ Touches”一章,解决方案变得清晰起来。

基本上,屏幕上的手指实例由UITouch表示。 多点触摸序列从第一个手指放到屏幕上时开始,到屏幕上没有手指时结束。在整个多点触摸过程中,一个UITouch对象本质上表示同一根手指。 系统将所有这些UITouch对象打包在一个称为UIEvent的信封中。这被发送到我们的UIWindow,UIWindow使用命中测试和填充将其发送到正确的视图。这是通过其sendEvent(:)方法完成的。

如果我们子类化UIWindow,则可以重写sendEvent(:)来拦截事件。我们要做的就是查看事件中的触摸,并确定应该滚动哪个选择器视图。我们将选择器视图置于最前面,然后调用super,它将正常发送事件。

IterativeImputer().transform(X)

哦,确保您的AppDelegate创建了UIWindow子类的实例,并为其分配了根视图控制器。

X

我对其进行了测试,并且可以正常工作。无论如何,希望能有所帮助:)