Category Archives: Swift Programming

Actions in Push Notifications

In last week’s tutorial, I discussed how to set up a push notification. I also showed you how to test a notification on a test platform with a JSON payload. This week, we’ll go tot the next level: using actions in a push notification. We’ll start here from where we left off last week. If you have not done that lesson, go there first. Because of its nature there is no download file. As I do there, I’m assuming you know how to make a local notification. If you do not, you might want to read this first.

No More Blank Screens

Before we get started, I missed something in the last lesson I should have done. Go to the storyboard, and add one label “Push Pizza Company.”
2017-01-09_03-59-42

I centered this label using the auto layout align function, setting update constraint to items of new constraints.

2017-01-09_03-33-58
This give us one more visual cue the app ran correctly.

Payload Changes

The beauty of the User Notification framework is actions for a push notification work almost the same as a local notification. You set a category in the content, this time in the payload. You add one more key to the aps dictionary for the category:

{
     "aps":{
          "alert":{
              "title":"Push Pizza Co.",
              "body":"Your pizza is almost ready!"
           },
           "badge":42,
           "sound":"default",
           "category":"pizza.category"
    }
}

Setting Categories and Actions

Then you set the category in the AppDelegate. I usually create a new function to do this. Add this function:

func setCategories(){

}

Your first step in this function is to make an action using the UNNotificationAction constructor:

    let snoozeAction = UNNotificationAction(
        identifier: "snooze.action",
        title: "Snooze",
        options: [])

The action wants a unique identifier, which you’ll use to identify it when an action fires. The method also wants a title for the button that will appear on the notification for the action. We have no options, so leave that as a blank array.

Next we stick the action in a category, I’m using the same category identifier as the payload category.

    let pizzaCategory = UNNotificationCategory(
        identifier: "pizza.category",
        actions: [snoozeAction],
        intentIdentifiers: [],
        options: [])

Notice actions is an array. I have only one here, but you can add several if you wanted to. Finally I send the category to the system

    NUserNotificationCenter.current().setNotificationCategories(
        [pizzaCategory])
}

With these three lines You’ve created the snoozeAction, made a category pizza.catergory with them, and then set the notification into the notification center. This had to one of the first thing you do when the application launches, so call the function on didFinishLoading

setCategories()

Build and run. When the push pizza company label appears, on the app, you can close it. Launch the notification on the test platform. (for setup of the test platform see last week’s post) You’ll see the notification, then view it.

img_0023

We have the snooze button, but it does nothing.

Running Code from Actions

In order to make the snooze button do something, You need a delegate method found in the UNNotificationCenterDelegate delegate. Adopt the delegate

class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

In didfinishLoading, Set the delegate to self.

UNUserNotificationCenter.current().delegate = self

Add the didRecieveResonse delegate method to the class.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

}

By the time the notification reached this method, the payload from the push notifications is now content of a notification. For a snooze button I don’t touch the content, but I can copy it to a new local notification. Create three constants action, request, and content to have these easily available.

let action = response.actionIdentifier
let request = response.notification.request
let content = request.content

You identify the actionIdentifier then run code if it matches. Add this code for when the action is a snooze.action.

if action == "snooze.action"{
    let snoozeTrigger = UNTimeIntervalNotificationTrigger(
        timeInterval: 5.0,
        repeats: false)
    let snoozeRequest = UNNotificationRequest(
        identifier: "pizza.snooze",
        content: content,
        trigger: snoozeTrigger)
     center.add(snoozeRequest){
        (error) in
        if error != nil {
            print("Snooze Request Error: \(error?.localizedDescription)")
        }
     }
}

Our code will convert the remote notification to a local time interval notification. I’ll make a new trigger with a time interval of 5 seconds and non-repeating. I made new Notification request snoozeRequest using the identifier from the push notification, the content from the push notification, and the new snooze trigger. Finally, I added the notification to the Notification Center associated with the notification.

In the request identifier, I used a single literal identifier so any push notifications using this action will have only one snooze notification, updating the content of the last snooze. You won’t have fifty snoozes for fifty different pushes.

Finally, make sure you add the completion handler at the end of the delegate method.

completionHandler()

Run the application to load all these changes onto your device. Stop the app once it runs. Go to the test platform and send a notification.

2017-01-09_04-40-40

You’ll see the notification appear on the device. Open the notification and hit Snooze.

img_0023

Five seconds later, the notification appears again.

In this example I used a snooze button and made it a local notification from a push notification. If you have notifications that will repeat after the initial push notification, this is a very good practice. You have a limited number of push notification per app per day. Instead of squandering them on repeating the same message, use a local notification for repetitive notifications like snooze buttons. Also remember you only have four actions. Don’t go crazy adding actions everywhere.

The Whole Code

Like last week there is no download. There so little here you can copy it, and much of the meat is the certificates which you have to do yourself. Fr brevity, I removed extra unused methods from the app delegate.

//
//  AppDelegate.swift
//  PushNotification
//
//  Created by Steven Lipton on 12/30/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {

    var window: UIWindow?

    func setCategories(){
        let snoozeAction = UNNotificationAction(
            identifier: "snooze.action",
            title: "Snooze",
            options: [])
        let pizzaCategory = UNNotificationCategory(
            identifier: "pizza.category",
            actions: [snoozeAction],
            intentIdentifiers: [],
            options: [])
        UNUserNotificationCenter.current().setNotificationCategories(
            [pizzaCategory])
    }
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        setCategories()
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge], completionHandler: {(granted,error) in
            if granted{
                application.registerForRemoteNotifications()
            }
        })
        
        return true
    }
    
    
    func tokenString(_ deviceToken:Data) -> String{
        //code to make a token string
        let bytes = [UInt8](deviceToken)
        var token = ""
        for byte in bytes{
            token += String(format: "%02x",byte)
        }
        return token
    }
    
    
    //MARK: - Register Handling
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print ("token -- \n \(tokenString(deviceToken))")
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Error = \(error.localizedDescription)")
    }

    //MARK: - Delegates for Notifications
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let action = response.actionIdentifier
        let request = response.notification.request
        let content = request.content
        if action == "snooze.action"{
            let snoozeTrigger = UNTimeIntervalNotificationTrigger(
                timeInterval: 5.0,
                repeats: false)
            let snoozeRequest = UNNotificationRequest(
                identifier: "pizza.snooze",
                content: content,
                trigger: snoozeTrigger)
            center.add(snoozeRequest){
                (error) in
                if error != nil {
                    print("Snooze Request Error: \(error?.localizedDescription)")
                }
            }
        }
        completionHandler()
    }
}


Basic Push Notifications in iOS 10 and Swift

Push notifications are messages from a  remote server to your device.  You find them in many types of applications: Messaging apps, notifications from social media platforms, weather, news, and sports reporting. Because this information comes from an outside source, there’s a lot more security and scrutiny needed to the messages delivered. There’s also a large concern to keep the data transmitted small: you can at most transmit 4K of data.

In this lesson, I’ll show you how to set up your application to receive a push notification. I’m going to make some  assumptions here: You are using iOS 10 to do this and the User Notification framework. If you are not, there plenty of other tutorials by others on the older methods.  Secondly, I’m going to keep to the client side of this. I’m not going to talk at all about setting up a server or using a pre made server to send your push notifications. We’ll use an online tool to test our notifications.  I’ll also assume is you have a paid developer account and a phone to test with. Push notifications are one of those few things that need a paid account. Push notifications don’t run on the simulator either, so you’ll need a live phone to test.

With that said, let’s look at how push notifications differ from local notifications.

Local Notifications,  APNs, Tokens and Certificates

If you’ve read my piece on local notifications, local notifications are based on UNNotificationRequest objects added to the  current UNNotificationCenter. Requests have three parts, a unique identifier, a trigger and content. A trigger gives the conditions when the notification appears on a user’s device. The content is the text and attachments that  appear in the notification.  You as a developer set up all of this in code. For review, here’s a simple notification code snippet:

// ---- Make content -------
let content = UNMutableNotificationContent()
content.title = "MakeAppPie Pizza Co."
content.body = "Hello, Pizza!!"
content.categoryIdentifier = "pizza.category"
// ----- Use a time interval trigger of 5 seconds, non repeating       
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5.0, repeats: false)

// ----- Add the trigger and the content to the request
let request = UNNotificationRequest(identifier: "pizza", content: content, trigger: trigger)

// Add the request to the current notification center, and notify the developer of errors in the closure. 
UNUserNotificationCenter.current().add(request){
     (error) in
     if error != nil{
         print ("Add notification error: \(error?.localizedDescription)")
     }
}

In a remote notification,  all of that is unnecessary.  There’s a server somewhere pushing data towards your device. However between a user’s device and the server is the Apple Push Notifications Service or APNs. To simplify APNs, this service takes the server’s (or as Apple refers to it provider’s) data, checks if it is legitimate data from a legitimate source and sends a  payload  from APNs to the specific device registered to receive the push notification.

