Swift 2 examples – #8 Gestures Recognizers. Swipe, Tap, Rotate, Panning, etc.

This post is part of my collection: Swift 2 – For Beginners.

This example shows how gesture recognizers works.

Adding gestures programmatically

It is possible to add gesture recognizers programmatically to a view using the method addGestureRecognizer. To do that we can use these lines of code:

static func addSwipeGestureRecognizer(targetView: UIView, swipeDirection: UISwipeGestureRecognizerDirection, actionTarget: AnyObject?, action: Selector){
        // Create gesture recognizer
        let swipeRecognizer = UISwipeGestureRecognizer(target: actionTarget, action: action)
        
        // Set swipe direction
        swipeRecognizer.direction = swipeDirection
        
        // Add recognizer to target view
        targetView.addGestureRecognizer(swipeRecognizer)
}

Swipe

However you can also add gestures using “drag and drop” in your storyboard from the object library:
gestures

It is also possible to configure some parameters using XCode, i.e the swipe direction.
swipeDirection

Once you have your gesture in the storyboard you can select it and drag it to your ViewController to create the action that should be executed when the gesture was recognized.

    @IBAction func onViewSwipe(recognizer: UISwipeGestureRecognizer) {
        print("swipe down")
    }

Rotate

We can use the rotation gesture recognizer to rotate a view:

    @IBOutlet weak var ghostImage: UIImageView!

    // Rotate
    @IBAction func onGhostRotate(recognizer: UIRotationGestureRecognizer) {
        print("onGhostRotate \(recognizer.rotation)")

        // Apply gesture rotation to ghost view
        ghostImage.transform = CGAffineTransformRotate(ghostImage.transform, recognizer.rotation)

        // Reset recognizer rotation
        recognizer.rotation = 0
    }

rotatedGhost

Tap

Using a tap gesture recognizer and what we learnt before when can play a sound when the user tap our image:

@IBAction func onGhostTapped(sender: UITapGestureRecognizer) {
   let audioPath = NSBundle.mainBundle().pathForResource("punch", ofType: "mp3")!
   do  {
        // Initialize the player
        player = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
        // Play
        player.play()   
 
    } catch{
        print("Error creating audio player.")
    }    
}

Panning

The pan gesture recognizer allows us to drag and drop views in our application:

 @IBAction func onGhostPanning(recognizer: UIPanGestureRecognizer) {
        GestureHelper.applyGestureTranslationToGestureView(recognizer, parentView: self.view)
    }
static func applyGestureTranslationToGestureView(recognizer: UIPanGestureRecognizer, parentView: UIView){
        // Apply translation recognizer view
        if let recognizerView = recognizer.view {
            
            // Get recognized translation
            let translation = recognizer.translationInView(parentView)
            
            // Calculate new center
            let originalCenter = recognizerView.center
            recognizerView.center = CGPoint(x:originalCenter.x + translation.x, y:originalCenter.y + translation.y)
        }
        
        // Reset recognizer translation
        recognizer.setTranslation(CGPointZero, inView: parentView)
    }

Shake

There is not shake gesture recognizer, to do something when the user shake the phone we have to override the method motionEnded. This code resets the position and rotation of our ghost when the user shakes the phone:

    override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
        
        if event?.subtype == UIEventSubtype.MotionShake{
            // Reset initial position
            ghostImage.center = ghostInitialPosition
            // Reset rotation and scale
            ghostImage.transform = CGAffineTransform()
        }
    }

Complete example:

GestureHelper

import UIKit

public class GestureHelper {
    
    // this adds a helper in "code behind" normally you would do it adding the gesture using the storyboard and then the gesture action to the controller...
     static func addSwipeGestureRecognizer(targetView: UIView, swipeDirection: UISwipeGestureRecognizerDirection, actionTarget: AnyObject?, action: Selector){
        
        // Create gesture recognizer
        let swipeRecognizer = UISwipeGestureRecognizer(target: actionTarget, action: action)
        
        // Set swipe direction
        swipeRecognizer.direction = swipeDirection
        
        // Add recognizer to target view
        targetView.addGestureRecognizer(swipeRecognizer)
    }
    
    static func applyGestureTranslationToGestureView(recognizer: UIPanGestureRecognizer, parentView: UIView, inertia: Bool = true){
       
        // Apply translation recognizer view
        if let recognizerView = recognizer.view {
            
            // Get recognized translation
            let translation = recognizer.translationInView(parentView)
            
            // Calculate new center
            let originalCenter = recognizerView.center
            recognizerView.center = CGPoint(x:originalCenter.x + translation.x, y:originalCenter.y + translation.y)
            
            // Inertia
            if inertia && recognizer.state == UIGestureRecognizerState.Ended {
                
                // Get speed
                let velocity = recognizer.velocityInView(parentView)
                let magnitude = min(sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y)) / 10000, 0.1)
                
                // Calculate final point
                var finalPoint = CGPoint(
                    x:recognizerView.center.x + (velocity.x * magnitude),
                    y:recognizerView.center.y + (velocity.y * magnitude))
                
                // Consider bounds
                finalPoint.x = min(max(finalPoint.x, 0), parentView.bounds.size.width)
                finalPoint.y = min(max(finalPoint.y, 0), parentView.bounds.size.height)
                
                // Apply inertia animation
                UIView.animateWithDuration(0.5,
                    delay: 0,
                    options: UIViewAnimationOptions.CurveEaseOut,
                    animations: {recognizerView.center = finalPoint },
                    completion: nil)
            }
        }
        
        // Reset recognizer translation
        recognizer.setTranslation(CGPointZero, inView: parentView)
    }
}

