0
votes

Comment passer un événement tactile à une autre vue?

J'ai un UIView avec deux sous-vues UIPickerView, chacune pivotée de 60 degrés, une dans le sens des aiguilles d'une montre et une dans le sens inverse des aiguilles d'une montre. Je veux faire défiler chaque vue de sélection séparément en fonction de la direction dans laquelle l'utilisateur glisse. Comme l'un est au-dessus de l'autre, seule la vue de sélection supérieure peut être défilée. Donc, je veux pouvoir faire défiler la vue du bas lorsque l'utilisateur glisse dans sa direction.

Capture d'écran

La réponse la plus proche que j'ai trouvée remplace hitTest, mais je ne peux pas déterminer la direction du balayage. Je pense que je dois en quelque sorte utiliser touchesBegan, touchMoved et toucheEnded pour déterminer la direction du balayage.

Ma première idée était quelque chose comme ça, qui ne semblait pas fonctionner

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
}


2 commentaires

Cela a l'air amusant! 😀 Est-ce que celui du haut défile même si le mouvement est dans le sens de celui du bas? De plus, une fois qu'une vue a acquis une séquence tactile, définir userInteractionEnabled sur false n'aura aucun effet sur la séquence tactile actuelle.


Une fois que vous avez acquis une séquence tactile, serait-il possible de, en quelque sorte, redémarrer la séquence par programmation pendant que le doigt est sur l'écran?


3 Réponses :


0
votes

L'une des solutions possibles à ce problème consiste à utiliser une vue transparente de la taille de l'écran (votre ViewController racine):

protocol MyTransparentViewDelegate: AnyObject {
    func importantSwipeOccured()
}

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

Vous pouvez créer une classe pour cette vue transparente et y remplacer les événements tactiles:

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

Cela donnera beaucoup de flexibilité par rapport à l'utilisation de UIGestureRecognizers.

Maintenant, votre ViewController qui contient deux de vos sélecteurs et une vue transparente, peut être un délégué d'une vue transparente et rediriger les messages vers vos sélecteurs:

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

L'exemple complet peut être trouvé ici, vous pouvez exécuter le projet ou simplement jouer avec le code: https://github.com/gatamar/so61033169/blob/master/so61033169/ViewController.swift


3 commentaires

Merci pour la réponse, mais je ne vois pas comment cela fera défiler les sélecteurs?


J'ai essayé d'appeler touchMoved sur les sélecteurs avec les touches et l'événement de la vue transparente, mais cela ne fonctionne pas. Comme ceci: pickerView.touchesMoved (touches, avec: événement)


@MahaloObama Désolé, j'ai mal compris votre problème - je pensais que vous vouliez gérer une touche dans les deux vues. En ce qui concerne le défilement des UIPickerView , avez-vous essayé cette fonction: developer.apple.com/documentation/uikit/uipickerview/...



0
votes

Lorsqu'une séquence tactile se produit sur une région de vues qui se chevauchent, il y a essentiellement une course entre les gestes pour acquérir cette séquence tactile.

Essayez peut-être ceci?

Nous mettons une vue transparente sur le dessus et lui attribuons un outil de reconnaissance de gestes personnalisé. L'idée est de faire agir ce geste comme une sorte de courtier qui détermine quelle vue de sélecteur reçoit la séquence tactile; plutôt que de se battre entre eux et le top toujours gagnant.

Chaque vue de sélecteur a son propre geste, et nous dirons aux deux d'exiger que notre geste personnalisé échoue. Ce qui signifie que notre geste personnalisé doit d'abord échouer avant que ces gestes puissent avoir une chance d'acquérir la séquence tactile.

Dans le code de notre geste personnalisé, nous définirons toujours son état sur Échec, permettant à la séquence tactile d'être transmise aux autres gestes. Mais avant d'échouer, nous déterminerons quelle vue de sélecteur doit acquérir cette séquence tactile en définissant userInteractionEnabled sur les deux vues de sélecteur en conséquence. C'est bien sûr dans l'espoir que si nous désactivons userInteraction sur une vue, cette vue n'acquiert pas une séquence tactile transmise. Lol ça pourrait, dans ce cas nous trouverons autre chose.

Voici le code de notre geste personnalisé pour vous aider à démarrer:

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.

    }
}


