[ Applause ]
MIGUEL SANCHEZ: Hello, everyone.
Welcome session 216, Layout and Animation Techniques for WatchKit.
My name is Miguel Sanchez, I'm a WatchKit engineer.
Later you will hear from my colleague Tom Witkin.
So the agenda for today is to give you an overview of the layout fundamentals of WatchKit.
I will be going into more detail about how to use groups to set up your complex layouts, and then you will hear from Tom, talking to you about animation techniques existing already in Watch OS 1, as well as in the new APIs that we are introducing in Watch OS 2.
So our target audience for this talk is the whole range of WatchKit developers, people that haven't seen the platform yet, and are learning about the layout as well as as well as those of you who have already had experience with it.
So let's start with the layout fundamentals.
The WatchKit layout model, I should say off the bat is the same layout model that we have since WatchKit OS 1.
WatchKit and Watch OS q.
If you haven't used it yet, it's a different model from the UIKit and the AppKit models that you may have had experiences with.
The difference is that in WatchKit we are using a flow based layout and what I mean by that is, you've probably seen this animation before, in Interface Builder, you have your library of, and your catalog of WatchKit objects and you are dragging them on to your controller.
As you are dragging them, they are falling on the next available slot on the flow.
And that flow is initially vertical.
So here you have a second element.
It falls on its set slot, and, of course, you can add a horizontal flow if you introduce a grouping object and tell it to use horizontal layout.
So some important points as a programmer of WatchKit applications is that you are not writing object creation code.
All of your UI creation is happening in story boards and Interface Builder.
We are not providing APIsor allocating your typical WatchKit objects.
This does not mean that you don't have fine-tune control of your layout hierarchies and alignment and sizing, as well as animation, as you will see here.
So we'll go into each one of these in detail.
Let's start out with alignment and sizing.
All of your instances of WatchKit objects are subclasses of WKInterfaceObject.
All of these have properties for setting the alignment, and the sizing heuristics.
So alignment, we are referring to the alignment inside the containing object.
This is the horizontal and the vertical alignment and it can either be left, center or right.
For sizing, you are telling us what rules you want us to use when sizing your objects both width-wise and height-wise and this is either fixed, relative or sized to fit.
In Watch OS 2, these these properties have been available in Interface Builder since Watch OS 1, but in Watch OS 2, we are exposing more of these through APIs.
We want to give you more control in your code but also to animate them as you'll see later.
This is a new API set horizontal and vertical alignment with new enum types.
This does what you expect.
Here you have the world premier of WK blue box doing a left top alignment, center, center, and right bottom.
On the sizing API side, we already had said width and said height.
For washWatch OS 2, we are exposing the relative width and the sizing to fit APIs.
Again, it gives you more control in your code.
Graphically this looks like the following: so here's a fixed width and height of our blue square, once again.
If you have been using this in the previous release, you should know that we interpret the value of zero differently.
In Watch OS 1, if you give us a zero in code, we revert it to the storyboard value, in Watch OS 2, we are interpreting the zero as an absolute zero value.
So just keep that in mind.
Let's say that you want to size the rectangle to be 75% of the width of the containing element.
So you can use said relative width, with a .75 value and you get three-fourths of the width.
And you can do the same for the height, if you want it to be half of the height of your containing element.
The second parameter in these APIs is an adjustment value, which can be positive or negative, and it's applied after you do the initial sizing.
So in this case, I will be adding 30 to the width and subtracting 30 to the height and I get the following.
And finally, the sizing to fit and sizing to height does what you probably expect.
You know, depending on the content inside your rectangle, we are sizing appropriately.
Now, let's move on to group elements.
This is where you really start to fine tune your layout.
WKInterfaceGroup, think of it initially as a container without default content.
It can have content as I will illustrate later but initially it's just a container.
It's a tool for arranging all of your elements.
This is where you get the chance to pick whether you want a vertical or horizontal layout.
This is also where you start to introduce nesting.
And this is where you are tweaking the alignment and the sizing that we just saw before on the containing elements.
So as we saw in the first set of slides, now I'm abstracting to the blue shapes, you are laying your interface in Interface Builder and you have the vertical layout.
You introduce a group and you tell it in Interface Builder that you want the horizontal layout and you start laying out your objects there.
So once you have defined your containment hierarchy, you are able to define things like insets and spacings within your group.
So you can define the left inset here.
These are point values.
Left inset, bottom inset, top inset, right inset and right, as well as the spacing that is used to put aside to separate each one of your elements.
Now, don't forget that your top level container object is also is also a group.
So you can set the insets at that level, as well as the spacing.
So we use the same spacing for the number of elements that you have inside of your group.
So here you have a spacing of 10 that is used for the three top level elements in your group.
Now, once you apply once you start using nesting, you are able to achieve your complex layouts.
Here's a very simple example.
If you want the two squares stacked on top of each other, followed by two squares right next to each other, you first use a group with a vertical layout.
Then you use another group with a horizontal layout and you wrap those two in another group because that's the way you achieve the horizontal layout of those two elements.
And by default, there's no content in your groups.
So they are transparent and you only see your squares.
Now, let's make this more concrete with an example that I will be introducing in this part of the session and then Tom will start animating.
So we have WKRecipes.
This is a very simple recipe viewer.
It lists your recipes.
Fish tacos, pizza, barbecue wings.
You can see the list of ingredients for each one of your recipes, and you can see the number of servings for each one of the recipes.
And there are other screens which Tom will talk about in the context of animation.
For here, we are focusing on the static layout of these three elements.
So how do we do this?
The first screen that we see is a table of recipes.
So in WatchKit, you have a table controller that you can use to layout your rows, but it's your responsibility to tell us what each row looks like.
What is the structure of that?
So here we want an image and a label.
So we have a horizontal layout, that should automatically tell you that that's a group with a horizontal layout.
Then you have defined your containment hierarchy, and now you are going to define your spacings.
So you have your left inset, your bottom, top, and the spacing.
And your alignment.
Right? Your image is aligned center, vertically, but it's aligned to the left in the next slot that it falls on.
And the label also has a vertical alignment but left aligned.
Notice that it's left aligned in the slot that it corresponds to, right, because there's an image before it, so right next to it.
Sizing our images are square.
So we have a fixed size of 30 points on each side.
And the label, obviously sizes to fit.
So table row layouts show us that we can use the groups for horizontal sizing, and then we can fine tune our alignments, and set some spacing.
Now, let's move on to the ingredients controller.
Some of you might be noticing that five hot sauce, whatever units that is, is probably too much hot sauce for any taco recipe, but that's the wrong thing to focus on.
You actually want to notice that although we have our table layout, we have rows, we now have the circle element.
How do we achieve this?
The horizontal layout is the same.
We still have a group.
We have the label, obviously, but what is that red circle on the top left?
We can nest groups, as I said before.
So we are using a group here.
And here we are now moving into the area where groups do have backgrounds.
They don't have default content, but they can have backgrounds.
The backgrounds can be either colors or images.
So we can select the image of this group, as well as the radius that is used to draw the background.
And so we are setting a group with a colored background, blue in the schematic, red in the real example with a radius of 8 and then you get the nice blue circle.
Once you have a group, you can nest the label inside it and it's centered in the group.
Now you have the horizontal and the vertical centering so this is where you start to see the building of concepts that we have been talking about.
And yet again, sorry to be repetitive, but this is the model.
Now you define you find tune your left inset, top, bottom, and spacing.
So remember, for nested groups, you can use nesting to achieve complex layouts, as well as using backgrounds within your groups whether that's a color, or an image.
Lastly, we have the servings controller, which it highlights the number it's a circle that highlights the number of servings, four in this example, for your particular recipe.
So some of you might be thinking, this is a circular layout.
How does this how is this achieved in the grid-like flow layout that you have been talking about so far?
So we have a circle of 12 well, a circular pattern of 12 objects, but if we look at them in detail, these are actually just three top level groups.
So we have the first group, the second group, and the third group.
Inside of those groups, we subsequently use more nesting.
So let's focus on the middle group.
Sorry, let me talk about the at the top level groups we do have alignment of the groups.
For example, the top group is horizontally aligned in the center and fixed size.
The middle one is sized to fit to take up the whole width of the container.
So now the second group has more groups inside of it, right?
So we have a group with vertical layout.
Then we have a group with horizontal layout and finally we have a third group with horizontal with another vertical layout.
And if we look at each one of those, those themselves are made up of more subgroups.
Once we have this level of definition, we can indicate the alignment, the precise alignment on each one of those circles.
Now you can clearly see that that circle is horizontally aligned to the right and vertically on the top.
Then you have the left alignment and the center for this particular circle.
And right bottom for the other, right?
So you have seen how the grid-like flow layout really gives you a lot of power in terms of the things you can achieve and that's the same pattern that we follow for the whole for the whole circle here.
So group nesting is your key to achieving complex layouts.
Now that I have given you all of this rope, I need to warn you, don't hang yourself with it.
It is possible to abuse groups, right?
You have to keep that in mind.
You can go overboard by trying to nest everything.
So some things to keep in mind.
I can't give you an exact number.
It depends on what your backgrounds are or how your layout is behaving, but keep these things in mind.
At the beginning of the presentation, I said that we are not offering you APIs for direct element creation, or destruction.
This is all done in the storyboards and Interface Builder.
So anything you create in Interface Builder is instantiated.
When you're instantiating one of your controllers, you're instantiating all of the elements inside of it.
It's possible to hide some of them.
That's one of the properties that you have for some of the elements, but even though they are hidden, you are still we are still creating them.
So you might you are still taking the hit of the creation costs.
You might save on the layout costs because we are not doing the layout, but the creation is still happening.
So keep this in mind as you start adding a bunch of objects into your controllers, right?
Keep them in mind that those are all being created even if they are not visible at that particular time.
And finally, as I showed you with groups and their ability to have images, keep in mind that images have a transfer cost over into your Apple Watch.
In Watch OS 1 apps, the extension was running on the phone, and they have to get we have to get those images over into your watch, right?
Each time your user is using your interface.
So keep that in mind.
We have APIs for image caching which we kind of you tell us to move a certain set of images but they still have to get over the air to your watch.
In Watch OS 2, this is less relevant because the extension is running on your watch, but you still have to install the app.
So you still have a set of resources that you are installing.
So there is still a transfer cost for your users from an installation point of view.
So always remember to use appropriate sizes for your images.
If you give us an image, we will resize it for whatever you tell us to display on the screen.
But by doing, that you are adding more processing cycles from our from the WatchKit side on the watch, right?
So you have the ability to resize your images properly, either on the phone or in your servers, if they happen to be dynamic images and this makes an impact, in terms of the rendering of your UI.
Finally, you will have some images that need to be resized, for example, buttons.
You can use you don't need multiple sizes.
You can use image slicing.
This is a UI that you have in your asset catalogs and Interface Builder, you can slice your images and tell us how they are resizing and we do the right thing.
So you can give us one particular image set and we will it will be applicable for different sizes in your UI.
So this is the end of the static part of the talk and now I would like to invite Tom up to add more dynamic aspects to this model.
[ Applause ]
TOM WITKIN: Awesome.
Hello. I'm Tom, I also work on WatchKit.
We will be discussing animations and introducing those into both your existing and your new Watch OS 2 apps.
We will start off with some existing types of animation available in Watch OS 1.
So tables are really flexible.
In certain updates on tables will already animate.
If you insert rows, if you remove rows or if you update the content within a row, they will actually animate and we will talk about all of these.
So all of our examples are going to be within the context of our recipe app.
So let's start on the recipe list.
And it would be great if we could provide a way to sort the list, either so by ranking that we have assigned or maybe alphabetical.
It's not extremely clear how the list is being sorted as we are doing this.
And so it would be great if we could possibly insert a row and say how the list is sorted and then after a couple of seconds, take it out of the UI.
So how are we doing this?
It's really simple.
We have a table.
And we are calling insert rows and indexes and we are inserting a row of status type.
And then we are getting the row controller and updating the text on that row and then we are scheduling a timer for a few seconds to remove the row and that method is then implemented here.
And so inserting and removing rows can really add flexibility into your interfaces.
And one thing to point out is you can insert or remove rows of any type.
So for example, here we have a list of recipes, all the same type of cell, however we are inserting a row of an entirely different type.
So, again, it allows you to have extremely dynamic content.
So within your interfaces.
I will also point out that if you want to update your tables and not animate we have 2 API that I'm sure you all know if you've used WatchKit or Watch OS 1.
So moving on to the content within a row and we will tap into our detailed view of our recipe.
And we have a description.
And because of the size of Apple Watch, and the length of a description, we probably don't want to have the full description there all the time.
And so we have implemented our description within a table row and we show a truncated version.
It would be great if you tap on the row, it will expand in place and give the full description.
And so it's really nice it's a nice animation and it it provides you and so the information you want, exactly when you want it.
So how are we achieving this?
I will go back to our blue boxes.
So we have our table cell.
And within the cell, we have a few labels.
So we have our short label, which has a description with the truncated text.
We have another label, which says tap to show more.
And then also we actually have a third label.
So that's a full descriptionlabel.
So the initial state of our cell has the full description label hidden and all we are doing is the user taps on the cell, is we are reversing this.
So we will unhide the full description label and we will hide the other ones and just this will allow that animation to happen, that we handle in WatchKit.
So it's really simple.
And, again, here's the code.
So just in a few lines of code within your application, and it's a really great experience.
And so, again, rows reload, whenever their content changes in height.
So if you are reloading a row but it doesn't change in height, there's no animation.
And so in order to achieve this, make sure your rows have a size to fit height, because if they have a fixed height, again the row isn't actually changing.
So now we will go back to our recipe list and visit a different type of animation.
And back to our cell and I will call out these arrows here.
It would be great if, as its displayed, we could add a flourish of some type so the arrows spin around, for example.
We can accomplish this with an animated image.
So animated images are really powerful, and they allow you to cycle through a series of images over a given duration.
And they also allow you to repeat and reverse animations.
And it's the only type of animation within WatchKit that will allow that.
So, again, there are very powerful.
I do want to point out, though, if you do have a series of images so the number of images can quickly add up and so their costs in terms of load times and performance, as well as and so memory usage.
Keep that in mind and reduce the number of images as far as you can, and the size of the and the size of the images and you will get better performance.
I also want to point out now our new picker object, in Watch OS 2, and it allows you to scrub through the images within an animated image so with a Digital Crown.
It's really powerful and it's a great experience.
If you would like more information, there is a session, it's WatchKit In-Depth Part 2, it's to learn about the new picker object.
So that's existing functionality in WatchKit.
I know a lot of people are interested in the new stuff, and so the animation API that we are introducing in Watch OS 2.
So the animation API allows to you animate certain properties on your interface objects, including the opacity, the width and the height, the alignment, so left to right or top and bottom or center.
The background colors on groups, so the color or the tint on an image like we have here on the slide and the group insets.
So a lot of stuff that you can animate.
If you have used if you have programmed on iOS, the API may look familiar.
It takes a duration and an animation block and all the updates you perform within the animation block will be animated to the given duration.
So we will just jump right in and talk about some examples and hopefully show some techniques, as well as how you can integrate this into your WatchKit apps.
And so our first example will go back to our detailed view in the app and tap into the serving screen.
And we are going to add an animation of where the circles around the ring will fade in sequentially.
So like this.
So in code, we are going to loop through our groups around that outer ring.
So every circle is a group.
We will create a dispatch block and schedule our animation.
And then within the block, we will animate the alpha of the group, and it will fade in.
So it's a very simple, but you get a cool effect.
I also want to point out, we are introducing two new API.
So did appear and will disappear and they are on WKInterfaceController.
These are important for your animation and I will talk about that in a moment, I'll also point out that this is coming in a future seed.
So in this interface, use did appear not will activate if you are beginning an animation on the appearance of a view.
I know a lot of people are using will activate right now to start an animation, so will activate is probably called prior to it appearing on the screen so it's not a good place to start your animation.
So use the did appear method.
Also you can stagger your animations with timers or GCD.
If you do this, keep in mind, that you can only update a controller if it's active and so if you are animating after the controller has deactivated.
So those updates won't actually be applied and so keep that in mind and make sure your controller is active.
Also keep track of the total duration of your animation.
If you are chaining a lot of animations, the duration can quickly add up, and most likely the user is only interacting with your app for a few seconds and if you have a multisecond animation, it's just prolonging your times within your apps.
So be mindful of that.
And also just for convenience, you can set your initial values within the storyboard.
So for example, in this interface, all of the so all the circles are initially hidden and have an alpha zero at the start and we can have that in the storyboard and not have to worry about it at runtime.
So we will move on to our second example, which is our ingredients screen.
And it would be nice if we could have the labels fly in.
So as you can see here.
So we'll break out to our blue boxes.
And so we have our table row.
And as we showed earlier, we have our number background on the left which is the group and we have our label on the right.
And now we are going to add a new group in between the two and we will call it a spacer group.
And groups are really powerful in terms of laying out the content within them but they can also affect the content around them.
So because the content and so because our layout in WatchKit is flow based, a group within the flow can affect the other ones.
So, for example, here, our initial state of the cell has the spacer group to be a full width and what that does is it actually pushes the label off the side of the screen, and so you can use this within so within your apps, both for horizontal flow and vertical flow to kind of affect where things are laying out.
And then as we animate, all we are doing is updating the width of our spacer group and that label then will come in, because we are recalculating how our flow is laying out the interface.
So in code, we are looping through all of our table rows and we are fading in both our number background and label, as well as setting the width on that spacer group and we are wrapping all of this within in a single animation block.
If you can, you can improve your performance by wrapping all of your animations within a single block, however, as I just as I showed, if you are doing a sequential animation that's fine, but just keep in mind that having a lot of animations at once can affect the performance.
So invisible spacer groups are really powerful and you can adjust the width, height or alignment of them and affect your layout.
And one thing to point out is as you animate, we actually relay out your entire interface and this is how I can set the width on that group and the label is actually changing its position, even though I'm not and so I'm not changing it myself.
And so now on to our third example, which is the most complex of the examples.
So we have this screen where you can add a note, and we'll and if you tap on add note, we will have our default on text input on the system.
If you choose an option, we'll insert or we'll animate in a speech bubble.
And so already we have a more complex animation but we're going to go one step further on this.
And if you tap confirm, we will animate in a confirmation screen.
So all of this is within one interface and it's all using the new animation API.
And so we will break this up into two parts.
Let's talk about the speech bubble first.
So back to a wire frame.
Within our interface, we have a group and that's the text container I will call it.
Within that, we have our text bubble which is on top, which has our label in it.
And then below that, we have our confirmation button.
As we animate, our initial state has our text, so bottom and right aligned, as you can see here, the button is faded out.
As we animate, we will update the width of our text bubble and the height and fade both that and the button in, as you can see here.
And so we get that effect and so we get that effect with just a few lines of code.
On the text bubble, we were updating the width, the height and alpha as I said.
And the size to fit height is because we want to fit the full text of that label.
Regardless of how long that note is, it will fit.
We will fade in the button by updating the alpha and we'll update the height of the text container as well to fit everything.
And wrapping all of that within an animation block.
So that's part one of that animation.
Part two is where we show the confirmation screen.
And so back to our blue boxes.
Here we have our text container and I won't include the objects within that here just for clarity.
But also within the interface below, we have another group, which I will call the confirmation container.
And so what we are doing here is we are wrapping the different parts of our interfaces in their own groups, and what that allows us is we can we can collapse or expand them to show different parts of the interface.
For example, here is the initial state of that interface.
So the text container is full height and the confirmation container actually has a height of zero and so it's hidden on screen.
And because of how our new behavior in the set width and set height API, you can actually achieve a higher width -than zero.
As we animate, all we're doing is reversing the heights of these.
So the text container now has a height of zero and the confirmation container now is a full height of the screen.
In code, it's exactly what I said, we are updating the height of the text container, the height of the confirmation container and just wrapping that within an animation block.
And so that's how you get the effect of one part of the interface sort of zooming off screen and the other zooming on.
And so I want to bring out a few notes on animation, just to kind of wrap up and summarize but also just to point out a few gotchas that you might run into.
So any update that affects the sizing of your interface objects can actually can actually animate the layout.
So, for example, if you update the text of a label, that is sized to fit, that label is changing in width or height and so if you do that within an animation block, it will actually animate to the new frame of that label.
However, you won't actually be animating the application of the text, but just the sizing of that label.
If you have lots of animations at once or if you have a complex type of layout within your interface, that can affect the performance, and so definitely keep that in mind, and absolutely make sure to test on hardware.
So the simulator is a great way to build up your animations and to make sure everything is working, but for actual performance and to see how things will actually work, make sure to test on hardware.
That's extremely important.
And also a thing to point out is that this animation is functional within your applications.
However, if you call it within a glance or notification.
So those updates will still be applied but they won't be animated.
And so there's no point in using this within a glance or a notification.
A few more notes.
So use with restraint.
And that's because animation, it should never be the focus of your interface.
If the animation is the reason that your interface exists, it probably is time to rethink that.
And also, again, keep the total duration short.
We so the amount of time within your app is probably on the order of seconds and so don't prolong the animation, like, any more than you have to.
And there's a great session on this, "Designing with Animation", I highly recommend that.
And so to wrap up and conclude the talk, and so layout in WatchKit is different than UIKit and AppKitt where it's specified in design time but very flexible at runtime.
We have a lot of API and we're introducing a lot of new API that allows you to update, to update your UI at one time.
Again, it's flow based and so you are specifying the layout heuristics of your objects but WatchKit itself is doing all the layout.
And groups are really powerful.
And as you have seen, you can build very complex interfaces that you may not have thought were possible within WatchKit.
And now to add animation within your apps.
So animation is a great way to add liveliness or feedback within your apps, I'm sure you all know that.
We have lots of ways already within WatchKit that you can introduce these animations and we are making that even more powerful in Watch OS 2.
So for more information, there's a lot of documentation and technical support, we will also be releasing our sample code as a project on developer.Apple.com.
And for general questions we have our evangelist, Jake Barrens.
So ask him all the questions you may have.
There's tons of related content this week and some are still happening.
I want to point out the Tips and Tricks one tomorrow morning, and the Design Tips and Tricks one tomorrow afternoon.
So both are very good.
So thank you.
Enjoy your time here.
[ Applause ]