ViewController

import UIKit
import AVFoundation

class ViewController: UIViewController {

    var ghostInitialPosition: CGPoint = CGPoint();
    var player = AVAudioPlayer()

    @IBOutlet weak var ghostImage: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        ghostInitialPosition = ghostImage.center
    }
    
    // Swipe
    @IBAction func onViewSwipe(recognizer: UISwipeGestureRecognizer) {
        print("swipe down")
    }
    
    // Tap
    @IBAction func onGhostTapped(sender: UITapGestureRecognizer) {
        playPunchSound()
    }
    
    // Rotate
    @IBAction func onGhostRotate(recognizer: UIRotationGestureRecognizer) {
        print("onGhostRotate \(recognizer.rotation)")
        
        // Apply gesture rotation to ghost view
        ghostImage.transform = CGAffineTransformRotate(ghostImage.transform, recognizer.rotation)

        // Reset recognizer rotation
        recognizer.rotation = 0
    }
    
    // Panning
    @IBAction func onGhostPanning(recognizer: UIPanGestureRecognizer) {
        GestureHelper.applyGestureTranslationToGestureView(recognizer, parentView: self.view)
    }
    
    // Shake
    override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
        
        if event?.subtype == UIEventSubtype.MotionShake{
            // Reset initial position
            ghostImage.center = ghostInitialPosition
            // Reset rotation and scale
            ghostImage.transform = CGAffineTransform()
        }
    }
    
    func playPunchSound(){
        let audioPath = NSBundle.mainBundle().pathForResource("punch", ofType: "mp3")!
        
        do  {
            // Initialize the player
            player = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
            
            // Play
            player.play()
            
        } catch{
            print("Error creating audio player.")
        }
    }
}

Swift 2 examples – #7 Reproduce an audio file

This post is part of my collection: Swift 2 – For Beginners.

In this example we will see how to play and pause an audio file.

We can use the class AVAudioPlayer of AVFoundation to easily reproduce audio files, let’s start importing it.

import AVFoundation

We need the path to the audio file. E.g. To get a file named “allegro.mp3” in our project we can use the following line:

let audioPath = NSBundle.mainBundle().pathForResource("allegro", ofType: "mp3")!

That’s all, we are ready to create the AVAudioPlayer. The creation of the player could fail if for example the file would be corrupted. That’s why we need to create it in a “do try”:

do  {
   // Initialize the player with the audio file
   player = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
           
   // Play
   player.play()
            
} catch{
   print("Error creating audio player.")
}

To pause the audio we just call:

player.pause()

Complete example:

import UIKit
// 1. Import audio video foundation
import AVFoundation

class ViewController: UIViewController {
    
    // 2. Create player
    var player = AVAudioPlayer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 3. Get path to the audio file inside the application bundle
        let audioPath = NSBundle.mainBundle().pathForResource("allegro", ofType: "mp3")!
        
        do  {
            // 4. Initialize the player with the audio file inside a "do try cath"
            player = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
            
            // 5. Play
            player.play()
        } catch{
            print("Error creating audio player.")
        }
    }
}

Swift 2 examples – #6 Take a Snapshot of a MKMapView

This post is part of my collection: Swift 2 – For Beginners.

This is an example that shows how to take a snapshot of a MKMapView using MKMapSnapshotter. The annotations are not included.

First, we write a generic method that takes a snapshot and execute a callback, it allows us to do what we want with the generated UIImage:

    // Takes a snapshot and calls back with the generated UIImage
    static func takeSnapshot(mapView: MKMapView, withCallback: (UIImage?, NSError?) -> ()) {
        let options = MKMapSnapshotOptions()
        options.region = mapView.region
        options.size = mapView.frame.size
        options.scale = UIScreen.mainScreen().scale
        
        let snapshotter = MKMapSnapshotter(options: options)
        snapshotter.startWithCompletionHandler() { snapshot, error in
            guard snapshot != nil else {
                withCallback(nil, error)
                return
            }
            
            withCallback(snapshot!.image, nil)
        }
    }

We can use the previous method to write a method that directly saves the map snapshot as a png.

    // Takes a snapshot and saves the image locally in the DocumentDirectory
    static func takeSnapshot(mapView: MKMapView, filename: String) {
        
        MapHelper.takeSnapshot(mapView) { (image, error) -> () in
            guard image != nil else {
                print(error)
                return
            }
            
            // Save file in DocumentDirectory
            if let data = UIImagePNGRepresentation(image!) {
                let filename = getDocumentsDirectory().stringByAppendingPathComponent("\(filename).png")
                data.writeToFile(filename, atomically: true)
            }
        }
    }

