Mobile First & Declining Usability

Mobile App Design is Fashion

Mobile app design moves very quickly, and with “mobile-first” thinking becoming the mantra of designers of all kinds, it is a leading indicator for design of just about everything else. So, I think it’s important to take a step back and analyze how apps are designed today. In this I break down the most recent mobile app design trends that I believe are actually hurting us, and making our software worse.

The way mobile app designs is very much a fashion-of-the-week type of situation where the target changes on a nearly daily basis. What was cool a month ago is stale in comparison to today, I use the term “fashion” to generally describe this reality. Before we dig in to the specifics of modern app design, let’s talk a little about fashion itself.

Fashion is irrelevant

I recently found myself reading an essay by Milton Glaser, one of the world’s most celebrated designers. In the essay he talks about “The Bull” by Pablo Picasso. In the work, Picasso renders 11 different variations of a bull each in differing styles. The work ranges from the realistic rendering, to a cartoon rendering, then to a more abstract cubist rendering, and finally simple line art.

Picasso: The Bull

As Glaser puts it, “What is clear just from looking at this single print is that style is irrelevant. In every one of these cases, from extreme abstraction to acute naturalism they are extraordinary regardless of the style.” – Ten things I have learned, Milton Glaser

I thought this line of thinking was very interesting. Glaser makes the point that “Style is not to be trusted”. Or another way to think about that, is that style is fashion, and it comes and goes with the tide. In demonstrating that the style of the work is actually irrelevant to it’s quality, we could draw the conclusion that style in ap design (the aesthetic) is also irrelevant, except for one small problem…

Fashion is of utmost importance

Back in the early days of smartphones the designers at Apple had a clear design goal: Familiarize people with touchscreens. So the style was glossy buttons with shadows, and controls that seem like they want to be touched. If there was a real-world analogue that could be used to symbolically represent a 3D interactive object, they would use it. This is known as skeuomorphism in the design communities, and I won’t rehash why everyone decided they hate it in the past few years here. What I will say however is that there was value in those old ugly interfaces. What we were able to do with skeuomorphism is train users to use our apps with visual language. The best example I can think of from those days is the picker view (called a UIPickerViewController in programmer speak).

UIPickerViewController

Nothing about the design of the picker view makes much sense, really. The only thing it does well is it looks like a spinning wheel, which encourages you to spin it like a contestant on The Price is Right. It’s kind of fun, in fact. But, this interface actually kind of sucks to use. You can only see about 4 or 5 options at any given time, and selecting a specific option requires a fine-tuning sort of interaction where you slowly and carefully align a value with the center of the view. A much simpler approach would be to just put options in a big fullscreen list; which is actually what Apple ended up using more often in their own apps. (Also known as a UITableViewController.)

UITableViewController

While these types of interfaces have many drawbacks, I have to wonder if my elderly friends, or my infant nephews would have been able to use smart devices so readily where they not using these metaphors for reality. Would smartphones have become as successful as they are now without skeuomorphism?

It wasn’t just this picker either, skeuomorphism informed the iBooks interface, designed to look like an actual bookshelf:

iBooks

It inspired the Calendar interface, to look like an actual calendar:

Calendar

And just for fun: Designer Meng To posted this skeuomorphic version of Facebook that looks like an actual book. on dribble.com

Facebook by Meng To

2016

Fast-forwarding to 2016, we have completely abandoned the idea that people don’t know what to do with touchscreens. I think we actually have gone too far. Have you ever tried to use Snapchat? It’s like they have made it intentionally confusing to use. Everything is hidden using gestures and invisible buttons you are supposed to know to tap and hold, and weird things like that. Some of the apps most interesting features are completely hidden from view.

There was a lot of positive reactions to the app Clear from a few years ago. It was a huge hit because of it’s unique UI animations. But here’s my question: How many of those downloads translated in to regular users? I don’t want to sit here and hate on the Clear app because I think it was a brilliant design in terms of being unique and fashionable. What it was not however, is usable.

Clear

Okay, so I swipe to the right to finish a task… Oops wrong one, how do I undo that? Do I just re-add it? Wait, how do I add a task again? Oh, I take two fingers and separate two existing tasks, and that makes a new one appear in between my existing ones? That’s odd… what if I don’t have two tasks? Do I just sort of “unpinch” the negative space where my tasks would be if I had two?

Yep.

That’s exactly what you’re supposed to do, and that sucks…

To clear a task swipe to the left, to finish a task swipe to the right. Wait, what’s the difference? I’ve cleared some of my tasks, but deleted others. Either way they get removed from my list…

If you go look at the app store reviews for Clear, you’ll see a lot of people complaining about the same thing, and I can’t even fully explain it to you because I don’t understand it either. But apparently if you perform a swipe gesture up (or down?) it will delete the entire list. How’s that for a fun and wacky gesture! So fun! You just lost your entire task list, which is the entire point of the app! That dismiss animation was sick though, so the app must be great, right?

Clear Reviews

The tutorial for Clear says I should “Pinch together vertically” to collapse my current level and navigate up. So instead of tapping a back button to navigate up, I have to perform a two-handed unpinch gesture for that too? I thought that was the gesture for creating a new task? HOW DO I USE THIS APP I DONT UNDERSTAND!? ARGGGH!!

deletes app…

Fashion sucks

As disappointing as it is, this seems to be the new fashion in apps: I summarize it basically as intentionally terrible user experience. I was talking to a prospect recently about an iOS App we were planning to help them build. They showed me a mockup that involved quite a few hidden buttons and gestures to control the UI. I carefully explained that users were unlikely to find these gestures and that we should move these things in to buttons that are more obvious. As a compromise, we needed at least a quick tutorial to show them these features. The response I received was surprising, to say the least:

We want this to be a secret

If we were designing a video game level this would maybe make some sense. It’s typical to “hide” some areas of play so that it’s rewarding when they are discovered. This is basically what they were going for. But for a mobile app, this is just a poor UI decision that is going to leave people confused. This wasn’t some kind of special easter egg hidden feature. This was basic functionality for switching to a friends list in the app.

You want to hide the friends list from users? This kind of thinking is now fashionable. Whether we like it or not, fashion influences the way we write software. As someone who is going to spend hours of my life building out these features, I hate the idea that most users simply will never find them. That’s why I declined that particular project. I left money on the table, but saved myself from spending time on work I didn’t find meaningful.

It’s our responsibility to fix it

I could sit around critiquing popular app’s designs all day (and I might), but this post wouldn’t be much use to anyone if that was all I did. So, let’s talk solutions…

1. Recognize that you can fix things

First, I think it’s important to accept that application developers have the final say in what their work ends up producing. You may think the decisions are simply made by your client, your boss, your partner, or your dog. Blame who you want, but your apps are the output of your efforts. If you are being boxed in to a corner and being asked to do something that sucks, you should stand up against it. You should learn to say “no” more often. At the end of the day, if you deliver what you think is best to solve the problem at hand, and you do your best work, no-one can complain about that. If they do, then they’re probably not worth working with anyway.

2. Do hallway testing

Hallway testing is the kind of testing you do when you grab some random person “out of the hallway” to test your app. You may have NDAs or similar agreements that make this a challenge, which is why it’s important to make sure agreements like this permit hallway testing. You may not be able to place an ad and have dozens of testers come to your office, but you can at least get family member or friend to try things out.

There are two very important aspects to hallway testing that are required for it to work. The first is that you absolutely can not explain things to the user while testing. It will be your first reaction to want to explain away any rough edges, defend your work, or try to help move things along to avoid embarrassment. This no longer represents a real hallway test though, this represents what it would be like if every user of your app was accompanied by you with your explanation, which obviously is not happening.

The second thing you must do is take notes of any stumbles. If you watch a user struggle with a component of your app for several minutes, and then finally figure it out, you could very easily write it off and say “oh well, they eventually figured that out so it’s not a priority”. WRONG! Your testers will have infinitely more patience than real-world users. If a tester is struggling, you need to make a note of it. Make several notes in fact, you’re going to forget if you don’t, and then those issues won’t be addressed.

3. Don’t hide anything inside of non-obvious gestures

If you are making a feature that involves swiping, pinching, or any other gesture, you should make sure it actually is intuitive to do so. For example, panning on a map (like an Apple Map) is very intuitive… it’s obvious that you would want to scroll the view and it’s obvious how you would go about doing that.

You’ll have to use your judgement on this one, but one way to confirm that your gestures are clear is by doing hallway testing, as mentioned above. You can trust your instinct with this stuff, but you also need to confirm you were right. Often something that seems intuitive to you will not be obvious to your testers.

4. Don’t be clever

There’s a famous quote often cited in software development projects.

Everyone knows that debugging is twice as hard as writing a program in the first place.
So if you’re as clever as you can be when you write it, how will you ever debug it?
“The Elements of Programming Style”, 2nd edition, Chapter 2

