Update Note: This tutorial has been updated to Xcode 8.3 and Swift 3.1 by Joshua Greene. The original tutorial was also written by Joshua Greene.
There’s been a lot of buzz about CocoaPods lately. You may have heard about it from another developer, or seen it referenced on a GitHub repository. If you’ve never used it before, you’re probably wondering, “What exactly is CocoaPods?”
It’s probably best to let the CocoaPods website provide the answer:
CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects. It has over 30 thousand libraries and is used in over 1.9 million apps. CocoaPods can help you scale your projects elegantly.
Wow, that’s a lot of libraries used in a ton of apps! Scaling your project elegantly sounds great too. :]
But, what is a dependency manager? And why do you even need one?
A dependency manager makes it easy to add, remove, update and manage third-party dependencies used by your app.
For example, instead of reinventing your own networking library, you can easily pull in Alamofire using a dependency manager. You can even specify the exact version to use or a range of acceptable versions.
So even if Alamofire gets updated with backwards-incompatible changes, your app can continue using the older version until you’re ready to update it.
CocoaPods Tutorial
In this tutorial, you’ll learn how to use CocoaPods with Swift. Specifically, you’ll:
- Install CocoaPods.
- Work with a functional demo app that gets you thinking about ice cream.
- Use CocoaPods to add networking.
- Learn about semantic versioning.
- Add another library using a flexible version.
Note: This CocoaPods tutorial requires basic familiarity with iOS and Swift development. If you’re completely new to iOS and/or Swift, then please check out some of the other written and/or video tutorials on this site before doing this tutorial. Or, dive into the iOS Apprentice.
This tutorial also includes classes that use Core Graphics. While knowledge of Core Graphics is beneficial, it’s not strictly required. If you’d like to learn more about Core Graphics, read our Modern Core Graphics With Swift series.
Getting Started
You first need to install CocoaPods. Fortunately, CocoaPods is built on Ruby, which ships with all recent versions of Mac OS X. This has been the case since OS X 10.7.
Open Terminal and enter the following command:
sudo gem install cocoapods
Enter your password when requested. The Terminal output should look something like this:
You must use sudo
to install CocoaPods, but you won’t need to use sudo after it’s installed.
Lastly, enter this command in Terminal to complete the setup:
pod setup --verbose
This process will likely take a few minutes as it clones the CocoaPods Master Specs repository into ~/.cocoapods/ on your computer.
The verbose
option logs progress as the process runs, allowing you to watch the process instead of seeing a seemingly “frozen” screen.
Awesome, you’re now set up to use CocoaPods!
Ice Cream Shop, Inc.
Your top client is Ice Cream Shop, Inc. Their ice cream is so popular they can’t keep up with customer orders at the counter. They’ve recruited you to create a sleek iOS app that will allow customers to order ice cream right from their iPhones.
You’ve started developing the app, and it’s coming along well. Download the CocoaPods tutorial starter project from here.
Open IceCreamShop.xcodeproj, then build and run to see a mouth-watering vanilla ice cream cone:
The user should be able to choose an ice cream flavor from this screen, but that’s not possible because you haven’t finished implementing this functionality.
Open Main.storyboard from the Views/Storyboards & Nibs group to see the app’s layout. Here’s a quick overview of the heart of the app, the “Choose Your Flavor” scene:
PickFlavorViewController
is the view controller for this scene. It handles user interaction and is the data source for the collection view that displays the different ice cream flavors.IceCreamView
is a custom view that displays an ice cream cone, and it’s backed by aFlavor
model.ScoopCell
is a custom collection view cell that contains aScoopView
, which gets colors from aFlavor
model.
While every Ice Cream Shop location will have signature flavors in common, each carries their own local flavors too. For this reason, the data contained Flavor
instances need to be provided by a web service.
However, this still doesn’t answer the question, “Why can’t the user select an ice cream flavor?”
Open PickFlavorViewController.swift, found under the Controllers group, and you’ll see a stubbed method:
fileprivate func loadFlavors() {
// TO-DO: Implement this
}
Ah-ha, there’s no flavors! You need to // Implement this
.
While you could use URLSession
and write your own networking classes, there’s an easier way: use Alamofire!
You might be tempted to simply download this library and drag the source files right into your project. However, that’d be doing it the hard way. CocoaPods provides a much more elegant and nimble solution.
So, without further ado…
Installing Your First Dependency
You first need to close Xcode.
Yeah, you read that right. It’s time to create the Podfile, where you’ll define your project’s dependencies.
Open Terminal and navigate to the directory that contains your IceCreamShop project by using the cd command:
cd ~/Path/To/Folder/Containing/IceCreamShop
Next, enter the following command:
pod init
This creates a Podfile for your project.
Finally, type the following command to open the Podfile using Xcode for editing:
open -a Xcode Podfile
Note: You shouldn’t use TextEdit to edit the Podfile because it replaces standard quotes with more graphically appealing typeset quotes. This can cause CocoaPods to become confused and throw errors, so it’s best to use Xcode or another programming text editor to edit your Podfile.
The default Podfile looks like this:
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'IceCreamShop' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for IceCreamShop
end
Delete the # and space before platform, and delete the other lines starting with #.
Your Podfile should now look like this:
platform :ios, '9.0'
target 'IceCreamShop' do
use_frameworks!
end
This tells CocoaPods your project is targeting iOS 9.0 and will be using frameworks instead of static libraries.
In order to use CocoaPods written in Swift, you must explicitly include use_frameworks!
to opt into using frameworks. If you forget to include this, and CocoaPods detects you’re trying to use a Swift CocoaPod, you’ll get an error when you try to install the pods.
If you’ve only ever programmed in Swift, this may look a bit strange − that’s because the Podfile is actually written in Ruby. You don’t need to know Ruby to use CocoaPods, but you should be aware that even minor text errors will cause CocoaPods to throw an error.
A Word About Libraries
You’ll see the term “library” often used as a general term that actually means a library or framework. This tutorial is guilty of casually intermixing these words too. In actuality, when someone refers to a “Swift library,” they actually mean a “Swift dynamic framework” because Swift static libraries aren’t allowed.
You may be wondering, “What’s the difference between a library, a framework and a CocoaPod?” And trust me, it’s okay if you find the whole thing a touch confusing!
A CocoaPod, or “pod” for short, is a general term for either a library or framework that’s added to your project by using CocoaPods.
iOS 8 introduced dynamic frameworks, and these allow code, images and other assets to be bundled together. Prior to iOS 8, CocoaPods were created as “fat” static libraries. “Fat” means they contained several code instruction sets (e.g. i386 for the simulator, armv7 for devices, etc.), but static libraries aren’t allowed to contain any resources such as images or assets.
Another important difference is dynamic frameworks have namespace classes and static libraries don’t. So, if you had two classes called MyTestClass
in different static libraries within a single project, Xcode would not be able to build the project because it would fail to link correctly citing duplicate symbols. However, Xcode is perfectly happy building a project that has two classes with the same name in different frameworks.
Why does this matter? Unlike Objective-C, the standard Swift runtime libraries aren’t included in iOS! This means your framework must include the necessary Swift runtime libraries. As a consequence, pods written in Swift must be created as dynamic frameworks. If Apple allowed Swift static libraries, it would cause duplicate symbols across different libraries that use the same standard runtime dependencies.
Fortunately, CocoaPods takes care of all of this for you. It even takes care of only including required dependencies once. All you have to do is remember to include use_frameworks! in your Podfile when working with Swift CocoaPods and you’ll be just fine.
Amazing, right?
Back to Installing Your First Dependency
It’s finally time to add your first dependency using CocoaPods. Add the following to your Podfile, right after use_frameworks!:
pod 'Alamofire', '4.4.0'
This tells CocoaPods you want to include Alamofire version 4.4.0 – the latest, stable version at the time of writing this tutorial – as a dependency for your project.
Save and close the Podfile.
You now need to tell CocoaPods to install the dependencies for your project. Enter the following command in Terminal, after ensuring you’re still in the directory containing the IceCreamShop project and Podfile:
pod install
You should see output similar to the following:
Analyzing dependencies
Downloading dependencies
Installing Alamofire (4.4.0)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `IceCreamShop.xcworkspace` for this project from now on.
Open the project folder using Finder, and you’ll see CocoaPods created a new IceCreamShop.xcworkspace file and a Pods folder in which to store all the project’s dependencies.
Note: From now on, as the command-line warning mentioned, you must always open the project with the .xcworkspace file and not the .xcodeproj, otherwise you’ll encounter build errors.
Excellent! You’ve just added your first dependency using CocoaPods!
Using Installed Pods
If the Xcode project is open, close it now and open IceCreamShop.xcworkspace.
Open PickFlavorViewController.swift and add the following just below the existing import:
import Alamofire
Hit ⌘+b to build the project. If all went well, you shouldn’t receive any compilation errors.
Next, replace loadFlavors()
with the following:
fileprivate func loadFlavors() {
// 1
Alamofire.request(
"https://www.raywenderlich.com/downloads/Flavors.plist",
method: .get,
encoding: PropertyListEncoding(format: .xml, options: 0)).responsePropertyList {
[weak self] response in
// 2
guard let strongSelf = self else { return }
// 3
guard response.result.isSuccess,
let dictionaryArray = response.result.value as? [[String: String]] else {
return
}
// 4
strongSelf.flavors = strongSelf.flavorFactory.flavors(from: dictionaryArray)
// 5
strongSelf.collectionView.reloadData()
strongSelf.selectFirstFlavor()
}
}
Here’s the play-by-play of what’s happening:
- You use Alamofire to create a GET request and download a plist containing ice cream flavors.
- In order to break a strong reference cycle, you use a weak reference to self in the response completion block. Once the block executes, you immediately get a strong reference to self so you can set properties on it later.
- You next verify the
response.result
indicates it was successful, and theresponse.result.value
is an array of dictionaries. - If all goes well, you set
strongSelf.flavors
to an array ofFlavor
objects created by aFlavorFactory
. This is a class a “colleague” wrote for you (you’re welcome!), which takes an array of dictionaries and uses them to create instances ofFlavor
. - Lastly, you reload the collection view and select the first flavor.
Build and run. You should now be able to choose an ice cream flavor!
Now For a Tasty Topping
The app is looking good, but you can still improve it.
Did you notice the app takes a second to download the flavors file? You may not have if you’re on a fast Internet connection, but customers won’t always be so lucky.
To help customers understand the app is actually loading something, and not just twiddling its libraries, you can show a loading indicator. MBProgressHUD is a really nice indicator that will work well here. And it supports CocoaPods, what a coincidence! :]
You need to add this to your Podfile. Rather than opening the Podfile from the command line, you can now find it in the Pods target in the workspace:
Open Podfile and add the following right after the Alamofire line:
pod 'MBProgressHUD', '~> 1.0'
Save the file, and install the dependencies via pod install in Terminal, just as you did before.
Notice anything different this time? Yep, you’ve specified the version number as ~> 1.0. What’s going on here?
CocoaPods recommends that all pods use Semantic Versioning.
Semantic Versioning
The three numbers are defined as major, minor, and patch version numbers.
For example, the version number 1.0.0 would be interpreted as:
When the major number is increased, this means that non-backwards compatible changes were introduced. When you upgrade a pod to the next major version, you may need to fix build errors, or the pod may behave differently than before.
When the minor number is increased, this means new functionality was added, but it’s backwards compatible. When you decide to upgrade, you may or may not need the new functionality, but it shouldn’t cause any build errors or change existing behavior.
When the patch number is increased, this means bug fixes were added, but no new functionality was added or behavior changes made. In general, you always want to upgrade patch versions as soon as possible to have the latest, stable version of the pod.
Lastly, the highest order number (major then minor then patch) must be increased per the above rules and any lower order numbers must be reset to zero.
Need an Example?
Consider a pod that has a current version number of 1.2.3.
If changes are made that are not backwards compatible, don’t have new functionality, but fix existing bugs, the next version would be 2.0.0.
Challenge Time
If a pod has a current version of 2.4.6 and changes are made that fix bugs and add backwards-compatible functionality, what should the new version number be?
If a pod has a current version of 3.5.8 and changes are made to existing functionality which aren’t backwards compatible, what should the new version number be?
If a pod has a current version of 10.20.30 and only bugs are fixed, what should the new version number be?
Having said all this, there is one exception to these rules:
If a pod’s version number is less than 1.0.0, it’s considered to be a beta version, and minor number increases may include backwards incompatible changes.
So in the case of MBProgressHUB using ~> 1.0
means you should install the latest version that’s greater than or equal to 1.0
but less than 2.0
.
This ensures you get the latest bug fixes and features when you install this pod, but you won’t accidentally pull in backwards-incompatible changes.
There’s several other operators available, too. For a complete list, see the Podfile Syntax Reference.
Showing Progess
Now, back in PickFlavorViewController.swift, add the following right after the other imports:
import MBProgressHUD
Next, add the following helper methods after loadFlavors()
:
private func showLoadingHUD() {
let hud = MBProgressHUD.showAdded(to: contentView, animated: true)
hud.label.text = "Loading..."
}
private func hideLoadingHUD() {
MBProgressHUD.hide(for: contentView, animated: true)
}
Now in loadFlavors()
, add the following two lines (as indicated):
fileprivate func loadFlavors() {
showLoadingHUD() // <-- Add this line
Alamofire.request(
"https://www.raywenderlich.com/downloads/Flavors.plist",
method: .get,
encoding: PropertyListEncoding(format: .xml, options: 0)).responsePropertyList {
[weak self] response in
guard let strongSelf = self else { return }
strongSelf.hideLoadingHUD() // <-- Add this line
// ...
As the method names imply, showLoadingHUD()
shows an instance of MBProgressHUD
while the GET request downloads, and hideLoadingHUD()
hides the HUD when the request is finished. Since showLoadingHUD()
is outside the closure it does not need to be prefixed with self.
Build and run. You should now see a loading indicator while the flavors are loading:
Great work! Customers can now select their favorite ice cream flavor, and they are shown a loading indicator while flavors are being downloaded.
Where to Go From Here
You can download the completed project from here.
Congratulations, you now know the basics of using CocoaPods, including creating and modifying dependencies, and understanding semantic versioning. You're now ready to start using them both in your own projects!
There's lots more that you can do with CocoaPods. You can search for existing pods on the official CocoaPods website. Also refer to the CocoaPods Guides to learn the finer details of using this excellent tool. But be warned, once you do begin using it you'll wonder how you ever managed without it! :]
I hope you enjoyed reading this CocoaPods tutorial as much I did writing it.
What are some of your favorite CocoaPods? Which ones do you rely on the most for everyday projects? Feel free to share in the comments below, or on the forums!
The post CocoaPods Tutorial for Swift: Getting Started appeared first on Ray Wenderlich.