APNs coordinates security and device identity through a certificate key and a token. You as a developer register your app with apple and get an app  certificate, an encoded file that identifies your app as a legitimate app to send notifications to. When you run the application, a token gets generated to identify this specific device to APNs.  The developer sends this certificate and token to the provider of the push notification.  Once the provider has this information, when the logic of the provider indicates a need to push a notification, it sends the token in a payload with the message to APNs, who knows how to handle it.

You can read the official documentation or check out the WWDC 2016 video on this for more detail, but for our purposes that’s what you need to know.

Start a New Project

We’ll start this as a new project in Xcode. Open a new single-view project named PushPizzaDemo in Swift. Save the file. The Project opens to  the settings file. Under General, Change the display name to Push Pizza. This will set the icons on the phone to a nicer caption.

2017-01-02_06-49-05

Next to the General Tab now selected, You’ll find the Capabilities tab. Click that and you’ll find a series of extra services you can turn on. The second on the list is Push Notifications. Click the switch to On.

2017-01-02_06-54-40

This sets the app ready to use push notifications and informs Apple you are doing so in your App ID.

Scroll down a bit further, and you’ll find the Background Modes switch.  Open up that Capability, turn it on and select Remote Notifications.

2017-01-02_06-59-11

This tells the system to allow background processing for remote notifications.

We’ve set up the permissions in Xcode to use notifications, we have two more places to get permission: in code and with the certificate.

Register for Notifications

In order to use remote notifications, you have to tell the system before doing anything else you want them. For this reason, you’ll be coding remote notifications in the app delegate. Open the AppDelegate.swift file  and under the import UIKit add the notification framework:

import UserNotifications

Like any other notification, you must ask for permission before using them. For this app, I’ll ask for permission for the alert, sound and badge. Before return true in the application:didFinishLaunchingWithOptions: method add this.

UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]){
     (granted,error) in
     if granted{
         application.registerForRemoteNotifications()
     } else {
         print("User Notification permission denied: \(error?.localizedDescription)")
     }
            
}

If you’ve made a local notification before, most of this code should look familiar. You request authorization for an alert sound and badge notification. In the closure, you deal with the results of that request. If not granted, you post a message with an error to the console.You can change this code to your wish, possibly adding an alert if the user refuses. What’s different from local notifications   is if you are granted access. The application.registerForRemoteNotifications() method registers the app to recieve notifications from APNs.

There’s two more methods of the app delegate you’ll need to add, both of which you should find in the auto completion of Xcode. Add the first one with a comment.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        //TODO: Add code here later to deal with tokens.
    }

The application:didRegisterForremoteNotificationsWithDeviceToken: method runs if registration is successful. You’ll send the deviceToken in the argument from here to your provider. We’ll get back to that later, so for now add the comment.
The second method
application:didFailToRegisterForRemoteNotificationsWithError runs if the registration is not successful. That could be for lack of internet connection, lack of certificate, or you are running on the simulator which prohibits remote notifications among others. Add the application:didFailToRegisterForRemoteNotificationsWithError to print an error to the console.

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications: \(error.localizedDescription)")
    }

Get a Certificate Key

Your next step is to get the  SSL certificate. For that you’ll have to head over to your apple developer account at https://developer.apple.com/account. As I said earlier, you need a paid account to do the next step. Select the Certificates, Identifiers & Profiles button if it appears.
2017-01-02_07-34-40

Under certificates, Select the APNs Auth Key

2017-01-02_14-55-00

You’ll get a screen like this:

2017-01-02_07-50-25

Click the Certificate Signing Request link. It will ask you for an app ID you want to use. Select the PushPizzaDemo:

2017-01-02_07-51-38

Your next step is creating a certificate request. In you Mac’s applications folder find the Utilities folder. In there, run Keychain Access.

2017-01-03_08-10-00

From the top menu of Keychain Access, select Certificate Assistant> Request a Certificate From a Certificate Authority…

2017-01-02_07-54-47

The certificate assistant appears. Use your email address of your developer id for User Email Address.  Add your name to the common name. Make sure to click the radio button Saved to disk.

2017-01-02_07-55-33

Your file will save to disk, I save mine in downloads.

2017-01-02_07-56-16

Once you have that CSR, go back to the certificate request. choose the file you just downloaded .

2017-01-02_07-57-08

Your certificate will generate and will be ready for download.

2017-01-02_07-58-19

Download it and then double click it to add it to your keychain. Once there go back to keychain access and find the entry for it. open the entry to see the private key.

2017-01-02_14-52-08

You only need the key for what’s ahead. Right click and export the private key.

2017-01-02_14-52-23

For simplicity, I left the password blank, and just pressed OK. For security, add a password.

2017-01-02_14-34-52

Keychain Access will ask you if you really want to do this

2017-01-02_14-35-11

Select Allow, and save the exported file somewhere you can find it.

Back in the developer accounts,  Click App ID’s. Find the XC Comm PushPizza Demo entry and click it. All of the application services appear.

2017-01-03_05-33-51

Toward the bottom of this list You’ll find Push Notifications and under that, an edit button. Click the Edit button, the click the push notification entry.

2017-01-02_14-37-56

You’ll see the results of all the hard work in getting your SSL certificate.  You can generate more here if you need them. I used a production certificate for this example since it can be used for both production and development. You could use a sandboxed development one as well. You need a new certificate for each app, so this isa process you’ll be repeating frequently if working with remote notifications.

Get a Token from APNs

To use a remote notification provider, you need a certificate and a token. The app generates the token with information about the device running the application. It asks APNs some questions, and APNs returns the token. While this is an oversimplification, imagine that the SSL certificate is permission to talk to APNs and the token is where the provider wants to send a message from APNs. Back in Xcode, we stubbed a method earlier that gives us that token.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        //TODO: Add code to get token here
    }

The deviceToken is of type Data. While there might be some systems that accept a type Data token, you may find many, including online test platforms, want a string. You’ll need to convert this into a string of 8-bit hexadecimal numbers. Check with the provider’s documentation on what you need.

I wrote a small function to do this conversion. Add this to your AppDelegate:

func tokenString(_ deviceToken:Data) -> String{
//code to make a token string
    let bytes = [UInt8](deviceToken)
    var token = ""
    for byte in bytes{
        token += String(format: "%02x",byte)
     }
     return token
}

This makes an array of 8-bit unsigned integers, then converts each of those integers into a 2-digit hexadecimal number, which gets concatenated to a token string. I can add this to the application:didRegisterForRemoteNotificationsWithDeviceToken: method to print it out on the console.

print("Successful registration. Token is:")
print(tokenString(deviceToken))

You might send it to your provider though a provider specific add-on library or payload. Check the documentation for how to do that. In this example, I’m cutting and pasting this into a website, so sending to the console is what I want.

Run the Code

For the application, we’ve gotten everything put together in code. The app will run, register a device then return a token to the console. That’s all it needs to do. Connect a device (i used an iPad mini)  with an internet connection to Xcode and set the scheme to run the device. Since we set nothing up on the storyboard, the device will be a pretty boring blank screen. First it will ask for permission to use notifications.

2017-01-03_08-20-47

If you allow, it will print in the console something like

Successful registration. Token is:
3d1864107665c4cc4a55130e0249d3f6aab07a4fde3bf91426a45e7f2f13b4e1

Shut down the app in Xcode. On your device, Go to the home screen.

Test a Payload with an Online Tester

You now have a certificate and token. With both you can send a notification. There’s several ad-supported sites which work the same way. I’ll use pushtry.com due to flexibility in certificates. Load the site and you’ll see several fields for the iOS version

2017-01-03_06-34-14

At the top, you add your certificate by clicking the Choose File button. If you used a password  for the certificate place it in the password field. I didn’t for this very insecure example, so I’ll leave it blank. For the token field, cut and paste the token from the console (don’t use mine – it won’t work).

2017-01-03_06-35-59

The bottom fields show if this is a production system or a developer. Using a production certificate you can do either, so I left it as a development mode.  At the very bottom is two radio buttons switching from text to JSON. APNs prefers JSON, so click JSON, which puts a sample payload in for you.

2017-01-03_06-36-29

Press send. if it all works right, you see a message that the notification sent correctly.

2017-01-03_06-53-01

 

From all time messing in the website, your device probably went sleep.  The push notification will wake it up.

img_0009

If it was still awake, you’ll get a banner, and see a badge on the icon:

img_0010

Tap either, and you’ll launch the application into the foreground, which for us is a bank white screen.

A Note about PEM Certificates

You may have noticed that Pushtry.com takes two types of files PEM and p12.  Many systems and test platforms need you to use PEM. I use Pushtry for this reason: for early testing I can skip a step.  However, you will find a place that needs a PEM, and to get that, you’ll need some command line work. Open up a terminal instance by going to applications>Utilities>terminal.

Go to where you saved the certificate. I try to download these to the desktop to make this step easier.

Go to the desktop, or wherever you saved the certificate.

cd desktop

Run this command, which converts the file.

openssl pkcs12 -in PushPIzzaCertificates.p12 -out pushpizzacert.pem -nodes

Now you can use the PEM. file as well, which you’ll find on your desktop.

Payloads are Your Content

Much of what we just did replaces the trigger in a local notification. Content for a notification is found in the payload. Going back to the testing platform, you’ll find this:

