28

I am creating a game, similar to Flappy Bird but the user holds their finger on the screen and dodges the obstacles, rather than tapping to make the bird fly.

I am doing this by having a UIScrollView, in which UIView's are used as obstacles. When the user touches a UIView, the game is over.

How do I detect the users touch of a UIView from within a UIScrollView? I am using Swift with Xcode Beta 4.

EDIT: This is screenshot of the game

This is screenshot of the game

As you can see, the user moves their finger between the grey blocks (UIViews) as they scroll up.

Lee
  • 850
  • 11
  • 23
user2397282
  • 3,798
  • 15
  • 48
  • 94

2 Answers2

51

By setting userInteractionEnabled to NO for your scroll view, the view controller will start receiving touch events since UIViewController is a subclass of UIResponder. You can override one or more of these methods in your view controller to respond to these touches:

  • touchesBegan: withEvent:
  • touchesMoved: withEvent:
  • touchesEnded: withEvent:
  • touchesCancelled: withEvent:

I created some example code to demonstrate how you could do this:

class ViewController: UIViewController {
    @IBOutlet weak var scrollView: UIScrollView!

    // This array keeps track of all obstacle views
    var obstacleViews : [UIView] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create an obstacle view and add it to the scroll view for testing purposes
        let obstacleView = UIView(frame: CGRectMake(100,100,100,100))
        obstacleView.backgroundColor = UIColor.redColor()
        scrollView.addSubview(obstacleView)

        // Add the obstacle view to the array
        obstacleViews += obstacleView
    }

    override func touchesBegan(touches: NSSet!, withEvent event: UIEvent!) {
        testTouches(touches)
    }

    override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) {
        testTouches(touches)
    }

    func testTouches(touches: NSSet!) {
        // Get the first touch and its location in this view controller's view coordinate system
        let touch = touches.allObjects[0] as UITouch
        let touchLocation = touch.locationInView(self.view)

        for obstacleView in obstacleViews {
            // Convert the location of the obstacle view to this view controller's view coordinate system
            let obstacleViewFrame = self.view.convertRect(obstacleView.frame, fromView: obstacleView.superview)

            // Check if the touch is inside the obstacle view
            if CGRectContainsPoint(obstacleViewFrame, touchLocation) {
                println("Game over!")
            }
        }
    }

}
s1m0n
  • 7,825
  • 1
  • 32
  • 45
  • This is a good method. However, doesn't it redefine touch whenever touchesMoved is called, which isn't necessary? Efficiency could perhaps be improved somewhat. CGRectContainsPoint is the way to go for finding the location, but you need to call it twice for the UIView on each side – trumpeter201 Aug 03 '14 at 19:12
  • Yup, it does indeed call testTouches as long as the user keeps moving his finger. But the OP will probably replace `println("Game over!")` by some code that replaces the current screen with a game over screen on the first contact so I don't think that's will be a problem. And you're right that `CGRectContainsPoint` needs to be called at least twice. But the obstacleViews array contains all the obstacle views, including the two 'critical' views so that's fine. If the performance is subpar, the OP can always optimize the code to only check the two 'critical' views or currently visible views. – s1m0n Aug 03 '14 at 19:20
  • Fair. All the code is here, perhaps another function could be created to check the initial touch. – trumpeter201 Aug 03 '14 at 19:39
  • 1
    can you please post an swift 3 version? – Peter Kreinz Oct 10 '16 at 13:46
16

You can programmatically add a gesture recognizer as follows

var touch = UITapGestureRecognizer(target:self, action:"action")
scrollView.addGestureRecognizer(touch)

However, this gesture recognizer won't work for you. UITapGestureRecognizer will only return for a tap, not a tap and hold, and UILongPressGestureRecognizer doesn't give information about location, so you want to use a UIPanGestureRecognizer. It continually tells you how far the touch has moved.

var touch = UIPanGestureRecognizer(target:self, action:"handlePan")
scrollView.addGestureRecognizer(touch)

@IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
    let translation = recognizer.translationInView(self.view)
    recognizer.setTranslation(CGPointZero, inView: self.view)
}

You can use the constant "translation" to move your object, it represents the distance the person has slid their finger. Use that plus the location of your bird to move the bird to a new point. You have to reset the translation to zero after this function is called.

Edit: With the format of your game, this code should be the best method.

So, all together, the code to find the location of your finger should be as follows.

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    for touch: AnyObject in touches {
        let location = touch.locationInView(yourScrollView)
    }
}


@IBAction func handlePan(recognizer:UIPanGestureRecognizer) {
    let translation = recognizer.translationInView(self.view)
    var currentLocation : CGPoint = CGPointMake(location.x+translation.x, location.y+translation.y)
    recognizer.setTranslation(CGPointZero, inView: self.view)
}

currentLocation is a CGPoint containing the location of the current touch, wherever the finger is slid to. As I do not know how you are creating the views to be avoided, you will have to use the y coordinate of currentLocation to determine the x boundaries of the views that are to be avoided at that y, and use < or > comparators to determine if the x boundary of the touch is inside either of those views.

Note: you have to declare location so it can be accessed in handlePan

var location : CGPoint = CGPointZero
trumpeter201
  • 8,487
  • 3
  • 18
  • 24
  • This looks good, but what do I need to do to get the application to send a message or something when the users finger touches one of the UIViews? I don't need to move a bird or anything, because it's only the users finger that is interacting and dodging the obstacles. When the user touches one of the many UIViews, the game will end. – user2397282 Aug 02 '14 at 17:11
  • You misunderstood the question: there is no bird: the game is about holding your finger on screen and avoiding the obstacles. – wander Aug 02 '14 at 23:35
  • 1
    Noted. What you should do is use a touchesBegan method to find the initial y value of the touch, then add the y value of the constant translation in the code I gave, and then use some logic to find out if those values overlap with the boundaries of your view controller. – trumpeter201 Aug 02 '14 at 23:38