Как передать сенсорное событие в другой вид? - PullRequest
0 голосов
/ 04 апреля 2020

У меня есть UIView с двумя подпредставлениями UIPickerView, каждое повернуто на 60 градусов, по часовой стрелке и против часовой стрелки. Я хочу прокрутить каждый просмотр в отдельности в зависимости от того, в каком направлении пролистывает пользователь. Так как один находится над другим, можно прокручивать только верхнюю панель выбора. Итак, я хочу иметь возможность прокручивать нижнюю панель выбора, когда пользователь проводит пальцем вдоль ее направления.

Снимок экрана

Самый близкий ответ, который я нашел, это переопределение hitTest, но тогда я не могу определить направление удара. Я думаю, что мне как-то нужно использовать прикосновения Beg, 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 голосов
/ 05 апреля 2020

Когда последовательность касаний происходит в области перекрывающихся видов, по сути, между жестами происходит захват этой последовательности касаний.

Может быть, попробовать это?

Мы ставим прозрачный вид на сверху, и назначьте ему собственный распознаватель жестов. Идея состоит в том, чтобы заставить этот жест действовать как своего рода посредник, который определяет, какой вид выбора получает последовательность касаний; вместо того, чтобы они боролись между собой, а верхний всегда побеждает.

У каждого представления выбора есть свой собственный жест, и мы скажем обоим требовать, чтобы наш пользовательский жест потерпел неудачу. Это означает, что наш собственный жест должен сначала потерпеть неудачу, прежде чем эти жесты смогут получить последовательность касания.

В коде нашего настраиваемого жеста мы всегда устанавливаем его состояние как сбойное, что позволяет последовательности касания быть перешел на другие жесты. Но прежде чем мы потерпим неудачу, мы определим, какое представление выбора должно получить эту последовательность касаний, установив 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.

    }
}
0 голосов
/ 09 апреля 2020

Попробовав много разных вещей безрезультатно, я вернулся и перечитал главу «Прикосновения» из книги Мэтта Нойбурга о iOS, и решение стало ясным.

По сути, пример пальца экран представлен UITouch. Последовательность мультитача начинается, когда первый палец опускается на экран, и заканчивается, когда на экране больше нет пальцев. Один объект UITouch по существу представляет один и тот же палец во всей последовательности мультитач. Система упаковывает все эти объекты UITouch в конверт, называемый UIEvent. Это отправляется в наше окно UIWindow, которое отправляет его в правильное представление, используя тестирование попаданий и прочее. Это делается с помощью метода sendEvent (:).

Если мы создаем подкласс UIWindow, мы можем переопределить sendEvent (:), чтобы перехватить событие. Все, что нам нужно сделать, это посмотреть на прикосновения в этом событии и определить, какой вид выбора следует прокрутить. Мы перенесем этот вид выбора на передний план, а затем вызовем super, который отправит событие нормально.

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

Oh, и убедитесь, что ваш AppDelegate создает экземпляр нашего подкласса UIWindow и назначает ему root просмотр контроллера.

@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
    }
}

Я проверил его, и он работает. Во всяком случае, надеюсь, что это помогает:)

0 голосов
/ 04 апреля 2020

Одним из возможных решений этой проблемы является использование прозрачного представления о размере экрана (ваш root 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()
            }
        }
    }
}

Это даст большую гибкость по сравнению с использованием UIGestureRecognizer.

Теперь ваш ViewController, который содержит два ваших средства выбора и прозрачное представление, может быть делегатом прозрачного представления и перенаправлять сообщения на ваши средства выбора:

protocol MyTransparentViewDelegate: class {
    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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...