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

Developing tvOS Apps for Apple TV [Part 1]

tvOS - Apple TV SDK Released

Here’s what you’ll have at the end of part 2 of this tutorial if you follow along:

Looking for some help building your Apple TV tvOS App? I’m available for consulting and development, contact me.

Apple TV Developer Tools

Before we get started you’ll need the Xcode 7.1 beta, which you can download here:
Download Xcode 7.1 Beta
Note that this does require an Apple Developer account, and is currently pre-release, so some things may change!

One thing to note when installing is that there is a known issue if you rename the Xcode 7.1 app, apparently people do this. So just a forewarning… don’t do that or you’ll get a crashy tvOS Simulator.

Also note that while Yosemite is supported, the capabilities are limited. It is recommend that for tvOS development you use OS X 10.11 El Capitan or newer. The El Capitan beta can be downloaded here.

Let’s start with some simple definitions…

Becoming an Apple TV Developer – Vocabulary

TVMLKit

TVMLKit is Apple’s new framework designed for working with Javascript and XML to provide a rich user-interface on tvOS, while allowing your apps logic to be written in Swift or Objective-C.

TVML

TVML is short for “TV Markup Language” and is basically just some XML that describes how items are laid out in a client-server based tvOS app. When laying out an interface, we want to use some TVML templates that Apple provides to create our UI, and then we can script the interactions in TVJS.

TVJS

As far as I can tell, TVJS is just JavaScript like you might already be familiar with.

Hello World

Let’s start out by making a basic hello world. In terms of Apple TV, we could just log “Hello World” to the console and that would be a decent start, but let’s start digging in to some of the Apple TV’s TVMLKit elements and put up a template on-screen.

First, open Xcode 7.1 and create a new project. You’ll see a list of templates appear. On the left-hand side we want to select **CHANGE tvOS, and then select Single View Application* template.

This will create a number of files from the tvOS template which are going to be useful in setting up our UI, and gives us a simple entry point to start coding Swift as well.

Setting up the main TVJS file

So basically the “server” in client-server tvOS apps is the TVML and JavaScript files, and whatever associated data they need to operate. The JavaScript file will be what loads in the TVML, and adds the page to the view stack. Another way to think of this is that the JavaScript file acts as a router or controller for the TVML files which are basically the views.

Kicking things off

First, we want to make some modifications to the AppDelegate.swift file that launches the app. For one, we need our application to adhere to the TVApplicationControllerDelegate protocol. This is defined in the TVMLKit framework, so we’ll need to import that as well. So you’ll want to update your AppDelegate.swift to include those two items:

import TVMLKit
 
class AppDelegate: UIResponder,
                   UIApplicationDelegate,
                   TVApplicationControllerDelegate {
 
....

This interface has four functions that tvOS calls upon the implementing AppDelegate to notify our app of the lifecycle of the tvOS application. We don’t need to worry about these just yet, but we’ll dig in to these in later tutorials. For now, just add the protocol as shown in the code snippet above, and that’s enough to get us started.

Next, we need to add some code to actually launch our JS file. This is a beta, and actually requires we do this work ourselves, I’m sure in future Xcode version’s this will simply be a template.

In the application didFinishLaunchingWithOptions function we need to perform a few steps. These steps are unlikely to change for each individual app so you can simply copy this snippet here:

// We'll hold a reference to the app controller in an optional property
var appController: TVApplicationController?
 
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  self.window = UIWindow(frame:UIScreen.mainScreen().bounds)
 
  let appControllerContext = TVApplicationControllerContext()
 
  let jsFilePath = NSURL(string: "http://localhost:8000/main.js")
  let javascriptURL = jsFilePath!
 
  appControllerContext.javaScriptApplicationURL = javascriptURL
  if let options = launchOptions
  {
    for (kind, value) in options
    {
      if let kindStr = kind as? String
      {
        appControllerContext.launchOptions[kindStr] = value
      }
    }
  }
 
  self.appController = TVApplicationController(context: appControllerContext, window: self.window, delegate: self)
 
  return true
}

In short, what this code does is it gets a reference to an TVApplicationControllerContext, a class that simply provides some launch data to our AppDelegate class, and gives us an interface to make adjustments to the launch process. Then it passes the URL of the main.js file we’re going to execute, and sets the path for the app controller to be that path.

We need to now add our javascript file, so click File > New, and then under the iOS tab you can select an Other > Empty file. Name this file main.js.

Do the same thing for a new file called hello.tvml.

In your main.js file we’ll add some relatively simple JavaScript to load in the hello.tvml file:

function getDocument(url) {
  var templateXHR = new XMLHttpRequest();
  templateXHR.responseType = "document";
  templateXHR.addEventListener("load", function() {pushDoc(templateXHR.responseXML);}, false);
  templateXHR.open("GET", url, true);
  templateXHR.send();
  return templateXHR;
}
 
function pushDoc(document) {
  navigationDocument.pushDocument(document);
}
 
App.onLaunch = function(options) {
  var templateURL = 'http://localhost:8000/hello.tvml';
  getDocument(templateURL);
}
 
App.onExit = function() {
  console.log('App finished');
}

Now in the hello.tvml file:

<document>
  <alertTemplate>
      <title>Hello tvOS!</title>
  </alertTemplate>
</document>

This TVML file is the meat of the UI. Documents must use templates or you’ll get crashes at runtime with the code we’re using. It just contains a simple template and a single title element.

One issue I found with setting things up is that I was unable to locally refer to these files, and instead they have to be on a web server. So, what is easiest to do is navigate to the location of your tvml and js files we just created and type the following command in the command line:

Setting up the server to host the files

python -m SimpleHTTPServer 8000

This just uses Mac OS’s built-in python interpreter to start a web server on port 8000 serving up the local directory. If you copied my code shown above you should now be able to boot in to your tvOS simulator by pressing the play button in Xcode! There is one more issue you should be aware of, which is that this is an http unsecured request. In iOS 9 this is blocked by default by App Transport Security. In order to allow localhost to be used in this way, we need to add a key to the Info.plist file.

Allowing Arbitrary Loads

Select your Info.plist file and add a new record by pressing one of the (+) buttons. In the list find “App Transport Security Settings” and press return. This will create a new Dictionary row, expand this and press the (+) button on this row to add a child row. For this one find, “Allows Arbitrary Loads” and set it to true. With this in place we can run the app and preview using the simulator.

Adding Buttons

What you’re seeing in this example is actually a template from Apple called the alertTemplate. You can also embed a few basic controls, such as text and buttons within the template. Try adding a few buttons to select from:

<document>
    <alertTemplate>
        <title>Hello tvOS!</title>
        <button>
            <text>A Button</text>
        </button>
        <button>
            <text>A Second Button</text>
        </button>
    </alertTemplate>
</document>

Here we just are adding child button elements, each with it’s own child text element. This in turn displays the fullscreen alert and the two buttons on the tvOS simulator. The official Apple documentation has a list of each template, and control you can use if you want to hit the ground running. Otherwise stay tight, subscribe, and I’ll be bringing you a full tutorial of a fully developed app very soon.

Next Steps

Proceed to PART 2 » of this tutorial to learn how to add interactivity to the app.

If you want me to email you letting you know when new posts are up, be sure to subscribe to my newsletter.

These are still early days, so expect issues. If you get stuck don’t hesitate to contact me on twitter @jquave

GO TO PART 2 »

Looking for an Apple TV Developer for your tvOS App? I’m available for consulting and development, contact me for more info.


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

Subscribe via RSS

Core Data Migrations Swift Tutorial

In this post we’re going to discuss migrations in Core Data.

