Siri Integration in iOS 10 with Swift – SiriKit Tutorial (Part 1)

Siri integration on iOS 10 – Swift Tutorial

This tutorial written on June 13th, 2016 using the Xcode 8 Beta 1, and is using the Swift 3.0 toolchain.

Get Xcode 8 set up for iOS 10 and Swift 3 compilation.

If you have not yet downloaded Xcode 8 Beta 1, please do so here.

(Optional) Compiling from the command line

To opt in to the Swift 3.0 toolchain you shouldn’t need to change anything unless you want to build from the command line. If you plan to build from the command line, open Xcode-beta and from the OS menu bar select Xcode > Preferences. Then select the Locations tab. At the bottom of the page here you will see “Command Line Tools”. Make sure this is set to Xcode 8.0.

Now if you navigate to the project directory containing the .xcodeproj file, you can optional compile your project by calling xcodebuild from the command line.

(Optional) Migrating from an existing Swift 2 app

If you are working with an existing Swift 2 project and want to add Siri integration with Swift 3.0, click on the root of your project and select Build Settings. Under Swift Compiler – Version, find the field labeled Use Legacy Swift Language Version and set it to No. This will lead to compiler errors most likely that you will need to fix throughout your project, but it’s a step I recommend to keep up with Swift’s ever-changing semantics.

Getting started with SiriKit

First, in your app (or in a new single-view Swift app template if you are starting fresh), switch to the general view by selecting the root of your project. Under this tab you can click the (+) icon in the lower land corner of the side-pane on the left. From the dropdown that appears selection iOS > Application Extension, and then select Intents Extension.

Select Intents Extension

This adds a new intent to the project, and we’ll use it to listen for Siri commands. The product name should be something similar to your app so it’s easy to identify, for example if your app is called MusicMatcher, you could call the Product Name of this intent MusicMatcherSiriIntent. Make sure to also check the checkbox to Include UI Extension. We will need this later in the tutorial, and it’s easiest to just include the additional extension now.

Intents Extension Options

What we’ve created are two new targets as you can see in the project heirarchy. Let’s jump in to the boilerplate code and take a look at the example in the IntentHandler.swift file inside of the Intent extension folder. By default this will be populated with some sample code for the workout intent, allowing a user to say commands such as “Start my workout using MusicMatcher”, where MusicMatcher is the name of our app.

IntentHandler.swift

Run the Template App as-is

It’s helpful at this point to compile this code as-is and try out the command on an actual iOS device. So go ahead and build the app target, by selecting the app MusicMatcher from the Scheme dropdown, and when the target device set to your test iOS device, press the Build & Run button.

Select the MusicMatcher target

You should see a blank app appear, and in the background your extensions will also be loaded in to the device’s file system. Now you can close your app using the Stop button in Xcode to kill the app.

Then, switch your scheme to select the Intent target, and press build & run again.

Select the MusicMatcherSiriIntent target

This will now prompt asking which app to attach to, just select the app you just ran, MusicMatcher. This will present the app again on your device (a white screen/blank app most likely), but this time the debugger will be attached to the Intent extension.

Select the app to run with the extension

You can now exit to the home screen by pressing the home button, or the app may exit on it’s own since you are running the Intent and not the app itself (This is not a crash!!!)

Enable the extension

The extension should now be in place, but we as an iOS device user still may need to enable the extension in our Siri settings. On your test device enter the Settings app. Select the Siri menu, and near the bottom you should see MusicMatcher listed as a Siri App. Make sure the app is enabled in order to enable Siri to pick up the intents from the sample app.

Testing our first Siri command!

Try the Siri command. Activate Siri either by long pressing the Home button, or by saying “Hey Siri!” (note the “Hey Siri!” feature must be enabled in the settings first)

Try out some of the command “Start my workout using MusicMatcher”.

“Sorry, you’ll need to continue in the app.”

If you’re like me this will bail with an error saying “Sorry, you’ll need to continue in the app.” (For some reason this occassionally was not a problem. Ghosts?)

In the console you may see something like this:

dyld: Library not loaded: @rpath/libswiftCoreLocation.dylib
  Referenced from: /private/var/containers/Bundle/Application/CC815FA3-EB04-4322-B2BB-8E3F960681A0/LockScreenWidgets.app/PlugIns/JQIntentWithUI.appex/JQIntentWithUI
  Reason: image not found
Program ended with exit code: 1

We need to add the CoreLocation library to our main project, to make sure it gets copied in with our compiled Swift code.

