Jean Valjean
Jean Valjean

Reputation: 736

Swift 3: UIScrollView and (disabling) Gesture Recognizers

I'm in swift 3 and I have a class that's a subclass of UIScrollView. Here it is:

import SpriteKit

/// Scroll direction
enum ScrollDirection {
    case vertical
    case horizontal
}

class CustomScrollView: UIScrollView {

    // MARK: - Static Properties

    /// Touches allowed
    static var disabledTouches = false

    /// Scroll view
    private static var scrollView: UIScrollView!

    // MARK: - Properties

    /// Current scene
    private let currentScene: SKScene

    /// Moveable node
    private let moveableNode: SKNode

    /// Scroll direction
    private let scrollDirection: ScrollDirection

    /// Touched nodes
    private var nodesTouched = [AnyObject]()

    // MARK: - Init
    init(frame: CGRect, scene: SKScene, moveableNode: SKNode, scrollDirection: ScrollDirection) {
        self.currentScene = scene
        self.moveableNode = moveableNode
        self.scrollDirection = scrollDirection
        super.init(frame: frame)

        CustomScrollView.scrollView = self
        self.frame = frame
        delegate = self
        indicatorStyle = .white
        isScrollEnabled = true
        isUserInteractionEnabled = true
        //canCancelContentTouches = false
        //self.minimumZoomScale = 1
        //self.maximumZoomScale = 3

        if scrollDirection == .horizontal {
            let flip = CGAffineTransform(scaleX: -1,y: -1)
            transform = flip
        }
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    /// Began
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("begin " + String(CustomScrollView.disabledTouches))

        for touch in touches {
            let location = touch.location(in: currentScene)
            guard !CustomScrollView.disabledTouches else { return }

            /// Call touches began in current scene
            currentScene.touchesBegan(touches, with: event)

            /// Call touches began in all touched nodes in the current scene
            nodesTouched = currentScene.nodes(at: location)
            for node in nodesTouched {
                node.touchesBegan(touches, with: event)
            }
        }
    }

    /// Moved
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("moved " + String(CustomScrollView.disabledTouches))

        for touch in touches {
            let location = touch.location(in: currentScene)

            guard !CustomScrollView.disabledTouches else { return }

            /// Call touches moved in current scene
            currentScene.touchesMoved(touches, with: event)

            /// Call touches moved in all touched nodes in the current scene
            nodesTouched = currentScene.nodes(at: location)
            for node in nodesTouched {
                node.touchesMoved(touches, with: event)
            }
        }
    }

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

        for touch in touches {
            let location = touch.location(in: currentScene)

            guard !CustomScrollView.disabledTouches else { return }

            /// Call touches ended in current scene
            currentScene.touchesEnded(touches, with: event)

            /// Call touches ended in all touched nodes in the current scene
            nodesTouched = currentScene.nodes(at: location)
            for node in nodesTouched {
                node.touchesEnded(touches, with: event)
            }
        }
    }

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

        print("cancelled " + String(CustomScrollView.disabledTouches))
        for touch in touches {
            let location = touch.location(in: currentScene)

            guard !CustomScrollView.disabledTouches else { return }

            /// Call touches cancelled in current scene
            currentScene.touchesCancelled(touches, with: event)

            /// Call touches cancelled in all touched nodes in the current scene
            nodesTouched = currentScene.nodes(at: location)
            for node in nodesTouched {
                node.touchesCancelled(touches, with: event)
            }
        }
    }
}

// MARK: - Touch Controls
extension CustomScrollView {

    /// Disable
    class func disable() {
        CustomScrollView.scrollView?.isUserInteractionEnabled = false
        CustomScrollView.disabledTouches = true
    }

    /// Enable
    class func enable() {
        CustomScrollView.scrollView?.isUserInteractionEnabled = true
        CustomScrollView.disabledTouches = false
    }
}

// MARK: - Delegates
extension CustomScrollView: UIScrollViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        if scrollDirection == .horizontal {
            moveableNode.position.x = scrollView.contentOffset.x
        } else {
            moveableNode.position.y = scrollView.contentOffset.y
        }
    }
}

It's main function is to create a scrollable menu, and for the most part it works. I create an object of it in GameScene, and how it's supposed to work is that when a touch is registered, the overridden touch functions (touchBegan, touchMoved, etc.) in CustomScrollView are called, which then call the touch functions in GameScene. This does happen, the menu scrolls fine, and GameScene's methods ARE called.

The catch is my overridden functions (and GameScene's) are only called when you're swiping horizontally. When you swipe up or down (past a certain degree), the menu still scrolls but I think that it's UIScrollView's touch methods that are being called.

When you swipe vertically, my touchCancelled method gets called, which makes me think this has something to do with UIScrollView's gesture recognizers (the pan/drag recognizer I think) firing when they shouldn't be.

Is this the case? If so, can I disable the recognizer? And if I can, should I? On a side note, is this the best (or at least an acceptable) way to implement UIScrollView so that GameScene's touch methods are still called?

Upvotes: 1

Views: 1259

Answers (1)

buildc0de
buildc0de

Reputation: 323

If the conflicting gesture recognizers need to be recognized simultaneously, you can make use of gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:),

Upvotes: 2

Related Questions