This is part three of a tutorial series covering the usage of Core Data in Swift to write iOS apps with persistence. While I think you can benefit strictly from reading this post, it may be easier to follow along if you go back and read Core Data in Swift Tutorial (Part 1) first.

This post compatible with Xcode 6.3 Beta, Updated on February 16, 2015

At this point in the tutorial, we’ve created a simple app that can create and delete log item’s backed by a Core Data store. We’ve learned about creating, deleting, and modifying Core Data objects. We looked at sorting lists of objects, filtering them out using predicates, and even combined predicates to make compound predicates. With this much information, we can do a ton of stuff with our apps.

Why Make Core Data Migrations?

However there is a problem. Core Data expects a consistent schema. Meaning that if we add or remove entities, attributes, or change their types, it will cause a conflict. This means that if you:
1) Store some data inside of an app using a set schema. For example a Model named LogItem with a title(String) attribute.
2) Change any of the schema. For example add a new full title attribute to become ‘fullTitle’.
3) Run the app again

There will be a conflict, and the app will do a hard crash. This includes anyone who has downloaded your app from the app store! Not only that, if you change the text attribute, and you don’t perform any kind of migration, you will be risking losing user’s data. If you want people to uninstall your app, this is a *fantastic* way to make that happen. If not, keep reading 🙂

Causing a migration failure

To demonstrate the default behavior, it’s useful to see what happens if we don’t take care to perform migrations. If you’ve been following along you should have a Core Data model that contains one entity, LogItem with two String attributes, itemText and title, as shown in the figure below.

Core Data Model For Migrations

Core Data Model For Migrations

First off, we need to run our app and create some records. If you’ve been following along you can just add a few log items. This is enough to populate the Core Data store, and create a set schema.

Next, let’s add our “fullTitle” String attribute. Just click the “Add Attribute” button and punch in fullTitle as the name, and String as the type.

Now, run the app again, what do you find? Well, you get a rather massive looking confusing error in the console:

CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///Users/USERNAME/Library/Developer/CoreSimulator/Devices/70F453BF-0E8E-4916-8B1F-1D3FDAC9AAE3/data/Containers/Data/Application/90912396-CB66-4B4A-8502-BDE916F6243D/Documents/MyLog.sqlite options:(null) ... returned error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x7ffd126842c0 {metadata={
    NSPersistenceFrameworkVersion = 519;
    NSStoreModelVersionHashes =     {
        LogItem = <3954a6a4 f606a77c d84981b4 c8ec4869 5eda2e90 4c7e0069 b090d4b8 825d8ee3>;
    };
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers =     (
        ""
    );
    NSStoreType = SQLite;
    NSStoreUUID = "";
    "_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary {
    metadata =     {
        NSPersistenceFrameworkVersion = 519;
        NSStoreModelVersionHashes =         {
            LogItem = <3954a6a4 f606a77c d84981b4 c8ec4869 5eda2e90 4c7e0069 b090d4b8 825d8ee3>;
        };
        NSStoreModelVersionHashesVersion = 3;
        NSStoreModelVersionIdentifiers =         (
            ""
        );
        NSStoreType = SQLite;
        NSStoreUUID = "";
        "_NSAutoVacuumLevel" = 2;
    };
    reason = "The model used to open the store is incompatible with the one used to create the store";
}
2014-12-15 14:00:36.135 MyLog[67495:21018487] Unresolved error Optional(Error Domain=YOUR_ERROR_DOMAIN Code=9999 "Failed to initialize the application's saved data" UserInfo=0x7ffd13b26970 {NSLocalizedDescription=Failed to initialize the application's saved data, NSLocalizedFailureReason=There was an error creating or loading the application's saved data., NSUnderlyingError=0x7ffd12684300 "The operation couldn’t be completed. (Cocoa error 134100.)"}), Optional([NSLocalizedDescription: Failed to initialize the application's saved data, NSLocalizedFailureReason: There was an error creating or loading the application's saved data., NSUnderlyingError: Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x7ffd126842c0 {metadata={
    NSPersistenceFrameworkVersion = 519;
    NSStoreModelVersionHashes =     {
        LogItem = <3954a6a4 f606a77c d84981b4 c8ec4869 5eda2e90 4c7e0069 b090d4b8 825d8ee3>;
    };
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers =     (
        ""
    );
    NSStoreType = SQLite;
    NSStoreUUID = "";
    "_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store}])

This is a rather intimidating error message. There’s so much to read, and so little of it is familiar. I want you to focus on on particular key though, “reason”.
When you get a Core Data error, it comes back with a ton of information, and the relevant part is almost always the value of “reason”. In our error, we see a couple of reasons, but we’re most interested in the very first one.

reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary

The error pretty much says what I described earlier. We changed the schema, and Core Data doesn’t like it. Let’s change the schema back by removing fullTitle, and set up a migration instead. (Run your app again to make sure it still works!)
If you find you have a conflicted Core Data model, but you don’t want to create a migration yet, you can also just delete the app from your simulator or device. This will create the new schema instead. This is generally my solution when my model is going through a lot of migrations, but you NEVER want to do this with a deployed model version. You’ll just have people on the app store with your app crashing. They aren’t going to know to delete the app and reinstall either, they’ll just delete it and leave you a 1-star review.

Create A Migration

To create a migration, we need to first make some changes to our persistent store. This is the code that Xcode generates for us when we create a project using an Xcode template. By default, Xcode does not include any kind of migration options at all.

In the code that Xcode provides there is a lazily computed variable called persistentStoreCoordinator, and it’s responsible for making migrations happens. It looks like this, and we’re most interested in line 8.

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("MyLog.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }
    
    return coordinator
}()

Now, the persistent store coordinator is being added using the addPersistentStoreWithType method. This method looks like it is maybe not updated for Swift, but that’s okay, we just need to pay attention to what’s optional and what’s not. Let’s take a look at the documentation, click here to open in a new window.

Opening up these docs you can see some details on what these parameters are. In particular it describes “options”.

Apple Documentation For Core Data

Apple Documentation For Core Data

You’ll notice in the parameters table, the “options” parameter is a dictionary. It also has a link to a particular set of parameters you could use in the dictionary, “Migrations Options”. Clicking on that will bring you to the constants the iOS SDK has prepared for us already:

Migration options, specified in the dictionary of options when adding a persistent store using addPersistentStoreWithType:configuration:URL:options:error:.

Declaration
SWIFT
let NSIgnorePersistentStoreVersioningOption: NSString!
let NSMigratePersistentStoresAutomaticallyOption: NSString!
let NSInferMappingModelAutomaticallyOption: NSString!
Constants
NSIgnorePersistentStoreVersioningOption

Source: Apple.com

NSMigratePersistentStoresAutomaticallyOption sounds pretty nice. Automatic migration? Sign me up! NSInferMappingModelAutomaticallyOption will create the mapping model, while NSMigratePersistentStoresAutomaticallyOption will perform the migration.

So, to set this up, we just have to create a dictionary with these keys set, and pass that in as the options parameter of addPersistentStoreWithType

So let’s create the dictionary:

let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true]

And pass it in to the options parameter:

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("MyLog.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true]
    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: mOptions, error: &error) == nil {
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }
    
    return coordinator
}()

So basically what we just did, is we specified we would like Core Data to try and automatically merge and map our models between versions.

If you think about what we actually changed in our model, it’s strange that it causes a hard crash. We weren’t using the fullTitle attribute anywhere, it’s just a new field to add really. It could just be automatically added to every model with a null value. Or, if we specified a default value for the attribute, it could just apply that to all existing records. That would be much better than crashing, but Xcode doesn’t want to just make the assumption that we want to do that. So this is how we specify we want Xcode to try anyway.

Now, let’s actually add the new attribute. Before, we just opened up our model and added it as a new String attribute, but that’s not going to create a migration model. We need two versions of the model so that Core Data can understand the differences in the two models.

Add a Model Version

In the Project Navigator, select your Core Data model, for example MyLog.xcdatamodeld. Now in your menu bar, select Editor > Add Model Version.. as shown in the figure below.

Adding a Model Version in Xcode

Adding a Model Version in Xcode

You can call it whatever you want, I’ll be calling it MyLogV2, and press Finish.

You’ll now find you can expand your xcdatamodel file to see multiple versions. Select your second version as shown in the figure below.

Multiple Versions of a Core Data Model

Multiple Versions of a Core Data Model

Now, in our version 2 of the model, let’s add our fullTitle(String) attribute.

We’ll also want to update LogItem.swift, the NSManagedObject subclass we created to represent this model.

If you followed along in the earlier tutorials you should have something like this:

import Foundation
import CoreData

class LogItem: NSManagedObject {

    @NSManaged var itemText: String
    @NSManaged var title: String
    
    class func createInManagedObjectContext(moc: NSManagedObjectContext, title: String, text: String) -> LogItem {
        let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: moc) as LogItem
        newItem.title = title
        newItem.itemText = text
        
        return newItem
    }

}

We’re missing our new fullTitle attribute, both as a property and in the createInManagedObjectContext initializer. Let’s add it…

import Foundation
import CoreData

class LogItem: NSManagedObject {

    @NSManaged var itemText: String
    @NSManaged var title: String
    @NSManaged var fullTitle: String
    
    class func createInManagedObjectContext(moc: NSManagedObjectContext, title: String, fullTitle: String, text: String) -> LogItem {
        let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: moc) as LogItem
        newItem.title = title
        newItem.itemText = text
        newItem.fullTitle = fullTitle
        
        return newItem
    }

}

