Open Source Swift – A Look At The Top Swift Repositories

Github, the most popular open source repository for open source software, offers a feature that let’s us view repositories by language. In this post, I want to dissect some of the most popular Swift repositories as of June 5th, 2015. So, let’s kick it off with the most starred Swift repository, Alamofire.

Alamofire

Alamofire is an “Elegant HTTP Networking in Swift”, written by Matt Thompson, a well known Objective-C developer responsible for the AFNetworking library. The library takes the built-in iOS networking features and abstracts them away in to a simpler, and more Swift-y API.

Take for example the case of a simple GET request that returns JSON:

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseString { (_, _, string, _) in
                  println(string)
         }

It’s the most popular library for Swift to date, so you should probably be using it, right?

Well, maybe… The library makes some common tasks simpler and less verbose, but if you don’t know the basics of networking in Swift (or Objective-C), it’s probably best to get a good understanding of the existing APIs first. After understanding what’s going on under the hood, you can make a more informed decision about whether or not you need the extra layer of abstraction. Alamofire is a big framework, and networking is a huge topic, so I don’t want to get too far in to the details on this library. But, suffice to say if you are working with complex networking requests with lots of back and forth with a web server, and/or working with a complicated authentication process, using Alamofire might reduce some of the repetitive coding tasks. If you are new to iOS development, I would recommend just stick to the APIs provided by Apple for now.

SwiftyJSON

SwiftyJSON is “The better way to deal with JSON data in Swift” according to it’s Github entry. This framework is one of the first I saw when Swift was first released, that combined with the fact that JSON parsing is such a common problem is how it became a top repository, helping to deal with the messiness of Apple’s built-in JSON parser. In particular, the static typing of Swift and optional syntax led to a lot of verbose JSON parsing code, guessing and checking each step of the way for each key and checking every cast. The truth is though, using this library is VERY similar to just using optional chaining and Swift’s normal casting syntax. There is not much benefit here, and from what I’ve seen in production, SwiftyJSON has some performance problems, as a result I’m not sure I would recommend using it right now, except in prototype apps, or during the learning phase.

Take a look at the example they give as the standard approach to parsing JSON, which they describe as “not good”:

let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)

if let statusesArray = JSONObject as? [AnyObject],
   let status = statusesArray[0] as? [String: AnyObject],
   let user = status["user"] as? [String: AnyObject],
   let username = user["name"] as? String {
    // Finally we got the username
}

They then present their alternative version of the syntax:

let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string{
  //Now you got your value
}

There’s a few issues here, first of which is that the simplifications they are showing are partially just taking advantage of language features that would actually work with the regular parser. Second, it seems like their example actually would not work.

Based on the example code shown above, the example JSON they are parsing looks something like this:

{
    "statuses": [
        {
            "user": {
                "name": "Bob"
            }
        }
    ]
}

One issue with this sample right off the bat is that they are casting the initial value of the JSON to an array, which would suggest that the root element is an array, which is invalid JSON. The type of the root object in valid JSON is always going to be a key/value. Or equivalently in Swift, a Dictionary of type [String:AnyObject]. Additionally, it’s good practice to actually check if the JSON parsing succeeded or not.

Once we start going through and fixing all the issues with the sample code, assuming we want to explicitly cast everything as they have shown, we end up with something like this:

if let JSONObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [String:AnyObject],
    statusesArray = JSONObject["statuses"] as? [[String:AnyObject]] {
        let status = statusesArray[0] as [String: AnyObject]
        if let user = status["user"] as? [String: AnyObject],
            let username = user["name"] as? String {
                println("username: \(username)")
        }
}
else {
    println("Failed to parse JSON, handle this problem")
}

Now, this is in fact pretty verbose, and could be reduced quite a bit, but let’s stop a think about what we’re doing here. In this example, we are trying to download a list of statuses which are owned by users, and they have names. In an actual Swift application, I would expect there to be a model representing these objects. Maybe something like this:

struct User {
    let name: String
}
struct Status {
    let user: User
}

Assume we are using SwiftyJSON for a moment, how would we add these records to the model of our app? Maybe with a bit of code like this…

struct User {
    let name: String
}
struct Status {
    let user: User
}

let parsedJson = JSON(data: data)
for (key, val) in parsedJson["statuses"] {
    if let username = val["user"]["name"].string {
        let owner = User(name: username)
        let newStatus = Status(user: owner)
    }
}

This works relatively well, assuming we are just creating objects from a JSON feed rather than synchronizing them. But what if there is a server error and the JSON comes back invalid? For example if there is a server error which changes the JSON to present an “error” key, and it no longer includes “statuses”, this loop simply would not be executed. Failing silently is better than crashing, but it would be nice to check for issues and try again, or adjust something in the app.

Since we need to check for the presence of statuses, and this for loop doesn’t actually do that, we need to check the count of statuses first, which means we need to cast it to an array, and *then* check the count…

if(parsedJson["statuses"].count<1) {
    println("Oops! An error occurred")
}

And that's that! Right?
Well, no...

If the key isn't defined, this count property evaluates to 0, which could just mean there is no new statuses to see. The count really should not be zero, it should be null.. but SwiftyJSON is telling us it's 0. This seems like the kind of thing I really *don't* want a JSON parser to be doing. They really seem to not like the optional syntax in Swift, and instead reinvented it with these type properties. Why not just stick with convention?
Our final code might look something like this:

struct User {
    let name: String
}
struct Status {
    let user: User
}

let parsedJson = JSON(data: data)
for (key, val) in parsedJson["statuses"] {
    if let username = val["user"]["name"].string {
        let owner = User(name: username)
        let newStatus = Status(user: owner)
    }
}
if(parsedJson["statuses"].count<1) {
    println("Oops! An error occurred")
}
if let err = parsedJson["error"].string {
    println(err)
}

Our code is starting to grow, and this doesn't cover a ton of things we would need in a real-world application, such as updating the model, including more properties, checking for equality, enforcing uniqueness, cascading relationship changes, and a host of other things. Core Data can handle much of this, and it's common practice to implement models as Core Data models, but that still creates a situation where we have to custom implement all kinds of methods for converting the entire model object (such as Status) *back* in to JSON to update the server.

In the Objective-C world there is Mantle, a great library for handling such things. Before that there was RestKit. RestKit however made some ...interesting... design decisions a few years ago in a big update, and haven't ever really recovered since then. Unfortunately I haven't found a good solution for Swift just yet, and trying to work with Mantle proves to be problematic in it's current form, unless you implement all your models in Obj-C, something I'm not sure we all want to do at this stage.

I know this isn't all problems with SwiftyJSON, but they ironically break a lot of Swift conventions in dealing with optional values. SwiftyJSON is really a terrible name, they are very much not Swifty at all. However, the syntax is a little easier on the eyes. Personally, I don't use the library in my projects.

Spring

Spring is "A library to simplify iOS animations in Swift." How does it do this? Let's take a look.

Trying out some sample code I threw together this quick demo UIViewController that adds a blue square to the screen and animates it in, give it a try yourself, it's pretty nifty:

import UIKit
import Spring

class ViewController: UIViewController {
    var square = SpringView(frame: CGRectMake(0, 0, 200, 200))
    override func viewDidLoad() {
        super.viewDidLoad()
        
        square.center = self.view.center
        square.backgroundColor = UIColor.blueColor()
        square.animation = "squeezeDown"
        square.animate()

        self.view.addSubview(square)
    }
}

The SpringView seems to basically just be a UIView subclass with the animations added in. I don't know if I really like the idea of having to use their UIView, but I suppose most of the time I just use the basic UIView, and even if I didn't, I could just subclass SpringView instead.

Spring sports quite a few animation types, set as a string. The square.animation = "squeezeDown" here is what's determining the animation to play. The library goes beyond this, and in fact allows simple animations to be created in storyboards. So in theory you could put Spring in your Xcode project, and then pass it off to a designer to set up some nifty animations using this library. Very interesting idea, I would like to hear from someone who has tried to do exactly this.

Quick

Quick is "The Swift (and Objective-C) testing framework."