{"aps":{"alert":"Enter your message","badge":1,"sound":"default"}}

For compactness, the white space was removed. When sending payloads avoid whitespace, but they are hard to read this way. It looks better like this:

{
     "aps":{
            "alert":"Enter your message",
            "badge":1,
            "sound":"default"
     }
}

The aps is a JSON dictionary with entries that describe your content. The alert entry is can be a string like it is here, or a dictionary describing the content of the alert that shows on your device. The badge give the number to show on the badge icon. The sound plays the default sound. You can modify this payload to change the content displayed in the alert. As the alert can be both a dictionary or a string you can add more to it. Change the payload to this:

{
     "aps":{
            "alert":{
                    "title":"Push Pizza Co.",
                    "body":"Your pizza is ready!"
             },
                "badge":42,
                "sound":"default"
     }
}

This will add a title and a message about your pizza being ready. It will also change the badge to 42.
Without spaces that’s this

{"aps":{"alert":{"title":"Push Pizza Co.","body":"Your pizza is ready!"},"badge":42,"sound":"default"}}

Change the payload in the tester to the above code and send the notification. You’ll get this

img_0011

The notification appears with the title and body. The badge appears with the number 42.

There’s a lot more you can do with the payload, including categories and setting up action buttons. We’ll discuss all that in the next lesson.

 

The Whole Code

All the code this time was in the app delegate, so I’m only posting a trimmed version with the methods we use. Since there’s so many external parts to push notification, there’s no downloads this time.

//
//  AppDelegate.swift
//  PushPizzaDemo
//
//  Created by Steven Lipton on 1/2/17.
//  Copyright © 2017 Steven Lipton. All rights reserved.
//

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

// Check if you have permission to use notifications. 
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge])
        {(granted,error) in
            if granted{
                application.registerForRemoteNotifications()
            } else {
                print("User Notification permission denied: \(error?.localizedDescription)")
            }
            
        }
        return true
    }
         
 //code to make a token string  
    func tokenString(_ deviceToken:Data) -> String{
        let bytes = [UInt8](deviceToken)
        var token = ""
        for byte in bytes{
            token += String(format: "%02x",byte)
        }
        return token
    }
// Successful registration and you have a token. Send the token to your provider, in this case the console for cut and paste. 
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      
        print("Successful registration. Token is:")
        print(tokenString(deviceToken))
    }
// Failed registration. Explain why.     
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("Failed to register for remote notifications: \(error.localizedDescription)")
    }

}


 

Parsing Strings from Time and Fractions to Doubles

This week’s lesson covers a topic the I left off on last week’s lesson, but can be used outside that context. I thus wanted to cover it separately. Last week I showed you how to use a UIPickerView to input numbers with a lot less validation. We looked at doubles, fractions and time intervals.

2016-12-12_07-33-06  2016-12-12_07-35-25     2016-12-12_07-34-30

You can go here to read the article on how I set that up. I left the article with one deficiency: the values returned from the picker view are strings, not numbers. In this lesson, I’ll show you how to convert the string to the much more useful Double and its alias TimeInerval. Since that’s  something you can use outside the application from last week, I’m presenting it separately. Those who want this  in the picker view should be able to cut and paste these functions into the code from last week.

For this Lesson I’m going to use a swift playground. You can use one in Xcode or on your iPad by downloading this file and loading it into playgrounds: .  I’ve heavily commented that file to explain what’s going on here.

Converting a String to Double.

The easiest of the conversion cases will also be the building block of all the rest: converting a string to a double. In a new playground, add this:

let numberString = "3.1415926"
if let number = Double(numberString){
    print(number)
}

The constructor for type Double has a converter built-in for converting strings to doubles. All you need is Double("3.14") to convert the string to a number – almost. Double returns an optional value of Double if you convert a string, where nil is an invalid string for conversion. for 3.l4l59@6 instead of 3.1415926. returns nil. Before you use the value, you’ll need to unwrap it. For these conversions,  I use either if let or guard to do that, so I can return nil if there is any reason the string is invalid for conversion. Here, I ignore any nil case, but that will change shortly.

Converting Minutes and Seconds to TimeInterval

TimeInterval is a commonly used type, which is really an alias for Double. TimeInterval is different from the other time type Date you’ll often see in cod . TimeInterval is a measure of seconds, independent of a starting and ending time.  Date is a  time based on a reference date. Both have their uses. Apple has the DateFormatter classes and its dateFromString method for handling dates, so I’m not concerned with those as much as TimeInterval. Unlike Date which has a lot of localization issues, almost everyone expresses the measures involved in time intervals constantly  to  hh:mm:ss.ss. The only variations are leaving off the hours or adding days. That means writing a straightforward function is relatively easy. I’ll start even easier and convert a string of minutes and seconds only. Make a new function in the playground of this:

func minutesSecondsInterval(_ timeString:String)->TimeInterval!{
}

We’ll assume that the string timeString looks like mm:ss.ss. If that’s the case, then there’s a very handy string function components(separatedBy:) change the function to this:

func minutesSecondsInterval(_ timeString:String)->TimeInterval!{
    let time = timeString.components(separatedBy: ":")
}

The components(separatedBy:) function creates a string array separated by a colon. if timeString was “12:34.56” time becomes [“12″,”34.56”]. I can take the components of that array and after checking for a non-nil value, add them together to find the number of seconds.

func minutesSecondsInterval(_ timeString:String)->TimeInterval!{
    var time = timeString.components(separatedBy: ":")
    if let seconds = Double(time[1]){
        if let minutes = Double(time[0]){
            return (seconds + (minutes * 60.0))
        }
    }
    return nil
}

If minutes and seconds are true values, I’ll add the seconds to minutes multiplied to 60 seconds. If either are nil, I drop out of the if and return nil.

A Flexible TimeInverval Converter

That’s okay, but not great. If I want hours, I’d need to write a new function. A string with more than one colon gives wrong results. If I put 1:10:12.5, the time array would be [“1″,”10″,”12.5”], adding 1 minute time 60 for 60 seconds and 10 seconds together for 70 seconds, which is wrong. This should be a lot more robust.

Think this out. Where hours, minutes and seconds appear in the array changes depending on the string. Reverse the array elements though, and if they exist, the time component is always in the same position in the array. 10:12.15 is [“12.15”,”10] reversed and 1:10:12.15 is [“12.15″,”10″,”1”] reversed. Seconds is always index 0, minutes index 1, hours index 2, if it exists. I multiply the number of seconds in a minute (60) and the number of seconds in an hour (3600) to the component before I add it to the result. If I have a matching array of those component multipliers, I could do that and put the whole thing in a loop that loops only the length of the array. If I have two components only do minute and seconds, If I have three, do all three. If I find five, return nil, because that’s an error. That all becomes this function:

func timeInterval(_ timeString:String)->TimeInterval!{
    let timeMultipilers = [1.0,60.0,3600.0] //seconds for unit
    var time = 0.0
    var timeComponents = timeString.components(separatedBy: ":")
    timeComponents.reverse()
    if timeComponents.count > timeMultipilers.count{return nil}
    for index in 0..<timeComponents.count{
        guard let timeComponent = Double(timeComponents[index]) else { return nil}
        time += timeComponent * timeMultipilers[index]
    }
    return time
}

I have a constant array timeMultipliers with the multiplier value for the component. This could be expanded to days, if I add another element of 86400.00, but I rarely need that for time intervals. I initialize a value time where I’ll add the components together. I break apart the timeString argument into an array then reverse it with the reverse() method of Array. I check the array if there are more components than I have multipliers for. If there is, it’s an invalid string, and I return nil.

There’s a loop from 0 to the last value in the timeComponents array. I use guard to convert the element in timeComponents to a Double, making sure the value is non-nil. If nil, I return nil. If not nil, I multiply by the multiplier, and add that result to time. When the loop is over, I return the time.

This will work with a value in seconds, seconds and minutes, and hours, seconds, and minutes, returning nil for any invalid answer.

Converting Fractions.

In the picker view, I made input for fractions. Fractions have three components: a whole number, a numerator and a denominator. The double value is the whole number added to the numerator divided by the denominator. In the picker view, I picked a format of w n/d, so thirty-three and a third is a string 33 1/3. This has two separators, a space and a slash instead of the single separator of the time interval. The String method you’ve used so far uses a single character. It also can use a character set. Add this function to your code:

func double(fractionString:String)->Double!{
    let separators = CharacterSet(charactersIn: " /")
    let components = fractionString.components(separatedBy: separators)
}

Before breaking the string apart to an array, you make a list of separators as a CharacterSet, in our case a space and a slash. This breaks the array into three components ["w","n","d"]. So the string “33 1/3” becomes ["33',"1","3"]. This never has a change of format, so I can directly use these values, and assume there are only three components, so check for a count of 3 in the array for validity. Get the components, then do the math to get the double.

func double(fractionString:String)->Double!{
    let seperators = CharacterSet(charactersIn: " /")
    let components = fractionString.components(separatedBy: seperators)
    if components.count == 3{
        if let wholeNumber = Double(components[0]){
            if let numerator = Double(components[1]){
                if let denominator = Double(components[2]){
                    return wholeNumber + (numerator/denominator)
                }
            }
        }
    }
    return nil //failure case
}

Try this out and you’ll get some doubles

One more bug

However, there’s a problem. Try this one:

double(fractionString: "12 0/5")

You should get 12.0 back. You get nil instead.
In cases where we don’t have three components, this doesn’t work. If I had two or one component, I’d like to return just the whole number and ignore whatever is wrong with the fraction. The if let optional chaining presents a problem though. All my calls are local, and make it hard to return just the whole number. This is the beauty of guard. I’ll change this code to use guard, check for the proper number of components and act accordingly.

Make a new function like the first but chage the parameter to (fraction fractionString:String) so we can use it in the playground without duplication complaints from the compiler.

func double(fraction fractionString:String)->Double!{
    let separators = CharacterSet(charactersIn: " /")
    let components = fractionString.components(separatedBy: separators)
}

I’m breaking this into two steps instead of one. I’ll check for components to be in the range of 1 to 3. of it isn’t we have an invalid string and will return nil. I’ll use guard to get a constant number. However since this is within the if clause it’s local, so if successful, I’ll assign to a variable wholeNumber the value of number

var wholeNumber:Double = 0.0
if components.count <= 3 && components.count > 0 {
     guard let number = Double(components[0]) else{
          return nil //invalid whole number
     }
     wholeNumber = number
} else {
     return nil // wrong number of components
}

Anything that survives that first if clause is a valid whole number and there are 1, 2, or 3 elements in the array. If I have 3 elements, as I did in the previous example, I have a mixed fraction, and can find the value of the numerator and denominator once again using guard, return the whole number if the value is invalid. Then I can return the value of the fraction, like I did in the last example.

if components.count == 3{
     guard  let numerator = Double(components[1]) else {return wholeNumber}
     guard  let denominator = Double(components[2]) else {return wholeNumber}
     if denominator != 0{
          return wholeNumber + (numerator/denominator)
     } else {return wholeNumber} //division by zero will result in zero for the fraction
}
return wholeNumber

You’ll notice my other paranoid thing I did. I prevented division by zero, returning the whole number if denominator is zero. I also return wholeNumber if I have only one or two components.

Test this out:

double(fraction: "33 1/3")
double(fraction: "33 0/3")
double(fraction: "33 1/0")
double(fraction: "33")

You get an extra added feature. Since the code converts everything to Double, this works:

double(fraction: "33.1234")

And so does this.

double(fraction: "33.1234 1.1/1.1")

Since it doesn’t harm anything and might be useful in a few places where I might be converting just decimals in one string and fractions in another, I’m leaving this the way it is.

Adding to Last Week’s Project

The rest of this is for those working through last weeks lesson. If you didn’t, you can skip this. If you worked through last week’s post and are wondering how to use this in that code, copy the double(fraction fractionString:String) and timeInterval(_ timeString:String) into the ViewController class of that project:

func double(fraction fractionString:String)->Double!{
        let separators = CharacterSet(charactersIn: " /")
        let components = fractionString.components(separatedBy: separators)
        print (components)
        var wholeNumber:Double = 0.0
        if components.count <= 3 && components.count > 0 {
            guard let number = Double(components[0]) else{
                return nil //invalid whole number
            }
            wholeNumber = number
        } else {
            return nil // wrong number of components
        }
        if components.count == 3{
            guard  let numerator = Double(components[1]) else {return wholeNumber}
            guard  let denominator = Double(components[2]) else {return wholeNumber}
            if denominator != 0{
                return wholeNumber + (numerator/denominator)
            } else {return wholeNumber} //division by zero will result in zero
        }
        return wholeNumber
    }

    func timeInterval(_ timeString:String)->TimeInterval!{
        let timeMultipiler = [1.0,60.0,3600.0] //seconds for unit
        var time = 0.0
        var timeComponents = timeString.components(separatedBy: ":")
        if timeComponents.count > timeMultipiler.count{
            return nil
        }
        timeComponents.reverse()
        for index in 0..<timeComponents.count{
            guard let timeComponent = Double(timeComponents[index]) else { return nil}
            time += timeComponent * timeMultipiler[index]
        }
        return time
    }

In the pickerView:didSelectRow: delegate, change the display to the label to this:

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        resultString = ""
        for index in 0..<components.count{ let digit = components[index][pickerView.selectedRow(inComponent: index)] if digit.characters.count > 1 {//add space if more than one character
                resultString += " " //add space if more than one character
            }
            resultString += digit
        }
        
//--- New Code for displaying doubles ----
        // display results as a string and as a double
        var value:Double! = 0.0
        if segmentedControl.selectedSegmentIndex == 2{
            value = timeInterval(resultString) //time
        }else{
            value = double(fraction: resultString) //fraction or decimal
        }
        displayLabel.text = "\(resultString) is \(value)"

    }

I got sneaky here. I only needed two conversion functions and not three because of that extra added feature of double(fraction:). Time calls the timeInterval function, everything else will call  the double(fraction:) function. Instead of unwrapping the value I used string interpolation to present the value. The resulting string will tell me this is an optional value. For a real app you’ll be doing some more unwrapping of course.

Build and run. For a decimal value you get this:
2016-12-20_08-03-05

For a time you get this:
2016-12-20_08-02-48

Unfortunately, for a string of 3 for the fraction you get this

2016-12-20_08-07-58

But it does work in this case.
2016-12-20_08-09-11

This is a problem with the picker. In the numberPickerComponent function the first element of the x case, "0" is a simple number character with no delimiter, and the string does not get broken:

case "x":
   return ["0","1/16","1/8","3/16",
           "1/4","5/16","3/8","7/16",
          "1/2","9/16","5/8","11/16",
           "3/4","13/16","7/8","15/16"]

Changing "0" to " 0" by adding a space in front is a cheap and easy way to solve that problem.

case x:            
return [" 0","1/16","1/8","3/16",
        "1/4","5/16","3/8","7/16",
        "1/2","9/16","5/8","11/16",
        "3/4","13/16","7/8","15/16"]

Build and run. Now it works.

2016-12-20_08-14-41

 

The process for converting any string into a double is the same. Find the characters that separate components. divide the string, check that the components are valid numbers, then add them to for your final result. As I’ve shown here, that might be different for the format you are using, but the general principles are the same.

The Whole Code

 

Here’s the week’s lesson as a Swift playground, formatted for the iPad playgrounds. Copy and paste into a playground on Xcode or iPad Playgrounds.   You can download and unzip the file here as well: numberstringparser-playground

import UIKit
//: A playground for converting Strings to Doubles (TimeInterval)

/*: #Case 1: A String that looks like number
 Double converts to an optional, where `nil` is the unconvertable value. */

let numberString = "3.1415926"
if let number = Double(numberString){
    print(number)
}

/*: # Case 2: A String that looks like a *mm:ss.ss* time
  - Break into components, using `components(separatedBy:)`
  - Unwrap each component, and add together
  - If anything goes wrong, return `nil` */
func minutesSecondsInterval(_ timeString:String)->TimeInterval!{
    var time = timeString.components(separatedBy: ":")
    if let seconds = Double(time[1]){
        if let minutes = Double(time[0]){
            return (seconds + (minutes * 60.0))
        }
    }
    return nil
}
//: Try this out
minutesSecondsInterval("10:13.6")

/*: # Case 3: A Flexible TimeInterval converter.
 - This has a constant array `timeMultiplier` holding a mutiplier for the number of seconds for the component
 - The function reverses the array with the `reverse()` method so components are alwys in the same position.
 - The function uses a loop to access the correct component and multiply by `timeMultiplier` before adding together.
 */
func timeInterval(_ timeString:String)->TimeInterval!{
    let timeMultipiler = [1.0,60.0,3600.0] //seconds for unit
    var time = 0.0
    var timeComponents = timeString.components(separatedBy: ":")
    if timeComponents.count > timeMultipiler.count{
        return nil
    }
    timeComponents.reverse()
    for index in 0..<timeComponents.count{ guard let timeComponent = Double(timeComponents[index]) else { return nil} time += timeComponent * timeMultipiler[index] } return time } //: Try it out: timeInterval("1:10:13.6") /*: Case 4: Fractions using a slash. - Fractions are strings like **33 1/3** or **w n/h** - Formula is `wholeNumber + (numerator/denominator)` - There are two separators, a space and a slash. Use the `components(separatedBy: separators)` function for a character set, creating a CharaterSet of the separators by `CharacterSet(charactersIn:)` */ func double(fractionString:String)->Double!{
    let separators = CharacterSet(charactersIn: "_/")
    let components = fractionString.components(separatedBy: separators)
       if components.count == 3{
        if let wholeNumber = Double(components[0]){
            if let numerator = Double(components[1]){
                if let denominator = Double(components[2]){
                    return wholeNumber + (numerator/denominator)
                } else {return wholeNumber}//no or incomplete fraction
            } else {return wholeNumber} //no or incomplete fraction.
        }
    }
    return nil //failure case
}
//: Try it out:
double(fractionString: "12 0/0")


