Content Compression Resistance Priority controls how the frame of a UIView will be calculated when one or more Auto Layout constraints describe its width or height as being smaller than its intrinsic content size. Let's look at a bare bones example:
Here's a button with a really long name:
We've added a simple constraint telling Auto Layout to try to keep the width of our button at 44 points. Auto Layout does as its told and collapses our button making it completely unreadable. Don't worry, we can use Compression Resistance to stop this.
We select our button in Interface Builder, head over to the size inspector (⌘⌥ + 5), and set it's horizontal Compression Resistance Priority to 1000.
Now, we'll change the priority of our original 44 point width constraint to something less than 1000. We'll use 999 to emphasize the point, but this could be any number from 0 - 999.
Success! Auto Layout now allows our button'sintrinsic content size to take precedent over our width contraint:
Now that we understand Compression Resistance, understanding Content Hugging Priority is easy. It works in a very similar manner, but instead of managing whether a view is made smaller than its intrinsic content size, it deals with whether or not a view can be made larger than its intrinsic content size. Let's look at one more example to illustrate this, here's another button:
If we were to add a set of leading and trailing constraints, telling the button to be as wide as the view controller it's sitting on, it might look something like this:
But, if we set the horizontal Content Hugging Priority of our button to 1000, and the priority of those leading and trailing constraints to 999, our **button **becomes nice and small again:
Measuring the performance of an app is the first step in optimizing it. Lucky for us, Apple's developer tools have plenty of profiling features. Let's take a look at one, Time Profiler. It's claim to fame is helping us fine tune performance by tracking down slow functions.
We start by opening our project in Xcode, and selecting Product > Profile from the menu.
This will build our app, and launch Instruments. When it does, we'll select the Time ProfilerInstrument and click Profile. Now the fun part: We click 🔴 in the top left and Instruments will launch our app, and begin measuring its performance. One common way to use Time Profiler is to discover any under-performing functions that might be bogging down our main thread.
Remember, a bored main thread with nothing do is a happy one. Once our app is running, we'll turn on all the Call Tree options. This will clean up the data that's collected making it easier to sift through. We can flip these on and off after profiling to learn about different parts of the code being executed. From here we simply interact with our app, and after a few seconds we'll see data starting to appear. We expand the Main Thread, and see that a function called insertNewObject is at the top of the list, meaning of all the functions that were called, this one caused our main thread to block the most. We'll double click on it to see the exact spot in our code calling the sluggish function, ready for us to optimize:
One of the best parts of CloudKit is how great it is at handling not just our models, but also larger assets like images, audio, or video.
Assets are saved just like any other property. Here we'll attach an image captured from the user's camera to a new record. Then we'll upload it to CloudKit using a CKModifyRecordsOperation (covered in more detail in Bite #31). In our case we're only saving a single record, but we're using an operation anyway, so we can take advantage of its perRecordProgressBlock, and track the upload progress of our asset.
funcimagePickerController(picker:UIImagePickerController,didFinishPickingMediaWithInfoinfo:[String:AnyObject]){guardletmediaURL=info[UIImagePickerControllerMediaURL]as?NSURLelse{return}letspaceshipRecord=CKRecord(recordType:"Spaceship")spaceshipRecord["model"]="Tantive IV"spaceshipRecord["maxSpeed"]=950// in kmspaceshipRecord["image"]=CKAsset(fileURL:mediaURL)letoperation=CKModifyRecordsOperation(recordsToSave:[spaceshipRecord],recordIDsToDelete:nil)operation.perRecordProgressBlock={self.progressView.progress=$1}operation.completionBlock={self.progressView.hidden=true}progressView.hidden=falseCKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)}
It's worth noting that CloudKit doesn't seem to report progress constantly as we might expect. It seems to report between 0-3 times depending on the size of the asset we're uploading.
After fetching a record containing an asset, we can grab the downloaded file from disk using the fileURL property of the CKAsset:
Don't worry, NSTimeZone has our backs. It can help us easily translate NSDate objects from one time zone to another. Let's dive deeper with a concrete example.
Here's a common scenario:
We're working on an app that downloads some data from an API, then displays it to the user. In our case, we know the API is returning timestamp information in the GMTtime zone.
Translating the API's values to the user's local time zone would go something like this:
letinput="2015-08-24T09:42:00"// came from HTTP APIletGMTTZ=NSTimeZone(forSecondsFromGMT:0)letlocalTZ=NSTimeZone.localTimeZone()letformatter=NSDateFormatter()formatter.dateFormat="yyyy-MM-dd'T'HH:mm:ss"formatter.timeZone=GMTTZifletdate=formatter.dateFromString(input){formatter.timeZone=localTZformatter.dateFormat="MMMM d, H:mm a"timestampLabel.text=formatter.stringFromDate(date)// "August 24, 2:42 AM"}
After we parse the date, we change our formatter'stime zone to the local one, then render our date back to a string to show the user.
The NSTimeZone.localTimeZone() function (as well as any NSTimeZone object) is our gateway to a few more useful values:
There's a ton of power tucked away inside of Xcode Schemes. Let's take a look at just a few of the helpful things we can do by clicking on our current Scheme and selecting Edit Scheme...
Default Simulated Locations
We can configure a default simulated location when our app is run by selecting Run in the left column and then Options.
Test Multiple States
Don't be afraid to go Scheme-crazy. Why not make one that runs with an application data bundle of a logged in user, and one of a logged out user? Now we can easily switch between developing in the two states.
Walking Dead Objects
Selecting Run in the left column, then Diagnostics to enable Zombie Objects. This will help you track down those pesky EXC_BAD_ACCESS crashes by holding on to deallocated objects(ding) and logging a message when something tries to interact with them.
Add Carthage Support
Carthage (covered in Bite #11) support is easy to add. We just need to make sure we share the Scheme that builds our framework.
Easier Profiling
We can Duplicate our original Scheme again and choose an Instrument under Profile > Instrument. Now we can easily launch straight into a particular kind of Profiling with a single click.
Test Background Fetch
We can easily test our app when launched from a background fetch by editing our original Scheme then clicking Duplicate Scheme. On this new Scheme, we'll enable Launch due to a Background Fetch under Run > Options.
Game Center makes it simple to create a multiplayer, turn-based game. It handles matchmaking, turn expirations and much more. Let's take a look at how to use it. First, we'll authenticate the local player (shown in Bite #57), then register a listener on it:
At the start of each turn, we download the latest match data from Game Center, and modify it as the player plays. Then at the end of each turn we send our modified match data object back up to Game Center, so other players can download it. We need to download updated match data when the match begins and when our *local player's *turn starts.
For this we implement two functions from two different protocols:
When our local player completes their turn, we end the turn and tell Game Center which of the match's participants should go next (only 2 in our case, so we always send the other player):
The Multipeer Connectivity framework allows devices to communicate directly with one another without the need for a central server. It uses all sorts of technology under the hood: Peer-to-peer WiFi, "regular" WiFi, as well as Bluetooth. It can handle all kinds of data: streams, files, or just good 'ol NSData. Let's try it out. First we need a service type, here's ours (just a global variable):
letCrewChatServiceType="crew-chat"
When a user wants to start a new chat session, we need to advertise it to other nearby devices, we can do that with an MCNearbyServiceAdvertiser:
Next, we need to properly respond to invitations from other peers. To do this we'll need to implement just one (very verbosely named) function. It is advertiser(advertiser:didRecieveInvitationFromPeer:withContent:invitationHandler:).
We'll create a session, then pass it to the provided handler.
Lastly, we'll test things out by sending new peers a welcome message after they connect, using the session delegate's session(session:peer:didChangeState:) function:
funcsession(session:MCSession,peerpeerID:MCPeerID,didChangeStatestate:MCSessionState){ifstate==MCSessionState.Connected{letmessage="Hello \(peerID.displayName), welcome to the chat!"letmessageData=message.dataUsingEncoding(NSUTF8StringEncoding)!try!session.sendData(messageData,toPeers:[peerID],withMode:.Reliable)}}
JLRoutes is a library from Joel Levin that makes it very easy to manage "routes" for URL schemes in an app. It's common to need to "deep link" in to an app. Other apps can use these to open your app to a specific spot, or we can use them within our own app such as when the user opens a push notification. Let's take a look.
First we'll add a new URL Type for our app. In Xcode, we'll go to our project, then target settings, then to the Info tab, and finally to the URL Types section.
Neat, now URLs in the form of spaceships://something will open our app! Now let's add our first route.
These can added anywhere, but it's best to set them up early, when the app has first launched.
A few things to note here. First notice the :id portion of the route path. This lets us easily extract out portions of the route from the params dictionary.
Also note how we use guard and return false if we can't handle the URL for some reason.
In addition, JLRoutes has support for wildcard matching as well as route namespaces.
More info about JLRoutes can be found at git.io/routes
Continuing where we left off with Game Center in Bite #57, let's add support for achievements to our game. We start by adding a couple of achievements in iTunes Connect. When viewing our app, we'll click Game Center, then Add Achievement.
Most of this process is self-explanatory, but one thing to note is that each achievement can have a point value of no more than 100. Additionally, the point values of all the achievements in our game added together can't be more than 1000.
Now all we need to do is tell Game Center when a player completes (or makes progress towards) an achievement. Every time our user scores a point, we'll update their progress.
ifscore<=5{letachievement=GKAchievement(identifier:"achievement.womprats.five")achievement.percentComplete=Double(score/5)achievement.showsCompletionBanner=true// use Game Center's UIGKAchievement.reportAchievements([achievement],withCompletionHandler:nil)}
We can also load the user's current achievement progress anytime like this:
CocoaPods are an extremely popular way to publish and use third-party libraries on iOS and OS X. Let's create and publish one of our own. Our Pod will provide shortcuts to commonly used directories, like the user's documents directory as NSURLs. Let's get started.
We start by generating our new Pod like this:
➜ pod lib create common-paths
We'll be asked a few questions such as what language we want to use (Objective-C/Swift), whether we'd like to create a demo application, etc. then a complete Xcode workspace and directory structure will be created.
We'll open up the newly created workspace and get started by filling out all the metadata (summary, author, etc.) about our new Pod inside the .podspec file.
CocoaPods generated a blank .swift showing us where to put our code. We do as we're told, and replace it with a new file called NSURL+CommonPaths.swift.
Neat, now let's publish it! We create a new Github repo matching the source we entered in our .podspec. Then we commit our changes, tag them, and push them up: