With additions like UIStackView, Auto Layout has matured into quite a powerful system for managing how our views are arranged on screen. When things go wrong though, it can sometimes be difficult to diagnose the specific cause of the issue. Today we'll look at a few techniques for making sense of the madness caused by... Auto Layout Bugs! ππͺπͺπͺ
Most issues arise from "Unsatisfiable Layouts". That's fancy-talk for "two or more constraints you gave Auto Layout conflict with each other." The solution will of course be different in every case, but here's some sensible things to ask ourselves when an error first occurs:
Is translatesAutoresizingMaskIntoConstraints set to false on the views we're adding constraints to?
Are the priorities of each of constraint, as well as content hugging and compression resistance priorities (Bite #69) what we expect them to be?
"Can this requiredconstraint work at a 999priority?" Remember, Auto Layout will try to get as close to our desired result as possible, while still satisfying all other constraints.
Identifiers
constraint.identifier="image-fixed-width"
Identifiers help us more easily spot the important bits in those giant log outputs Auto Layout loves to show us. They can be added in code or in Interface Builder.
When debugging complex layouts, it can sometimes be helpful to look at only the constraints involving a specific problem view or area. We can use this function to grab an array of the constraints affecting a particular axis. Neat.
Brain Surgery
When all else fails, don't be afraid to go in and start temporarily commenting-out or disabling constraints then observe the results. This can often lead to unexpected insights into how constraints are behaving.
Practice!
A great way to fight Auto Layout issues is to try to catch them before they happen. That means becoming more familiar with Auto Layout, which means practice. For example: When a question or issue comes up, create a new 'dummy' Xcode project. Throw some views and constraints in there and try it out. Tweak some priorities, observe their effects in the isolated environment. When in doubt, try it out!
Teaching users how to use our apps is incredibly important. We should strive to do this through familiar UI patterns, intuitive flows, good error handling, etc. Sometimes though, we just need to explain what's going on.
Next, we'll extend our view controller so it conforms to the CoachMarksControllerDataSourceprotocol. Instructions is highly customizable. It has support for custom body and arrow views, positions, highlights, and more. Let's keep things simple here and add a regular coach mark for a button in our app.
funcnumberOfCoachMarksForCoachMarksController(coachMarksController:CoachMarksController)->Int{return1}funccoachMarksController(coachMarksController:CoachMarksController,coachMarksForIndexindex:Int)->CoachMark{returncoachMarksController.coachMarkForView(launchButton)}funccoachMarksController(coachMarksController:CoachMarksController,coachMarkViewsForIndexindex:Int,coachMark:CoachMark)->(bodyView:CoachMarkBodyView,arrowView:CoachMarkArrowView?){letcoachViews=coachMarksController.defaultCoachViewsWithArrow(true,arrowOrientation:coachMark.arrowOrientation)coachViews.bodyView.hintLabel.text="This button launches the spaceship, proceed with caution!"coachViews.bodyView.nextLabel.text="Got it"return(bodyView:coachViews.bodyView,arrowView:coachViews.arrowView)}
It's good practice to let users who don't need our coach marks skip them. Instructions has us covered. We can setup a "skip view" on our controller. We'll use the default one, which shows in the nav bar:
Layout Guides & Anchors were added in iOS 9/OS X 10.11 as a more convenient way to add Auto Layout constraints. Let's dive in.
Layout Guides let us create sort of "invisible boxes" that exist solely for the purpose of layout. Instead of adding "spacer" views or using other such tricks, we can add some layout guides to a view, then add some constraints to them. It's a nice way to avoid needless performance wastes like rendering the dummy "spacer" views, and the code itself ends up being much more readable:
...into a set of much more readable short lines of code (shown below). The new code reads much more easily from left to right, and is much easier to scan and reason about what's going on.
UIView, NSView, and UILayoutGuide all have lots of new anchorproperties available to make adding constraints to them much simpler than before. We also get a little extra type safety as a bonus.
Animation is one of the greatest parts about building (and using) iOS apps. The APIs however, can feel a bit scattered. UIView's animation functions are wonderful, but some animations require using Core Animation directly.
When they do, things get progressively more complex depending on if we need to run multiple animations in succession, or just run code after an animation completes.
Today we'll look at a great library from Marin Todorov called EasyAnimation that improves on all of this. Let's dive in:
EasyAnimation makes animating CALayers that normally would require CABasicAnimation (or one of its siblings) work with the standard UIView.animateWithDurationfunctions:
Under the hood, EasyAnimation does all the heavy lifting of translating our animations back into CAAnimation code and handling all of the implementation details for us. Neat!
Normally, if we wanted to run code after one the animations on a CALayer finished, we'd need to wire up an animation delegate, implement the callback functions, make sure to clean up after ourselves, etc.
With EasyAnimation though, we're able to just use the normal completion closure.
Last but certainly not least, EasyAnimation makes "chaining" multiple animations together (running one after another) extremely convenient. It also supports cancelling the chain, repeating, delays and more:
UICollectionViewController's new property called installsStandardGestureForInteractiveMovement defaults to true, so once we implement delegate function above, we're good to go.
To customize things further, we'll need to manually call UICollectionView's new interactive movement functions. Let's look at a very rough idea of how we might make the picked up cell "hover" and the others "wiggle" like iOS's home screen.
We'll also start/stop wiggling when we dequeue cells. Lastly, we'll apply the same changes that are in animatePickingUpCell to the cell's layout attributes. To do this we can subclass UICollectionViewFlowLayout and override layoutAttributesForInteractivelyMovingItemAtIndexPath.
After all that's done, this is the outcome:
Here's a direct link to this example video, just in case.
Download the project at j.mp/bite104 to see a complete working example.
Update on March 22nd, 2019: Thanks to reader Le Zeng, the project download now supports Swift 4.2. Thanks so much!
Like in Bite #99, we'll use Auto Layout Visual Format Language to pin the stack view's edges to its superview. Look at that, not bad for our first try!
Those views are close talkers, let's add some spacing:
stackView.spacing=10.0
Ack! What the heck is going on here?!
Let's break it down: In the default βFill' distribution mode, if views don't naturally fill the axis of the stack view, the stack view will resize one (or more) according to their hugging priority (covered in Bite #69).
We'll solve our issue by setting a low hugging priority on our label, signaling to the stack view that it be the one to stretch, not our image view.
We've covered a few of the new 3D Touch APIs in iOS 9 like Static and Dynamic Shortcut Items (Bite #79, #88) as well as View Controller Previews (Bite #80), but today we'll look at how to access and utilize raw force values from a user's touches. Let's get started.
We'll start with the "Single View" template. We'll open up our Main.storyboard and drag a UIProgressView out to the top of the screen. Then we'll give it an @IBOutlet in our ViewController and wire it up.
Next, the fun part. We'll create a new function called updateForTouches, that will take in an optional Set of UITouch objects. We'll guard to make sure 3D Touch is available, and assume our force is 0.0 unless a touch is present. Then, we update the progress property of our progress view and for good measure, we'll set a red background color on our view, mapping it's opacity to the touch's force as well.
Lastly we'll need to implement all the touchesBegan, touchesMoves, touchesCancelled, and touchesEnded functions, calling our function and passing in the touches on each:
Success! If we build and run on an iPhone 6S or iPhone 6S Plus, we can start pressing the screen lightly and watch as the progress view and background color change as we begin pressing more firmly.
It might not be immediately obvious when playing around with Shortcut Items or View Controller Previews, but the hardware does in fact report every single tiny change in the force in real time. Neat!
Universal Links arrived with iOS 9. Conceptually they're a way for us to logically tie the content in our app to the content on our website. Once this is done, iOS will be able to launch our app when a user taps a link somewhere, rather than opening our site in Safari. Let's take a further look by adding Universal Links to a fictional Little Bites of Cocoaapp. We'll start by registering and setting up SSL for our domain: littlebitesofcocoa.com.
Then, we'll head into Xcode and to the Capabilities tab of our project. We'll flip on the switch for Associated Domains, then click the + button and add our domain. Note that we prefix it with the phrase applinks:.
Now, our app will silently make an HTTP GET request to https://littlebitesofcocoa.com/apple-app-site-association.
It will expect us to return some JSON which describes our app's Bundle ID, and which paths should open it.
We'll open all paths on our domain using a wildcard character here, but we could easily, for example, limit to just Bite URLs.
After creating the file, we'll need to sign it so it is returned with a Content-Type of application/pkcs7-mime on our server. We'll use a command like the one shown here to sign the file. (This part stinks, but there's no way around it).
Lastly, we'll wire up the other side of the equation. When a user opens a Universal Link that iOS recognizes, it will call the same delegate function that's used to implement features like Handoff (Bite #29) and Spotlight Search (Bite #23). We'll check if it's a Universal Links activity type, then use JLRoutes (Bite #62) to open a section of our app.
We covered Static Shortcut Items back in Bite #79, when 3D Touch was first introduced. Today we'll be taking a look at their close relative, Dynamic Shortcut Items. These offer the same behavior and functionality (allowing users to quickly jump to a particular section or feature of an app, straight from the home screen) but they can be configured and managed in code, and don't need to be defined in our Info.plist.
Dynamic Shortcut Items are particularly great for offering quick access to things such as user created content. Let's add a couple to our app. We'll use ours to let the user open recently viewed documents.
We'll create a function which we'll call whenever a document is opened in our app. It will update the current set of Dynamic Shortcut Items to match the 2 most recently opened documents.
We'll grab our recent documents, then define a new shortcut item for each one. We can handle tapping a Dynamic Shortcut Item just as we did in Bite #79, by passing a URL in the shortcut'suserInfoproperty then using JLRoutes (Bite #62) to route it correctly inside application(application:performActionForShortcutItem shortcutItem:completionHandler:).
Now both our static and Dynamic Shortcut Items are available by force pressing our app'sicon on the home screen.
Note: At publish time of this Bite, Xcode did not offer a way to test shortcut items in the Simulator. To generate the screenshot above (and test shortcut items' functionality) this project by Conrad Kramer was used.
UIImagePickerController has been part of iOS since it's first release and it's evolved quite a bit over the years. Let's take a look at what it can do:
Capture images and videos
Choose images and videos from the Photos library
Crop images after choosing/capturing
Trim videos after choosing/capturing
Whew! That's quite a bit of functionality packed into this one class.
We can't cover all of that in this Bite, instead let's look at a simple example use case. We'll be letting our users take a photo, crop it, and then show how to access it for use in our app.
The first step is to find out the device we're running on has a camera, can take photos. Then we'll configure the UIImagePickerController and present it.
Then we'll add a function from UIImagePickerControllerDelegate where we'll get a userInfodictionary. We'll use the values inside to extract the captured image in either it's original or cropped form. We can also access a few other details, like the cropped rect as a CGRect or the image's metadata as a dictionary.
Note that we'll need to declare conformance to the UINavigationControllerDelegate protocol since UIImagePickerController is actually a subclass of UINavigationController under the hood.