Finally, we need to actually make version 2 of our model active.

Click on the MyLog.xcdatamodeld parent object in the Project Navigator. Then open the Utilities panel on the right-hand side of Xcode, and select the first tab, the File Inspector.

Here under “Model Version” there is a dropdown where you can select the new version 2 of the model, as shown in the figure below.

Changing the Core Data Model Version in Xcode

Changing the Core Data Model Version in Xcode

Now, if you’ve been following along, there are some errors you’ll need to fix where we are initializing the LogItem objects without specifying the fullTitle. We added the attribute to the initializer, so now we need to go one by one and specify the fullTitle in each of these cases.

In particular we need to add these in viewDidLoad where we create some starter objects.

// Loop through, creating items
for (itemTitle, itemText) in items {
    // Create an individual item
    LogItem.createInManagedObjectContext(moc, title: itemTitle, fullTitle: "\(itemTitle) \(itemText)", text: itemText)
}

In earlier tutorials, we also created a saveNewItem function that uses the initializer inside of ViewController.swift. So we need to specify a full title for that as well. For now, we’ll just use a static string “My Full Title” as the fullTitle value.

func saveNewItem(title : String) {
    // Create the new  log item
    var newLogItem = LogItem.createInManagedObjectContext(self.managedObjectContext!,
        title: title,
        fullTitle: "My Full Title",
        text: "")
    
    // Update the array containing the table view row data
    self.fetchLog()
    
    // Animate in the new row
    // Use Swift's find() function to figure out the index of the newLogItem
    // after it's been added and sorted in our logItems array
    if let newItemIndex = find(logItems, newLogItem) {
        // Create an NSIndexPath from the newItemIndex
        let newLogItemIndexPath = NSIndexPath(forRow: newItemIndex, inSection: 0)
        // Animate in the insertion of this row
        logTableView.insertRowsAtIndexPaths([ newLogItemIndexPath ], withRowAnimation: .Automatic)
        save()
    }
}

Now, for the moment of truth… run the app. If you get an error here, let us know on the forums. I know this is a difficult topic, and I know the tutorial is never going to be easy to follow. But, I know if you’ve come this far you can and will succeed in learning this material. We just need to make sure you don’t get stuck.

Otherwise, congratulations, you’ve created your first auto-migration! This is known as a light-weight migration. There is more to migrations than this, as you can specify custom mappings, and there are caveats once you have more than 2 versions of a model. But knowing what we discussed in this tutorial is good enough to get your app to v2.0, and published.

Full source code for this tutorial here: Core Data In Swift Tutorial Code.

Liked this tutorial? It is a modified version of a draft chapter in my Swift Book.

P.S. Wanna write for this site? I need help writing quality content. Learn More Here.


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

Subscribe via RSS

Taking control of the iPhone camera in iOS 8 with Swift (Part 1)

Updated on September 20, 2014 for Xcode 6 GM

Using the AVFoundation API, we are going to set up a capture session and make an app that allows us to use all the new fine-grained controls added to iOS 8. This includes manually controlling focus, exposure, and ISO. First off, we just need to set up a basic camera preview. By the end of Part 1 we’ll have that in place along with a nifty way to control focus. This tutorial is adapted from the more detailed project PhotoSwapr in my book. Ready? Let’s get going…


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

First off we’ll create a new Xcode Project using Swift as the language, and using the Single View template.

singleAppTemplate

Now, in the ViewController.swift file we can start adding in our custom code inside of viewDidLoad().

First we create a AVCaptureSession object to work with. Let’s do this as a class variable.

let captureSession = AVCaptureSession()

This may give an error due to not being able to find AVCaptureSession. So near the top of the file make sure to add:

import AVFoundation

Now, in viewDidLoad let’s set our quality settings and find a device to record from.

First, let’s take a look at the list of devices available.

captureSession.sessionPreset = AVCaptureSessionPresetLow
let devices = AVCaptureDevice.devices()
println(devices)

Run this and you’ll see something like this:

[<AVCaptureFigVideoDevice: 0x16e7f720 [Back Camera][com.apple.avfoundation.avcapturedevice.built-in_video:0]>,
<AVCaptureFigVideoDevice: 0x16d91a00 [Front Camera][com.apple.avfoundation.avcapturedevice.built-in_video:1]>,
<AVCaptureFigAudioDevice: 0x16e88c00 [iPhone Microphone][com.apple.avfoundation.avcapturedevice.built-in_audio:0]>]
pple.avfoundation.avcapturedevice.built-in_video:1]>,
<AVCaptureFigAudioDevice: 0x16e88c00 [iPhone Microphone][com.apple.avfoundation.avcapturedevice.built-in_audio:0]>]

This is from my iPhone 5S. Looks like we have two microphones, and the front and back cameras. Cool. For our purposes let’s try and grab the back camera.

Let’s add this to a ViewController, and store the front facing camera if we find one

import UIKit
import AVFoundation

class ViewController: UIViewController {

    let captureSession = AVCaptureSession()

    // If we find a device we'll store it here for later use
    var captureDevice : AVCaptureDevice?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        captureSession.sessionPreset = AVCaptureSessionPresetLow

        let devices = AVCaptureDevice.devices()

        // Loop through all the capture devices on this phone
        for device in devices {
            // Make sure this particular device supports video
            if (device.hasMediaType(AVMediaTypeVideo)) {
                // Finally check the position and confirm we've got the back camera
                if(device.position == AVCaptureDevicePosition.Back) {
                    captureDevice = device as? AVCaptureDevice
                }
            }
        }

    }

}

After we set the captureDevice, let’s begin the session by implementing a function to start the session

if captureDevice != nil {
    beginSession()
}

…and later in the class we implement beginSession()…

func beginSession() {
    var err : NSError? = nil
    captureSession.addInput(AVCaptureDeviceInput(device: captureDevice, error: &err))

    if err != nil {
        println("error: \(err?.localizedDescription)")
    }

    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    self.view.layer.addSublayer(previewLayer)
    previewLayer?.frame = self.view.layer.frame
    captureSession.startRunning()
}