/*: # Case 5: A better fraction converter
 - Deals with the bug of a fraction of zero case 3 doesn't.
 - Returns the whole number part if numerator or denominator invalid value. Nil for invalid whole number.
 - All values are doubles so *22.5 10.2/2.5* will return a correct decimal value of 26.58
 */
func double(fraction fractionString:String)->Double!{
    let separators = CharacterSet(charactersIn: " /")
    let components = fractionString.components(separatedBy: separators)
    var wholeNumber:Double = 0.0
    if components.count <= 3 && components.count > 0 {
        guard let number = Double(components[0]) else{
            return nil //invalid whole number
        }
        wholeNumber = number
    } else {
        return nil // wrong number of components
    }
    if components.count == 3{
        guard  let numerator = Double(components[1]) else {return wholeNumber}
        guard  let denominator = Double(components[2]) else {return wholeNumber}
        if denominator != 0{
            return wholeNumber + (numerator/denominator)
        } else {return wholeNumber} //division by zero will result in zero
    }
    return wholeNumber
}
double(fraction: "33 1/3")
double(fraction: "33 0/3")
double(fraction: "33 1/0")
double(fraction: "33")
double(fraction: "33.1234")
double(fraction: "33.1234 1.1/1.1")

Data Entry with UIPickerView

The keyboard can be a curse. Trying to validate data via keyboard can be a nightmare for many projects. One solution to this problem is using a UIPickerView to limit the values the user can enter. In this lesson, we’ll explore the UIPickerView for numerical input.

UIPickerViews are are series of wheel like scrolling objects. For more on the basics, you can read this article on them. They are made of series of components which contain rows of strings. Picker views use delegates and data sources for most of their functionality instead of actions. More often than not I see people using them totally and completely wrong. Often they are lazy man’s table views or a control exploited to pack more information than a user can handle. What they are good at is giving one pice of data with multiple parts. The digits of a number for example, or Apple’s subclass of UIPickerView UIDatePickerView, which returns a date.

Set Up the Project

For this project you’ll find a started file  advanced-picker-demo-_startwhere I added a label, picker view and a segmented control to a storyboard view controller.

2016-12-12_07-29-51

I gave some specific titles on the segmented control, 99.9, 999.9,59:59.99, and 99x, which I’ll discuss in a moment. I added an outlet for the picker and label, and an action and outlet for the segmented control.

 @IBOutlet weak var displayLabel: UILabel!
 @IBOutlet weak var picker: UIPickerView!
 @IBOutlet weak var segmentedControl: UISegmentedControl!
 @IBAction func segmentedControl(_ sender: UISegmentedControl) {
 }

Again you can find this in the starter file advanced-picker-demo-_start if you wish.

Setting Up a Picker View.

Picker views don’t have actions, but delegates and data sources, just like table views. Adopt the delegate and data source in the class declaration.

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource

Add two properties to the view controller class. components will hold the components for the UIPickerView and resultString will be the string that the picker view outputs.

var components = [[String]]()
var resultString = ""

The components property is an array of string arrays. The outer array is each digit of the picker, called a component. The inner array is the string title for a given row in the the component. The two data sources tell the picker how many elements are in the outer and inner arrays. For the outer array, Add the following:

 //:MARK - Delegates and data sources
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return components.count
    }
 

For the number of rows in each inner array, add the following:

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return components[component].count
    }

There’s two  more or less mandatory delegate methods for picker views . One we’ll add now, the other a bit later. Add the following to show the titles in the picker view

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return components[component][row]
}

Adding Component Content

The delegate and data sources you just added are pretty generic. The content to the components property are where the magic happens. Add the following function that returns a string array. We’ll use it to set a component on the picker view.

func numberPickerComponent(from char:Character) -&gt; [String]{
    switch char{
        case "9":
            return ["0","1","2","3","4","5","6","7","8","9"]
        case "5":
            return ["0","1","2","3","4","5"]
        case "x":
            return ["0"," 1/16","1/8","3/16","1/4","5/16","3/8","7/16","1/2","9/16","5/8","11/16","3/4","13/16","7/8","15/16"]
        default:
            return [String(char)]
    }
}

This function returns an array of strings that starts at 0 and goes to the digit indicated. You could make a case for all the numbers between 0 a 9, but I only picked a few for the purposes I have in mind. Most decimal numbers will be 0 through 9. Time functions will need 0 to 5 for minutes and seconds. The x will stand for fractions. I set my fractions to increments of 1/16 to 15/16. The default returns a string of a single character for all other cases.

With these four cases, you can create formats in your picker from a control string. I stuck those control strings in the segmented control, but you could assign them directly of course. A string of 99.9 is a value from 0 to 99.9. A string of 99x is a string of 0 to 99 15/16. While the UIDatePickerView gets times, if you need a TimeInterval for values with less than second, you’ll find it difficult. 59:59.99 give us times from 0 seconds to 59 minutes 59.99 seconds or 3599.99 seconds.

Add the function to take a string and make it into an array of components. This iterates over the string, calls the numberPickerComponent function you just defined to grab an array, and adds that array to the components array. Once assembled, the function returns the completed array.

 func numberPickerComponents(from string:String)->[[String]]{
        
        var components = [[String]]()
        for char in string.characters{
            components += [numberPickerComponent(from:char)]
        }
        return components
    }

Since I’m changing the pickerComponents in a segmented control, on a change of segment, the code updates the picker. It grabs the string for the segment’s titles and makes that the components. Add this:

    @IBAction func segmentedControl(_ sender: UISegmentedControl) {
        let index = sender.selectedSegmentIndex
        let pickerComponentString = segmentedControl.titleForSegment(at: index)!
        components = numberPickerComponents(from: pickerComponentString)
        resetPicker()
            }

This function calls a function we haven’t defined yet, resetPicker. There’s two jobs for the resetPicker function. One is to reload the components of the picker so the correct ones are there. the reloadAllComponenets method of UIPickerView does that. The second can be a bit tricky. The wheels will be inaccurate if you leave the setting from one format to another. I took the simplest approach and zero the components with a loop that selects the first element in each component. The selectRow:inComponenet:Animated: method can animate the change, which I set to true to roll the components back to zero. Add this to your code:

 func resetPicker(){
     picker.reloadAllComponents()
     for index in 0..<components.count{
       picker.selectRow(0, inComponent: index, animated: true)
     }
 }

This code initializes all this in viewDidLoad, setting the delegate and data source to self, and setting the initial segement control, picker view and components to the first segment. Add this to viewDidLoad:

override func viewDidLoad() {
   super.viewDidLoad()
   picker.dataSource = self
   picker.delegate = self
   segmentedControl.selectedSegmentIndex = 0
   let pickerComponentString = segmentedControl.titleForSegment(at: 0)!
   components = numberPickerComponents(from: pickerComponentString)
}

That sets the picker to display correctly. We’d like some output, in this case a string we’ll parse later. The last delegate function is the selection function. I’ll output the selection to the label. Add this to your code where you placed your data sources.

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        resultString = ""
    
        displayLabel.text = resultString
    }

This is another tricky spot. The parameters of the selection method only gives us the component that changed, not all the components. There’s a picker view method selectedRow(inComponent:) which can get us the index of any component. I loop through all the components and concatenate each to resultString. Add this below resultString:

for index in 0..<components.count{
    let digit = components[index][pickerView.selectedRow(inComponent: index)]
    resultString += digit
}

This almost works. It still has a bug. If using fractions, this code will make 33 1/3 be 331/3. For cases where the component is larger than one character, add an extra space. Add this under the let digit assignment:

if digit.characters.count > 1 {
    resultString += " " 
}

Build and run. The picker view will look like this.

2016-12-12_07-31-26
You can dial a number of 3.1.

2016-12-12_07-32-14
Set the segmented control to 999.99. Dial up 203.14.
2016-12-12_07-33-06

Go to the time of 59:99.00, put a time of 12:54.37 and you get this
2016-12-12_07-34-30
Finally click on 99X for the fractions and try 12 3/8
2016-12-12_07-35-25

You get the values showing up as a string in the title. However you may not want a string for any of these, but a double instead. We’ll need to parse the string into a number. In next week’s lesson, we’ll tackle that part of the picker.

The Whole Code

You can find the finished lesson here for download:advanced-picker-demo.

//
//  ViewController.swift
//  Advanced Picker Demo
//
//  Created by Steven Lipton on 12/12/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UIPickerViewDelegate,UIPickerViewDataSource {
    
    var components = [[String]]()
    var resultString = ""
    
    
    @IBOutlet weak var displayLabel: UILabel!
    @IBOutlet weak var picker: UIPickerView!
    @IBOutlet weak var segmentedControl: UISegmentedControl!

    @IBAction func segmentedControl(_ sender: UISegmentedControl) {
        let index = sender.selectedSegmentIndex
        let pickerComponentString = segmentedControl.titleForSegment(at: index)!
        components = numberPickerComponents(from: pickerComponentString)
        resetPicker()
    }
    
    func numberPickerComponent(from char:Character) -> [String]{
        switch char{
        case "9":
            return ["0","1","2","3","4","5","6","7","8","9"]
        case "5":
            return ["0","1","2","3","4","5"]
        case "x":
            return ["0"," 1/16","1/8","3/16","1/4","5/16","3/8","7/16","1/2","9/16","5/8","11/16","3/4","13/16","7/8","15/16"]
        default:
            return [String(char)]
        }
    }
    
    func numberPickerComponents(from string:String)->[[String]]{
        
        var components = [[String]]()
        for char in string.characters{
            components += [numberPickerComponent(from:char)]
        }
        return components
    }
    func resetPicker(){
        picker.reloadAllComponents()
        for index in 0..<components.count{ picker.selectRow(0, inComponent: index, animated: true) } } override func viewDidLoad() { super.viewDidLoad() picker.dataSource = self picker.delegate = self segmentedControl.selectedSegmentIndex = 0 let pickerComponentString = segmentedControl.titleForSegment(at: 0)! components = numberPickerComponents(from: pickerComponentString) } //:MARK - Delegates and data sources func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return components.count
    }
    
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return components[component].count
    }
    
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return components[component][row]
    }
    
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        resultString = ""
        for index in 0..<components.count{ let digit = components[index][pickerView.selectedRow(inComponent: index)] if digit.characters.count > 1 {//add space if more than one character
                resultString += " " //add space if more than one character
            }
            resultString += digit
        }
        displayLabel.text = resultString
    }

}

Add Actions and Categories to Notification in Swift

In  earlier lessons I’ve shown you how to make a notification and how to manage notifications with the UserNotifications frame work. One very exciting part of the frame work is executing  the app’s code in the background. You don’t have to open the app to do custom actions or even to input data. Using categories and actions you can build code that does that directly from the notification. In this lesson I’ll show you how to use this powerful feature.

Categories, Actions and Delegates — Oh my!

Notifications have two special objects called categories and actions.  Actions are controls added to a notification, usually a button. Categories are a set of actions we can link to the content of a notification. You can mix and match categories and actions as much as you wish.

Actions have no executable code. Instead you specify in a method of UNUserNotificationCenterDelegate  the code based on an identifier  in the action.

This makes for a very flexible system for adding small bits of code to a application. There are time and memory limits on the code, so keep it short and simple.

Setting up the Demo

Let’s set up an example application to show  a very simple alarm application. The alarm will time for 10 seconds, then display an alarm and sound a sound. We’ll add two actions to this alarm: one as a five second snooze button and the other to place a comment on the notification.  You’ll find a starter file here notificationcategorydemo_start if you want to skip this section.

Open a new single view project in Xcode called NotificationCategoryDemo. Make it a Swift Application with a Universal device.

Go to the storyboard. Add a label and a button. I’m not going to get fancy here, but I set up my button and label like this:

2016-12-01_06-17-19

I used the Title 1 font.  I suggest changing the attributes of the label. Set the Lines to 0 and Line Break to Word Wrap. This way, long text entries will word wrap by themselves.
2016-12-02_06-21-08
Open   to the assistant editor and control-drag from the button to the code to make an IBAction named StartButton.  Control drag from the label to the code and make a IBOutlet named commentsLabel.   Close the assistant editor and go to Viewcontroller.swift.

I’m going to go fast here and just give you the code. I’ll assume you’ve read the post on how to make a user notification. Under import UIKit, add the following:

import UserNotifications 

Add the following properties and constants to the ViewController class

let time:TimeInterval = 10.0
let snooze:TimeInterval = 5.0
var isGrantedAccess = false

Set up the required authorization check. Change viewDidLoad to this:

    override func viewDidLoad() {
        super.viewDidLoad()
      UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert,.sound,.badge],
            completionHandler: { (granted,error) in
                self.isGrantedAccess = granted
                if granted{
                    self.setCategories()
                } else {
                    let alert = UIAlertController(title: "Notification Access", message: "In order to use this application, turn on notification permissions.", preferredStyle: .alert)
                    let alertAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
                    alert.addAction(alertAction)
                    self.present(alert , animated: true, completion: nil)
                }
        })

Create a function setCategories which we’ll use in the lesson and resolve the error in viewDidLoad

func setCategories(){
}

Create a function addNotification which simplifies adding notifications to the rest of the code. I particularly hate retyping that error handler closure a zillion times.

func addNotification(
    content:UNNotificationContent,
    trigger:UNNotificationTrigger?,
    indentifier:String)
{
    let request = UNNotificationRequest(
        identifier: indentifier, 
        content: content, 
        trigger: trigger)
    UNUserNotificationCenter.current().add(request,
        withCompletionHandler: { (errorObject) in
            if let error = errorObject{
                print("Error \(error.localizedDescription) in notification \(indentifier)")
            }
        }
     )
}

We’ll add the notification from the  Start button. Change the startButton method to this:

@IBAction func startButton(_ sender: UIButton) {
    if isGrantedAccess{
        let content = UNMutableNotificationContent()
        content.title = "Alarm"
        content.subtitle = "First Alarm"
        content.body = "First Alarm"
        content.sound = UNNotificationSound.default()
        let trigger = UNTimeIntervalNotificationTrigger(
             timeInterval: time,
             repeats: false)
        addNotification(
             content: content, 
             trigger: trigger , 
             indentifier: "Alarm")
        }
    }

I did add one new type of content I haven’t talked about before. If you specify a value in the sound property of your notification content and grant permission for using a sound, sounds will play during your notification. I’m using the default sound available at UNNotificationSound.default().

I’ll use in-app notification in this application, and you’ll need the delegate anyway for actions. Add the UNUserNotificationDelegate to the ViewController class:

class ViewController: UIViewController,
     UNUserNotificationCenterDelegate {

Set the delegate to self in viewDidLoad

UNUserNotificationCenter.current().delegate = self

Then add to your code the userNotificationCenter(willpresent notification:...) method.

// MARK: - Delegates
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert,.sound])
    }

Be certain to add .sound to hear the sound in the in-app notification.

The setup is complete. You can download a starter file by clicking  notificationcategorydemo_start

Adding Categories and Actions

Categories and actions must register with the system before you make any notifications. Generally you do that in viewDidLoad. If you look at the code there,  you’ll find I set a function call to setCategories if notification access is granted. You’ll add the categories and actions in setCategories.

Actions are contained in categories. Add actions first then categories. Actions are objects of class UNNotificationAction. You use a constructor for UNNotificationAction to make an actions. Add this to the setCategories function.

func setCategories(){
    let snoozeAction = UNNotificationAction(
        identifier: "snooze",
        title: "Snooze 5 Sec",
        options: [])

There’s three parameters here. The identifier is a unique string to identify the action in the delegate. The title is the button’s title when the actions display on the device. The options parameter are a list of options you can change the behavior or look of the button, such as a destructive button or forcing authentication before carrying out an action. Leave this blank to keep things simple.

Add this action to a category which we’ll call alarmCategory. Categories are UNNotificationCategory objects, and like UNNotificationAction, have a constructor. Add this to your code:

let alarmCategory = UNNotificationCategory(
    identifier: "alarm.category",
    actions: [snoozeAction],
    intentIdentifiers: [],
    options: [])

There are four parameters for this constructor. There is a unique identifier which you’ll use to link the category to a notification’s content. The actions parameter is an array of actions associated with in this category. You can have several categories which mix and match actions differently for different notifications or different contexts for a single notification. The parameter intentIdentifiers is a Siri thing and that’s way beyond the scope of this lesson, so leave it blank. Finally, there are options for a custom dismiss action and allowing CarPlay to use the actions. Again, I left the options blank.

The final step is to register the categories in the current UserNotificationCenter. You specify a set of categories the system should know. Add this code.

UNUserNotificationCenter.current().setNotificationCategories([alarmCategory])

Since we have only one category, this is a very simple list.

Using Categories in Notifications

Having your categories set up, add them to your notification content. The UNMutableNotificationContent  has a property categoryIdentifier. In the startButton method, specify the category identifier in your content.

content.categoryIdentifier = "alarm.category"

You are almost set to run your code. Set your simulator to iPhone 6. There’s one feature of the simulator that rides close to that thin line of being a feature or a bug. On anyone not using a 3D touch trackpad it is a bug from my perspective. The simulator assumes you have 3D Touch on your Mac if you simulate devices that have 3D touch. Accessing notification actions requires 3D touch on those devices in the simulator. Setting your simulator to an iPhone 6 lets any Mac running the simulator access the actions.

Build and run. The app appears.

2016-12-02_07-38-33

Press the Start button. Wait ten seconds. The notification appears.

2016-12-02_07-39-19

 

You can swipe down from the notification and see the action, but it does nothing. There’s no code for it do anything.

2016-12-02_07-40-31

Make an Action Do Something

It is the user notification center’s delegate that does the heavy lifting. The userNotficationCenter(didReceive response: completionHandler:) method is based on the actionIdentifier you defined earlier. The delegate method executes a bit of code to handle that action.

Where you keep your delegates in your code, add this.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
}

Before you do anything else, add the completionHandler to the bottom of the code. In this method, the system uses the closure, but at the end of your code you have to call it. I try to add it first so I don’t forget it.