Select the project root again and then select your main MusicMatcher target. Here under General you’ll find area area for Linked Frameworks and Libraries. Click the (+) symbol and add CoreLocation.framework. Now you can rebuild and run your app on the device, then follow the same steps as above and rebuild and run your intent target.

Finally, you can activate Siri again from your home screen.

“Hey Siri!”
“Start my workout using MusicMatcher”

Siri should finally respond, “OK. exercise started on MusicMatcher” and a UI will appear saying “Workout Started”

MusicMatcher Workout Started

How does it work?

The IntentHandler class defined in the template uses a laundry list of protocols:

First an foremost is INExtension which is what allows us to use the class as an intent extension in the first place. The remaining protocols are all intent handler types that we want to get callbacks for in our class:

INStartWorkoutIntentHandling
INPauseWorkoutIntentHandling
INResumeWorkoutIntentHandling
INCancelWorkoutIntentHandling
INEndWorkoutIntentHandling

The first one is the one we just tested, INStartWorkoutIntentHandling.

If you command-click this protocol name you’ll see in the Apple docs this documentation:

/*!
 @brief Protocol to declare support for handling an INStartWorkoutIntent
 @abstract By implementing this protocol, a class can provide logic for resolving, confirming and handling the intent.
 @discussion The minimum requirement for an implementing class is that it should be able to handle the intent. The resolution and confirmation methods are optional. The handling method is always called last, after resolving and confirming the intent.
 */

Or in other words, this protocol tells SiriKit that we’re prepared to handle the English phrase “Start my workout with AppName Here.”
This will vary based on the language spoken by the user, but the intent will always be to start a workout. The INStartWorkoutIntentHandling protocol calls on several more methods, and they are implemented in the sample code. I’ll leave you to learn more about them if you want to build a workout app, but what I’d rather do in the remainder of this tutorial is add a new intent handler for handling the sending of messages.

Let’s Add a New Message Intent

Now that we’ve confirmed that works, let’s move on to adding a new type of intent for sending messages. The docs here show the following:

Send a message
Handler:INSendMessageIntentHandling protocol
Intent:INSendMessageIntent
Response:INSendMessageIntentResponse

So let’s add the INSendMessageIntentHandling protocol to our class. First we’ll just specify we want to use it by appending it to the list of protocols our class adheres to in IntentHandler.swift. Since I don’t actually want the workout intent’s, I’ll also remove those, leaving us with just this for the class declaration:

class IntentHandler: INExtension, INSendMessageIntentHandling {
    ...

If we just left it at that we wouldn’t be able to compile our code since we stil need to implement the required methods from the INSendMessageIntentHandling protocol.

Again, if you ever need to check what those methods are, just command+click the text INSendMessageIntentHandling and take a look at what method signatures are present that are not marked with the optional keyword.

In this case we find only one required method:

/*!
 @brief handling method
 
 @abstract Execute the task represented by the INSendMessageIntent that's passed in
 @discussion This method is called to actually execute the intent. The app must return a response for this intent.
 
 @param  sendMessageIntent The input intent
 @param  completion The response handling block takes a INSendMessageIntentResponse containing the details of the result of having executed the intent
 
 @see  INSendMessageIntentResponse
 */
public func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Swift.Void)

Adhering to the new Message Intent protocol

So back in our IntentHandler.swift, let’s add a line seperator (useful for navigating code with the jump bar)

// MARK: - INSendMessageIntentHandling

Underneath this MARK, we can implement the function. I find it’s most useful with Xcode 8 to simply begin typing the method name, and let autocomplete take it from there, choosing the relevant option.

Fill out the handle method with autocomplete

In our handler, we’ll need to construct an INSendMessageIntentResponse in order to call back the completion handler. We’ll just assume all messages are successful here and return a success value for the user activity in the INSendMessageIntentResponse constructor, similar to how this is done in the template app. We’ll also add a print statement so we can see when this handle method is triggered by a Siri event:

func handle(sendMessage intent: INSendMessageIntent, completion: (INSendMessageIntentResponse) -> Void) {
    print("Message intent is being handled.")
    let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent))
    let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
    completion(response)
}

Adding the intent type to the Info.plist

Before this app will be capable of handling INSendMessageIntent, we need to add the value to our Info.plist. Think of this as something like an app entitlement.

In the Info.plist file of the intent, find and expand the NSExtension key. Then extend NSExtensionAttributes, and then IntentsSupported under that. Here we need to add a new row for our INSendMessageIntent to allow the app to process Message intents.

Add support for INSendMessageIntent aka messages intent