Really? It's THE testing framework? Let's take a look at how Quick works as opposed to XCTest, or expecta.

In XCTest, you might define an assertion that you're testing against like this:

class JSONSwiftTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testContrivedExample() {
        let name = "jameson"
        XCTAssertEqual(name, "jameson", "Name should be \"jameson\"")
    }
}

This is okay, it makes the test basically confirm that name is equal to "jameson". It's simple enough, but there is a common trend/desire among developers to instead express test cases in terms of desired behavior, rather than specifically implementing what the desired behavior causes. Those may sound like the same thing, but take a look at how Quick (due to it's usage of the library Nimble) expresses the same thing like this:

import Quick
import Nimble

class AQuickTest: QuickSpec {
    override func spec() {
        describe("the user") {
            it("has the name 'Jameson'") {
                let name = "Jameson"
                expect(name).to(equal("Jameson"))
            }
        }
    }
}

More than anything else, this framework encourages behavioral tests, which is why this example includes more information about our expectations.

Quick also eases some of the pain of asynchronous testing. In XCTest I personally tend to use XCTestAsync, although Xcode 6 does introduce a way to do this using XCTestExpectation. The basic way that works is you can create an expectation object, and then fulfill it when the async operation is complete. It's not a bad approach.

import Quick
import Nimble

@objc class AsyncExample {
    var longTaskIsDone = false
    var timer: NSTimer?
    func startLongTask() {
        timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "taskIsDone", userInfo: nil, repeats: false)
    }
    func taskIsDone() {
        println("task done")
        longTaskIsDone = true
    }
}

class AQuickTest: QuickSpec {
    override func spec() {
        describe("the user") {
            it("has the name 'Jameson'") {
                let name = "Jameson"
                expect(name).to(equal("Jameson"))
            }
        }
        
        describe("Async Example") {
            describe("its long task") {
                it("should finish in 5 seconds") {
                    let asyncExample = AsyncExample()
                    asyncExample.startLongTask()
                    expect(asyncExample.longTaskIsDone).toEventually(beTruthy(), timeout: 3, pollInterval: 0.4)
                }
            }
        }
        
    }
}

In this example we just create an NSTimer that fires in 2 seconds, as a simulated async example. Then in the Async Example test, we can use the .toEventually() method to wait around and keep checking in to the asyncExample.longTaskIsDone property. This is slightly cleaner in that using expectations, because with this method we don't need to change our code to make sure the test is notified of this variable changing. Having an ongoing timer keep checking is great (just be careful not to have it calling methods with side effects!)

Overall Quick seems pretty interesting, the approach is sure to appeal to those in professional environments, or working with an Agile team where specs change fast.

That's it for this time, if you would like to see any of these libraries covered in greater detail be sure to let me know. You can find me on Twitter.


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

Subscribe via RSS

Local Notifications in iOS 10 with Swift (Part 1)

This post has been updated for compatibility with XCode 8 and iOS 10

Local notifications are a simple way to display information to your user even when your app is in the background. They allow you to display an alert, play a sound or badge your app’s icon. Local notifications can be triggered at a scheduled time or when your user enters or leaves a geographical area. In this tutorial, we’ll create a simple to-do list application and delve into a number of UILocalNotification features and quirks.

First, let’s create a new single view application in Xcode and call it LocalNotificationsTutorial. Remember to choose Swift as the language.

Screen Shot 2015-01-30 at 10.15.42 PM

Before we delve into writing any code, let’s get our view controllers and views set up. This is a necessary step, and I’ll be covering some of the basics of using Interface Builder, but if want to skip it and jump right into dealing with scheduling notifications you can follow along by getting the configured application from here.

Configuring the Views

Our finished application will have two views in a navigation controller: a root view that displays a chronologically ordered list of to-do items with deadlines, and a view for creating to-do list items.

iOS Simulator Screen Shot Feb 1, 2015, 11.43.36 PM iOS Simulator Screen Shot Feb 4, 2015, 10.26.58 PM

Creating the View Controllers

Before opening Interface Builder, we should generate view controllers to back each of our views. Ctrl or right-click on the project group in the project navigator and select “New File”.

Screen Shot 2015-01-30 at 10.29.29 PM

Select “Cocoa Touch Class” from the “iOS -> Source” menu and create a subclass of UITableViewController named “TodoTableViewController”. Don’t create a XIB file, and, of course, the language should be Swift. This will be our root view controller and it will be in charge of displaying the to-do list.

We need a separate view for creating our to-do items. Repeat the process, this time create a UIViewController subclass and name it “TodoSchedulingViewController”.

Setting up Navigation

Now that our view controllers are created, let’s hook them into our project’s storyboard. Click “Main.storyboard” and delete the root view controller. Go ahead and delete “ViewController.swift” as well. We won’t be using it.

Drag a new navigation controller from the object library onto the now blank storyboard. We’ve deleted our root view, so drag a Storyboard Entry Point onto the navigation controller so our application will have a root view.

Screen Shot 2015-01-30 at 11.11.26 PM

The object library

Select the navigation controller’s root view (a table view) and set its custom class to “TodoTableViewController” in the identity inspector.

Screen Shot 2015-01-30 at 11.22.17 PM

The identity inspector

Since we’re going to display deadlines for each to-do item, we need to select the table view’s first (and only) prototype cell, switch to the attributes inspector, and set the cell’s style to “Subtitle”.  It needs a reuse identifier as well, so we can refer to it in our code. We’ll use “todoCell”.

Screen Shot 2015-01-30 at 11.39.00 PM

The attributes inspector

Keep the attributes inspector selected. Drag a navigation item onto the table view and give it the title “Todo List”, then drag a bar button item onto that and set the identifier to “Add”.

Now we’ll set up the view on which users can schedule and title their to-do items. Drag a view controller into the storyboard. Its custom class should be set to “TodoSchedulingViewController”.

Ctrl or right-click on the “Add” button, drag from the “action” to the new view, and select “show”. Now all our navigation is linked up.

Screen Shot 2015-01-31 at 12.03.33 AM

We need to drag three controls onto this view: a text field (with “Title” as the placeholder text), a date picker and a button (titled “Save”). Just center and widen all three, then “add missing constraints” to all views in the view controller (Under “Resolve Auto Layout Issues”, the triangle icon towards the bottom right of Xcode). Adding constraints ensures that the view is laid out predictably across various screen sizes (instead of having portions of controls cut off or misaligned).

Screen Shot 2015-01-31 at 12.25.08 AM

Connecting Controls to Code

Now that our views and navigation are laid out, we have to link our text field and date picker controls to an IBOutlet in TodoSchedulingViewController.swift. This will allow us to access these controls (and their values) in our code. There are a few ways to do this, but the simplest is to enable the Assistant editor by clicking the interlocking circles in the top right of XCode, Ctrl or right-click the control, and drag the “New Referencing Outlet” circle into the TodoSchedulingViewController class body.

Screen Shot 2015-01-31 at 12.45.14 AM

Do this for both the text field and the date picker, naming them “titleField” and “deadlinePicker” respectively.

@IBOutlet weak var titleField: UITextField!
@IBOutlet weak var deadlinePicker: UIDatePicker!

The final step is to connect the save button to an IBAction (an event handler function). Just Ctrl or right-click the button, and drag from the “Touch Up Inside” circle to the code window. Name the action “savePressed” and optionally set the sender type to UIButton (no other controls will be firing this action, so we can be more specific).

@IBAction func savePressed(_ sender: UIButton) {
}

The views and navigation for this project are all set up. Go ahead and run the app in the simulator. Try it for a few different devices. You’ll notice that, because of the constraints we added, your views stretch or squeeze to fit the various screen sizes.

Now, let’s get out of Interface Builder and write some code.

Registering Notification Settings

We need to register our intent to use notifications with the application. Otherwise, the notifications we schedule simply won’t fire. Switch over to your Application Delegate (AppDelegate.swift) and add the following line to application:didFinishLaunchingWithOptions:

application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))