If you run the app on a device now, you should see a preview of the camera. This is pretty much just the standard iOS camera. Let’s now modify the focus mode. Add a new method called configureDevice() and have beginSession() call it on the first line starting the capture session.

func configureDevice() {
    if let device = captureDevice {
        device.lockForConfiguration(nil)
        device.focusMode = .Locked
        device.unlockForConfiguration()
    }
}

Add this method, it locks the device, sets the focus to locked, and then unlocks the device.

Run the app now and try tapping to focus on different parts of the scene. The default focus behavior should now be disabled. This means we can control the focus on our own. Let’s add a UISlider to control the focus.

Now, let’s add a manual focusTo function based on a value from 0.0 to 1.0

func focusTo(value : Float) {
    if let device = captureDevice {
        if(device.lockForConfiguration(nil)) {
            device.setFocusModeLockedWithLensPosition(value, completionHandler: { (time) -> Void in
                //
            })
            device.unlockForConfiguration()
        }
    }
}

First, we validate that the device exists, then we lock the device. If the lock is successful we call the setFocusModeLockedWithLensPosition() API to tell the lens to focus on the point ‘value’, which is passed in to the focusTo() method.

Now let’s implement touch controls using these methods:

let screenWidth = UIScreen.mainScreen().bounds.size.width
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    var anyTouch = touches.anyObject() as UITouch
    var touchPercent = anyTouch.locationInView(self.view).x / screenWidth
    focusTo(Float(touchPercent))
}

override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
    var anyTouch = touches.anyObject() as UITouch
    var touchPercent = anyTouch.locationInView(self.view).x / screenWidth
    focusTo(Float(touchPercent))
}

This just gets a value from 0.0 to 1.0 based on how far you are touching on the screen horizontally. Run the app now and slide a finger left to right on the screen. You can now manually control focus this way! Cool right?

Next time we’ll add an option for manually setting the ISO and exposure. But for now, this is a start. Make sure to subscribe to my newsletter to be notified of Part 2. Coming soon!

Want a deeper look at the AVFoundation API? Pre-order my upcoming book on developing iOS 8 Apps in Swift.

Here is the final code from this post:

Part 1 on Github

import UIKit
import AVFoundation

class ViewController: UIViewController {

    let captureSession = AVCaptureSession()
    var previewLayer : AVCaptureVideoPreviewLayer?

    // If we find a device we'll store it here for later use
    var captureDevice : AVCaptureDevice?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.
        captureSession.sessionPreset = AVCaptureSessionPresetHigh

        let devices = AVCaptureDevice.devices()

        // Loop through all the capture devices on this phone
        for device in devices {
            // Make sure this particular device supports video
            if (device.hasMediaType(AVMediaTypeVideo)) {
                // Finally check the position and confirm we've got the back camera
                if(device.position == AVCaptureDevicePosition.Back) {
                    captureDevice = device as? AVCaptureDevice
                    if captureDevice != nil {
                        println("Capture device found")
                        beginSession()
                    }
                }
            }
        }

    }

    func focusTo(value : Float) {
        if let device = captureDevice {
            if(device.lockForConfiguration(nil)) {
                device.setFocusModeLockedWithLensPosition(value, completionHandler: { (time) -> Void in
                    //
                })
                device.unlockForConfiguration()
            }
        }
    }

    let screenWidth = UIScreen.mainScreen().bounds.size.width
    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        var anyTouch = touches.anyObject() as UITouch
        var touchPercent = anyTouch.locationInView(self.view).x / screenWidth
        focusTo(Float(touchPercent))
    }

    override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
        var anyTouch = touches.anyObject() as UITouch
        var touchPercent = anyTouch.locationInView(self.view).x / screenWidth
        focusTo(Float(touchPercent))
    }

    func configureDevice() {
        if let device = captureDevice {
            device.lockForConfiguration(nil)
            device.focusMode = .Locked
            device.unlockForConfiguration()
        }

    }

    func beginSession() {

        configureDevice()

        var err : NSError? = nil
        captureSession.addInput(AVCaptureDeviceInput(device: captureDevice, error: &err))

        if err != nil {
            println("error: \(err?.localizedDescription)")
        }

        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        self.view.layer.addSublayer(previewLayer)
        previewLayer?.frame = self.view.layer.frame
        captureSession.startRunning()
    }

}


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

Subscribe via RSS

Access Control In Swift

Updated December 21 for Xcode 6.1.1

The Swift feature of Access Control is really important from a software architecture perspective, because it allows us to properly implement encapsulation. Without the ability to hide members and methods of classes, it’s very easy to accidentally (or not) reach in to classes and mess with internals that were not designed to be directly modified.

Swift offers three levels of access control: public, private, and internal.

public makes the entities visible anywhere within the module, or from other modules (as long as the module is imported)
internal makes the entities visible only within the same module. This is the default behavior in Swift.
private makes the entities visible only within the same source file.

To demonstrate Swift’s new access controls, let’s build a small math class for Swift. For now, it’ll be really simple. It’s a class that has two properties of type Double, and has a single computed property named sum. If you want to follow along, just create a single-view template project and add this in to an otherwise empty swift file called Math.swift

import Foundation

class QMath {
    
    var num1: Double?
    var num2: Double?
    
    var sum: Double {
        return num1! + num2!
    }
    
    init(_ num1: Double, _ num2: Double) {
        self.num1 = num1
        self.num2 = num2
    }
    
}

The class QMath has a constructor that takes two parameters, num1 and num2 of type Double. If you haven’t seen the underscores before, those are the external parameter names, which we’ve set to not be specified by using the underscore. If we instantiate a QMath instance in another class we can get the sum property and confirm it works as expected.

If you’re following along, you insert this in to your applicationdidFinishLaunchingWithOptions: method inside of AppDelegate.swift

var m = QMath(4, 50)
println(m.sum)
54.0

You’ll notice that in our sum getter, we implicitly unwrap num1 and num2 using the exclamation mark (!). Although they’re both optionals, we can make this assumption if we know the init(,) method we’ve provided is called, because the arguments to that method are not optional and will definitely be set upon initialization.

Except one minor problem:
We could actually set either of those values to be nil.

var m = QMath(4, 50)
m.num1 = nil
println(m.sum)

This code compiles just fine, num1 is Optional and can be set to nil, not an issue. However, calling the sum getter now implicitly unwraps a nil optional. If you run this code you’ll see the following error:

fatal error: unexpectedly found nil while unwrapping an Optional value

We could simply set the num1 and num2 properties to not be optional, which would require we have a default value such as 0. But, an better approach that allows us to avoid the unnecessary initial value is to simply disallow modifications to these internal variables. We want this class to be a black box, where num1 and num2 can’t directly be modified.

So, to solve our above mentioned issue, we can make the two number properties private.

private var num1: Double?
private var num2: Double?

Attempting to build the app again now produces an error on the line we used to set num1 to nil.

m.num1 = nil
'QMath' does not have a member named 'num1'

Our external reference to the num1 property is no longer valid, it has no visibility to num1 and therefore this line of code is an error. There’s only one thing to do now, remove this line and start using the class as it was designed to be used! Mission accomplished!

If you want to dive deeper and tinker with Swift, it’s a good idea to read my post on Running Swift Scripts From The Command Line. When you’re ready to get serious make sure to also learn about my upcoming Swift eBook and video tutorial series.

Warning: Incoming opinion
Good software design principles suggest everything should be private by default, and entities should be exposed deliberately on an as-needed basis. This makes it easier to write more modular code and leads to cleaner programming interfaces. Disagree? Yell at me about it on Twitter.


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

Subscribe via RSS

Developing iOS 8 Apps Using Swift – Animations, Audio, and Custom Table View Cells (Part 7)

This section completely updated to reflect changes in Xcode 6.3, as of April 17, 2015

In parts 1 through 6 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven’t read that yet, check out Part 1

In this tutorial we’re going to implement an Album detail view, that makes a second API call to retrieve a list of tracks for an album, downloads higher resolution album art, and allows of to play previews of the tracks within our app. As an optional extra, we are going to also implement some nifty animations using the Core Animation API provided by the iOS SDK. When we’re done, we’re going to have something like this (video taken in iOS 7 Simulator)

Setting up our API Controller

Because we’re going to be adding additional API calls in this part of the tutorial, we should modify our API Controller for some code re-use. Let’s start with a more generic get request.

In your API Controller add the function get(), which takes path as a String argument, and converts it to an NSURL:

func get(path: String) {
    let url = NSURL(string: path)
    ...

Now get the NSURLSession and send it using dataTaskWithURL as we did before, in fact the code is exactly the same as what is currently inside of our searchItunesFor() function, so just copy and paste it from there. Start cutting right after the line

let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"

And move everything in to the get() method. Your complete APIController.swift file should look something like this now:

import Foundation

protocol APIControllerProtocol {
    func didReceiveAPIResults(results: NSArray)
}

class APIController {
    var delegate: APIControllerProtocol
    
    init(delegate: APIControllerProtocol) {
        self.delegate = delegate
    }
    
    func get(path: String) {
        let url = NSURL(string: path)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithURL(url!, completionHandler: {data, response, error -> Void in
            println("Task completed")
            if(error != nil) {
                // If there is an error in the web request, print it to the console
                println(error.localizedDescription)
            }
            var err: NSError?
            if let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as? NSDictionary {
                if(err != nil) {
                    // If there is an error parsing JSON, print it to the console
                    println("JSON Error \(err!.localizedDescription)")
                }
                if let results: NSArray = jsonResult["results"] as? NSArray {
                    self.delegate.didReceiveAPIResults(results)
                }
            }
        })
        
        // The task is just an object with all these properties set
        // In order to actually make the web request, we need to "resume"
        task.resume()
    }
    
    func searchItunesFor(searchTerm: String) {
        // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
        
        // Now escape anything else that isn't URL-friendly
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
            let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"
        }
    }
    
}

Now in our searchItunesFor function, we can simply call on our new get() function and slim it down to the bare essentials. Just add a call to the get(urlPath) method on the end. The final method should look like this:

func searchItunesFor(searchTerm: String) {
    // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
    let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
    
    // Now escape anything else that isn't URL-friendly
    if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
        let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"
        get(urlPath)
    }
}

See the difference? The only part that was specific to the search function was the escaping of search terms, and embedding the term inside of the URL, so there’s no reason not to just break the get() part out in to it’s own method.

Now, we can quickly add a second API function to lookup a specific album. But first, let’s modify our album model to store a collectionId variable, used by iTunes to identify individual albums.

In our Album struct, add a new variable collectionId of type Int.

let collectionId: Int

..modify the constructor to accept collectionId as an argument, and add a line to set the collectionId as one of our variables being passed in through init()

init(name: String, price: String, thumbnailImageURL: String, largeImageURL: String, itemURL: String, artistURL: String, collectionId: Int) {
    self.title = name
    self.price = price
    self.thumbnailImageURL = thumbnailImageURL
    self.largeImageURL = largeImageURL
    self.itemURL = itemURL
    self.artistURL = artistURL
    self.collectionId = collectionId
}

Great! We can now initialize Albums with a collectionId, but now our existing albumsWithJSON code is wrong, it’s missing the collectionId parameter.
Find the line that creates the newAlbum just before it appends to the array returned from albumsWithJSON().

Modify this to get the collectionId our of the result dictionary, and pass it in to the Album constructor. Since we really need the collectionId to not be nil in order for this app to work, we’ll bundle the whole album creation inside of an if let clause so that only valid albums will show up on the list.

if let collectionId = result["collectionId"] as? Int {
    var newAlbum = Album(name: name!,
        price: price!,
        thumbnailImageURL: thumbnailURL,
        largeImageURL: imageURL,
        itemURL: itemURL!,
        artistURL: artistURL,
        collectionId: collectionId)
    albums.append(newAlbum)
}

The reason we need to add this collectionId variable is so that we can perform lookups of albums when they are selected. With the collectionId, it’s easy to do a second query of the iTunes API to gather lots of details about an individual album. For example, we can get a list of tracks with media URLs that will give us a 30 second preview.


Setting up the Details View

In the last tutorial we added a DetailsViewController to our storyboard. Let’s add a TableView to this view as well. You can lay it out however you like, but I recommend giving the Table View the majority of the screen space. This is where we’re going to load in our list of tracks.

Let’s now connect this new TableView to a property in DetailsViewController called tracksTableView.

@IBOutlet weak var tracksTableView: UITableView!

Now, set the dataSource and delegate of the table view to the DetailsViewController, and implement the protocol as we did before:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 0
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    return UITableViewCell()
}

It’s probably useful at this point to try and run the app. You should be able to drill in to an album and see an empty list of tracks.


Everything working? Cool, let’s keep going…

If we’re going to show tracks we’re going to need another model. Create a new Swift file called ‘Track.swift’, and give it three String properties for title, price, and previewUrl.

import Foundation
struct Track {
    let title: String
    let price: String
    let previewUrl: String
    
    init(title: String, price: String, previewUrl: String) {
        self.title = title
        self.price = price
        self.previewUrl = previewUrl
    }
}

This model is set up in pretty much exactly the same way as the Album model, not much new here.

In DetailsViewController, let’s add an array of tracks as a new property.

var tracks = [Track]()

Now, to get track information for the album, we need to modify our API Controller again. Fortunately for us, we have an easy to use get() function that makes this pretty simple.

Let’s add a new function to APIController that takes an Int collectionId argument, and tell it to use get() to get track information

func lookupAlbum(collectionId: Int) {
    get("https://itunes.apple.com/lookup?id=\(collectionId)&entity=song")
}

We’re going to need to use this in our DetailsViewController, so we now need to implement the APIControllerProtocol we wrote earlier in to DetailsViewController. So modify the class definition of DetailsViewController to include this, and our api object.