Testing the new intent

Now that we’ve got our new intent set up, let’s give it a try. Recall that you must build the app, run it on the device, and then run the extension in order to debug the extension. If you don’t do run in this order the extension will either not work, or it will not log to the Xcode console.

Try calling upon our intent in Siri, and you will now see a new message window appear! The window is pretty empty, and there isn’t much logic to tie in to our app just yet. We need to implement the remaining callbacks and add some of our app’s messaging logic to provide a better experience. We’ll cover that in Part 2, which is available now. If you want me to email you about it when other tutorials come out as well, sign up for my newsletter to get the scoop.


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.

Subscribe via RSS

Core Data in Swift Tutorial (Part 1)

This post compatible with Xcode 6.3 Beta, Updated on February 16, 2015
Don’t have 6.3 yet? Make sure to download it here using your iOS Developer account.

Core Data is the de facto standard way to persist and manage data in both iPhone and Mac applications, and with Swift it’s a bit easier. So it’s only natural that we should take some time to learn about it when building apps. Eager to see what we’ll have created by the end of this tutorial? Take a look at the video, we’ll be creating this table view, populating it with data, adding the ability to delete records, add records, and sort/search records all backed by Core Data. This data is persistent and lasts even through a complete shut down of your phone.

The first thing to know about Core Data before diving in is that it is not a relational database, and although it uses SQLite as a backing engine, is not an ORM to a relational database either. The SQLite backend is more of an implementation detail, and in fact binary files or plists can be used instead.

The official Apple documentation describes Core Data like this:

“The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence.”
[developer.apple.com]

Before we get too technical about what Core Data is, I think it’s useful to dive in and start playing with the API a bit.

Create a new Xcode 6 project using a single-view template, Swift as the language, and with Core Data enabled. I’ll call the project MyLog.


Looking for something more in-depth than a tutorial? Try my book & video courses
Get The Swift Book
Learn About My Book & Video Packages »

Looking at the AppDelegate.swift file you’ll notice using this option has added quite a few functions. Most of these are setting up the Core Data stack. The defaults are fine for now. The primary object that needs to be used to work with Core Data is the managedObjectContext defined here.

If you used the Core Data template as shown above, this code will already be present.

lazy var managedObjectContext: NSManagedObjectContext? = {
    // Returns the managed object context for the application (which is already bound to the persistent store
    // coordinator for the application.) This property is optional since there are legitimate error
    // conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    if coordinator == nil {
        return nil
    }
    var managedObjectContext = NSManagedObjectContext()
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()

All you really need to know about this, is that managedObjectContext is a lazy variable on AppDelegate that is at our disposable for use in performing Core Data calls. Knowing this we can access the managedObjectContext from our ViewController.swift file. For example in viewDidLoad() of ViewController.swift, we can use this code to print the managedObjectContext’s description to the console. (New lines are highlighted)

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    // Retreive the managedObjectContext from AppDelegate
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
    
    // Print it to the console
    println(managedObjectContext)
}

We’ll be accessing the managedObjectContext pretty frequently, so we should pull this out of the viewDidLoad() method and move it somewhere we can access it easily. How about if we just store it as an instance variable on the ViewController?

import UIKit

class ViewController: UIViewController {
    
    // Retreive the managedObjectContext from AppDelegate
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // Print it to the console
        println(managedObjectContext)
    }

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

}

The managedObjectContext variable is computed using the existing managedObjectContext in the application’s delegate. In viewdidLoad() we cause this variable to be computed by printing it to the console. If your application is set up right you should see something like this:

Optional(<NSManagedObjectContext: 0x7fe68b58c800>)

You’ll notice the Xcode template produced an additional file, MyLog.xcdatamodeld.

Opening up this file you can see the Core Data model editor.

Let’s add a new Core Data entity called LogItem. Our log app will show a list of LogItems, which have a bit of text in them.

Click the “Add Entity” button and then in the right-hand panel select the “Data Model Inspector”. From here we can rename the default name, Entity, to LogItem.

Next, at the bottom we can add our first attribute by pressing the “+ Atrribute” button at the bottom.

Name this attribute title, and give it a type of String. We’ll also add a second attribute of type String called itemText.

IMPORTANT!

From this point on, any changes you make to your Core Data model, such as adding a new Entity or Attribute will lead to an inconsistency in the model of the app in the iPhone Simulator. If this happens to you, you’ll get a really scary looking crash in your app as soon as it starts. You’ll also see something like this show up at the very bottom of your console, “reason=The model used to open the store is incompatible with the one used to create the store”.