The same thing applies to user interface design. If it was a really clever idea, you should always be wary. Often the dead-simple obvious solution is the best one. This is basically just Occam’s Razor restated, which is often quoted for a reason: it’s true.

5. You tell me

What are the other takeaways here? How do you make sure your apps are highly usable? Tell me in the comments or on Twitter.

P.S. As an interesting side note, “The Bull” is also reportedly the subject of Apple’s training materials on their design thinking, for entirely different reasons.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Your Parse backend was always a bad idea.

So if you haven’t heard, Facebook is shutting down Parse, the backend as a service (BaaS) that was acquired by Facebook a little while ago. Lots of developers are feeling a little lost, and even betrayed by Facebook. I tweeted this screen cap someone made from the Parse homepage before the shutdown, and it pretty much says it all:

I didn’t need to add the emphasis there, they already did that. Thousands of developers TRUST US. You can see from this type of presentation of their image why developers are feeling so betrayed. Why would anyone continue using React Native, React JS, HHVM, Relay, or any other Facebook technology knowing that they may just randomly decide to pull the plug on it?

Sure, these are open source and the community can take over, but open source projects need maintainers and corporate backers are a huge boon. Facebook has proven that we can’t trust them, but this shouldn’t be that surprising to anyone who has worked with Facebook APIs in the past, or any third party social media API for that matter. I’m going to get in to that later, but let’s completely change the subject for a second to talk about the other elephant in the room, Twitter… and more importantly Twitter Fabric, which now owns Crashlytics and has integrated a bunch of the amazing work done by Felix Krause.

But to understand how Twitter has treated it’s development community in the past, I think we should talk about a little app called Meerkat. I promise we’ll get back to talking about Parse and Facebook, but this story falls under the same umbrella, so bear with me.

Meerkat

So a little backstory: I live in Austin, TX which means every year at SXSW I get a front-row seat to the startups that are going to be big over the next year. Twitter, Foursquare, GameSalad, and even the Four-Hour Work Week were all launched at SXSW. These are some of the bigger successes, but every year tons of wide-eyed founders show up to Austin to present their work, and hope it takes off with people at the festival. In 2015 there was an extremely clear SXSW winner called Meerkat. meerkat-app-tweet-live-video-twitter

 

Meerkat is basically a live streaming P2P platform for people to stream directly from their iPhones to other users, and it took SXSW by storm. Just walking around Austin last year at SXSW you would see Meerkat shirts everywhere. Everyone who was anyone was streaming live music, SXSW sessions, their lunch, or just about anything they were doing. Then, suddenly… it all stopped, for a very specific reason:

At the height of Meerkat’s launch, Twitter yanked API access right out from under their feet with only 2 hours notice.

If you’re familiar with the iOS App Store process you might understand how this is sort of an issue, considering even if the Meerkat folks could somehow rewrite their entire app to not rely on the Twitter APIs they could not get an app approved and on the app store with an update within 2 hours… Actually it’d probably take more like 3 weeks or so.

At the height of Meerkat’s launch, Twitter yanked API access right out from under their feet with only 2 hours notice.

Personally I was not that surprised, but many people were wondering why Twitter pull such a move… Did they violate the terms of the API agreement? Where they doing something illegal?

Well, no…

Actually it turns out the whole reason Twitter decided to handicap the most successful Twitter-based app in years is because they had their own competitor in the pipeline, called Periscope, which I refuse to link to.

So here we are, having Twitter once again asking for the trust of the development community. Sigh…

Parse

See, I told you we would get back to Parse!

Seeing as how Twitter and Facebook are basically just two sides of the same social media coin, It seems to me that trusting either of them has similar implications. When I wonder about how a tech company will proceed in to the future I always repeat the mantra, “Follow The Money”. This generally tells you how large companies will behave well in to the future, especially publicly traded ones (both TWTR and FB are public). American public companies answer to their shareholders regarding quarterly earnings reports, sometimes to the detriment of their customers and/or partners. When I saw Parse had such a huge threshold for their “Free Tier”, it really worried me. It seems to me 99% of apps (or more) would never cross that threshold, and if they did they would only do so by a few cents. What exactly are Facebook’s motivations when it comes to their developer tools, and in particular the backend as a service? I think the answer is pretty simply that they wanted your data, but it turned out to be worthless. So they decided Parse wasn’t worth their time any more. They can’t turn a profit by providing a free backend to hundreds of thousands of developers; so they shut it down. In Facebook’s own words:

We’re proud that we’ve been able to help so many of you build great mobile apps, but we need to focus our resources elsewhere.

Translation: “You aren’t making us enough money”

Facebook generates all of it’s bottom line from advertising, just like Twitter, just like Google who is now the most valuable company in the world, surpassing Apple. In fact there is only one major platform vendor who doesn’t make the majority of their profit from advertising, and it’s Apple.

Following The Money

I tweeted this earlier today:

It’s true, you really can’t trust these social media companies with your backend so blindly. You have to Follow The Money to find the motivations of the parties involved. If their motivations are not to provide you with a great service that benefits their bottom-line, it’s unlikely it’ll stick around for very long. This is also a great way to analyze your own business if you are a startup founder or CEO. When you work with anyone, you must be certain their financial motivations align with yours, otherwise there will always be a disconnect. This applies to employees, co-founders, partners, and vendors alike.

The Facebook API

Back in the early days of the Facebook API, you could easily retrieve a list of a user’s contacts. This is what led to the mass-spamming from Facebook games, and the rise of Farmville. But Facebook decided they didn’t like that, so they yanked that privilege to the detriment of many apps, and Zynga’s stock. Seriously, check out what that did to Zynga’s stock:

If your business depends on an app, then your backend is an extremely important business asset that you absolutely must control. It took almost a decade for the likes of Salesforce and other cloud-based enterprise companies to make their way in to large corporations, and even today most of them are using on-site hosted versions of the software. The reason is that in a well run business you will own anything that is mission-critical. This increases the maintenance cost as well as the cost to initially deploy, but without this control your business is dependent on the whims of some shady figures who are mining your data to serve ads. Is that who you want in control of your server? How much do you trust Facebook, Twitter, and Google?

Build Your Own Damn Backend

The only real answer to providing your mobile app with a stable backend that you control is to build it yourself. I know this sounds hard, but it really isn’t that difficult to use Ruby on Rails or NodeJS to produce a simple API to power your mobile apps. Frankly, the Parse backend with it’s javascript-based events is not all that different from writing a NodeJS app in express, grabbing some node modules for easy API delivery, and backing the whole thing with a MongoDB database. If this sounds really hard, just take a few hours reading some tutorials online, and you will realize how easy this all is to do yourself. Alternatively, you can just hire my company who does this routinely *end shameless plug*.

If you do hire a vendor to build your backend, make sure you are getting the source code, and the tools necessary to load it up on whatever server you need. Docker is a nice way to contain all the environmental requirements for an app, and services like Heroku make deploying Rails apps easy.

Fun Fact: About 95% the way through writing this post, my ironic Twitter embeds trashed all the formatting and I had to reformat the entire thing. ^_^

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

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.

Note: There is an updated API that I have done an additional video tutorial for here:

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 »

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

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.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

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.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Apple Watch Announced. Here’s what we know so far

Apple Watch

Apple Watch Announced. Here’s what we know so far. This page is a work in progress.

There are three versions of Apple Watch:

  • Apple Watch
  • Apple Watch Sport
  • Apple Watch Edition – 18k Gold

The price starts at $349, and will be available early 2015

Apple Watch

An iPhone required to use Apple Watch

The device uses a Crown as the input device in addition to two types of touch detection. A ‘tap’ and a ‘force push’. The difference being how hard you push on the screen.

The Apple Watch provides support for Maps, Calendar, iMessage, Siri, Dictation, connects to wireless speakers, and a variety of other Apple ecosystem features.

The battery uses a wireless charger that attaches magnetically to the back of the watch. The watch uses haptic feedback for navigation and other use cases.

The second button on the watch will bring up your list of contacts. Digital touch allows you to ‘tap’ someone remotely using their watch, then drawing back and forth will wirelessly sync to other Apple Watch wearers.

User’s can also share their heartbeat, making the other user feel their heartbeat, or chat using the walkie-talkie feature.

 

Apple Watch Edition

WatchKit is used to allow developers to create glances, apps, and notifications.

The device can be used to track heart rate, and makes use of accelerometers.

Built-in activity app measures standing, moving, and exercising. These categories are referred to as rings, and hitting a quota visually fills the ring.

Move
Measure calories burned

Excercise
Measures brisk activitiy, at a brisk walk or above

Stand
Measures how often you stood up from sitting.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

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.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Running Swift Scripts From The Command Line

Swift Scripts From The Command Line

This post updated for Xcode 6.1

One of the great features of swift is it’s ability to be used on the command line. It’s actually really easy to do, and I’ll show you how to make an executable Swift file in this tutorial.

First let’s create a new Swift file. In any text editor, create a new file and save it as Hello.swift.

In order to make this file executable we need to give it permissions. In the terminal app, navigate to your file using the cd command, and then type this:

chmod +x Hello.swift

Now open up your Swift file. We’re going to tell it to use xcrun to run our code.
First, let’s get a simple script going that prints a response to the console:

#!/usr/bin/env xcrun swift

println("Hello World!")

This is great, but what about arguments? It’s very common for a script to take some arguments, do something with them, and then return a result.
Let’s say we wanted to make a command line utility that takes any number of numbers as arguments, and just adds them together. We’ll call it SwiftSum.

A reasonable first step in building this script would be to simply print out the command line arguments. Similar to other languages, Swift automatically creates an argument count variable and argument value pointer.

C_ARGC is the number of arguments passed in
C_ARGV is an array of the argument’s values

We can loop through the arguments and print them fairly easily…

#!/usr/bin/env xcrun swift
for i in 1..<C_ARGC {
  let index = Int(i)
  if let argStr = String.fromCString(C_ARGV[index]) {
    println(argStr)
  }
}
SwiftSum.swift 1 2 3

Outputs:

1
2
3

If we want to add these, we just need to attempt converting them to an Int, and if that succeeds add it to a total…

#!/usr/bin/env xcrun swift

var total : Int = 0

for i in 1..<C_ARGC {
  let index = Int(i)
  if let argStr = String.fromCString(C_ARGV[index]) {
    if let argInt = argStr.toInt() {
      total += argInt
    }
  }
}

println(total)
$ ./SwiftSum.swift 1 2 3

Outputs:

6

In my book I talk more about using Swift from the command line, so I hope you choose to follow along. If not, make sure you subscribe to get the latest tutorials from me.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

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.

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS

Using open source iPhone app components with Cocoa Pods

For a while after the initial release of the iPhone App Store and it’s SDK, there was not much in terms of open source code to use and learn from. But times have changed, and these days there is a huge database of open source components, and even full projects ripe for use in your next app.

This post is a bit of an instructional guide for those looking to take advantage of open source in their iPhone projects. Whether you are new to iOS, a seasoned developer, or a project manager, you can benefit from this short guide.

Currently, the biggest repository of open source iOS components is CocoaPods. The official list of components, known as “Pods’, is maintained in a Github repository located here: https://github.com/CocoaPods/Specs

Unfortunately this list is not particularly easy to browse. To find out the details of any one Pod, you have to select one from the list, pick a version, open the.podspec file, and then pick out the description in the file. Fortunately for you, the handsome reader, you can browse a (possibly slightly out of date) list of these Pods on the site https://www.cocoacontrols.com/cocoapods, in a much more easy to digest format.

So how do you use these? If you’ve got a ruby install set up with ruby gems, you can just navigate to your project directory and create a Podfile file, with no extension. It looks like this:

platform :ios, ‘6.0’

pod ‘TestFlightSDK’, ‘>= 1.1’
pod ‘SVProgressHUD’
pod ‘iRate’
pod ‘TimesSquare’, ‘1.0.1’
pod ‘AFNetworking’, ‘1.1.0’
pod ‘iCarousel’

Once you’ve created this Podfile you can run this command in Terminal:

$ pod install

If it gives you some kind of error, you might need to install Cocoa Pods. If that’s the case you first need to run this:

$ gem install cocoapods

And if that doesn’t work, then you still need ruby gems, and maybe even ruby.

Once you generate the pod install, make sure you close your Xcode project if you already have it open, and from now on use the .xcworkspace file when working on your project. What you’ll find is that Cocoa Pods has now created a subproject for your Pods. Yay! This means you can now compile your dependencies separately from the project, and changes to your project shouldn’t call for a full recompile. More importantly, you can easily update your dependencies by just modifying your Podfile, and running ‘pod install’ again.

So to recap:

1. Install ruby

$ \curl -sSL https://get.rvm.io | bash -s stable

2. Install ruby gems if you don’t have ut

Visit http://rubygems.org/pages/download

3. Install cocoapods

$ gem install cocoapods

4. Create a Podfile in your Xcode project directory

5. Add any relevant pods you might want to use. At this stage I do not specify a version, I let it use the most recent, and then lock it to that version to avoid unwanted updates. I’ll later remove the version specification when I feel it is time to get everything up to date.

6. Run pod install

$ pod install

7. Open your project from the xcworkspace file instead of the xcodeproj file.

8. Enjoy!

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

Did this tutorial help you?

Support my Patreon

Your support on Patreon allows me to make better tutorials more often.

Subscribe via RSS