completionHandler()

The UNNotificationResponse object has two properties: the actionIdentifier of the action that fired, and the delivered notification. I tend to be most interested in the request, so make two constants to work with these values easily.

let identifier = response.actionIdentifier
let request = response.notification.request

The identifier is a string I can compare to the identifier names I defined in the setCategories method. So to execute actions for the snooze action identifier, add an if clause

        
if identifier == "snooze"{
}

Inside the if clause, add code to do the actions when the user presses the Snooze 5 Sec button. For this demo, I’ll make a notification that fires five seconds later. You need mutable content from the request. Add this inside the if clause:

let newContent = request.content.mutableCopy() as! UNMutableNotificationContent

By copying the old request’s content, all content is set up. You just have to change the subtitle and body like this:

newContent.body = "Snooze 5 Seconds"
newContent.subtitle = "Snooze 5 Seconds"

The trigger will change from 10 seconds to 5 seconds. Define a new trigger method.

let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: snooze, repeats: false)

Send the new trigger and content. Add a notification using the addNotification method you defined earlier.

addNotification(content: newContent, trigger: newTrigger, indentifier: request.identifier)

Build and run. Tap the start button and wait. When the notification appears, swipe down to see the button. Tap the snooze button, and five seconds later, the snooze notification appears.

2016-12-02_07-50-03

 

Since we copied the content, you can swipe down again, and snooze as many times as you like.

2016-12-02_07-52-22

Add Text Input

There’s a subclass of UNNotificationAction you’ve used if you replied to a text message in a notification. There is a text input action in notifications. Adding text input, with a few variations, is the same as adding any other action.

There’s two constructors for the UNTextinputNotificationAction object. The shorter has the same parameters as the UNNotificationAction. The longer of the two adds two parameters for placeholder text and the submit button title. Use that one for this project, adding this to setCategories, between the snoozeAction and the alarmCategory.

 let commentAction = UNTextInputNotificationAction(identifier: "comment", title: "Add Comment", options: [], textInputButtonTitle: "Add", textInputPlaceholder: "Add Comment Here")

Add the action to the category. Change the actions parameter for the alarmCategory from [snoozeAction] to [snoozeAction,commentAction].

let alarmCategory = UNNotificationCategory(identifier: "alarm.category", actions: [snoozeAction,commentAction], intentIdentifiers: [], options: [])

In the delegate method usernotificationCenter( didReceive response: completionHandler:) add another if clause:

if identifier == "comment"{
}

The delegate method thinks that response is a UNNotificationResponse, not a UNTextInputNotificationResponse. In the if clause, downcast the response to the proper type.

let textResponse = response as! UNTextInputNotificationResponse

There’s one extra property on a UNTextInputNotificationResponse: a string named userText. Send that string to the label in the app:

commentsLabel.text = textResponse.userText

Make new notification that will include your comment. Use the body in the notification for your comment:

let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
newContent.body = textResponse.userText
addNotification(content: newContent, trigger: request.trigger, indentifier: request.identifier)

Build and run. Press Start, then Command-L to lock the screen. Ten seconds later the notification appears.

2016-12-02_07-58-22

Swipe to the left, and two buttons appear.

2016-12-02_07-58-40

Tap View. the actions appear.

2016-12-02_07-58-59

Tap Add Comment. The keyboard should appear in the simulator. If it does not press Command-K.

2016-12-02_07-59-20

Type a comment then tap Add.

2016-12-02_08-00-25

Wait and the notification appears with your comment. Body text can be as long as you want, as long as it is one character. An empty body hides the notification.

2016-12-02_08-00-56

Tap the notification, open the phone with command-shift-H and app title has changed.

2016-12-02_08-01-18

 

Actions on Apple Watch

It’s not that difficult to add actions to a notification. When you have some code you want to execute without opening the app, it is extremely useful to use actions. As an added benefit, if you create an action on your iPhone app’s notification, any user with an Apple Watch will get both the notification and the actions on their watch. When the phone if sleeping or locked, the notification will go to the watch, with the actions below the notification.

img_7915

The text action  in Add Comment uses the text input system of the watch, so you can dictate, scribble, use emoji or your quick phrases.

img_7913

Tapping a text action will run the code on your phone for the notification. You’ll see the notification on your watch

img_7917

And the text label on the phone changes.

img_7918

You never have to pull your phone out of your pocket or bag to respond with actions. With not that much code, you get a lot of performance with notification actions.

 

The Whole Code

There is a download of the completed project here: notificationcategorydemo.zip

//
//  ViewController.swift
//  NotificationCategoryDemo
//
//  Created by Steven Lipton on 12/2/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit
import UserNotifications

class ViewController: UIViewController,UNUserNotificationCenterDelegate {
    
    //MARK: Properties and outlets
    let time:TimeInterval = 10.0
    let snooze:TimeInterval = 5.0
    var isGrantedAccess = false
    @IBOutlet weak var commentsLabel: UILabel!
    //MARK: - Functions
    func setCategories(){
        let snoozeAction = UNNotificationAction(identifier: "snooze", title: "Snooze 5 Sec", options: [])
         let commentAction = UNTextInputNotificationAction(identifier: "comment", title: "Add Comment", options: [], textInputButtonTitle: "Add", textInputPlaceholder: "Add Comment Here")
        let alarmCategory = UNNotificationCategory(identifier: "alarm.category",actions: [snoozeAction,commentAction],intentIdentifiers: [], options: [])
        UNUserNotificationCenter.current().setNotificationCategories([alarmCategory])
    }
    
    func addNotification(content:UNNotificationContent,trigger:UNNotificationTrigger?, indentifier:String){
        let request = UNNotificationRequest(identifier: indentifier, content: content, trigger: trigger)
        UNUserNotificationCenter.current().add(request, withCompletionHandler: {
            (errorObject) in
            if let error = errorObject{
                print("Error \(error.localizedDescription) in notification \(indentifier)")
            }
        })
    }
    
    //MARK: - Actions
    @IBAction func startButton(_ sender: UIButton) {
        if isGrantedAccess{
            let content = UNMutableNotificationContent()
            content.title = "Alarm"
            content.subtitle = "First Alarm"
            content.body = "First Alarm"
            content.sound = UNNotificationSound.default()
            content.categoryIdentifier = "alarm.category"
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: time, repeats: false)
            addNotification(content: content, trigger: trigger , indentifier: "Alarm")
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        UNUserNotificationCenter.current().delegate = self
        UNUserNotificationCenter.current().requestAuthorization(
            options: [.alert,.sound,.badge],
            completionHandler: { (granted,error) in
                self.isGrantedAccess = granted
                if granted{
                    self.setCategories()
                } else {
                    let alert = UIAlertController(title: "Notification Access", message: "In order to use this application, turn on notification permissions.", preferredStyle: .alert)
                    let alertAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
                    alert.addAction(alertAction)
                    self.present(alert , animated: true, completion: nil)
                }
        })
    }
    
    // MARK: - Delegates
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert,.sound])
    }
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let identifier = response.actionIdentifier
        let request = response.notification.request
        if identifier == "snooze"{
            let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
            newContent.body = "Snooze 5 Seconds"
            newContent.subtitle = "Snooze 5 Seconds"
            let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: snooze, repeats: false)
            addNotification(content: newContent, trigger: newTrigger, indentifier: request.identifier)
            
        }
        
        if identifier == "comment"{
            let textResponse = response as! UNTextInputNotificationResponse
            commentsLabel.text = textResponse.userText
            let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
            newContent.body = textResponse.userText
            addNotification(content: newContent, trigger: request.trigger, indentifier: request.identifier)
        }
        
        completionHandler()
    }
}

 

 

Where is Update Frames in Xcode 8.1?

A tech author’s work is never done. As soon as he or she completes manuscript and gets it published, the manuscript almost immediately becomes obsolete. In my case, Practical Autolayout for Xcode 8 went obsolete  a day before I published, but I had no idea about a major change in Xcode 8.1.

Until Xcode 8.1, if you wanted to update a frame with new constraints, you had two possibilities. The first was in the pinpinMenuButton and align alignment iconmenu to update as you were setting the constraints.

2016-11-29_06-05-32

The second was a selection in the resolver resolver button 2016-10-01_13-27-48

It seems everyone, including me was not ready for a change Apple made in Xcode 8.1. If you go to look for Update Frames in the resolver resolver button, it is missing:

2016-11-29_06-11-52

So where did it go?

Apple moved this to an icon on the auto layout toolbar and deleted it from the menus.

2016-11-28_07-29-22

If it were me, I wouldn’t have deleted it from the menus in such an abrupt way. Apple did. This Update Frame button  has some different behaviors  from its predecessor on the menu, and I’d like to explain that using some examples from Chapter 3 of Practical Autolayout for Xcode 8

Set up a storyboard that looks something like this with a label Hello Pizza, a text view, and  three  buttons, Pepperoni, Cheese, and Done:

2016-11-29_05-54-14

Select the Hello Pizza label.  Click the pin buttonpinMenuButton in the auto layout toolbar. In the popup, set the top to 0 points, the left to 0 points and the left to 0 points.  Leave Update Frames as None