On the first launch of the app, users will now be prompted to allow your app to send notifications. If the user grants permission, we will be able to schedule notifications that display a banner, play a sound, and update our app icon’s badge number (which we’ll cover in part 2).

iOS Simulator Screen Shot Feb 3, 2015, 2.56.37 PM

Modeling the Application

For a simple app like this, it may be tempting to handle all of the logic in the view controllers that we just created, but we’ll have an easier time understanding and maintaining our code if we keep the management of the to-do list and the presentation logic separate.

I chose to model individual to-do items with a lightweight struct. Let’s create that now. Just click “File -> New -> File”, choose “Swift File” and name it “TodoItem”. Each to-do list item has a title and a deadline, so we’ll create properties for both.

struct TodoItem {
  var title: String
  var deadline: Date
}

Ultimately, each to-do list item needs to be backed by an on disk representation, so that the list will persist if the application is terminated. Instances of UILocalNotification have a userInfo property – a dictionary that we can use to store miscellaneous data like the title, but we can’t rely on that alone in this case. Local notifications are automatically unscheduled after they are fired, which means that we wouldn’t be able to retrieve past-due items. We’ll have to use another method to persist our items, and we need a way to associate an item we’ve retrieved from the disk with its corresponding local notification. For that, we’ll use a universally unique identifier (UUID).

struct TodoItem {
    var title: String
    var deadline: Date
    var UUID: String

    init(deadline: Date, title: String, UUID: String) {
        self.deadline = deadline
        self.title = title
        self.UUID = UUID
    }
}

Since we’re going to display overdue items in red, lets also add a convenience method that returns whether or not an item is overdue.

var isOverdue: Bool {
    // Optionally, you can omit "ComparisonResult" and it will be inferred.
    return (Date().compare(self.deadline) == ComparisonResult.orderedDescending) // deadline is earlier than current date
}

Saving To-Do Items (Scheduling Notifications)

We need a class to represent the list of items and handle persisting them. Create a new Swift file named “TodoList”.

Our application is only concerned with maintaining a single to-do list, so it makes sense to make a single shared instance available throughout the app.

class TodoList {
    class var sharedInstance : TodoList {
        struct Static {
            static let instance: TodoList = TodoList()
        }
        return Static.instance
    }
}

This method is the community-accepted way to implement the singleton pattern in Swift, which you can adapt to your own projects. If you’re curious, you can read the details about what it’s doing and why in this Stack Overflow answer.

UserDefaults provides a simple way to persist our to-do items to disk. The following snippet defines a method that adds a dictionary representation of a to-do item to standard user defaults (with UUID as the key), and then creates the associated local notification.

fileprivate let ITEMS_KEY = "todoItems"
func addItem(_ item: TodoItem) {
    // persist a representation of this todo item in UserDefaults
    var todoDictionary = UserDefaults.standard.dictionary(forKey: ITEMS_KEY) ?? Dictionary() // if todoItems hasn't been set in user defaults, initialize todoDictionary to an empty dictionary using nil-coalescing operator (??)
    todoDictionary[item.UUID] = ["deadline": item.deadline, "title": item.title, "UUID": item.UUID] // store NSData representation of todo item in dictionary with UUID as key
    UserDefaults.standard.set(todoDictionary, forKey: ITEMS_KEY) // save/overwrite todo item list
 
    // create a corresponding local notification
    let notification = UILocalNotification()
    notification.alertBody = "Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification 
    notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view" 
    notification.fireDate = item.deadline as Date // todo item due date (when notification will be fired) notification.soundName = UILocalNotificationDefaultSoundName // play default sound 
    notification.userInfo = ["title": item.title, "UUID": item.UUID] // assign a unique identifier to the notification so that we can retrieve it later
 
    UIApplication.shared.scheduleLocalNotification(notification)
}

Notice that we’re just playing the default sound when the notification fires. You can provide your own sound file, but audio files over 30 seconds in length are not supported. The default sound will play instead.

We’re almost to the point where users can create new list items. It’s time to implement savePressed: in TodoSchedulingViewController.

@IBAction func savePressed(_ sender: UIButton) {
    let todoItem = TodoItem(deadline: deadlinePicker.date, title: titleField.text!, UUID: UUID().uuidString)
    TodoList.sharedInstance.addItem(todoItem) // schedule a local notification to persist this item
    let _ = self.navigationController?.popToRootViewController(animated: true) // return to list view
}

Note that, since this is a new to-do list entry, we’re passing in a newly generated UUID.

Try out the app now. Launch it in the simulator, create a new item due a minute in the future, and return to the home or lock screen (Shift-CMD-H or CMD-L) to view the notification. The notification won’t necessarily fire right on the stroke of the minute (due to a hidden ‘seconds’ value on the time picker control), but you’ll see it within the minute.

iOS Simulator Screen Shot Feb 3, 2015, 4.29.05 PMiOS Simulator Screen Shot Feb 3, 2015, 4.33.13 PM

The 64 Notification Limit

It’s important to note that you’re limited to scheduling 64 local notifications. If you schedule more, the system will keep the 64 soonest firing notifications and automatically discard the rest.

We can avoid running into this issue by disallowing the creation of new items if 64 already exist.

In TodoTableViewController:

func refreshList() {
    todoItems = TodoList.sharedInstance.allItems()
    if (todoItems.count >= 64) {
        self.navigationItem.rightBarButtonItem!.enabled = false // disable 'add' button
    }
    tableView.reloadData()
}

Retrieving To-Do Items

The fact that to-do items are persisted as an array of dictionaries is an implementation detail that outside classes shouldn’t have to worry about. Our TodoList class needs a public facing function that the list view controller can query to retrieve a list of to-do items.

func allItems() -> [TodoItem] {
    let todoDictionary = UserDefaults.standard.dictionary(forKey: ITEMS_KEY) ?? [:]
    let items = Array(todoDictionary.values)
    return items.map({
        let item = $0 as! [String:AnyObject]
        return TodoItem(deadline: item["deadline"] as! Date, title: item["title"] as! String, UUID: item["UUID"] as! String!)
    }).sorted(by: {(left: TodoItem, right:TodoItem) -> Bool in
        (left.deadline.compare(right.deadline) == .orderedAscending)
    })
}

This function retrieves the array of item representation from disk, converts it to an array of TodoItem instances using an unnamed closure we pass to map, and sorts that array chronologically. Describing the map and sort functions in detail is beyond the scope of this tutorial, but you can find more information in the Swift language guide’s section on closures.

Now we can hook up TodoTableViewController to display the list.

class TodoTableViewController: UITableViewController {
    var todoItems: [TodoItem] = []
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        refreshList()
    }

    func refreshList() {
        todoItems = TodoList.sharedInstance.allItems()

        if (todoItems.count >= 64) {
            self.navigationItem.rightBarButtonItem!.enabled = false // disable 'add' button
        }
        tableView.reloadData()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

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

    override func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "todoCell", for: indexPath) // retrieve the prototype cell (subtitle style)
        let todoItem = todoItems[(indexPath as NSIndexPath).row] as TodoItem
        cell.textLabel?.text = todoItem.title as String!
        if (todoItem.isOverdue) { // the current time is later than the to-do item's deadline
            cell.detailTextLabel?.textColor = UIColor.red
        } else {
            cell.detailTextLabel?.textColor = UIColor.black // we need to reset this because a cell with red subtitle may be returned by dequeueReusableCellWithIdentifier:indexPath:
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "'Due' MMM dd 'at' h:mm a" // example: "Due Jan 01 at 12:00 PM"
        cell.detailTextLabel?.text = dateFormatter.string(from: todoItem.deadline as Date)
        return cell
    }
}

Our to-do list now shows each item in chronological order, with the date label in red if the item is overdue.

iOS Simulator Screen Shot Feb 4, 2015, 10.26.58 PM

There are two issues we need to deal with here. Users currently don’t receive any visual feedback that a notification has fired (and a to-do item is overdue) when the app is running in the foreground. Also, when the app resumes, the list won’t automatically be refreshed, meaning that missed deadlines may not appear in red. Lets solve both issues now.

In TodoTableViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(TodoTableViewController.refreshList), name: NSNotification.Name(rawValue: "TodoListShouldRefresh"), object: nil)
}

In AppDelegate:

func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
    NotificationCenter.default.post(name: Notification.Name(rawValue: "TodoListShouldRefresh"), object: self)
}
func applicationDidBecomeActive(_ application: UIApplication) {      
    NotificationCenter.default.post(name: Notification.Name(rawValue: "TodoListShouldRefresh"), object: self)
}

Please note that, despite the presence of the word “notification”, NotificationCenter is unrelated to UILocalNotification. NotificationCenter’s purpose is to provide a simple way to implement the observer pattern in your apps.

Here we register TodoTableViewController as an observer to the “TodoListShouldRefresh” notification. Whenever a notification with that name is posted, the reloadData method will be called.

I’ve omitted this step, but it is generally better to define notification names as static constants to avoid repeating yourself.

Completing To-Do Items (Canceling Notifications)

Our to-do list application isn’t very useful without a way to clear out completed items, and the simplest way to do that is to delete them. We need to add some functionality to TodoList.

func removeItem(_ item: TodoItem) {
    let scheduledNotifications: [UILocalNotification]? = UIApplication.shared.scheduledLocalNotifications
    guard scheduledNotifications != nil else {return} // Nothing to remove, so return
 
    for notification in scheduledNotifications! { // loop through notifications...    
        if (notification.userInfo!["UUID"] as! String == item.UUID) { // ...and cancel the notification that corresponds to this TodoItem instance (matched
            UIApplication.shared.cancelLocalNotification(notification) // there should be a maximum of one match on UUID
            break
        }
    }

    if var todoItems = UserDefaults.standard.dictionaryForKey(ITEMS_KEY) {
        todoItems.removeValue(forKey: item.UUID)
        UserDefaults.standard.set(todoItems, forKey: ITEMS_KEY) // save/overwrite todo item list
    }
}

Note that passing an existing notification to scheduleLocalNotification: will cause a duplicate to be created. If you want to give users the ability to edit existing local notifications, you’ll need to retrieve the old one and cancel it before scheduling the new one.

Now we just need to allow users to remove items by swiping the item’s cell and pressing “Complete”.

In TodoTableViewController:

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true // all cells are editable
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete { // the only editing style we'll support
        // delete the row from the data source
        let item = todoItems.remove(at: (indexPath as NSIndexPath).row) // remove TodoItem from notifications array, assign removed item to 'item'
        tableView.deleteRows(at: [indexPath], with: .fade) 
        TodoList.sharedInstance.removeItem(item) // delete backing property list entry and unschedule local notification (if it still exists) 
        self.navigationItem.rightBarButtonItem!.isEnabled = true // we definitely have under 64 notifications scheduled now, make sure 'add' button is enabled
    }
}

iOS Simulator Screen Shot Feb 4, 2015, 10.26.58 PM

Conclusion

We now have a working to-do list application that lets our users schedule and cancel local notifications with sound and custom alert messages. The source code can be downloaded here.

In part 2 of this series, which builds on this project, we’ll add support for an application icon badge and learn about notification actions, a new feature that allows us to trigger code from a notification without ever opening the app.

Go to part 2 now »


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

Subscribe via RSS

Local Notifications in iOS 9+ with Swift (Part 2)

This post has been updated for compatibility with XCode 8 and iOS 10

In part 1 of this series, we created a simple to-do list application that used local notifications to alert users when to-do items were overdue. This time, we’re going to build on that the project by enabling application icon badges to display the number of overdue items and add support for notification actions, allowing our users to complete and edit to-do items without even opening the app.

You can download the source code for part 1 here.

Badging the App Icon

It bears mentioning that we can badge the app icon without using local notifications. The applicationWillResignActive: method in AppDelegate is a good place to do so, since it will be fired just before the user returns to the home screen where they can see the app icon.

func applicationWillResignActive(_ application: UIApplication) { // fired when user quits the application
    let todoItems: [TodoItem] = TodoList.sharedInstance.allItems() // retrieve list of all to-do items
    let overdueItems = todoItems.filter({ (todoItem) -> Bool in
        return todoItem.deadline.compare(Date()) != .orderedDescending
    })
    UIApplication.shared.applicationIconBadgeNumber = overdueItems.count // set our badge number to number of overdue items
}

iOS Simulator Screen Shot Feb 4, 2015, 10.31.14 PM iOS Simulator Screen Shot Feb 4, 2015, 11.51.25 PM

This is a good start, but we need the badge number to automatically update when to-do items become overdue. Unfortunately, we can’t instruct the app to simply increment the badge value when our notifications fire, but we can pre-compute and provide a value for the “applicationIconBadgeNumber” property of our local notifications. Lets provide a method in TodoList to set an associated badge number for each notification.

func setBadgeNumbers() {
    let scheduledNotifications: [UILocalNotification]? = UIApplication.shared.scheduledLocalNotifications // all scheduled notifications
    guard scheduledNotifications != nil else {return} // nothing to remove, so return
 
    let todoItems: [TodoItem] = self.allItems()
 
    // we can't modify scheduled notifications, so we'll loop through the scheduled notifications and
    // unschedule/reschedule items which need to be updated.
    var notifications: [UILocalNotification] = []

    for notification in scheduledNotifications! {
        print(UIApplication.shared.scheduledLocalNotifications!.count)
        let overdueItems = todoItems.filter({ (todoItem) -> Bool in // array of to-do items in which item deadline is on or before notification fire date
            return (todoItem.deadline.compare(notification.fireDate!) != .orderedDescending)
        })

        // set new badge number
        notification.applicationIconBadgeNumber = overdueItems.count 
        notifications.append(notification)
    }
 
    // don't modify a collection while you're iterating through it
    UIApplication.shared.cancelAllLocalNotifications() // cancel all notifications
 
    for note in notifications {
        UIApplication.shared.scheduleLocalNotification(note) // reschedule the new versions
    }
}

There’s no way to update a scheduled notification, but you can achieve the same effect by simply canceling the notification, making your changes and rescheduling it.

The applicationIconBadgeNumber property can accept values up to 2,147,483,647 (NSIntegerMax), though anything over five digits will be truncated in the icon badge. Setting it to zero or a negative number will result in no change.

Screen Shot 2015-02-04 at 11.36.36 PMScreen Shot 2015-02-04 at 11.38.14 PM

Now we just need to call this method when our to-do list changes. Add the following line to the bottom of addItem: and removeItem: in TodoList

self.setBadgeNumbers()

Now, when a notification fires, the badge number will be automatically updated.

Repeating Notifications

UILocalNotificaiton instances have a repeatInterval property that we can use to, unsurprisingly, repeat a notification at a regular interval. This is a good way to get around the 64 notification limit in some cases; a repeating notification is only counted against it once.

Unfortunately, we have to choose between using repeatInterval and applicationIconBadgeNumber in this app. Badge numbers are set on the application icon each time a notification is fired. Older notifications could end up “out of phase” with newer notifications and actually lower the badge number when repeated. We could get around this by scheduling two notifications for each to-do item, a repeating notification that displays the alert and plays the sound, and a non-repeating notification that updates the badge count, but this would cut the number of to-do items we could schedule in half.

The biggest limitation with repeating notifications is that the repeatInterval doesn’t accept a custom time interval. You have to provide an NSCalendarUnit value like “HourCalendarUnit” or “DayCalendarUnit”. If you want a notification to fire every 30 minutes, for example, you’ll have to schedule two notifications (offset by 30 minutes) and set them both to repeat hourly. If you want it to fire every 31 minutes, then you’re out of luck.

Performing Actions in the Background

iOS 8 introduced a really useful new feature, notification actions, which let our users trigger code execution without even having to open the app. Lets give our users the ability to complete and schedule reminders for to-do items directly from the notification banner.

iOS Simulator Screen Shot Feb 6, 2015, 12.43.34 AM iOS Simulator Screen Shot Feb 6, 2015, 12.43.37 AM

In AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    let completeAction = UIMutableUserNotificationAction()
    completeAction.identifier = "COMPLETE_TODO" // the unique identifier for this action
    completeAction.title = "Complete" // title for the action button
    completeAction.activationMode = .background // UIUserNotificationActivationMode.Background - don't bring app to foreground
    completeAction.isAuthenticationRequired = false // don't require unlocking before performing action
    completeAction.isDestructive = true // display action in red
 
    let remindAction = UIMutableUserNotificationAction()
    remindAction.identifier = "REMIND"
    remindAction.title = "Remind in 30 minutes"
    remindAction.activationMode = .background
    remindAction.isDestructive = false
 
    let todoCategory = UIMutableUserNotificationCategory() // notification categories allow us to create groups of actions that we can associate with a notification
    todoCategory.identifier = "TODO_CATEGORY"
    todoCategory.setActions([remindAction, completeAction], for: .default) // UIUserNotificationActionContext.Default (4 actions max)
    todoCategory.setActions([completeAction, remindAction], for: .minimal) // UIUserNotificationActionContext.Minimal - for when space is limited (2 actions max)

    // we're now providing a set containing our category as an argument
    application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: Set([todoCategory])))
    return true
}

Notice that we’re calling todoCategory.setActions() twice, once for each of the two available action contexts. If your users are displaying notifications from your app as banners, then the actions in the minimal context will be displayed. If notifications are displayed as alerts (the “default” context), up to four actions will be displayed.

iOS Simulator Screen Shot Feb 6, 2015, 12.41.20 AM iOS Simulator Screen Shot Feb 6, 2015, 12.06.33 AM

The order of the actions in the array we pass to setActions: is the order that the actions will be displayed in the UI, though, oddly, the items are ordered right-to-left in the minimal context.

Lets make sure to set this category for the notification we’re scheduling in TodoList’s addItem: method.

notification.category = "TODO_CATEGORY"

We already have a method for “completing” to-do items, removeItem:, but we need to implement one for scheduling a reminder in TodoList.

func scheduleReminder(forItem item: TodoItem) {
    let notification = UILocalNotification() // create a new reminder notification
    notification.alertBody = "Reminder: Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification
    notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view"
    notification.fireDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes from current time
    notification.soundName = UILocalNotificationDefaultSoundName // play default sound
    notification.userInfo = ["title": item.title, "UUID": item.UUID] // assign a unique identifier to the notification that we can use to retrieve it later
    notification.category = "TODO_CATEGORY"
 
    UIApplication.shared.scheduleLocalNotification(notification)
}

Note that we aren’t changing the due date on the to-do item (or trying to cancel the original notification – it’s been automatically removed). Now we just have to jump back to AppDelegate and implement a handler for the actions:

func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
    let item = TodoItem(deadline: notification.fireDate!, title: notification.userInfo!["title"] as! String, UUID: notification.userInfo!["UUID"] as! String!)
    switch (identifier!) {
    case "COMPLETE_TODO":
        TodoList.sharedInstance.remove(item)
    case "REMIND":
        TodoList.sharedInstance.scheduleReminder(forItem: item)
    default: // switch statements must be exhaustive - this condition should never be met
        print("Error: unexpected notification action identifier!")
    }
    completionHandler() // per developer documentation, app will terminate if we fail to call this
}

Go ahead and try it out now (you may want to pass a lower value to dateByAddingTimeInterval: for testing purposes).

iOS Simulator Screen Shot Feb 6, 2015, 1.25.36 AM

We’ve covered all of the non-geographic functionality of UILocalNotification and now have a pretty full-featured to-do list app, so this concludes our series. You can download the full source code for this project from here.


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

Subscribe via RSS

Should I learn Objective-C or Swift first?

“Should I learn Objective-C or Swift first?”

I get asked this question a lot. Sometime’s people will also ask about learning C or C++ first. So, I want to take a moment and give you the low-down on how I feel as a professional iOS & Mac developer, six months after Swift’s introduction. If this is your first time here, here’s a little background on me:

About me…

I’ve been developing software for as long as I can remember, at least 20 years now. I never was really the Mac guy, but I liked Linux and was always looking at new technologies. So, when the iPhone came out in 2008, I got myself a Mac and entered that ecosystem. Around that time I learned Objective-C, and that became my primary development language. It has been since then, and I’ve seen the language and Mac/iOS APIs twist and turn this way and that for the past 6+ years. In June I picked up Swift for the first time like everyone else, and although I still won’t call myself an expert, I will say I’ve done extensive study on the language. I’ve developed (and released) 3 apps using Swift since the announcement. Learning Swift is something I think is really important to iOS developers now, in fact it’s critical. To help people out I decided to write my Swift Book. This also serves as a way to help me learn the language, but I have already seen what a valuable resource it is for others; it’s exciting to be a part of… Additionally I’ve worked on an SDK that uses Swift, which will be used as part of a major platform worldwide in 2015. It’s very exciting to ship something like this that so many other developers will be using.

So, that’s my Swift-related experience in a nutshell. Now, here’s how I want to answer your question…

So… Swift or Objective-C?

Part of me wants to say, “Yes, go learn C first and then Objective-C. That’s what I did, so that’s what you should do.”

But here’s the thing: Just because that’s the path I took, doesn’t mean it’s the best path today. When I was first learning C, people told me I needed to learn Assembly to really get what was going on. They told me that without an underlying understanding of Assembly, I was going to be forever writing code and not understanding it. I ended up ignoring this advice and was very happy and successful as a developer without Assembly knowledge. In my college years, I finally picked up Assembly as part of my Electrical Engineering degree program. It helped enlighten some things, but for the most part I don’t feel knowing Assembly had much of an effect on my day-to-day programming. It had no effect on how I separate objects, how I decide what gets encapsulated, where to inherit, or where to compose. Most importantly, it didn’t help me to build better software. It was basically just academic, and as interesting as it was and is, the only place it’s even remotely relevant day-to-day is in debugging or reverse engineering; and only in limited capacities.

There’s certainly some sort of fear in me, like if we don’t all learn Assembly it will become a lost art. But, I don’t think that’s a realistic concern, honestly. The more I think about the idea of Assembly becoming a lost art the more I realize it will never happen. A single preserved book on the topic can get anyone where they need to be to be productive in Assembly, you just probably don’t want to.

Computer Science is an industry where we need to let go of the past, and we need to do it as quickly as we can. This industry is not going to wait for you to learn all the languages leading up to the latest and greatest. The marketplace certainly won’t reward that. What it will reward though, is knowing how to write code to make working software. That’s sort of the general thesis of this site, and it’s why I produce it. I don’t want to teach you to write code; I want to teach you to make software.

So here’s my answer to your question:

Swift

You should learn Swift first. You should learn it first because it’s the future of development on Apple platforms, and frankly it’s just easier to understand than Obj-C or C. What you may find as you learn it is that the Cocoa framework is getting a little stale. It’s starting to look very much like an Objective-C API in a Swift world. But that’s probably going to change. This wouldn’t be the first time Apple made a major change to their underlying APIs. Back in the days before Cocoa developers used Carbon, a C-based API that had some interoperability with Objective-C.

Apple is well-known for making swift (get it?) changes to their development stack. The move from Mac OS 9 to Mac OS X is a great example of their commitment to innovation. As a developer on Apple platforms, it’s important to understand this fact. Apple is about building the future of technology products, and they are not afraid to forego backwards-compatibility in order to achieve that. If you are still writing Objective-C day-to-day, you’re writing legacy code at this point. If you are writing Swift, then welcome to our world, you are the future.


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

Subscribe via RSS

Core Data in Swift Tutorial (Part 1)

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

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

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

The official Apple documentation describes Core Data like this:

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

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

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


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

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

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

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

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

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

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

import UIKit

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

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

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

}

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

Optional(<NSManagedObjectContext: 0x7fe68b58c800>)

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

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

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

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

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

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

IMPORTANT!

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

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

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

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

import Foundation
import CoreData

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

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

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

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