class DetailsViewController: UIViewController, APIControllerProtocol {
    lazy var api : APIController = APIController(delegate: self)
    ...

Your project will have an error at this point about the protocol we haven’t yet implemented, but that’s ok let’s keep moving.

In the DetailsViewController viewDidLoad method, we want to add a portion to pull down tracks based on the selected album, so let’s add the following:

// Load in tracks
if self.album != nil {
    api.lookupAlbum(self.album!.collectionId)
}

This is all stuff we’ve seen before. We create an instance of our APIController with the delegate set to self, and use our new lookupTrack method to get details on the tracks in the selected album. Here we use the lazy keyword to indicate we don’t want the APIController instance api to be instantiated until it is used. We need to do this to avoid the circular dependency of DetailsViewController needing to be initialized to pass it in as an argument to the APIController(delegate:) constructor. Earlier we used an optional APIController to solve this problem, but using the lazy keyword is another way to solve this problem and it’s a little cleaner.

To fully adhere to our APIControllerProtocol, we need to implement the didReceiveAPIResults() function in this class too. We’ll use this to load in our track data. We’ll implement this exactly as we did for the SearchResultsViewController, by offloading the responsibility of converting the JSON response in to a list of tracks to the Track model.

// MARK: APIControllerProtocol
func didReceiveAPIResults(results: NSArray) {
    dispatch_async(dispatch_get_main_queue(), {
        self.tracks = Track.tracksWithJSON(results)
        self.tracksTableView.reloadData()
        UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    })
}

We’re using a non-existent tracksWithJSON() static method on Track. So we need to add that before this will compile. Open up Track.swift and add a method similar to our albumsWithJSON method.

static func tracksWithJSON(results: NSArray) -> [Track] {
    var tracks = [Track]()
    for trackInfo in results {
        // Create the track
        if let kind = trackInfo["kind"] as? String {
            if kind=="song" {
                var trackPrice = trackInfo["trackPrice"] as? String
                var trackTitle = trackInfo["trackName"] as? String
                var trackPreviewUrl = trackInfo["previewUrl"] as? String
                if(trackTitle == nil) {
                    trackTitle = "Unknown"
                }
                else if(trackPrice == nil) {
                    println("No trackPrice in \(trackInfo)")
                    trackPrice = "?"
                }
                else if(trackPreviewUrl == nil) {
                    trackPreviewUrl = ""
                }
                var track = Track(title: trackTitle!, price: trackPrice!, previewUrl: trackPreviewUrl!)
                tracks.append(track)
            }
        }
    }
    return tracks
}

This API call returns the album before it returns the list of tracks, so we also add a check to make sure the “kind” key is set to “song”, as you see on line 8. Otherwise this function is just extracting some data from the JSON; Then we check that the three fields we need aren’t null, and if so set some reasonable defaults.

Now in DetailsViewController let’s modify the numberOfRowsInSection to be the track count

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tracks.count
}

And let’s modify the cellForRowAtIndexPath method to load in our track data.

First, we need to add a prototype cell to the TableView in our storyboard, because we’re going to use a custom cell.
So select the Table View in the storyboard, and set the number of prototype cells to 1.
Then, select the cell itself and set the Identifier to “TrackCell” in the Attributes Inspector (on right-hand panel while selecting the Table View.)

Adding a Custom Table View Cell

To demonstrate what the prototype cells are really for, I think we should add some custom controls to this one. Create a new Swift class called TrackCell that inherits from UITableViewCell, and give it two IBOutlet UILabels called playIcon and titleLabel.

Now, back in your Storyboard file. Change the prototype cell’s class to ‘TrackCell’ under the Identity Inspector in the right-hand panel.
Next, add two UILabel’s to the cell by dragging the views on to the cell itself. Put one on the left for our play/pause button, and one taking up most of the space on the right to say the title of the track.

Drag two labels on to the prototype cell. Make one of them small and on the left, around 23×23 points, for a ‘Play/Stop’ icon. The second one will be the track title and should take up the rest of the cell. Click in to your play button label and then in the Mac OS menu bar hit Edit->Emoji & Symbols and find a play button looking icon. I found some under Emoji->Objects & Symbols. As an optional challenge, try using an image for the button icon!

import UIKit
class TrackCell: UITableViewCell {
    @IBOutlet weak var playIcon: UILabel!
    @IBOutlet weak var titleLabel: UILabel!
}

When you’re done you should have a prototype cell looking something like this:

In the DetailsViewController, we can now implement the custom cells by getting the TrackCell object and casting it to our class with ‘as TrackCell’

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("TrackCell") as! TrackCell
    let track = tracks[indexPath.row]
    cell.titleLabel.text = track.title
    cell.playIcon.text = "YOUR_PLAY_ICON"
    return cell
}

The logic is mostly the same as our other table view, with the exception that we cast cell to our custom class, TrackCell, on the first line. The “YOUR_PLAY_ICON” text should be replaced with the play icon, which again, you can get by hitting Edit->Emoji & Symbols in the Mac OS menu bar. Don’t forget to put quotes around it!

Next we grab the track we need from our tracks array, just as before with albums.

Finally we access our custom IBOutlet variable, titleLabel, set it’s text to be the track title, and do the same with playIcon.


Congrats on getting this far, we’re in the home stretch!


Play some music

Okay, next we want to set up a way to actually hear some audio. We’re going to use the MPMoviePlayerController class to do this. It’s easy to work with, and works just fine with audio-only streams.

First off, in our DetailsViewController class let’s add the mediaPlayer as a property, right under the class definition add:

var mediaPlayer: MPMoviePlayerController = MPMoviePlayerController()

ERROR! Use of undeclared type MPMoviePlayerController.

It’s okay, this is just because we need to import the framework MediaPlayer, it isn’t included by default in our project.

Just add the following to the top of your DetailsViewController:

import MediaPlayer

Next, let’s kick off the audio playing when a user selects one of the track’s rows. Add the following to our DetailsViewController:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    var track = tracks[indexPath.row]
    mediaPlayer.stop()
    mediaPlayer.contentURL = NSURL(string: track.previewUrl)
    mediaPlayer.play()
    if let cell = tableView.cellForRowAtIndexPath(indexPath) as? TrackCell {
        cell.playIcon.text = "YOUR_STOP_ICON"
    }
}

The line mediaPlayer.stop() stop’s the currently playing track. If there isn’t one playing, nothing happens. We don’t want to play multiple tracks at once so let’s make sure we stop a track if another one is clicked 🙂

Next, mediaPlayer.contentURL sets a url for where the media player should load it’s content. In our case it’s from the url stored in track.previewUrl.

Finally, we call mediaPlayer.play(), and get the track cell for the tapped row index.
If this row is still visible, it’ll set ‘cell’ and here we can change the playIcon label to instead show the stopped icon, which we set again by using Edit->Emoji & Symbols on the Mac OS menu bar.

If you run your app, you should now have a fully working iTunes music preview application! This by itself is pretty great, but let’s add one more thing to make it even more slick, some smooth table view cell animations.

Adding Animations

This is actually really easy, and has a very cool visual effect.

All we’re going to do is add the following function to both our SearchResultsViewController, and our DetailsViewController:

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)
    UIView.animateWithDuration(0.25, animations: {
        cell.layer.transform = CATransform3DMakeScale(1,1,1)
        })
}

Now run the app and scroll around, neat right?

So how’s it work?
The function willDisplayCell is called from the TableView delegate, similar to the rest of our callback functions that set up the row. But this one is only called the moment before a cell appears on-screen, either through initial loading or through scrolling.

cell.layer.transform = CATransform3DMakeScale(0.1,0.1,1)

This first line uses CATransform3DMakeScale() to create a transform matrix that scales down any object in x, y, and z. If you are familiar with linear algebra you’ll know what this means right away. If not, it’s not super important. The point is, it makes things scale, and here we’re scaling it down to 10% by setting the x and y values to 0.1.

So, we are basically just setting the cell layer’s transform to be 90% smaller.

Next we set the cell layer’s transform to a new scale, this time of (1,1,1). This just means that it should return to it’s original scale. Because this line is run inside of the animateWithDuration() block, we get the animation for free courtesy of Core Animation.

Experienced Obj-C developers will probably recognize this is not the only way to perform such an animation. However, I believe this method is the easiest to understand, in addition to being the most Swifty.

In my upcoming book I go in to great detail about how to efficiently use Core Animation to make beautiful animations for your apps. Using Core Animation in this way really makes your app pop.

The full source code for this section is available here.

A reader of this tutorial series contributed the next section, which covers producing a nicer play/pause icon purely in code. Check it out here.
Make sure to sign up to be notified of the next tutorial series.


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

Subscribe via RSS

Developing iOS 8 Apps Using Swift – Interaction with multiple views (Part 6)

This section completely updated to reflect changes in Xcode 6.3, as of April 16, 2015

In parts 1 through 5 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some API results from iTunes inside of them. If you haven’t read that yet, check out Part 1