2016-11-29_05-56-06

Add the 3 constraints. The Hello Pizza Label will show misplacement constraints.

2016-11-29_05-56-32

Press the Update Frames button update frames  and the frame updates.

2016-11-29_06-37-41

This is not always the result. You must have all constraints satisfied before the button will update frames. For example, select the text view. Press the align button alignment iconand center the text view by checking on Horizontally in Container and Vertically in Container.

2016-11-29_05-54-38

Again don’t update frames, but click  Add 2 constraints. You’ll see an ambiguous constraint in red.

2016-11-29_05-55-13

If you click the update frames button nothing happens. Until a frame has no ambiguity(i.e. no red constraint errors), you cannot update it. Most often that is setting a size. For the text box, set an absolute size in the pin menu pinMenuButton  of 175 points in both directions.

2016-11-29_05-57-41

Add the constraints. The errors all turn to misplacements.

 2016-11-29_05-58-39

Once all misplacements, you can update the frame with update frames.

2016-11-29_07-15-35

Priorities are not assumed with the new update frames button. When there is an ambiguity in size between two frames that depend on each other for size, you must specify the a priority for them or set a size.  Take for example these two buttons.

2016-11-29_05-45-35

Pepperoni is pinned to the left margin, the label above it and the text view below it. Cheese is pinned 10 points from Pepperoni, aligned to the top of Pepperoni, and pinned 10 points from the right margin. We’d like to have two buttons that fill the available space.

The option used in Practical Auto Layout for these buttons is to make them the same size. Control drag from Pepperoni to Cheese. A menu appears.

2016-11-29_06-56-57

Shift select Equal Width and Equal Heights, then hit the Add Constraints selection. The ambiguity changes to misplacements.

2016-11-29_06-57-14

Select both the Pepperoni and Cheese buttons. Hit the Update Frame button update frames and two equally sized buttons appear

2016-11-29_06-58-00

The other, more advanced option is to change priority of one of the buttons so they are not equal. Both are by default 250.  Going back to the original ambiguous layout,

2016-11-29_05-45-35

changing the content hugging priority of Pepperoni from 250 to 251 tells auto layout for Pepperoni to keep its size and Cheese to stretch to make up the difference.

2016-11-29_06-56-19

Priorities are covered in detail in Chapter 12 of Practical Autolayout for Xcode 8.

I’ll be updating the book shortly. Until then or if you cannot update your book,  consider this an errata to the versions now available.

practical-autolayout-x8-newsletterPurchase the book for  Kindle and iTunes  here:

get_it_on_ibooks_badge_us_1114

Basic Tap, Pinch and Rotate Gestures

Many controls such as table views, map views, scroll views and buttons use gestures. You as a developer might want to use a gesture for your own purposes outside of these controls. The gesture recognizer classes can do that. The standard ones such a as tap, pinch and rotate are rather easy to set up, either by storyboard or by code. In this tutorial, I’ll show you how to set them up in code.

Set Up the Project

Make a new  single view project called TapPinchDemo in Swift with a Universal device.  Go to the storyboard. Add a label to the upper left of the storyboard. I used auto layout to pin the label 10up, 10 left and 10 right. Change the label’s font to Title.

Open the assistant editor.  Control drag from the label to the code and make an outlet named statusLabel.  Close the assistant editor and go to the ViewController.swift code. Add a constant at the beginning of the ViewController class for a color.

 let background = UIColor(red: 1.0, green: 0.98, blue: 0.96, alpha: 1.0)

In viewDidLoad add this color as the background.

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = background
}

Tap Gestures

There are three parts to a gesture: Configuring a gesture recognizer object, making an action for the object, and adding it to the view.  For the first gesture, you’ll make a tap gesture.  In viewDidLoad add the following line to make a tap gesture:

 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAction(sender:)))

There’s two parameters in the constructor. Both indicate the location of an action that will be called when the gesture occurs. the first give the class, and the second the selector. selectors are functions called by the parameters of another function. While really a Objective-C thing, Swift had a compiler tag #selector() to indicate a selector. In the tap gesture tapAction(selector:) is the function called.

You’ll notice an error. Once you use a #selector, you must implement the function.  Add the function tapAction
above viewDidLoad

func tapAction(sender:UITapGestureRecognizer){
}

We’ll come back in a minute to finish filling out this code. Go back to viewDidLoad and configure tapGesture.

tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1

This sets the gesture recognizer to one tap with one finger. Add the gesture recognizer to the view using the addGestureRecognizer method.

view.addGestureRecognizer(tapGesture)

Go back up to the function tapAction. Gestures have states. You must check the state before you do anything. Specific states are necessary for specific gestures to work. For a tap, the state must be .ended. Add this code to the tap action selector:

if sender.state == .ended{
    statusLabel.text = "Tapped"
    view.backgroundColor = background
}

Build and run. Tap the screen and the label reads tapped.

2016-11-28_05-51-41

Pinch Gesture

The pinch gesture is a movement of two fingers moving towards or away from eacth other. Pinch gestures have no properties you need to set. Adding them is even easier than a tap. Just call the UIPinchGestureRecognizer constructor and add the gesture recognizer to the view. Add this to viewDidLoad.

 let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(sender:)))
        view.addGestureRecognizer(pinchGesture)

The action specified is a bit more complex than the tap. You have three states you might want to look at: .began, .changed, and .ended. Most often your concern is the changed state which occurs when a user moves his or her fingers. But you might find uses for beginning and ending a pinch. Add the following method to the ViewController code.

func pinchAction(sender:UIPinchGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Pinch Began"
            view.backgroundColor = UIColor.yellow
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"Pinch scale: %1.3f",sender.scale)
        }
        if sender.state == .ended{
            statusLabel.text = "Pinch Ended"
            view.backgroundColor = background
        }
    }

Pinches are examples of continuous gestures. These are gestures which will have more than one state to watch for. This changes the background when you have started the pinch and restores when you finish the pinch. When you change the value, it shows the scale value for the gesture. Scale is a value that shows the change between the user’s first pinch and the movement of the user’s fingers. Scale is the value you use in your applications. Build and run. Try pinching the screen. In the simulator, hold down the option key and drag on the trackpad or mouse.

Rotation Gesture

The last gesture we’ll discuss is rotation. Rotations set up just like pinches, except they use a UIRotationGestureRecognizer constructor. Add this to viewDidLoad:

let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotateAction(sender:)))
view.addGestureRecognizer(rotateGesture)

And add this function for the action. Like pinch, rotate is a continuous gesture, so I’ll set it up similar to  pinchAction.

func rotateAction(sender:UIRotationGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Rotate Began"
            view.backgroundColor = UIColor.cyan
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"rotation: %1.3f",sender.rotation)
        }
        if sender.state == .ended{
            statusLabel.text = "Rotate Ended"
            view.backgroundColor = background
        }
    }

The code will turn the background cyan when the user starts a rotate action. The display will give the rotation angle in radians using the property of a rotator gesture rotation. When the rotation ends with  the user removing fingers, the background returns to the default background color. Build and run. To simulate a rotation click down on the simulator using the mouse and then press Option(it’s annoyingly tricky). Move the cursor and you will have a rotation gesture.

This is only the beginning to using gestures, but should give you a good foundation for other gestures. While I most often use code to add gestures, you can also add then by dragging them onto the storyboard. Once added, you find them in the document outline, where you can control drag them to the code like any other object and make an action. Gestures my also conflict with each other, so try to keep the number in a single view to a minimum.

The Whole Code

//
//  ViewController.swift
//  tapSwipeDemo
//
//  Created by Steven Lipton on 11/25/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var statusLabel: UILabel!
    let background = UIColor(red: 1.0, green: 0.98, blue: 0.96, alpha: 1.0)
    func tapAction(sender:UITapGestureRecognizer){
        
        if sender.state == .ended{
            statusLabel.text = "Tapped"
            view.backgroundColor = background
        }
    }
    func pinchAction(sender:UIPinchGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Pinch Began"
            view.backgroundColor = UIColor.yellow
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"Pinch scale: %1.3f",sender.scale)
        }
        if sender.state == .ended{
            statusLabel.text = "Pinch Ended"
            view.backgroundColor = background
        }
    }
    
    func rotateAction(sender:UIRotationGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Rotate Began"
            view.backgroundColor = UIColor.cyan
        }
        if sender.state == .changed{
            statusLabel.text = String(format:"rotation: %1.3f",sender.rotation)
        }
        if sender.state == .ended{
            statusLabel.text = "Rotate Ended"
            view.backgroundColor = background
        }
    }

    func swipeAction(sender:UISwipeGestureRecognizer){
        if sender.state == .began{
            statusLabel.text = "Swipe!"
        }
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = background
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAction(sender:)))
        tapGesture.numberOfTapsRequired = 1
        tapGesture.numberOfTouchesRequired = 1
        view.addGestureRecognizer(tapGesture)
        
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinchAction(sender:)))
        view.addGestureRecognizer(pinchGesture)
        
        let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(rotateAction(sender:)))
        view.addGestureRecognizer(rotateGesture)
        
    }
    

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}