Hi there. My name's Jacob Xiao and I'm really excited to talk to you today about Building Adaptive Apps. Before we get started though, I want to tell you what we mean by "adaptive". So first, let's go back. In the beginning, there was the iPhone. One device, one screen, it was pretty simple to build applications for. But then when you consider rotation, now you have this portrait and landscape orientations, and then after that, we introduced the iPad as well as the iPhone 5 with its four-inch display, and now you have all these different devices and all these different screen sizes, and it can seem intimidating to design apps for. Well, in iOS 8 we want to make it simple for you to build one application that's both universal and is able to adapt to all of these different devices, screen sizes, and orientations.
So today, I'd like to tell you about some new concepts that we've added for adaptivity inside of UIKit, and then we'll take a look at some of the changes we've made to both view controllers and interface builder to support this new adaptive world. All right, to start off, let's take a look at size classes, a new concept that we've introduced in the iOS 8. In the past you've used UIInterfaceOrientation and UIUser InterfaceIdiom to differentiate between portrait and landscape and iPhone and iPad, but in iOS 8, we're recommending against using these two concepts, and instead we're advocating this new concept that we call size classes.
So, let me show you a little bit about what me mean by these differences that are represented by size class.
If you think about a typical iPad application and the screens that it shows, you'll see features like Split View Controllers, Form Sheets, and Popovers. But really, none of these features are specific to the iPad itself, we're just using these in this iPad version of the application because we have this large horizontal canvas to display within. Now on the other hand, if you look at a typical iPhone application, you have a much more constrained layout where things are usually presented fullscreen and shown in a single column. However this is also not intrinsically tied to the fact that it's an iPhone, but it's just tied to the fact that we have this smaller horizontal canvas size. And you'll see the same kind of thing in the master side of a Split View Controller, and even in the content of a Popover.
And both of these are on iPad. And both of these are on iPad. So, we call this difference "regular" and "compact", and we call this axis the horizontal size class. Now, if we consider the same kinds of distinctions in the vertical direction, you can see that on things like an iPad or an iPhone, we have these taller full-size bars, whereas in a more constrained vertical situation like an iPhone in landscape, we have both condensed bars, and now in iOS 8, we even hide the status bar completely. And we call these also regular and compact, but this time in the vertical size class. Now, you can think of these two size classes in a similar way to the way size works. Just like a size has a horizontal and vertical dimension, so does a size class. So if you put these two concepts together, you get a two by two grid that defines any of the possibilities of regular and compact in both the horizontal and vertical directions.
And you can have a View Controller with any of these four possibilities, which results in different layouts like you'd see here.
In addition to View Controllers having any of these size class combinations, our devices also have default size classes that they'll use. An iPad, in both landscape and portrait, will have a regular size class in both the horizontal and vertical direction.
An iPhone, when in portrait, will have a compact horizontal size class and a regular vertical size class, and when it's in landscape it will be compact in both the vertical and horizontal size classes.
So this is all a little theoretical. Let's take a look at a sample application to see how this works in practice. All right, so the sample code that I'll be showing you today is available on the WWDC website.
Just go to the Sample Code section and search for Adaptive Photos, the name of our sample app. It's a basic photo sharing application, and this is what it looks like on iPhone. You can see our list of our contacts, and I can tap on any of these to see the photos that they've sent to me. If someone who's only sent me a single photo, I'll go directly to that image, and if they've sent me multiple photos, I can see a list of the photos that they've sent me, and then view individual ones. Now, on the Photo page, you'll notice that in addition to the picture, we're also showing a comment overlay and a rating control that lets me rate the photos that they've sent me.
All right, now when I rotate this application to landscape, notice that our bars become condensed and the status bar disappears entirely. This all happens automatically, but in our application, we've done a few more customizations. Inside of a photo view, when I rotate, the Comments will have smaller margins, and the Rating Control will shrink down to a smaller size. Now, our application is of course universal, so let me run it inside of the iPad simulator.
Here, you'll notice that I have all of the same functionality, and all of the same controls inside of my Photo View, but watch when I rotate to landscape.
All of the bars still have their full height, and none of my controls here have shrunk down. And this is because we still have this large, vertical real estate, and in our application we're using the vertical size class instead of the interface orientation, so we get the right behavior on both iPhone and iPad.
so we get the right behavior on both iPhone and iPad.
Now, one of the new features we've added in iOS 8 is a resizable simulator. So if you notice here at the bottom, I can type in new dimensions and you'll see that my application has resized, and everything still looks great because we're using Auto Layout and standard system components.
But in addition to just changing the size, I can also change the size class and now, you'll see that we're showing the iPhone version of our application even on the iPad just by changing the size class.
So, I highly recommend that you test out your application with the new resizable simulator in iOS 8. All right, so now that you know how size classes work in general, how do you actually get one of these size classes in your application? of these size classes in your application? Well, to do that, you use a new system we call Traits.
Traits are essentially properties that you can use to determine how the layout of your application should change as its environment changes.
They consist of a set of properties, including the horizontal and vertical size classes that we just talked about, as well as the userInterfaceIdiom and also the displayScale. Now, all of these traits are wrapped up inside a container that we call a Trait Collection.
This includes the Trait Properties and also their values, and this new object is called a UI Trait Collection. To get one of these Trait Collections you just need to use a Trait Environment.
Trait Environments are a new protocol that are able to return their current Trait Collection, and these include Screens, Windows, View Controllers, and also Views. View Controllers, and also Views. All of these are able to return their current Trait Collection to you to use to determine how your interface should be laid out.
One other object that's also a Trait Environment is a UIPresentationController. This is a new helper object that assists with View Controller presentation, and it's also able to participate in adaptivity.
We don't have time to talk today about presentation controllers, but you can come back tomorrow at 11:30 to the "A Look Inside Presentation Controllers" talk to learn more about how presentation controllers work, including how they work with traits and adaptivity.
Now all of these Trait Environments make up a hierarchy, and the trait collections that they have will flow from parent to child.
So by default, the trait collections that any given child Trait Environment has will be the ones that it's inherited from its parent, all the way up to the screen which makes up the root Trait Environment. Now, in addition to getting the current trait collection from Trait Environments, they also have another method, and that's called traitCollectionDidChange. This gets called whenever the traits for a given Trait Environment have just changed, and you can override this in your View Controller, or View Subclass, to know when you should be changing all of your UI elements that depend on traits, and we'll see an example of that a little bit later in our sample application. Now let's take a look at a typical trait collection. This trait collection is one you might see on an iPhone in portrait. We have a compact horizontal size class, a regular vertical size class, the idiom of phone, and a display scale of two.
We call this a fully specified trade collection because it has values for all of its trait properties. Now it's also possible to have a trait collection that's missing some of its values, and we call these missing values Unspecified. values Unspecified. Generally though, when you ask a Trait Environment for its trait collection, you'll get back a fully specified trait collection like the one on the left. However, if a Trait Environment like a View or a View Controller is not inside of the view hierarchy, you might get back unspecified values like the trait collection on the right. You'll also get back these partially specified trait collections if you create your own trait collection using one of our creation methods, like traitCollectionWith HorizontalSizeClass, which would allow you to create a trait collection just like the one on the right. Now, one operation that we can perform on multiple trait collections is comparing them to each other. And comparing a trait collection involves asking if one trait collection contains another one. Now, what this means about containment is that for any trait that's specified in a second trait collection, the value of that trait in the first trait collection has to have the same-has to match exactly.
So here, the second trait collection only has specified a horizontal size class and those horizontal size classes are equal, so we'd say that the first trait collection contains the second one, and the way you can ask this question of trait collections is by using the containsTraitsInCollection method on UITraitCollection. If we were to change the horizontal size class of the second trait collection to regular, you can see that now, these two horizontal size classes don't match, so the second trait collection is now longer contained by the first one.
Now, you can perform these comparisons yourself in your own code to determine how you should lay out your views or View Controllers, but UIkit also uses this internally for some of its functionality, and one example of that is the Appearance Proxy. The Appearance Proxy is a system that we introduced a while ago for customizing the properties of your views, and we've extended it in iOS 8 to support trait collections. and we've extended it in iOS 8 to support trait collections.
We now have a new method, appearanceForTraitCollection, that returns you a new appearance proxy with a given trait collection that you've passed in, and any customizations that you perform on that appearance proxy will only take effect on views that are-that conform to that trait collection that you've passed in. So, [applause] I'm glad you like it. Generally you pass in a partially specified trait collection like a horizontal size class of Compact, and then you'd be able to customize all of your views when they are inside of a compact horizontal size class, and this is really great for customizing all of your views together.
Another class that we've added trait collection support to is UIImage. In the past, you'd have a 1X and 2X version of your UIImages, and you'd generally put these in your image catalog. However, in iOS 8, we've extended this to allow you to add multiple versions of your image for different trait collections.
So for example, we could have this smaller image that's used when we have a vertically compact size class, and then the other image that we use in any other time for any other trait collections. Now when you use one of these images inside of a UIImageView, the image view will automatically pull out the right version of the image for its current trait collection.
So for example, if our image view has a regular vertical size class, we'd be using this larger image, and what's really cool is when our image view changes to have a compact vertical size class, it will automatically update the image that it's using to be the smaller image, and even change its own intrinsic content size to shrink down to exactly match that image's size.
And this makes it really easy for you to use images that have different versions for different trait collections, and automatically get the right behavior in wherever your application adapts to. in wherever your application adapts to. Now like I said, this all happens automatically with UIImageView, but we also have a new class called UIImageAsset that gives you even more control. An image asset wraps up all of these different versions of the image, and it allows you to ask for a specific image matching a given trait collection that you can pass in, and you can even add and remove your own representations of an image using other methods on image assets, so check out the UIImageAsset header file to see all of the details of how this works.
Now, one last thing that we can do with trait collections is add them together. When we add one trait collection to a second, we get a combined trait collection, and we can do that with the traitCollection WithTraitsFromCollections method. Any time that one of the traits is unspecified, except for in one of the trait collections that we're adding, we'll get only the specified trait in the final trait collection. However, if there are multiple versions, multiple values for the trait that are being added together, then the last trait collection will be the one that wins. So here, we're adding Compact to Regular for the horizontal size class, so our resulting trait collection will also have a regular horizontal size class, and we'll see where this can be used a little bit later in our talk.
Now that you know how traits work, let's see how we use them in our application. So, let's run our application on the iPhone again - - and we'll take a look again at these two views that change their appearance when their vertical size classes change.
The first one is our rating control, and we can look at the code for that right here. All that we're doing in this view is creating image views and setting images on them that we're pulling out of our asset catalog. There's actually no code in here at all that deals with traits or size classes. Instead, we just have assets that define a regular version of the image, and a vertically compact version of the image, and our Image View automatically updates to give us this resizing behavior for our ratings control, and since we're using Auto Layout, our entire control will shrink and grow to match those images changing.
Notice that we also have black versions of these images here, and they're turning to be blue in our application, and this is because we've added Image Rendering Mode support to the asset catalog.
All right, next let's take a look at our Overlay view. All right, next let's take a look at our Overlay view.
Here, in our intrinsic content size method, we're looking at the horizontal and vertical size classes that we're currently in, and using that to determine margins to add on to our intrinsic content size, and this is how we automatically change between this larger and smaller margins as we rotate between a vertically compact and regular size class.
However, we also need to tell the system when it needs to update this intrinsic content size, and we do this by overriding traitCollectionDidChange. Here, we check if either the vertical or horizontal size class has changed along with this trait collection change, and if it has, then we just invalidate our intrinsic content size, and that's all we have to do. Now, one last view in our application is this Profile View, and this shows some information about the current user, as well as the last image that I've sent. as well as the last image that I've sent. However, when I rotate this to landscape, you'll see that our layout changes to show a side-by-side view instead of this up and down view, and we've implemented that by using Trait Collections and Size Classes as well. If I go to my Profile View Controller, you can see that we have a method that updates all of our auto layout constraints, and it takes a trait collection that we pass in, checks its vertical size class, and uses one set of constraints when it's compact to show the side-by-side view, and uses a different set of constraints, otherwise, to show the up and down view.
All right, now let's look back at slides, again. The next thing I'd like to tell you about is some of the details of how our View Controllers have adopted these concepts of traits and size classes to automatically perform, be more adaptive in your applications. One of the View Controller classes that has changed the most in iOS 8 is UISplitViewController. In the past, you'd often use a Split View Controller in the iPad version of your application, and then you'd have to write a completely different View Controller hierarchy for your iPhone application. Well in iOS 8, we've made the Split View Controller available on both platforms so you can just write one View Controller hierarchy that works great on iPhone and iPad, and that's what we've done in our sample application.
However, we've even gone a little bit further there and forced the Split View Controller to have its side-by-side two-column view in iPhone in landscape just like it would have in iPad. So, let me show you exactly how we did that. Let's first take a look at the Trait Environment hierarchy that our application has when it's on the iPad. Here, you can see that the Split View Controller is inheriting its trait collections from its parents and this gives it a regular horizontal size class.
When the split View Controller is in a regular horizontal size class, it automatically will show the two column view that you expect on an iPad.
However, if we now change to what our iPhone's Trait Environment looks like in landscape here, you'll see that once again the Split View Controller is inheriting its trait collections, but here it has a compact horizontal size class, and this triggers it to show the one column view. Well, to change this in our application, we'll insert our own container View Controller as the parent of the Split View Controller. Then, we'll use the new method in iOS 8 called setOverrideTraitCollection: for ChildViewController. This allows us to add our own trait collection to the one that's inherited by the Child View Controller.
In this case, we'll be adding a regular horizontal size class in our trait collection, and notice that it's partially specified, so when we add it together with the inherited trait collection, our Split View Controller will now have a regular horizontal size class in addition to what else it inherits from its Trait Environments, and this regular size class will cause it to show the two-column split view appearance. So what this ends up with when we rotate from portrait to landscape, is it will change from one to two columns. All right, now that we've seen how we can change the trait collections of our Child View Controllers, let's take a look in detail at how the trait collection transition occurs, for example, when we rotate. In this case, we'd be rotating from portrait to landscape, and let's take a look at the timeline of changes that occurs. The first phase we have is the setup where we get ready to perform this transition. Then, we show animations to indicate the visual changes, and when all of that's done, we do some cleanup to finalize the transition, and in iOS 8, when Trait Collections change as part of this collection, we've given you some callbacks that you can tie into to participate in this change.
The first one is willTransitionToTraitCollection: withTransitionCoordinator, and this gets called at the very beginning of the setup before the trait collection has changed to its new value. You can use this to get ready for the change that's about to occur. After that, the trait collection change itself happens, and immediately following that we call traitCollectionDidChange, as I mentioned earlier, and you can use this in View Controllers or View Subclasses. However, that's not the end of the story. We still have these Animation and Cleanup Stages, and you can actually use the willTransitionToTraitCollection method to tie into those, as well. The transition coordinator that you get passed in this method has an animate alongside method that allows you to add your own animation blocks to this transition that will run along with the transition's own animations, for example, the rotation in this case.
There's also a way to add your own completion blocks that will get run inside of the cleanup stage.
So let's take a look at where we might use these two specific callbacks.
WillTransitionToTraitCollection is great for animating View Controller changes along with these trait collection changes, so we use this in our application in the profile view that we just saw, since we want this change to occur right alongside that rotation transition.
However, WillTransitionToTraitCollection is only available on View Controllers, not on other Trait Environments like UIViews.
And so, traitCollectionDidChange is great for use in UIView subclasses where you want to update your UI as the traits are changing. So we've used this one in our Comment Overlay View that you saw earlier. So now that we've seen these trait changes, let's drill in a little bit deeper into the behavior that happens when a Split View Controller collapses from a two-column to a one-column view.
As part of this change, there are two changes that need to occur. The first thing that has to happen is we need to find a new primary View Controller to show in this collapsed one-column state. By default, Split View Controller will use the primary View Controller from your expanded two-column state as the new primary View Controller in your collapsed one-column state as well. However, you can also change this by overriding the splitViewControllerDelegate method, primaryViewControllerFor CollapsingSplitViewController. This allows you to return any View Controller you want to be the new primary View Controller in the collapsed version of your Split View Controller. Now, once that new primary View Controller has been chosen, the next step that has to happen is the secondary View Controller has to get merged into that primary View Controller, and in general, Split View Controller will try to automatically do the right thing here as well.
to automatically do the right thing here as well. In fact, in our sample application we didn't need to write or add any code to get the behavior that you see here where the secondary View Controller, our Photo View, automatically gets pushed onto the Navigation Controller Stack that we had from our primary View Controller. So generally, you won't have to do anything here.
However, there may be some special cases where you want to interact a little bit with this change. One example of that from our sample application is the No Conversation Selected View. We show this whenever we're in the two-column wiew and nothing has been selected on the left. However, if we just use the default behavior, the Split View Controller would take that Secondary View Controller and push that on top of our navigation stack, but this No Conversation View doesn't really add anything to the single column view because there's no way that the user can interact with it.
So, really we'd rather have a view that looks like this where we just show the top level List View Controller when we get collapsed, and it's possible to do that with another splitViewController method, collapseSecondaryViewController: ontoPrimaryViewController, and I'll show you in detail the code that we use to do that in our sample app a little bit later. Next, let's look at the transition that happens in the opposite direction when we expand from a single column view to the two-column view. Once again, there are two stages that need to occur. We need to find the new primary View Controller, and by default, split View Controller will also use the primary View Controller from the collapsed view and the expanded view, and once again, you can use a Split View Controller Delegate method to change that behavior.
This one is primaryViewControllerFor ExpandingSplitViewController. Now once that new primary View Controller has been chosen, we need to take the secondary View Controller and recreate it from the primary View Controller that was collapsed, and a Split View Controller will automatically do the right thing here as well by popping off our photo view and showing that as the new secondary View Controller in our Expanded view. However, once again, we want to do something a little bit special for the No Conversation view. Here, if we were showing the List view when we were collapsed, we want to recreate that no conversation selected view and make that appear as the new Detail View Controller on the right.
So we can do that using another Split View Controller delegate method, separateSecondaryViewController FromPrimaryViewController. Now, I've just been telling you that Split View Controller is doing all of these merging and unmerging of the secondary View Controller automatically, and that's not entirely true. The primary View Controller itself is actually helping to do this merging and unmerging, and it's doing this using these two methods on UIViewController, collapseSecondaryViewController forSplitViewController and separateSecondaryViewController ForSplitViewController. These get called by the Split View Controller as part of its default implementation of the collapsing and uncollapsing that occurs, and UINavigationController implements these to push and pop the secondary View Controller for you automatically.
However, you can also implement these in your own container View Controllers to get the same kind of behavior the navigation controller has, or even something completely custom for your application. Now, one other change that we've made to how View Controllers work in iOS 8 is in the way that you showViewControllers. In the past, if you had a leaf View Controller like a table View Controller and you showed a different one, perhaps by tapping on the cell, that View Controller would reach up through the View Controller hierarchy and grab the navigation controller that it was embedded inside of and call push View Controller on that, but this is a pretty tight coupling between the leaf View Controller and the exact environment that it's inside of, and we want to try to move away from this pattern in iOS 8.
So, instead we're introducing two new methods that help you de-couple this, showViewController and showDetailViewController. These methods work by starting at the leaf View Controller and walking up its parent View Controller hierarchy until they find the right Container View Controller for that specific action.
So, let me show you how these specific methods work. We'll start with ShowViewController and how it behaves when it's inside-when it's called inside of a UINavigationController. Here it will just push onto the Navigation Controller, so this is a great replacement for that Self.Navigation Controller/Push View Controller approach that you saw earlier.
And what's great about this method is that it will actually adapt to different View Controller containers and do something different. For example, when you're inside of a Split View Controller and you call this method it will instead show the new View Controller on the left side of the Split View Controller as the new Primary View Controller. In fact, even when you're not inside of any container, View Controller that implements this method will still give you some automatic behavior. Here, we'll show the View Controller as a modal View Controller presentation.
So, you can always be guaranteed that the View Controller that you pass to ShowViewController will be shown in exactly the right way for its current environment. Next, let's look at showDetailViewController, this works similarly and it's implemented by Split View Controller. Here, the Split View Controller will show the View Controller you pass on the right of its split.
However, if that Split View Controller is collapsed, as you might see in an iPhone application, then it will actually redirect that showDetailViewController method to showViewController and re-send it to its own Primary View Controller.
Let's look at a more concrete example, where we have UINavigationController as the Primary View Controller of the Split View Controller, just like in our application. Here, the Navigation Controller gets the showViewController method and will push just like you saw earlier. So, this showDetailViewController gives you great behavior in a Split View Controller where it'll show it on the right-hand side if it's expanded, but it will push onto a Navigation Controller if that's your Primary View Controller when it's collapsed.
And once again just like with showViewController, showDetailViewController will show that View Controller as a model presentation if it's not inside of any container View Controller that implements it. So, these are those two View - those two methods and what their method signature looks like but, in addition to being able to call them, you can also implement these in your own custom View Controller methods and this lets you get exactly the same kind of behavior as Navigation Controller and Split View Controller will get inside of all of your custom View Controllers.
Next, I'd like to tell you a little bit about how these methods are actually implemented. And that's using a new method called targetViewController ForAction:sender. This does all the work of going up the View Controller hierarchy until the right View Controller gets found. For example, if we call showViewController we'd walk up until we find the Navigation Controller since that's the first View Controller that implements it or if we called showDetailViewController we would keep walking up the View Controller hierarchy until we got to the Split View Controller.
And targetViewControllerForAction works by looking at the View Controller and seeing if it's overwritten the action method that you've passed in, and also whether that View Controller wants to receive that specific action. And the great thing about this is that since its public you can use it to make your own methods that work just like ShowViewController and ShowDetailViewController, and we'll see some specific examples of this as we've used it in our sample application. The last topic that I'd like to mention is View Controller presentation. In iOS 8 we've made this adaptive as well. So if you show a Popover, a View Controller presentation will now automatically adapt that to a fullscreen presentation when you're in a horizontally compact size class.
Once again we don't have time to talk about that today, but I highly encourage you to go to the "A Look Inside Presentation Controllers" talk, which is tomorrow at 11:30. It will show you all about how Presentation Controllers work and also how you can use them with traits and adaptivity. All right, let's look at a demo of how all of those View Controller features work in our application. First, let's look at that profile view again and let me show you what its transition looks like in slow motion. Notice that our labels and image view are moving right along with the rotation transition. And as I indicated, the way we do that is by calling this updateConstraints ForTraitCollection method inside a willTransitionToTraitCollection with transition coordinator.
Here we just use the animateAlongsideTransition method of the transition coordinator, call that updateConstraints method and then make our view update its layout. This will automatically cause this layout-these layout constraints to change alongside that rotation transition. All right, now let's look at how we can override the traits for our Split View Controller to automatically get that landscape view that shows two columns. We'll just add this viewWillTransitionToSize method and here, if our width is larger than 320, we'll add this Forced Trait Collection, which is - has a horizontal size class of regular. has a horizontal size class of regular. This is one of those partially specified Trait Collections that we talked about earlier. When we set that trait collection we'll use the setOverrideTraitCollection ForChildViewController method to add it to our Child View Controller, which in this case is a Split View Controller.
Now when I run the application again you'll see that we can rotate to landscape and get this two-column view. And everything just works in iPhone as you'd expect and in landscape it works just as it would on the iPad. Now let's look in detail at how we implemented the collapsing and expanding behavior. This happens in our Split View Controller delegate method. First, we have the collapseSecondaryViewController: ontoPrimaryViewController method and here we want to use this for when we're collapsing and we're going from a - showing the No Conversation view to hiding it and only showing this top level list.
First, we'll ask the Secondary View Controller if it contains a photo and we did this by adding a category method to your View Controller to return whether or not any given View Controller shows a photo and, if we do not have any photo in our current Secondary View Controller, we'll return yes.
This tells the Split View Controller that we've handled the collapse ourselves and turns off any of its default behaviors which would have pushed that Secondary View Controller on top of the navigation stack.
We also have some logic here to make sure that we don't push a View Controller on top of any View Controllers that don't match its current photo. You can look at the sample application yourself to see exactly how this works. Finally, we return "no" here when there was a photo and this tells the Split View Controller that we didn't implement the collapse and so it will implement-it will perform that behavior itself.
And that's how we get the automatic behavior of pushing this photo view off the navigation stack.
Now if we look in the other direction, where we're expanding our Split View Controller we'll take a look at all of the View Controllers that are on top of the navigation stack and, if any of them contain a photo, we'll return "no".
This also indicates to the Split View Controller that we want it to perform its default behavior here so if we had a photo and we were expanding then that photo would automatically get popped off of the navigation stack and shown on the right.
If we didn't contain any photo though, we'll want to create our Empty View Controller and that's how we re-show that No Conversation view when we're expanding. Now let's take a look at how our application moves between View Controllers.
First we'll look at the Conversation View Controller. This is the one that's shown here when one of our conversations have multiple photos. If we look at the method that gets called when we select a table view cell, we create our new View Controller and we call ShowDetailViewController on that new View Controller.
This shows it on the right of the Split View Controller if we're expanded and, as I mentioned, it will push it onto the navigation stack if we're collapsed without us having to write any device specific checks in our code.
We can also look at our List View Controller, which has similar but slightly more complicated behavior. And that's so that we can show a single View Controller immediately if there is only one photo, or this Conversation View Controller if there is more than one. Here we check if for any given row we should show that Conversation View and if we're showing the Conversation View we create it and use ShowViewController. This method will always push onto the - onto its navigation controller even when we're expanded in the two-column view.
On the other hand, if we only have single photo we'll create that View Controller and call ShowDetailViewController which gives us the same behavior as we had in the Conversation View Controller case. Now one more thing that you'll notice in our application is that when we rotate to transition between a single and two column view these disclosure indicators are hiding and appearing and this is to maintain the behavior that whenever tapping on a row would push we show a disclosure indicator and whenever it wouldn't we don't show the disclosure indicator.
So in a single-column, collapsed view we push on all of these rows, but in the expanded view we only push on these multiple conversation views. And we did that by adding our own category to UIViewController that implements two new methods, willShowing ViewControllerPushWithSender and willShowingDetail ViewControllerPushWithSender. These correspond to the ShowViewController and ShowDetailViewController methods that we talked about earlier, and they just return whether calling one of those methods would cause a push to occur or not which we use to determine whether or not to show a disclosure indicator.
The way we implement these is by using the targetViewControllerForAction method that we just talked about, we pass in the same action will showing View Controller push with sender and then we get back a targetViewController that implements that method. Once we have our targetViewController we just need to ask it whether it will push or not and if we don't have a target we'll return no. Then, you just need to override those methods and Navigation Controller to return yes, then showing a View Controller here will always push and will override it in Split View Controller to return no, since here showing a View Controller will show it in the left hand column as I mentioned earlier.
Now, the behavior for willShowingDetailView ControllerPush is slightly more complicated, because here we need to check if we're collapsed first and if we're collapsed then we'll take our current Primary View Controller and we'll redirect the willShowingDetailViewController method to willShowingViewController, and this is the same kind of behavior that Split View Controller implements with showDetailViewController. So, now that we've seen how those are implemented, let's look at how we used it in our Table View Controller classes.
Here, when we're laying out one of our cells we'll call will showing detail View Controller push and that's because the method that we would call when it was tapped is showDetailViewController.
We'll get back the result of whether or not that action will push and we'll use it to set the accessory type to either disclosure indicator or none. This is how we get these showing and hiding disclosure indicators. Now, we also use these methods to determine whether or not to deselect a row when it gets popped too and you can take a look at the sample application yourself to see exactly how that works by using the same willShowingView Controller and willShowingDetail ViewController methods.
The last thing I'd like to show you is a new notification we added in iOS 8, UIViewControllerShow DetailTargetDidChange notification and this is important for telling our table view when it needs to update its disclosure views.
This notification gets triggered by Split View Controller whenever the target that would be used for showDetailViewController changes and this is when a Split View Controller expands or collapses so we use this notification to go through all of our cells and update their disclosure indicators. That's all for our sample application, but I really encourage you to download it from the WWDC website.
Remember, it's called adaptive photos and take a look at how it implements all of these features. Next, I'd like to hand things over to Tony to talk to you about some of the changes we've made to Interface Builder, Interface Builder for supporting adaptivity. Thanks Jacob. Hi, I'm Tony Ricciardi and I'm an engineer on the Interface Builder team. Jacob just introduced a few new concepts in iOS 8 for developing adaptive UIs such as Size Classes and Trait Collections.
Now I'm going to show you guys a few new features we've added to Xcode to help you work with these concepts.
In Xcode 6, you can customize your layout for multiple Size Classes using a single Interface Builder document.
That means you can now target both the iPhone and the iPad with one storyboard or XIB. You can deploy these documents backwards to older versions of iOS, and you can preview your layout for different devices, orientations and OS versions all within Xcode.
Now documents using this feature will require Xcode 6 and auto layout, so they won't be compatible with older versions of Xcode. Let's head over to Xcode and take a look. Okay, so here we have the storyboard for an adventure game I have designed for the iPad. Today I want to extend this storyboard to target the iPhone. Before I get started, let me show you the app running in the stimulator. This is the main menu for my adventure game. On the left I have a few buttons that control the content that shows up on the right side. The Play button takes you to this menu for choosing your character, which can either be a Warrior or a Mage. The Store button takes you to the store page and this button takes you to the Settings page. As you can see, I've implemented this UI using a UISplitViewController. Over here I have my Primary View Controller with those three buttons.
Up here, I have my character menu and, down here, I have my Store page and my Settings page. Each of these three buttons is connected to one of those Secondary View Controllers using a replace segue. That means that when you tap one of those buttons, the Secondary View Controller is going to be replaced by the View Controller that is connected to that button. So as I mentioned today I want to enable - I want to extend the storyboard to target the iPhone and to do that I'm going to enable Size Classes. I'm going to head over to the File Inspector and check the U Size Classes box. When I do that, I get this dialog telling me that my document is going to be upgraded, upgrading will enable auto layout and it will convert your segues to the new adaptive segue types. If you've viewed storyboards before, you're familiar with the usual segue types like push, model, and replace that allow you to display View Controller when an event is triggered. In Xcode 6 we've added some new segue types that correspond to the new View Controller API that Jacob discussed earlier, like showViewController and showDetailViewController.
When you enable Size Classes it will automatically upgrade your segue to those new types. So, as you can see after I enabled Size Classes, my View Controller got resized to this 480 by 480 to the second, this square represents sizes of any width and any height so, when you see this square your edits will apply to all Size Classes unless you override your layout for a specific size class. In my case, I've already designed my layout to work well for the iPad and now I want to override my layout for the iPhone. To do that I'm going to use this button here at the bottom of the canvas, this button allows me to choose which Size Classes I'm editing for. Each of these squares in this 3 by 3 grid represents a combination of a width class and a height class, currently the middle square is selected and that corresponds to any width and any height.
When this square is selected, you're editing your default layout, which is inherited by all the other configurations. In the top left, we have a square for compact width and compact height and that corresponds to an iPhone in landscape. In the bottom right, we have a square for regular width and regular height, which corresponds to an iPad, and in my case since I'm interested in overriding my layout for an iPhone in portrait, I'm going to head over to the bottom left where I have a square for compact width and regular height.
for compact width and regular height. When I choose that square, my View Controller grows narrow and tall to show me that I'm now editing for compact width and regular height. Also, you might have noticed this bar at the bottom of the canvas turns blue to show me I'm no longer editing for the default layout.
As you can see in this configuration, my image view and my buttons are getting clipped. So, this is going to require a couple of fixes. First I want to give my image view a smaller image to use when the width is compact and second I want to change those buttons to be stacked on top of each other vertically rather than sitting side-by-side like they are now.
Let's fix the image view first. I'm going to head over to my asset catalog using the jump bar and then I'm going to select my logo image set and then I'm going to head over to the inspector and up here for the width attribute I'm going to choose any and compact. That gives me a couple of new slots here. The images that I put into these slots are going to be used at runtime instead of my default images whenever the width is compact. I already have a couple of images waiting here in the finder, so I'm just going to drag those over to those slots. Okay, now I'm going to head back to the storyboard and, as you can see, that image view has been updated with that smaller image and it's no longer getting clipped. Now let's fix these buttons. I'm going to start by just selecting the buttons so you can take a look at their constraints.
For those of you who are unfamiliar with auto layout, I'm not going to go into too much detail in layout constraints for this demo. I recommend checking out our session from last year's conference. However, I do want to point out that there is a hidden view here between these two buttons and that view only exists to hold constraints. It has a couple of constraints centering it in its container and it has a few more constraints pinning the buttons to it, but most importantly it has this width constraint here and that allows me to control how far apart the buttons are from each other. So, if I wanted to move them farther apart or closer together I would just have to edit that width constraint. However, now that I want these buttons to be laid out vertically, I no longer need this view or any of its constraints to hold the buttons together. So, the next thing I'm going to do is clear the restraints by using this menu at the bottom of the canvas. I'm just going to choose clear constraints and when I do that the constraints disappear from the canvas and you might wonder if that's going to cause my layout to stop working for the iPad.
However, since I'm currently editing for compact width and regular height my edits will only apply to that configuration. If we take a look at the outline view, you can see that those constraints are still there in my document they've just been turned off for the current editing mode. Okay, the next thing I'm going to do is remove this view since I no longer need it there to hold the buttons next to each other horizontally. So, I'm going to go over to the Attributes Inspector and down here at the bottom we have this check box that says Installed. Installed means that the view is present in the view hierarchy at runtime as a subview of its container.
I want this view to be installed in all configurations except for compact width and regular height so, I'm going to add - I'm going to use this "+" button here to add a customization for compact width and regular height.
When I do that, I get a new Installed checkbox and, when I uncheck that box, the view disappears, and if we take another look at the outline view over here you can see that it's once again still there in my document but it's just been turned off for this editing mode. Okay, now I'm just going to drag around these buttons so that they're going to be laid out vertically rather than sitting side-by-side. Okay, then I'll select the buttons and that label there and I'm going to go back to this menu and this time I'm going to ask Interface Builder to just give me some suggested constraints for those views. Okay, at this point I could hit the Run button and try this out in the iPhone simulator but instead what I'm going to do is show you how you can preview your layout directly within Xcode. So, I'm going to go up to the top and I'm going to open the assistant editor then I'm going to use the jump bar to go over to the Preview Assistant.
And since I'm interested in previewing for the iPhone 4 in portrait, I'm going to use this "+" button and choose iPhone 4-inch, and there we go, now I can see my layout as it would appear on an iPhone portrait. If I wanted to try it out in landscape I can use this button here at the bottom of the preview to rotate it, or if I want to see both orientations at the same time I can use the "+" button again to add another preview and now that lets me see both orientations at once. As you can see in landscape the buttons are once again sitting side-by-side and that's because we've only changed our layout for compact width and regular height. An iPhone in landscape corresponds to compact width, compact height and so those edits didn't apply. Just in case you didn't believe me, now I'll actually run this in the simulator.
All right, so here we have my Primary View Controller with those three buttons and, as you can see, it's now fullscreen since we're running on a phone. When I tap the Play button it takes me to my character menu and those buttons are stacked vertically just like we expected, and when I rotate they move to sit side-by-side. to sit side-by-side. Let's recap what we saw in that demo. First, we used auto layout to design a flexible UI that works well for multiple Size Classes. For example, my image view used a horizontal centering constraint to position itself for both compact width and regular width. Next, I showed you how you can override subviews and constraints for specific Size Classes when you need to. I did this to give my buttons a vertical layout for iPhones in portrait and a separate layout for all other cases.
And finally I showed you how you can preview different devices and orientations, OS versions, even localizations, all within Xcode using the Preview Assistant Editor. Today you saw a lot of new concepts for developing adaptive UIs. Jacob introduced you to Trait Collections and Size Classes which allow you to modify your UI in response to changes in size. in response to changes in size. He also introduced a new API that allows you to decouple Child View Controllers from their containers and to collapse and expand UISplitViewControllers. Finally I showed you some new features we've added to Interface Builder and Xcode 6. If you'd like more information you can contact our Evangelists or you can post on the Developer Forum. We recommend checking out the other View Controller-related sessions from this year's conference and we'll also be having an Interface Builder session later this afternoon in this room. And with that, thank you and I hope you enjoyed Developing Adaptive UIs [applause] in iOS 8. -