If not, and you just want to start from here, download the code for Part 5 to get started. We’ll use it as a template to begin.

In this tutorial we’re going to do quite a few things, so let’s get started!

Modifying The API Controller code

First off, our actual plan for this app is to show iTunes music information. So let’s modify the API controller to better handle this information.

One thing’s been bothering me though. When we create our APIController, we set a delegate after it’s created. But, an API Controller without a delegate isn’t all that useful, so why even offer the option to make one?

Let’s add a constructor that accepts the delegate as it’s only argument.

init(delegate: APIControllerProtocol) {
    self.delegate = delegate
}

Now, our delegate variable in the APIController isn’t actually going to be an optional any more. There is no APIController without a delegate!
So also change the delegate property to be an every day, non-optional APIControllerProtocol object.

var delegate: APIControllerProtocol

There’s also going to be an error at the end of the searchItunesFor method, because we’re treating the delegate object as an optional, but it’s not optional any more. So change the erroneous line to say this:

self.delegate.didReceiveAPIResults(results)

The only difference is we removed the ? from after the delegate property, to indicate it’s not an optional.

Now in our SearchResultsController, we need to change a few things. First, since the APIController constructor now needs the delegate object to be instantiated before *it* can be instantiated itself, we need to make it an implicitly unwrapped optional, and wait until viewDidLoad to assign it.

So in the api variable declaration change to this:

var api : APIController!

In the viewDidLoad method we need to unwrap the api object in order to call searchItunesFor(). You should end up with this

override func viewDidLoad() {
    super.viewDidLoad()
    api = APIController(delegate: self)
    api.searchItunesFor("JQ Software")
}

 

Searching for Albums
Let’s also modify our call to the searchItunesFor() in the APIController to use a search term for music. We’ll also show a networkActivityIndicator, to tell the user a network operation is happening. This will show up on the top status bar of the phone.

override func viewDidLoad() {
    super.viewDidLoad()
    api = APIController(delegate: self)
    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    api.searchItunesFor("Beatles")
}

Now in our urlPath in the APIController, let’s modify the API parameters to look specifically for albums.

let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"

We’ll now get results in the form of Album data, but this schema is a little different from apps. In fact running the app right now will we’ll just get default cells because the expected JSON data isn’t there. This is really fragile code, but we can make it slightly less fragile by doing some modeling of the data we expect.

Creating a Swift model for iTunes Albums

In order to facilitate passing around information about albums, we should create a model of what an album is exactly. Create a new swift file and call it Album.swift with the following contents:

import Foundation

struct Album {
    let title: String
    let price: String
    let thumbnailImageURL: String
    let largeImageURL: String
    let itemURL: String
    let artistURL: String
    
    init(name: String, price: String, thumbnailImageURL: String, largeImageURL: String, itemURL: String, artistURL: String) {
        self.title = name
        self.price = price
        self.thumbnailImageURL = thumbnailImageURL
        self.largeImageURL = largeImageURL
        self.itemURL = itemURL
        self.artistURL = artistURL
    }
}

It’s a pretty simple struct, it just holds a few properties about albums for us. We create the 6 different properties as strings, and add an initializer that sets all the properties based on our parameters.

So now we have a struct for albums, let’s use it!

Using our new Swift Album model

Back in our SearchResultsController, let’s modify the tableData array variable, and instead opt for a Swift native array for Albums. In swift, this is as easy as:

var albums = [Album]()

We can do away with do line var tableData = [], we won’t be using that any more.

This creates an empty array containing strictly Albums. We’ll now need to change our tableView dataSource and delegate methods to understand albums.
In the numberOfRowsInSection method, let’s change the number of items to the count of albums in our albums array:

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return albums.count
}

Now in cellForRowAtIndexPath, let’s swap out those dictionary lookups for a single album lookup:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as! UITableViewCell
    let album = self.albums[indexPath.row]

    // Get the formatted price string for display in the subtitle
    cell.detailTextLabel?.text = album.price
    // Update the textLabel text to use the title from the Album model
    cell.textLabel?.text = album.title
    
    // Start by setting the cell's image to a static file
    // Without this, we will end up without an image view!
    cell.imageView?.image = UIImage(named: "Blank52")
    
    let thumbnailURLString = album.thumbnailImageURL
    let thumbnailURL = NSURL(string: thumbnailURLString)!
    
    // If this image is already cached, don't re-download
    if let img = imageCache[thumbnailURLString] {
        cell.imageView?.image = img
    }
    else {
        // The image isn't cached, download the img data
        // We should perform this in a background thread
        let request: NSURLRequest = NSURLRequest(URL: thumbnailURL)
        let mainQueue = NSOperationQueue.mainQueue()
        NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
            if error == nil {
                // Convert the downloaded data in to a UIImage object
                let image = UIImage(data: data)
                // Store the image in to our cache
                self.imageCache[thumbnailURLString] = image
                // Update the cell
                dispatch_async(dispatch_get_main_queue(), {
                    if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
                        cellToUpdate.imageView?.image = image
                    }
                })
            }
            else {
                println("Error: \(error.localizedDescription)")
            }
        })
    }
    return cell
}

Then there is the didSelectRowAtIndexPath method that needs to be modified to use the albums array. But, actually we’re not going to need this any more, so let’s just delete the whole method.

Creating Album objects from JSON

Now, all of this is not much use if we aren’t creating our album information in the first place. We need to modify our didReceiveAPIResults method of SearchResultsViewController to take album JSON results, create Album objects, and save them in to the albums array. Since we have a model for Albums now, it makes sense to move this functionality in to the Album model itself. So let’s make a minor adjustment to didReceiveAPIResults and delegate the responsibility of construction the albums array to the Album class.

func didReceiveAPIResults(results: NSArray) {
    dispatch_async(dispatch_get_main_queue(), {
        self.albums = Album.albumsWithJSON(results)
        self.appsTableView!.reloadData()
        UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    })
}

Note that since this is where the api request comes to it’s conclusion, we also turn off the networkActivityIndicator.

Now in the Album.swift file we need to add a static method that creates a list of albums from a JSON list.

static func albumsWithJSON(results: NSArray) -> [Album] {
    // Create an empty array of Albums to append to from this list
    var albums = [Album]()
    
    // Store the results in our table data array
    if results.count>0 {
        
        // Sometimes iTunes returns a collection, not a track, so we check both for the 'name'
        for result in results {
            
            var name = result["trackName"] as? String
            if name == nil {
                name = result["collectionName"] as? String
            }
            
            // Sometimes price comes in as formattedPrice, sometimes as collectionPrice.. and sometimes it's a float instead of a string. Hooray!
            var price = result["formattedPrice"] as? String
            if price == nil {
                price = result["collectionPrice"] as? String
                if price == nil {
                    var priceFloat: Float? = result["collectionPrice"] as? Float
                    var nf: NSNumberFormatter = NSNumberFormatter()
                    nf.maximumFractionDigits = 2
                    if priceFloat != nil {
                        price = "$\(nf.stringFromNumber(priceFloat!)!)"
                    }
                }
            }
            
            let thumbnailURL = result["artworkUrl60"] as? String ?? ""
            let imageURL = result["artworkUrl100"] as? String ?? ""
            let artistURL = result["artistViewUrl"] as? String ?? ""
            
            var itemURL = result["collectionViewUrl"] as? String
            if itemURL == nil {
                itemURL = result["trackViewUrl"] as? String
            }
            
            var newAlbum = Album(name: name!, price: price!, thumbnailImageURL: thumbnailURL, largeImageURL: imageURL, itemURL: itemURL!, artistURL: artistURL)
            albums.append(newAlbum)
        }
    }
    return albums
}