To get the path to the documents directory we can use this method:

    // Gets the path to the current directory
    static func getDocumentsDirectory() -> NSString {
        let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
        let documentsDirectory = paths[0]
        return documentsDirectory
    }

Swift 2 examples – #5 Getting the User’s Location

This post is part of my collection: Swift 2 – For Beginners.

In this example we will see how to get the user’s location and we will update our map with it.

We will use the setMapLocation method that we created in Swift 2 examples – #3 Set the location of a MapView

To get the user’s location we need the CoreLocation.framework, the first thing that we should do is to add it to our project:
Add CoreLocation framework

Then we import the CoreLocation in our controller and implement CLLocationManagerDelegate

// 2 Import CoreLocation
import CoreLocation
// 3 Implement CLLocationManagerDelegate
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
...

The next step will be to create and run the location manager:

    // 4 Create Location manager
    var locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
  
        // 5 Configure and start the location manager
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest // GPS
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }

It is worth to mention this line locationManager.requestWhenInUseAuthorization().

It will shows a popup asking for permission to access the location, we can show a message within it to explain the user why.
AskUser for permissions

The message that will be shown has to be defined in the Info.plist

   	<key>NSLocationWhenInUseUsageDescription</key>
	<string>This is a message displayed to the user to ask for location permissions.</string>

	<key>NSLocationAlwaysUsageDescription</key>
	<string>This is a message displayed to the user to ask for permissions to get the location in background.</string>

If the user allows the location access this method will be called in the delegate when the application detect a location change.

    // 6 Called when the phone detect a new location
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 
        let userLocation: CLLocation = locations[0]
        
        MapHelper.setMapLocation(mapView: self.map, location: userLocation.coordinate, zoom: 0.01)
    }

The method has as a parameter an array of locations, it contains always at least one object representing the current location. If updates were deferred or if multiple locations arrived before they could be delivered, the array may contain additional entries.

All the code of this example together:

import UIKit
import MapKit

// 1 Add CoreLocation.framework to the project

// 2 Import CoreLocation
import CoreLocation

// 3 Implement CLLocationManagerDelegate
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

    @IBOutlet weak var map: MKMapView!
    
    // 4 Create Location manager
    var locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
  
        // 5 Configure and start the location manager
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest // GPS
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }
    
    // 6 Called when the phone detect a new location
    func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        let userLocation: CLLocation = locations[0]
        
        MapHelper.setMapLocation(mapView: self.map, location: userLocation.coordinate, zoom: 0.01)
    }
}

One last thing worth to mention is that the iOS emulator supports different location simulation modes. It helps a lot during development.
Location and the Emulator

Swift 2 examples – #4 Add user annotations to a MapView

This post is part of my collection: Swift 2 – For Beginners.

In this example we will see how to add an annotation to our map when the user tap and hold.

UserCreatedAnnotation

Before we create annotations you should add a MapView to your application, you can see how to do it here.

To detect when the user tapped and hold on the screen we can use the recognizer: UILongPressGestureRecognizer. The next method adds a long press recognizer to our map.

It has 4 parameters:

  • The map where the gesture recognized will be added.
  • Target, the class that has the action to be executed.
  • Action, the selector of the function that will be executed when the gesture is recognized.
  • tapDuration, the time in seconds that the user has to hold the tap before the action is executed, by default 1 second.
    static func addOnUserTapAction(mapView mapView: MKMapView, target: AnyObject, action: Selector, tapDuration: Double = 1){
      
        // Adding a colon at the end of the selector we pass the recognizer that triggered the action to the action
        let longPressRecognizer = UILongPressGestureRecognizer(target: target, action: action)
        longPressRecognizer.minimumPressDuration = tapDuration
        
        // Add gesture recognizer to the map
        mapView.addGestureRecognizer(longPressRecognizer)
    }

Now we can use the method in our controller:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        ...
        
        // Adding a colon at the end of the selector we pass the recognizer that triggered the action to the action
        MapHelper.addOnUserTapAction(mapView: map, target:self, action: "addAnnotation:")
    }

The method addAnnotation will be executed when our recognizer is fired, it will add an annotation to our map:

    func addAnnotation(gestureRecognizer:UIGestureRecognizer){
        print("add annotation")
        
        let location = MapHelper.getTappedLocation(mapView: self.map, gestureRecognizer: gestureRecognizer)
        
        // Add an annotation
        let annotation = MKPointAnnotation()
        annotation.coordinate = location;
        annotation.title = "You created this annotation!"
        map.addAnnotation(annotation)
    }
  

The last thing that we have to do is to write a method to get the coordinate in the map using the point on the screen where the user tapped:

    static func getTappedLocation(mapView mapView: MKMapView, gestureRecognizer: UIGestureRecognizer) -> CLLocationCoordinate2D{
        
        // Get the position on the screen where the user pressed, relative to the mapView
        let touchPoint = gestureRecognizer.locationInView(mapView)
        
        // Get location coordinate in map
        return mapView.convertPoint(touchPoint, toCoordinateFromView: mapView)
    }