5 commentaires

Cela ne semble pas fonctionner. A a créé une nouvelle vue, ajouté le geste à la vue, puis ajouté la vue au-dessus des vues du sélecteur. Je pense que le problème a quelque chose à voir avec ce dont parlait Peter Parker: qu'une fois que le doigt appuie sur l'écran, et que vous exécutez les touches, vous devez lever le doigt et le refaire, pour voir un changement.


Hmm, j'ai donc joué un peu, et il s'avère que s'il y a une vue au-dessus d'une vue de sélecteur, le sélecteur n'acquiert pas la séquence tactile. Il l'ignore totalement. C'est un truc de boîte noire.


J'ai également essayé de placer la vue (avec le geste du courtier) en dessous plutôt qu'en haut, et j'ai défini une vue de sélecteur pour que l'interaction soit désactivée initialement. Ainsi, le geste personnalisé activerait alors l'interaction sur le sélecteur juste avant d'échouer. Mais cela n'a pas fonctionné. Il semble que les vues dont l'interaction est désactivée au moment du démarrage d'une séquence tactile ne seront pas prises en compte.


Zut! Mettre la vue en dessous était la prochaine que je voulais essayer. Maintenant, je suis à court d'idées. Je pense que je vais devoir supprimer les vues des sélecteurs et penser à autre chose.


😃 J'ai découvert comment faire. C'est très simple. Je publierai bientôt le code.



0
votes

Après avoir essayé beaucoup de choses différentes en vain, je suis retourné et relu le chapitre Touches du livre de Matt Neuburg sur iOS, et une solution est devenue claire.

Fondamentalement, une instance d'un doigt sur l'écran est représentée par un UITouch. Une séquence multitouch commence lorsque le premier doigt descend sur l'écran et se termine lorsqu'il n'y a plus de doigts sur l'écran. Un objet UITouch représente essentiellement le même doigt tout au long de la séquence multitouch. Le système regroupe tous ces objets UITouch dans une enveloppe appelée UIEvent. Ceci est envoyé à notre UIWindow, qui l'envoie à la vue correcte à l'aide de tests de succès et d'autres choses. Cela se fait dans sa méthode sendEvent (:).

Si nous sous-classons UIWindow, nous pouvons surcharger sendEvent (:) pour intercepter l'événement. Tout ce que nous avons à faire est de regarder les touches dans cet événement et de déterminer quelle vue de sélecteur doit défiler. Nous allons amener cette vue de sélection au premier plan, puis appeler super, qui envoie l'événement normalement.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        self.window = MyWindow()
        self.window?.rootViewController = ViewController()
        self.window?.makeKeyAndVisible()

        return true
    }
}

Oh et, assurez-vous que votre AppDelegate crée une instance de notre sous-classe UIWindow et lui attribue un contrôleur de vue racine.

class MyWindow: UIWindow {

    override func sendEvent(_ event: UIEvent) {
        let touches = event.allTouches!.filter { $0.phase == .moved }

        if touches.isEmpty {
            super.sendEvent(event)
            return
        }

        let touch = touches.first!
        let vc = self.rootViewController as! ViewController

        // Use the touch to determine which view to put on top,
        // then call super.

        let loc_before = touch.previousLocation(in: vc.view)
        let loc_now = touch.location(in: vc.view)
        let delta = CGPoint(x: loc_now.x - loc_before.x, y: loc_now.y - loc_before.y)

        /*
         Q1    |    Q2
               |
         ------|------
               |
         Q3    |    Q4
         */

        let q1 = delta.x < 0 && delta.y < 0
        let q4 = delta.x > 0 && delta.y > 0

        let q2 = delta.x > 0 && delta.y < 0
        let q3 = delta.x < 0 && delta.y > 0

        if q1 || q4 {
            // Picker 1 should scroll
            vc.view.bringSubviewToFront(vc.picker1)
        }

        if q2 || q3 {
            // Picker 2 should scroll
            vc.view.bringSubviewToFront(vc.picker2)
        }
        super.sendEvent(event)
    }
}

Je l'ai testé et cela fonctionne. Quoi qu'il en soit, j'espère que cela aide :)


1 commentaires

C'est parfait!! Merci!