If this happens to you there is a very easy fix:
In the iPhone Simulator, or on your device, just delete the app, and then perform a new Build & Run command in Xcode. This will erase all out of date versions of the model, and allow you to do a fresh run.

Now that we have our first Entity created, we want to also be able to directly access this entity as a class in our code. Xcode provides an automated tool to do this. In the menubar select Editor->Create NSManagedObject Subclass…

In the first prompt, check the MyLog model and press next. Then, check the LogItem entity, and press next again.
A file save window should appear with an option to specify the language as Swift, select this. Finally hit Create, and you should now see a LogItem.swift file added. It’s contents should be something very close to this:

import Foundation
import CoreData

class LogItem: NSManagedObject {
    @NSManaged var title: String
    @NSManaged var itemText: String
}

This class is generated from the xcdatamodeld file. The entity we created is represented by the similarly named class LogItem, and the attributes are turned in to variables using the @NSManaged identifier, which gives the variables special treatment allowing them to operate with Core Data. For most intents and purposes though, you can just think of these as instance variables.

Because of the way Swift modules work, we need to make one modification to the core data model. In the field “Class” under the data model inspector for our entity, LogItem, we need to specify the project name as a prefix to the class name. So instead of just specifying “LogItem” as the class, it needs to say “MyLog.LogItem”, assuming your app is called “MyLog”.

In our ViewController.swift file in the viewDidLoad method, let’s instantiate some instances of LogItem. There are many ways to do this, but the least verbose is to use the insertNewObjectForEntityForName method of NSEntityDescription.

override func viewDidLoad() {
    super.viewDidLoad()

    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as! LogItem
}

Here we insert a new object in to the core data stack through the managedObjectContext that the template function added to AppDelegate for us. This method returns an NSManagedObject which is a generic type of Core Data object that responds to methods like valueForKey. If you don’t quite understand what that means, don’t worry too much about it, it’s not going to prevent you from being able to use Core Data. Let’s keep moving.

With an NSManagedObject version of newItem, we could say newItem.valueForKey(“title”) to get the title. But this is not the best approach because it leaves too much opportunity to mis-type an attribute name, or get the wrong object type unexpectedly and have hard crashes trying to access these attributes.

So, in our case, we instead cast the NSManagedObject that insertNewObjectForEntityForName returns, to our generated class LogItem.

What this means, simply put, is that we can set the title and itemText like this:

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as! LogItem

    newItem.title = "Wrote Core Data Tutorial"
    newItem.itemText = "Wrote and post a tutorial on the basics of Core Data to blog."
}

If we had not generated our LogItem.swift file earlier, the type LogItem would not be defined and we would be limited to working only with NSManagedObject types. This is a really nasty way to work with the Core Data API as it relies heavily on determining object classes, entities, state, and more at runtime based on string comparisons, yuck!

Now that we’ve created a new item, and set both it’s title and text, we can query it elsewhere in our app and get the object back. Let’s override the viewDidAppear() method and implement a way to look at our items info. We’ll perform a Core Data fetch (which is like a query if you have worked with SQL before), then we will present the contents of the row we retrieved in a new alert window.

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    // Create a new fetch request using the LogItem entity
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    
    // Execute the fetch request, and cast the results to an array of LogItem objects
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        
        // Create an Alert, and set it's message to whatever the itemText is
        let alert = UIAlertController(title: fetchResults[0].title,
            message: fetchResults[0].itemText,
            preferredStyle: .Alert)
        
        // Display the alert
        self.presentViewController(alert,
            animated: true,
            completion: nil)
    }
}

First, we create a new NSFetchRequest instance using the entity LogItem.
Next, we create a fetchResults variable by using the executeFetchRequest method of managedObjectContext. The only thing we have specified in our fetchRequest is the entity, so this particular fetch just returns every record. If you are familiar with SQL, a fetch request with no predicate on the LogItem entity is something like “SELECT * FROM LogItem”.
Next we create a UIAlertController instance to present a message to the screen, and set it’s title and message properties to the title and itemText properties of the first LogItem in our fetch results (which is an array of LogItem objects).

Run the app and you should see the item presented to the screen. You’ve now stored and retrieved data from Core Data. This is the first step in to building apps with persistent storage.

In part 2, we’ll talk about working with multiple records and using NSPredicate to perform filtered requests.

The full source code to this part is available here on Github.

If you found this post useful, check out my other tutorials, and take a look at my Swift eBook, which is now available for early access.


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.

Subscribe via RSS