override func viewDidLoad() {
    super.viewDidLoad()

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

Subscribe via RSS

Drawing Custom Views With Swift – Andrew VanWagoner

One of my readers, Andrew VanWagoner, decided to put together a short tutorial that follows up the iOS 8 Swift Tutorial I put together last month. So I’m sharing it here with you all! Andrew is a Senior Software Engineer working for Adobe in Lehi, Utah. You can find out more about Andrew at his site, http://thetalecrafter.com/ or follow him on Twitter at @thetalecrafter.

In Jameson Quave’s Swift tutorial he issued a challenge to use an image rather than Unicode character for a play and pause button on a table cell. Having most of my experience as a web engineer, my natural choice for an icon like this was SVG. After quite a bit of pain, and determination to not just use a raster image, I discovered another scalable alternative.

Trying to load an SVG image

I added a “play.svg” file to my project, replaced the UILabel with an UIImageView, and added the following to my controller.


playIcon.image = UIImage(named: "play")

Well, the first try didn’t work at all, even though Xcode will correctly identify the SVG file as an image and display a nice preview of it. When you run your app, all you get is a blank nothing.

So I took to Google to figure out what I had done wrong. To my surprise, UIImage doesn’t know how to deal with SVG natively. There are multiple projects that try to add support, but I’m still new to iOS in general, so building and including a 3rd party library took me a long time, and I couldn’t quite get it to work. And in my specific use case, most of the SVG features these libraries implement would go unused.

Along the way I found out that you can do drawing primitives using Core Graphics. Since these drawing functions are conceptually the same as SVG path instructions, and even more similar to 2d canvas drawing, this sounded much easier to me. It is also what the 3rd party SVG libraries do under the hood.

Creating a custom view

If you are following along with Jameson’s tutorial, replace the playIcon UILabel with a View in the storyboard, and in the Identity Inspector, give the view a Class name “PlayPauseIconView”. Then create a new Swift class file “PlayPauseIconView.swift”. We’ll need to import UIKit, and the class needs to be a subclass of UIView. We will want to use this same view to draw both the play and pause icon, so let’s add a member that keeps track of which one we should draw.


import UIKit
class PlayPauseIconView: UIView {
  var isPlaying = false
}

In order to do custom drawing, we override the drawRect function. Just to keep things clean, put the code for drawing the play and pause icons into their own functions. Stub them out for now.


override func drawRect(rect: CGRect) {
  let context = UIGraphicsGetCurrentContext()
  if isPlaying {
    // already playing, so draw a pause icon
    drawPausePathTo(context, boundedBy: rect)
  } else {
    // not playing, draw a play icon
    drawPlayPathTo(context, boundedBy: rect)
  }
}
func drawPlayPathTo(context: CGContextRef, boundedBy rect: CGRect) {
}
func drawPausePathTo(context: CGContextRef, boundedBy rect: CGRect) {
}

You’ll notice that in order to draw, we need a context to draw inside of. We can get the current context with UIGraphicsGetCurrentContext(). Then we decide which icon to draw and call the appropriate function.

For the play icon we want to draw a triangle in the middle of our view, taking roughly half of the visual space. To get a list of all of the drawing functions available to use, you can look at the CGContext Reference.

First we will set the color we want to draw, then guide the turtlecursor across the path we want, then fill in the shape. Using the provided rectangle as our point of reference, we can size our triangle to scale with the resolution of the device.


func drawPlayPathTo(context: CGContextRef, boundedBy rect: CGRect) {
  CGContextSetFillColorWithColor(context, UIColor.blackColor().CGColor)
  CGContextMoveToPoint(context, rect.width / 4, rect.height / 4)
  CGContextAddLineToPoint(context, rect.width * 3 / 4, rect.height / 2)
  CGContextAddLineToPoint(context, rect.width / 4, rect.height * 3 / 4)
  CGContextAddLineToPoint(context, rect.width / 4, rect.height / 4)
  CGContextFillPath(context)
}

Again, if you are familiar with other 2d drawing systems, including SVG path notation, this is pretty straightforward. Using the same Core Graphics functions, fill out your pause icon function.


func drawPausePathTo(context: CGContextRef, boundedBy rect: CGRect) {
  CGContextSetFillColorWithColor(context, UIColor.blackColor().CGColor)
  CGContextMoveToPoint(context, rect.width / 4, rect.height / 4)
  CGContextAddLineToPoint(context, rect.width / 4, rect.height * 3 / 4)
  CGContextAddLineToPoint(context, rect.width * 2 / 5, rect.height * 3 / 4)
  CGContextAddLineToPoint(context, rect.width * 2 / 5, rect.height / 4)
  CGContextAddLineToPoint(context, rect.width / 4, rect.height / 4)
  CGContextFillPath(context)
  CGContextMoveToPoint(context, rect.width * 3 / 4, rect.height / 4)
  CGContextAddLineToPoint(context, rect.width * 3 / 4, rect.height * 3 / 4)
  CGContextAddLineToPoint(context, rect.width * 3 / 5, rect.height * 3 / 4)
  CGContextAddLineToPoint(context, rect.width * 3 / 5, rect.height / 4)
  CGContextAddLineToPoint(context, rect.width * 3 / 4, rect.height / 4)
  CGContextFillPath(context)
}

Now that we have our custom drawing code, update cell.playIcon.text calls to instead set isPlaying on your PlayPauseIconView. Go ahead and run your app, and check out your custom drawn icon.

You’ll probably want to fine tune the size of your view in the storyboard. I ended up with a 22×22 square.

You may also find that your icon isn’t updating properly when you start previewing a song. Core Graphics tries to draw your view once, and just keep reusing the already drawn layer, so it needs some way to know that your view has changed and needs to be redrawn. Setting isPlaying isn’t enough. Luckily UIView has a function that does exactly that.


setNeedsDisplay()

To make sure we always call this function when we change the isPlaying state, either add a didSet listener to isPlaying, or just add functions that set isPlaying and then call setNeedsDisplay. Make sure to update your controller to use the functions instead of directly setting isPlaying.

That’s it. You should now have a custom view that draws your play and pause icons, and it properly updates when you start and stop previewing a song.

If you are more ambitious, you can add a spinner for while the song is loading from the network, and put your pause icon in the middle. You can see how I did it in my project on github.

Update: The repository for this section has been updated to reflect the changes to Swift in Xcode Beta 3/4. The beta also adds access controls, which you can learn about here.


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

Developing iOS Apps Using Swift Tutorial Part 2

This section completely updated to reflect changes in Xcode 8.3.1, as of April 17, 2017

In part 1 we went over some basics of Swift, and set up a simple example project that creates a Table View and a puts some text inside of them. If you haven’t read that yet, give it a read here.

For this section, we’re going to do something a little more ambitious. We’re going to hit the iTunes Search API for the iTunes Store, download the JSON results, parse them in to Dictionaries and then populate our Table View with this information. Then, we will add some user interaction by adding a click event to the tableview, so that when an item is clicked the iTunes store item will be opened.

If this sounds like a lot of work, don’t sweat it. This is pretty basic functionality for iOS apps and it’s one of the most common things any developer has to do. Let’s get going…

Connecting the UI

The first thing we need to do is get a reference to our tableView, so it can be used from code. Go ahead and add this line to your ViewController.swift file, just under the class definition, but outside of any functions.

@IBOutlet var appsTableView : UITableView!

This bit of code allows up to connect our Table View in our Storyboard to this variable, “appsTableView”. Save this file and open up your storyboard. Now select the View Controller object (the one with a yellow icon) and in the right-hand side pane click the last tab, the Connections Inspector. Here you should now see an outlet for “appsTableView”. Click and drag from the dot next to this outlet on to the Table View in our scene.

Connecting the Table View to the View Controller

Let’s also add a variable to hold the table data itself. Just under the class definition for ViewController add:

var tableData = [[String: String]]()

This variable is an Array type that contains multiple Dictionary types (or hashable types if you prefer). Inside these values the key is of type String as well as the value. Or in other words I can get or set String values by accessing this variable with any String key in the dictionary, as we’ll see in a moment. If that does’t make sense just yet, just keep going and you’ll see how it’s used later in the tutorial.

Making the API Request

Now that we have the UI connected, we’re ready to make an API call. Create a new function called searchItunesFor(searchTerm: String). We’ll use this to make our requests happen for arbitrary search terms.

To keep this tutorial short, I’m going to just post my final code and let the comments do some of the explaining. I’ll also break it down line-by-line afterward. Also, I’m always open to questions and further discussion in the comments though, so feel free to chime in!

func searchItunes(searchTerm: String) {
    // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
    let itunesSearchTerm = searchTerm.replacingOccurrences(of: " ", with: "+", options: .caseInsensitive, range: nil)
    // Also replace every character with a percent encoding
    let escapedSearchTerm = itunesSearchTerm.addingPercentEncoding(withAllowedCharacters: [])!
 
    // This is the URL that Apple offers for their search API
    let urlString = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
    let url = URL(string: urlString)!
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
            // If there is an error in the web request, print it to the console
            print(error)
            return
        }
        // Because we'll be calling a function that can throw an error
        // we need to wrap the attempt inside of a do { } block
        // and catch any error that comes back inside the catch block
        do {
            let jsonResult = try JSONSerialization.jsonObject(with: data!, options: []) as! [String: Any]
            self.didReceive(searchResult: jsonResult)
        }
        catch let err {
            print(err.localizedDescription)
        }
        // Close the dataTask block, and call resume() in order to make it run immediately
    }.resume()
}

