SammyS
SammyS

Reputation: 51

How to use "shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer"

I am trying to programmatically create 6 CGRect objects on the right side of the screen, with three being blue and three red. The user would then drag them to the left side of the screen, and they need to drag the blue blocks where the target is the faded-out region of the same color. The picture below depicts what I am trying to do. enter image description here

The issue is that when I drag any block over any of the faded areas, it accepts the block and deletes it. I realize that I need to implement shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer but I am confused on how to do so. I get that gestureRecognizer and otherGestureRecognizer represent the two conflicting objects, but I don't know how to implement it with the rest of my code so that I can make sure that only blue blocks can go over the faded blue block and activate the delete feature. What I have is below.

import UIKit

class ViewController: UIViewController {

    private struct Constants {
        static let padding: CGFloat = 75
        static let correctPadding: CGFloat = 0
        static let blockDimension: CGFloat = 50
    }

    var targetView0: UIView?
    var targetView1: UIView?
    var targetView2: UIView?
    var targetView3: UIView?
    var targetView4: UIView?
    var targetView5: UIView?
    
    var beginningPosition: CGPoint = .zero
    var initialMovableViewPosition: CGPoint = .zero

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        addMovableViews(count: 6)
        addtargetView0()
        addtargetView1()
        addtargetView2()
        addtargetView3()
        addtargetView4()
        addtargetView5()
    }
    //This programtically creates the blocks w/ x and y
    private func addMovableViews(count: Int) {
        var xOffset = Constants.padding
        for i in 0..<count {
            print(i)
            let movableView = UIView(frame: CGRect(x: 700, y: xOffset + 120, width: Constants.blockDimension, height: Constants.blockDimension))
            if (i == 0) {
                movableView.backgroundColor = .blue
                view.addSubview(movableView)
                let item0 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item0)
            } else if (i == 1) {
                movableView.backgroundColor = .red
                view.addSubview(movableView)
                let item1 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item1)
                
            } else if (i == 2) {
                movableView.backgroundColor = .blue
                view.addSubview(movableView)
                let item2 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item2)
                
            } else if (i == 3) {
                movableView.backgroundColor = .red
                view.addSubview(movableView)
                let item3 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item3)
                
            } else if (i == 4) {
                movableView.backgroundColor = .blue
                view.addSubview(movableView)
                let item4 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item4)
                
            } else if (i == 5) {
                movableView.backgroundColor = .red
                view.addSubview(movableView)
                let item5 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item5)
                
            } else if (i == 6) {
                movableView.backgroundColor = .blue
                view.addSubview(movableView)
                let item6 = UIPanGestureRecognizer(target: self, action: #selector(touched))
                movableView.addGestureRecognizer(item6)
            }
            xOffset += Constants.blockDimension + Constants.padding
        }
        
    }
    
    private func addtargetView0() {
        let xOffset = Constants.correctPadding
        targetView0 = UIView(frame: CGRect(x: xOffset + 100, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
        targetView0?.backgroundColor = UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)
        view.addSubview(targetView0!)
    }
    
    private func addtargetView1() {
        let xOffset = Constants.correctPadding
        targetView1 = UIView(frame: CGRect(x: 100 + xOffset + 50, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
        targetView1?.backgroundColor = UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)
        view.addSubview(targetView1!)
    }
    
    private func addtargetView2() {
        let xOffset = Constants.correctPadding
        targetView2 = UIView(frame: CGRect(x: 100 + xOffset + 100, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
        targetView2?.backgroundColor = UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)
        view.addSubview(targetView2!)
    }
    
    private func addtargetView3() {
        let xOffset = Constants.correctPadding
        targetView3 = UIView(frame: CGRect(x: 100 + xOffset + 150, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
        targetView3?.backgroundColor = UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)
        view.addSubview(targetView3!)
    }
    
    private func addtargetView4() {
        let xOffset = Constants.correctPadding
        targetView4 = UIView(frame: CGRect(x: 100 + xOffset + 200, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
        targetView4?.backgroundColor = UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)
        view.addSubview(targetView4!)
    }
    
    
    private func addtargetView5() {
        let xOffset = Constants.correctPadding
        targetView5 = UIView(frame: CGRect(x: 100 + xOffset + 250, y: 500, width: Constants.blockDimension, height: Constants.blockDimension))
        targetView5?.backgroundColor = UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)
        view.addSubview(targetView5!)
    }

    //Gesture recognizer
    @objc private func touched(_ gestureRecognizer: UIGestureRecognizer) {
        if let touchedView = gestureRecognizer.view {
            if gestureRecognizer.state == .began {
                beginningPosition = gestureRecognizer.location(in: touchedView)
                initialMovableViewPosition = touchedView.frame.origin
            } else if gestureRecognizer.state == .ended {
                //Moves it back to where it was
//                touchedView.frame.origin = initialMovableViewPosition
                
            } else if gestureRecognizer.state == .changed {
                let locationInView = gestureRecognizer.location(in: touchedView)
                touchedView.frame.origin = CGPoint(x: touchedView.frame.origin.x + locationInView.x - beginningPosition.x, y: touchedView.frame.origin.y + locationInView.y - beginningPosition.y)
                if touchedView.frame.intersects(targetView0!.frame) {
                    touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
//                    gestureRecognizer.isEnabled = false
                    targetView0?.backgroundColor = .blue
                    initialMovableViewPosition = .zero
                }
                if touchedView.frame.intersects(targetView1!.frame) {
                    touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
//                    gestureRecognizer.isEnabled = false
                    targetView1?.backgroundColor = .red
                    initialMovableViewPosition = .zero
                }
                if touchedView.frame.intersects(targetView2!.frame) {
                    touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
//                    gestureRecognizer.isEnabled = false
                    targetView2?.backgroundColor = .blue
                    initialMovableViewPosition = .zero
                }
                if touchedView.frame.intersects(targetView3!.frame) {
                    touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
//                    gestureRecognizer.isEnabled = false
                    targetView3?.backgroundColor = .red
                    initialMovableViewPosition = .zero
                }
                if touchedView.frame.intersects(targetView4!.frame) {
                    touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
//                    gestureRecognizer.isEnabled = false
                    targetView4?.backgroundColor = .blue
                    initialMovableViewPosition = .zero
                }
                if touchedView.frame.intersects(targetView5!.frame) {
                    touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
//                    gestureRecognizer.isEnabled = false
                    targetView5?.backgroundColor = .red
                    initialMovableViewPosition = .zero
                }
            }
        }
    }
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
           shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer)
            -> Bool {
                // If the gesture recognizers are on diferent views, do not allow
                // simultaneous recognition.
                if gestureRecognizer.view != otherGestureRecognizer.view {
                   return false
                }
     
                return true
    }
    
    init() {
        super.init(nibName: nil, bundle: nil)
    }

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

Can someone explain how to incorporate shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer, link a complete project doing something similar, or walk me through it. Any help is appreciated.

Upvotes: 0

Views: 1178

Answers (2)

yjoon
yjoon

Reputation: 116

There are a few issues here. Firstly, the delegate for your gestures are not being set. To do this, you need ViewController to extend UIGestureRecognizerDelegate like so class ViewController: UIViewController, UIGestureRecognizerDelegate. Then, you need to set the delegate of each of your gestures to your ViewController:

let item0 = UIPanGestureRecognizer(target: self, action: #selector(touched))
item0.delegate = self
movableView.addGestureRecognizer(item0)

Second, even with the delegates set, shouldRecognizeSimultaneouslyWith would never be called since you only have one type of gesture (UIPanGestureRecognizer) for each of your moveable views. Your target views themselves do not have any gesture recognisers assigned to them. If you want to test when shouldRecognizeSimultaneouslyWith is called, you can try do the following:

let item0 = UIPanGestureRecognizer(target: self, action: #selector(touched))
item0.delegate = self
movableView.addGestureRecognizer(item0)
                
// Add another gesture
let testitem = UITapGestureRecognizer(target: self, action: #selector(test))
movableView.addGestureRecognizer(testitem)

With the example above, both UITapGestureRecognizer and UIPanGestureRecognizer would be triggered the moment you start dragging the moveable views.

=== Solution ===

Instead of using shouldRecognizeSimultaneouslyWith might I suggest adding tags to each of your moveable and target views? For example, for all blue moveable and target views, movableView.tag = 0 and for all red moveable and target views, movableView.tag = 1.

Then, in your touched action, you can just check if the tags match like so:

if touchedView.frame.intersects(targetView0!.frame) {
   if touchedView.tag == targetView0?.tag {
      touchedView.removeFromSuperview() 
      targetView0?.backgroundColor = .blue
      initialMovableViewPosition = .zero
   }
}

Upvotes: 1

SammyS
SammyS

Reputation: 51

I figured it out. It's basically just in the gesture if statement.

@objc private func touched(_ gestureRecognizer: UIGestureRecognizer) {
        if let touchedView = gestureRecognizer.view {
            if gestureRecognizer.state == .began {
                beginningPosition = gestureRecognizer.location(in: touchedView)
                initialMovableViewPosition = touchedView.frame.origin
            } else if gestureRecognizer.state == .ended {
                //Moves it back to where it was
//                touchedView.frame.origin = initialMovableViewPosition
                
            } else if gestureRecognizer.state == .changed {
                let locationInView = gestureRecognizer.location(in: touchedView)
                touchedView.frame.origin = CGPoint(x: touchedView.frame.origin.x + locationInView.x - beginningPosition.x, y: touchedView.frame.origin.y + locationInView.y - beginningPosition.y)
                if ((touchedView.backgroundColor == .blue) && (targetView0?.backgroundColor == UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView0!.frame))) {
                        touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
    //                    gestureRecognizer.isEnabled = false
                        targetView0?.backgroundColor = .blue
                        initialMovableViewPosition = .zero
                }
                if ((touchedView.backgroundColor == .red) && (targetView1?.backgroundColor == UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView1!.frame))) {
                        touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
    //                    gestureRecognizer.isEnabled = false
                        targetView1?.backgroundColor = .red
                        initialMovableViewPosition = .zero
                }
                if ((touchedView.backgroundColor == .blue) && (targetView2?.backgroundColor == UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView2!.frame))) {
                        touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
    //                    gestureRecognizer.isEnabled = false
                        targetView2?.backgroundColor = .blue
                        initialMovableViewPosition = .zero
                }
                if ((touchedView.backgroundColor == .red) && (targetView3?.backgroundColor == UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView3!.frame))) {
                        touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
    //                    gestureRecognizer.isEnabled = false
                        targetView3?.backgroundColor = .red
                        initialMovableViewPosition = .zero
                }
                if ((touchedView.backgroundColor == .blue) && (targetView4?.backgroundColor == UIColor(red: 51/255.0, green: 153/255.0, blue: 255/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView4!.frame))) {
                        touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
    //                    gestureRecognizer.isEnabled = false
                        targetView4?.backgroundColor = .blue
                        initialMovableViewPosition = .zero
                }
                if ((touchedView.backgroundColor == .red) && (targetView5?.backgroundColor == UIColor(red: 255/255.0, green: 102/255.0, blue: 102/255.0, alpha: 1)) && (touchedView.frame.intersects(targetView5!.frame))) {
                        touchedView.removeFromSuperview() //Might uncomment when everything is set up so it deals with the overlay issue
    //                    gestureRecognizer.isEnabled = false
                        targetView5?.backgroundColor = .red
                        initialMovableViewPosition = .zero
                }
            }
        }
    }

You check if the gestureRecognizer background color is blue, check if the targetView background color is the faded blue via UIColor, and see if the view intersects, and then you do what you need. You can even do it to fill the target view in the order you want with an additional if statement.

Upvotes: 0

Related Questions