I know this looks like a lot of new code, but actually there’s not much going on here. It’s really just looping through the list coming from allResults, and its grabbing values for some keys, and setting defaults if they’re missing.

The ?? operator used here is pretty neat. It works like this:

let finalVariable = possiblyNilVariable ?? "Definitely Not Nil Variable"

The finalVariable value is set to possiblyNilVariable if it is not nil. But if it is nil? It uses the value of the thing on the right-hand side of the ?? operator. In this case, the string “Definitely Not Nil Variable”.

We use this here in order to prevent getting nil values passed in to our Album.

On line 39, we create an Album object. On line 40 the album is added to the list.
Finally on line 43 the list of albums is returned.

If you run the app now you should see a new list of Album’s pop up. Cool, right?

 

Creating a second view

Now to actually show the details of an album, we’ll need a new view. First let’s create the class.
Add a new file called DetailsViewController.swift that inherits from UIViewController.

Our view controller will be pretty simple to start. We’re just going to add an album, and implement UIViewController’s init method as well as viewDidLoad().

import UIKit

class DetailsViewController: UIViewController {
    
    var album: Album?
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

This code doesn’t do much yet, but that’s okay. We just need the class to exist in order to set up our storyboard.

Since we’ll be pushing views back and forth on the stack we’ll want a navigation bar as well. It’s hard to explain in text, and there is an NDA preventing me from showing parts of Xcode 6 in screenshots, so instead I created a short video demonstrating how to do this in Xcode 5. The process is nearly identical for Xcode 6 Beta, and is not under any sort of NDA.

In the video we did the following:

  1. Embedded our view controller in a navigation controller using the Xcode shortcut in the Editor menu, by clicking the view controller, then selecting Editor->Embed In->Navigation Controller
  2. Added a new view controller
  3. Set it’s class and storyboard ID to ‘DetailsViewController’
  4. Control+Clicked+Dragged from the table view cell in our first view controller to the new view controller we just created, and selected ‘show’ for the type of segue.

What this last step does is creates a segue on our navigation controller that pushes the new view on top of the stack. If you run the app now and click a cell, you should see this new view animate in.

Let’s build out a simple UI for this new view. It’ll contain a UIImageView that is 100×100 pixels, and a title Label. Drag these objects out of the object library and arrange them any way you like on the new view.

 

Providing the new view with Album information

When the storyboard segue fires off, it first calls a function on whatever view controller is currently on the screen called prepareForSegue. We’re going to intercept this call in order to tell our new view controller which album we’re looking at. Add the following in SearchResultsViewController:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if let detailsViewController: DetailsViewController = segue.destinationViewController as? DetailsViewController {
        var albumIndex = appsTableView!.indexPathForSelectedRow()!.row
        var selectedAlbum = self.albums[albumIndex]
        detailsViewController.album = selectedAlbum
    }
}

What’s happening here is the segue parameter being passed in has a member called destinationViewController, which is our fancy new DetailsViewController we just created. In order to set the album member on it, we first need to cast it to DetailsViewController using the ‘as’ keyword as shown above.
Then, by using the indexPathForSelectedRow() method of our table view we can determine which album is selected at the moment this segue happens.
Using this information, well tell our detailsViewController which album was clicked before it is displayed.

Now I’m going to show you a pretty nifty feature of Xcode. We’re going to let it write some code for us.

Open up your storyboard again let’s start creating IBOutlets for our image view, label, button, and text view. On the top-right hand corner of Xcode there is the ‘assistant’ button. The icon looks like a bowtie and suit jacket. Clicking on this will open up a code window right next to your storyboard window. Make sure that one of the panels is showing DetailsViewController.swift, and the other is showing Main.storyboard.

Now, hold control, and click+drag from your image view to your code file. Just a line under your class definition for DetailsViewController. It’ll prompt you for a name, so let’s call it ‘albumCover’. The default options should be fine here. After doing this you should see this line of code newly added:

@IBOutlet weak var albumCover: UIImageView!

We just created a new IBOutlet, and now it’s connected to our storyboard’s DetailsViewController. How cool is that?

Do the same thing for the label you added to your view, and call it ‘titleLabel’.

Next, let’s modify viewDidLoad so that it will load in the info we’re being passed to our view objects, here’s the final DetailsViewController code:

import UIKit

class DetailsViewController: UIViewController {
    var album: Album?
    @IBOutlet weak var albumCover: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        titleLabel.text = self.album?.title
        albumCover.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL)!)!)
    }
}

The @IBOutlets are the UI connections made by our storyboards, and our viewDidLoad method sets the title and album cover variables to load in from our Album object.

Now try running the app and taking a look. We can now drill in to details for albums and get a nice big detail view with the album cover and title. Because we pushed in a navigation controller, we also get a functional Back button for free!

If you made it this far, I want to personally congratulate you so let me know on twitter that you pulled it off! You are well on your way to creating real iOS apps with Swift.

I’ve decided this tutorial series is going to be expanded upon and refined, along with several other tutorials and essays on working with swift, Xcode, and Apple in a new book, which I have available for pre-order here. Also, I’ve decided to open up a new forum for all the followers of this tutorial.

Make sure to sign up to be notified of the new sessions.

The full source code for this section is available here.

In part 7, we set up a full Detail view with a working music player, and implement some great animations.

Go To Part 7 ->


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

Subscribe via RSS

8 Great Open Source Projects to use in your next iPhone App

I’ve scoured the internet looking for great open source iOS apps, and what I determined is that what is really more interesting is the open source iOS components and frameworks. So here’s my list of the most useful open source iOS components.

8. PSCollectionView
PSCollectionView is a custom collection view that allows for the easy creation of Pinterest-style image collections in your iOS apps. Source on Github

 

7. GMGridView
This little grid view library allows for the simple creation of highly interaction view sets. You can see a nice video of an example result hereSource on Github

 

6. PRTween

PRTween is an extension of the core animation framework. It is an effort to take the power of core animation, and make it easier to use. The commands available may remind some of the stupid-simple jQuery animation commands like fade, hide, or show. Source on Github

 

 

5. iHasApp
I recently was asked by a client if we could detect their other apps installed on the device, and if the user didn’t own them if we could have the app present cross-promotional ads. I wasn’t sure what the answer to their question was, it seemed like something that might fall under the dreaded “private api” category. But here this project is an App Store friendly library to detect other apps installed on the user’s phone. Source on Github

 

4. NUI
NUI is a very interesting project that aims to make styling of iOS apps as easy as styling in CSS. Take a look at the syntax on the project’s github page and you’ll see what makes this such an interesting project. Source on Github

 

3. grabKit
If you are writing an iPhone app that involves the user’s photos, one thing that’s always a bit tricky is adding support for all the various social networks that the user might prefer to host their photos on. grabKit makes this easier with this drop-in library that let’s the user import photos from Facebook, Flickr, Instagram, Picasa, or the iOS device itself. Source on Github

 

2. MultistrokeGestureRecognizer
This library is great for anyone creating a very touch-centric game, or any app that uses lots of complex gestures. The library is based on the N Multistroke Recognizer which is a great algorithm for detecting very complex touch gestures. If you remember the gestures in the game Black & White, it’s kind of like that. Source on Github

 

1. Filepicker
Similar to grabKit, Filepicker allows for the importing of files from various social networks, but also includes many more services such as Dropbox, Gmail, and even Github. The library is also not limited to only photos but can be used for a range of content types. Source on Github

 

Honorable mention: cupertino
Cupertino is not an iOS library, but rather a ruby gem that allows for administration of your Apple Developer Account. This has been a missing piece of the formula for automating iOS development work for years, so I’m glad to see some hackers have decided to take up the challenge of making a CLI for the Apple Developer Account system.


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

Subscribe via RSS