First, we fix some issues with the search terms. For one thing, the iTunes API is not going to like seing spaces, instead it wants plus signs (+), so the replacingOccurences function does this work, and we put the result in the itunesSearchTerm variable.

Next, we also want to make sure no other characters will cause issues. For example a question mark (?) may be viewed by the API as the end of the request if it was included in the search term. So we use the method addingPercentEncoding to encoded every character in to a percent-encoded form, and store this new value in to escapedSearchTerm.

After that we define our URL, which is just the iTunes search API URL with the escapedSearchTerm inserted in to the term field. We store this in the variable urlString. Next, we convert this URL String in to a proper URL object. This will basically validate that the URL is valid, or at least properly-formed, and then it gives it some useful methods as well that are common to URLs.

Now comes the network request. We make the request from the URLSession API that Apple provides. You could create a URLSession for this task, but Apple’s API will provide a pre-constructed one called shared. We access this with the simple dot-notation URLSession.shared.

From here, we use the shared sesion to create a new Data Task by calling it’s dataTask function with the url we created the line before. The next part is the completion handler. This itself is a function we define inline, inside of the curly braces.

So, everything here inside of the curly braces, where the indentation is increased, is only executed after the Data Task has been completed. Hopefully when this happens, what we get back from the server is JSON data containing the search results! It’s also possible that there was some kind of error, so we check whether or not an error is present by using the optional binding syntax:

if let error = error {
...

This syntax replaces the optional error: Error? with an unwrapped optional error: Error, but only if it exists. Most of the time, the value of error will be nil, and this block of code will be skipped entirely.

Next we are going to decode the result from a Data object in to a dictionary we can use to access each JSON element in the results.

Because the Apple APIs for JSON deserialization can throw an error, we perform this work inside of a do { } block, with a catch let err block after it. If something goes wrong during JSON deserialization, the catch block will be executed. But if everything goes to plan, the do block will complete executing.

Once we get the deserialized data back, we call a new method self.didReceive(searchResult: jsonResult).
This method is not defined yet, so we’ll define it next.

Finally we call resume() at the end of the dataTask block in order to execute the API request immediately.

Getting the data we need out of the JSON response

Now that we are getting a JSON response back, we will want to take out the values we care about. In this case that’s the price, a thumbnail, and the name of the app. So next, let’s create the didReceive method inside our ViewController class in order to parse out this data and store it inside the tableData variable that will inform our Table View.

func didReceive(searchResult: [String: Any]) {
    // Make sure the results are in the expected format of [String: Any]
    guard let results = searchResult["results"] as? [[String: Any]] else {
        print("Could not process search results...")
        return
    }
 
    // Create a temporary place to add the new list of app details to
    var apps = [[String: String]]()
 
    // Loop through all the results...
    for result in results {
        // Check that we have String values for each key we care about
        if let thumbnailURLString = result["artworkUrl100"] as? String,
            let appName = result["trackName"] as? String,
            let price = result["formattedPrice"] as? String {
            // All three data points are valid, add the record to the list
            apps.append(
                [
                    "thumbnailURLString": thumbnailURLString,
                    "appName": appName,
                    "price": price
                ]
            )
        }
    }
    // Update our tableData variable as a way to store
    // the list of app's data we pulled out of the JSON
    tableData = apps
    // Refresh the table with the new data
    DispatchQueue.main.async {
        self.appsTableView.reloadData()
    }
}

Here we are first off checking that we have the right data type. The results argument passed in to the didReceive function here is our JSON value returned from the iTunes Search API. We are going to want to check for three keys:

  • artworkUrl100
  • trackName
  • formattedPrice

Why these keys? This is just the keys Apple chose in their API docs. If you take a look at a sample API response you’ll see keys contain the info we want about an app.

So what we do is we loop through everything inside of the array of results, and then check that each of these three values are present, and are convertible to a String type. Here we’re using a compound form of the optional binding syntax that allows us to check for the valid presence of multiple values. if let varA = valueA, let varB = valueB { }. Not only are we checking for their presence, but if they can be represented as String objects.

If all three keys are present, and they can all be represented as strings, then we have an app record! We’ll add it to the list of apps, a temporary variable we created to store each new app we come across for this query.

I’m using slightly different keys for our app’s purposes. This is mainly just to show there is no reason these keys must match.

Finally, once the list of apps is completely updated, we assign the tableData variable that is going to be used to hold the Table View’s data to the array of apps we just created. From there, we call reloadData() on the appsTableView, which let’s the table know there is new data to work with, and that it should re-render itself.

You’ll notice the reloadData() is inside of another block. This one is from the Grand Central Dispatch API. By putting this code inside of the block DispatchQueue.main.async { }, we’re able to assure this is executed on the foreground thread. Because this code is all being executed in response to a network request (the API call), we end up on a background thread. This is because we know we have to wait a moment for the API to respond. The iOS App won’t just freeze the app up while it waits for the response. Instead, it will send the response in to a background thread.

So, now that we are actually updating some UI, we want to jump back on to the main thread and make sure it shows up immediately. If you are curious, you can call reloadData outside of this block and see how it effects the performance of the app (after we update the actual UI in the next step)

Update the UI with the new data

Now that we’ve got our API call updating the tableData variable, and calling reloadData() on the appsTableView we connected earlier, we can proceed to actually implement those UITableViewDataSource methods we set up in the beginning of the tutorial…

You may remember from last time we implemented the function for our Table View that we had a count method, which determines the number of rows; and a cell method, which actually creates the cell and modifies it for each row.

We’re going to update these now to use the data we pulled down from the web.

Swap out your methods with these two functions:

// MARK: UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return tableData.count
}
 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "MyTestCell")
 
    // Get the app from the list at this row's index
    let app = tableData[indexPath.row]
 
    // Pull out the relevant strings in the app record
    cell.textLabel?.text = app["appName"]
    cell.detailTextLabel?.text = app["price"]
 
    return cell
}

The first one is pretty simple. The number of rows should be the same as the count of the records in tableData (the number of app returned from the API).

The second function is what creates the cell, which is what is displayed in the Table View. First we call the constructor to make a fresh UITableViewCell to work with. Then we retrieve the current app that should be displayed by accessing tableData[indexPath.row]. The variable indexPath is passed in as an argument by Apple’s Table View class. It represents the current index that it is asking for a cell for. It’s kind of a backwards way of thinking about it, but basically this function is the table itself asking for an individual row. Which row? Well… the one at indexPath.row.

Next, we’ll set the text values for the text and detail labels of the cell. We’ll set these to be the value of the app array we created earlier in response to the API response.

Call the API

As a final step, we’ll manually make the API call for our searchItunes method by adding it inside of the viewDidLoad function. The viewDidLoad method is called on this View Controller object any time the screen it represents is being constructed. This is a common pattern in iOS development.

Find your viewDidLoad function (the Xcode template will have already added it) and add a call to the searchItunes function.

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    searchItunes(searchTerm: "JQ Software")
}

It’s alive!

Finally, run the app! Do you see the search results? You should see a few of my apps from the App Store pop up if you used my JQ Software search term. Try some other search terms and rebuild the app to see the different results that come back. You’ll notice we don’t yet have images in place, but we’ll get to that in later sections. We did get the data for that already after all.

If you run the App using JQ Software as a search term it will look like this at this stage:

JQ Software Apps List

Next time in Part 3 we’ll work on the interaction piece of the app, allowing users to search for anything they like, and making the table cells clickable!

Download the Source Code

Go to the Next Part

Go to Part 3 now ->


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

Subscribe via RSS

Swift Tutorial – Developing iOS Apps Part 1

Swift Tutorial

iOS Development moves to Swift

This tutorial updated to reflect changes in Xcode 8.3.1 and iOS 10, as of April 17, 2017

Apple announced at WWDC a major change to the way iOS apps are developed, it can now be done using a new programming language called Swift. In my efforts to adapt and learn the language’s common patterns and best practices, I will be posting regularly as I step through the learning process, sharing everything I find. I hope you decide to follow along!

In this tutorial we will build an iOS application that will pull down iTunes tracks from the iTunes web API (or application programming interface), display that information on the iPhone using a common UI element called a Table View, and finally allow us to drill down in to an album and play some of the tracks.

Tutorial Index

So I’m going to start with a pretty basic app and explain how the code works. Ready? Here we go…

The Basics

Swift opts to use a Javascript-like ‘var’ keyword to define any new variable.

So for example to create a new variable with a bit of text in it, you would have this:

var myString = "This is my string."

This is declared with a var so the variable can change at any time, similar to most languages.

However the let keyword creates constants. These can not ever be changed once they are defined. If you try, a compiler error will appear and the program just won’t run.

let someConstant = 40

In this case kSomeConstant is implicitly defined as an integer, or Int. If you want to be more specific you can specify which type it is like so:

let someOtherConstant: Int = 40

With both arrays and dictionaries, they are described using brackets []

var colorsArray = ["Blue", "Red", "Green", "Yellow"]
var colorsDictionary = ["PrimaryColor":"Green", "SecondaryColor":"Red"]

You can access the members of an array with integer value indexes, and the members of a dictionary with String keys (or other types, but that’ll come in later tutorials)

let firstColor = colorsArray[0]
// firstColor is now "Blue"
let aColor = colorsDictionary["PrimaryColor"]
// aColor is now "Green"

There’s a lot more to go over, but I think these basics are important to get a start going on to the tutorial. So with that, let’s move on to Hello World. If you want to play around with this a bit yourself before getting going on our first iPhone App. Be sure to check out the Playground containing this sample code on Github.

Hello World

Hello World in Swift and iOS

First, we’re going to write the simplest app imaginable to get started, Hello World. This segment comes directly from my upcoming book on Swift development, but it’s so important and fundamental to getting started I thought it would be good to release for free here.

Our app will only do one thing: print “Hello World” to the console. You’ll need a developer copy of Xcode in order to follow along, which requires a developer account. If you have one, head on over to http://developer.apple.com and get your copy before we begin.

So now you’ve got your IDE set up. Let’s write hello world out to the console. This example demonstrates the simplest app that can be built, and more importantly shows that you’re environment is set up correctly.

Set up an Xcode project using the single-view application template, and make sure you opt for Swift as the language.


You should now find an AppDelegate.swift file in the project hierarchy. Inside of this file find the line that says:

"// Override point for customization after application launch."

Replace this line with our amazing hello world code:

print("Hello World")

Hello World Swift Code

Now press run and you should see a blank app boot up, and the words “Hello World” print to the console.
Note that this will not show up in the iPhone simulator. Look at the bottom of your Xcode window and you’ll see a console that says ‘Hello World!’.

Hello World Output

Congratulations! You just wrote your first app in Swift! This app probably won’t win any awards, let’s trying doing something a little deeper…

Adding a Table View

In this section, we’re going to actually put some stuff on the screen.
Open up your Main.storyboard file in Xcode and lets drag in a “Table View” object from the Object Library (don’t use a table view controller.) Position this fullscreen in your app window and make sure it lines up with the edges. Then resize the height by dragging down the top edge and giving a little bit of space (this gives room for the status bar at the top of the phone.) If you run the app at this point, you should see an empty table view in the simulator.

UITableView drag on to StoryBoard

The empty table view in the iPhone Simulator:

The empty table view in the iPhone Simulator

Now we need to set up a delegate and data source for the table view. The Data Source is an object that informs the Table View of which data to show. The delegate let’s us handle interactions such as tapping a row on the Table View.

Setting these outlets is easy to do in interface builder. Just hold control, and then click and drag from the tableview to the “View Controller” object in your storyboard’s hierarchy, and select ‘data source’. Repeat with the ‘delegate’ options.

Setting up the UITableView Data Source and Delegates

I’ve received a ton of questions about this, and many people reporting errors about the table view not being set, so to make things a little easier I made a quick video showing how connecting Storyboard objects to your code works. Make sure to go fullscreen and select the 720p option to make sure you can see what’s happening. This will look slightly different from the Xcode interface you are using, but functionally all this works the same.


Connecting Storyboard Objects to Code in Xcode

Okay, now let’s dig in to the protocol methods for Table Views. Because we’re using the UITableViewDataSource and UITableViewDelegate in our view controller, we need to modify the class definition to say as much.

So open ViewController.swift and modify this line:

class ViewController: UIViewController {

to this:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

This will cause some errors to pop up, but don’t worry this is expected. In fact, the errors are the primary purpose of indicating these protocols on our class. It lets us know that we aren’t done actually implementing the UITableViewDataSource or the UITableViewDelegate yet.

Command+clicking on either of these protocols will show the required functions at the very top. In the case of a tableview dataSource, we need at least these two:

public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

So let’s modify our View Controller class by adding these two functions.

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 10
}
 
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "MyTestCell")
 
    cell.textLabel?.text = "Row \(indexPath.row)"
    cell.detailTextLabel?.text = "Subtitle \(indexPath.row)"
 
    return cell
}

As a handy trick, when you need to write these long methods for cases like this, you can just start typing the function in Xcode, focusing on the unique part of the name such as “cellForRo…” and Xcode will usually auto-complete what you were typing.

The first method is asking for the number of rows in our section, in this simple tutorial we just hard-code 10, but normally it would be the length of an array controller. This example is intentionally simple.

The second method is where the magic happens. Here we create a new instance of a UITableViewCell called cell, using the Subtitle cell style.
Then, we assign the text value of this cell to the string “Row #(indexPath.row)”
In Swift, this is how variables are embedded within a string. What we’re doing is retrieving the value of indexPath.row by inserting (indexPath.row) in to our string, and dynamically replacing it with the row number of the cell. This allows results such as “Row #1″, “Row #2″, etc.

The detail text label is only available in the Subtitle cell class, which we are using here. We set it similarly to “Subtitle #1″, “Subtitle #2″, and so on.

Go ahead and run your app and you’ll now see an amazing list of cells with titles and subtitles indicating their row numbers. Don’t see the cells? Make sure you connected your storyboard delegate and data source to the ViewController class. See the Youtube video here

This is one of the most common ways to display data in iOS, and will be sure to serve you well. For the full code to my View Controller file, take a look at the full source for this section on github.

You’re doing great!

Made it this far and everything is working? Awesome! If not, let me know if you got stuck. I’m happy to help 🙂

In my Swift book we spend some more time going over the basics, as well as delving much deeper in to the advanced details. Learn more about it here.

In part 2, we’re going to explore using the iTunes search API to create an app capable of finding and displaying albums within the iTunes store.

Full code for this part here »

Go To Part 2 »

Get The Source
Next Part


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

Subscribe via RSS