Improving Existing Apps with Modern Best Practices
The best way to avoid technical debt is to incrementally build-up technical credit. This session builds on last year's Modernizing Existing Apps with Swift presentation to show you how you can continue modernizing your codebase while adopting best practices and adding new feature work.
[ Music ]
My name's Woody.
I work in software engineering at Apple.
I'd like to welcome you to the conference,
both those of you here in the room and those of you
who are watching from home on the live screen.
There's a couple things I'd like to go through with you
in the next 40 minutes.
We're going to take a look at some ways
that you can today start reducing your technical debt.
Then, we'll take a look at Asset Catalogs.
We'll take a look at the new design pattern.
Well, it's not really new but a design pattern we'd like you
to start using called Dependency Injection.
And in the latter half of the presentation will be an update
from my presentation last year about Live playgrounds.
So, let's get going.
Last year in the session at one of the labs, I met one of you
that was describing a scenario.
You were describing something that I think is going
to be quite familiar to many of you.
You've got your boss that's piling on these requests
for features and the features generate revenue and you want
to get paid, so you do that.
Then you've got your customers and they want
to fix the bugs that cause them pain.
And you take pride in your work so you want to do that too.
Some technical debt that you want to resolve.
Sometimes I think about building software as like building
up a pile of Jenga blocks.
In the first version, it's pretty stable
and you start piling on stuff on top of it
and then it becomes unstable and begins to fall down after that.
So, you want to take a step back and maybe resolve some
of the lower-level issues.
With all these things, you then come to WWDC.
And we pile on new API.
And then we give you a new version of Swift
and it breaks source code compatibility
but introduces some new features.
And you were warned, so don't complain about it.
And then we introduced new platforms
and new extension points and some of our existing apps
like Siri and Messages.
And if you want to think of your role as a developer as a bit
of a run loop with all these events being pushed into it,
well, you know what happens then.
There's too much stuff.
So, what you do?
What are some things that you can start doing
to get ready for the fall?
First of all, if you were to support a deployment target
of iOS 8 and 9, you would cover 95% of the devices.
So, there's really no need any more to have any
of those deployment targets set to 7.
Let's get rid of that.
In fact, the general idea is
that you take the current shipping version, so 9.3,
and then set your deployment target
to one version back, maybe 8.4.
But don't go to 8.3 or 8.2 anymore because then you
and your customers don't benefit from the improvements
that we've implemented under the hood in that 8.4 release.
When we come out with iOS 10 in the fall for the public,
set your deployment target to 9.3.
Next, Issue Navigator.
Resolve those issues.
Take a look at them and fix them.
When we tell you that there's, when we tell you
that there is a deprecated API, we deprecate API for a variety
of reasons, including to achieve better error handling
or to better, have better reporting
or to permit more performance or just add flexibility
in the arguments return values.
And there's really no excuse for not moving on to the new API
because we tell you right there.
It tells you what to use.
So, just use that.
Switch over those.
Next, you might decide that you want to treat warnings as errors
and we've had that for a while with Objective-C.
But now we also have it in Xcode 8 for Swift.
I think I'm the first person to tell you that.
I really like this idea.
I think it's fantastic, treating warnings as errors,
because it forces you and your team to address the issues.
It's far too easy to ignore those yellow warnings
and think you'll come back to them later and then you don't
and that's the whole technical debt thing.
Also, do you think this would work if you pitched maybe a 1.0
of a new project, a new app, and you said your team,
we're going to ship 1.0, but we're not going
to have any art work in it, it's not ready yet.
We're not going to have any icons in it.
We'll ship it and we'll get those caught
up in the .1 release.
That would never fly.
No one's going to do that,
because this is how users interact with your app.
Well, actually it's how many users but not all
of them interact with your app.
Many of them interact with your app using the accessibility
features that we have built into the operating systems.
So, why would ship your app with artwork for people that need it
or want it or use it but then not have the accessibility
features in there?
That's not fair.
So, resolve some of the technical debt
that comes to accessibility.
Add support for that.
We think that the accessibility support is as much a part
of your user interface as the artwork is.
It's built into Xcode,
so you can use Interface Builder for that.
It's also easy to do programmatically.
We have a lot of locale-aware API.
You should be using those and that's less code
that you have to write.
That generally isn't new, though.
But these are.
Dimensions and measurement formatting.
If you've got recipe apps or fitness apps
and you've been converting between metric and imperial
on your own code, get rid of that code
and use our code that's now available in the fall release.
We'd like you to support Peek, Pop and Quick actions.
Right? You know, if you have iPhone 6S right now,
you're probably accustomed to let's push hard on this
and see if it does anything.
So, let's push hard on that and see if it does anything.
Well, that one does something.
It's hard to discover.
How awesome would it be if every app
that we had already had support for Peek,
Pop and Push and the 3-D touch?
Next. We want you to run the Swift migrator using the
developer preview of Xcode 8.
If it doesn't migrate your code, file a bug report and tell us.
Then we have an opportunity to fix that so
that when you run migrator for real in the fall
on the final release of Xcode 8, it'll likely work.
If it doesn't work, we resolve some
of our own technical debt with our API.
The public interface is the same
but the implementation might change between releases.
And it could be that the way you use whatever API is a way
that we didn't expect.
So, you have an edge case.
So, tell us about that too.
This is why we do these preview releases.
We want you to file bug reports.
The report isn't just banter in the hallway.
And it's not just writing on a dev forum
and is not just sending an email to Apple.
The proper way to file a bug report is this.
Fill in the blanks.
And then once you do that, you get a number
and then that's the number that you go
and post back into the dev forums.
There's a lot of Apple engineers that read the dev forums.
We like to hear what you are saying but when you file,
or when you report an issue in those groups
and you don't include the bug report number, it's difficult
for us internally to kind of follow through
and get traction on it.
So, do that.
Next, you're probably not a great fan
of filing bug reports with us.
Because you've filed them and then you wait
and nothing seems to happen.
You get nothing back.
In the words of Whitney Houston, where lonely bug reports go?
Well, they seem to go in this great big black hole
because you never get anything back.
Or you think maybe you get it in front of an engineer
and the engineer kind of ignores what you're saying.
I want to just assure you, this is not the case.
One of the first things I did
when I joined Apple is I did a search
in the bug tracking system, the issue tracking system,
to find all the issues that I filed as an external developer.
And a lot of them had traction.
I just didn't have any feedback from them.
So, I wanted to show you that if you do file a bug,
we do look at them.
The other thing that I find is you might spend a lot
of time filing a bug.
A lot of time maybe you're making some sample code.
You're trying a couple different devices.
You're checking for regression.
Like, that's great stuff to do.
It takes you maybe half an hour or maybe an hour,
and then you file it and it comes back
and it says it's a dupe.
And you're like, oh, I just wasted half an hour,
hour my life for something that somebody else already did.
So, I just want to briefly talk about dupes.
Because dupes first of all are not a voting system.
It's not like if you copy somebody else's bug verbatim
and you file a new bug with exactly the same thing and it,
we're going to say oh, there's another vote for that,
we should fix that first.
It's not quite like that.
What we do do with them though, even though they're a dupe,
it just means these two are related.
We need dupes too, not for voting purposes
but because we can have five people file the same root bug,
same root issue.
The first four don't give us enough information
to be able to find it.
But maybe it's your report, maybe it's your fifth one which,
albeit, is a dupe, but has the information in it
that we need to find the issue.
In fact, an Apple engineer that's been with the company
for gosh, a couple decades, made a quip a couple weeks ago.
Paul had said that each bug report as its host link.
So, in summary, things to get going today.
Fix your warnings.
Replace your deprecated API.
Localize your App if it hasn't already.
Or support accessibility rather.
Get Peek and Pop in there.
And then take a look at the next release of Xcode
and give us some feedback on that.
With that, let's move onto Asset Catalogs.
It's time to move onto Asset Catalogs.
For you. So, if you still have files in your File Navigator
like this, go ahead and get them into an Asset Catalog like that.
The approach is you add a catalog from the File menu.
By the way, you don't have to add just one catalog.
You can have as many catalogs as you like in the project.
Maybe you're working on an app and that app is a card game.
So, you have a catalog for the front images of the cards
and a second catalog for the back images of the cards.
We can do that.
Another example, you might have your graphic designer download
Xcode for free from the App Store
and create the Asset Catalog for you.
And then send it to you or check it into a code repository.
We could do that too.
When we copy files into an Asset Catalog, they are exactly that.
They are copies.
We don't reference the original location ever.
So, they do participate in version control.
To migrate your project, you tap the big plus button.
Choose Import From Project.
We're going to give you a list of all the Assets
that are eligible to be migrated from the File Navigator
into the Asset Catalog.
There you go.
You migrated your image Assets.
If you previously used to use the bundle API,
pathForResource etc., that doesn't work anymore
because once we compile the app,
the images aren't free-floating around anymore.
So you won't find anything.
But if you're using image named, then we'll find them if they're
in an Asset Catalog and we'll find them
if they're just free-floating in your File Navigator still.
This API has been around for a while.
This API Image Name has a lot of advantages above
and beyond just being able to find content
from an image catalog.
For example, it internally caches the first time
that you asked for an image by name.
We load it up, we give you back a reference.
The second time you ask for the same image by name,
we just return another reference to the same thing.
That's not how the contents of file API works.
Contents of file loads new images every time.
So, it's much more perform an, especially when you're scrolling
through a table, to use image names.
We support multiple representations of an asset.
So, with image names, you'd give it a name, the API name.
You'd let the framework consider the device
that you're running on.
Consider the resolution of the display, retina, non-retina,
retina on a Plus device.
And possibly other differentiating factors
for the media assets such as the amount of memory in the device
or the version of Metal that is supported in the device.
And you get an image back.
There's two broad kinds of asset types for images
that you can consider.
We have scaled images.
Those are like PNG's, JPEG's.
And then we have single vector images
or just vector-based images, like PDFs or SPG's.
We treat them a little bit differently.
On an asset by asset basis, you can say this asset's going
to be vector-based and that asset's going to be scale.
You do that by specifying the scale factor.
So, I'm going to talk first about some of the scaled images
and then move onto the vector-based images.
For scaled images, in this example we're asking you
to provide three different renderings of the artwork
from 1X devices, non-retina, 2X and 3X.
If we don't find, because you didn't include it,
the 2X and 3X, we'll take the 1X image and we'll scale it up so
that it becomes, well, an image that's used
on the higher end devices or the higher density devices.
Likewise, if you just provided us with 3X artwork,
we'd scale it down at runtime.
These two scenarios aren't that great.
In this scenario, if I take the 1X image and I scale it
up for an iPhone 6S Plus, it's going to look really jaggy.
It's going to have a visual artifact called aliasing.
It's not very desirable and the, well,
your customers aren't going to enjoy it.
This one, at first glance you might think oh,
I'll just provide the 3X artwork and let you scale it down.
But there's a huge problem with that because to scale
down a 3X image, we have to open a 3X image.
And it's really big.
And then we take an extraction of the pixels
and create a scaled-down version for it.
It's an order, two orders of magnitude, possibly larger.
So, consider this.
You're on a device like a 5S, 5C.
There's no need for 3X images on it
but you only provide 3X images.
So, we start off with the baseline of memory in use.
That's fine, but then we have to load up the 3X image,
down convert it, get rid of the 3X image
and keep this scaled-down version of it,
which just temporarily creates a memory spike.
And if you're good, it's probably going to be fine.
But if you good, I mean
if you're lucky it's going to be fine.
But let's say you're scrolling at a table view
and as you're scrolling, we have to do this a lot.
All of a sudden the memory utilization
for your app balloons and then what happens
when you use too much memory?
Anybody know, just say it out loud.
We terminate it.
It's terminated because you didn't provide the artwork.
So, provide the artwork.
In fact, you can do this really easily with the task or,
not a task but with an Automator workflow.
You give it a 3X image, let it scale it down, give it a name.
Scale it down, give it a name.
Great, throw those all into your Asset Catalog.
And if you wanted to, you could,
and this is another best practice.
Use this naming convention,
the non-retina ones is just a line justify.PNG.
And then add 2X and add 3X the other two.
And then when you drag and drop the three of them
into an Asset Catalog, we detect a naming convention.
We create one asset with the three different representations
as opposed to three separate assets.
For Vector Assets, Vector Assets are amazing
because the file contains a set of instructions on how
to draw the image as opposed to having it pre-rasturized.
This is the same image, not three different versions of it.
Same image scaled to different sizes.
When you specify that you're going
to use single vector scaling,
you're providing one vector-based image.
And at build time, we rasterize it
into the different sizes that we need.
It's much simpler for you.
This is the kind of thing you'd likely do for toolbar images
and navigation bar images.
There you go, scale factor single vector.
It's also possible to combine both.
You can set the scale factor to be vector and scales.
And what you do is you provide us a vector-based image,
like the limits in the well called All.
Then, if you want to do any over-rides
for the other scale factors, you just provide those
as either other vector images or PNG's, JPEG scaled images.
Then when we build it, if we see that you're missing some assets,
we'll just rasterize those based on the vector image that's
in the All truck well or target.
Otherwise, we'll use the scaled ones that are provided.
New in Xcode 8, you can adjust the compression.
Come on, you.
You can adjust the compression.
So, we can say for a JPEG image you want
to use glossy compression but maybe
for a PNG we don't want any compression all
because it's going to be an item in the toolbar.
We don't want to see any artifacts with that.
Another issue you can resolve by using Asset Catalogs,
just in case the pitch isn't strong enough yet,
is to fix the issue or resolve some issues.
We're referring to rounded corners.
The issue with rounded corners is we have adaptive UI.
So, your button might contain a text label
that fits perfectly well until the app is run
in a different language and then it changes the size.
And when we change the size of the button, we still want
to preserve the beautiful rounded corners on it.
The way you would even get a rounded button
in the first place is
in Interface Builder you'd select a button
and specify a background image.
And then we apply that as the background.
In this example, I have an asset called rounded rectangle
and I apply that as my background rectangle
but you can see in the two sizes of the button,
when it's stretched out it's really horrible looking.
So, one way you can fix this is the way
that you've been fixing it all along
which is using Stretchable Image.
And you say well, I want to preserve X number
of pixels on both sides.
And that's fine but one of the themes
of this presentation is having you write less code
and rely more on the frameworks and the tools that we provide.
So, instead this is built into the Asset Editor.
It's called Asset Slicer.
And you can use it to specify that, the portions
in red, don't stretch these.
Don't distort them.
The portion that's illuminated, repeat these pixels.
And by doing that, you get perfect rounded corners
and you don't have to do any code for it.
This is also something you could have your graphic designer do
because it's part of an Asset Catalog.
All right, next up, let's talk
about a design pattern called Dependency Injection.
Let's first of all talk about what we're kind of fixing
or trying to change by introducing this.
So, we have UITextField and it works with the delegate
and the delegate is called UITextField delegate.
Fine. And in it, it contains methods that relate
to text fields, like textFieldShouldBeginEditing.
Okay? We have WCSession.
And WCSession delegate.
What's inside the delegate?
Methods that relate to WCSession.
You can see the pattern here.
Which we break with this one.
We have the app delegate and of course it has methods
that relate to the application object.
But then we stick other stuff in there too like databases
and essentially eventually you have everything
in there including the kitchen sink just piling up.
And we do it because it's really easy to reach back
through the Application object, get the shared object,
cast it and retrieve it.
But it's such a strong coupling between your view controllers
and your app delegate and your app delegate doesn't really need
to be the place where that goes.
Instead of a pattern like this
where each view controller reaches back
to some common object, maybe that you're storing
in your app delegate, you could switch it.
Using Dependency Injection, you take the model object
that one view controller has and you pass it forward
to the next view controller at the time
that view controller is presented.
The idea is you give the view control everything
that it needs to do its job.
So, for mail, you'd have a list of mailboxes.
A view controller that shows a list
of mailboxes would have a model object representing an array
You tap on one of the mailboxes.
Another view controller's going to present
to show the messages in that mailbox.
The middle one.
And you pass forward the one mailbox and it shows those
and you keep passing it forward.
The way you would do that in a segue is to override prepare
for segue and pass it for repair.
The way you would do it
if you were programmatically presenting is in the action
for the button viewed in instantiate.
The view controller, pass model object there.
The benefit of this technique is your view controllers are
They don't have these strong ties and dependencies.
You can reuse them more.
Coming back, you've got some choices.
You can do what we do, which is often to write a protocol
and have you implement it so that there's a call back
to say the view controller's dismissed
and maybe update your model there.
You can pass in a closure.
You can pass object models by reference.
Or, you can, if you're using online segues,
it's exactly the same as overwriting prepare for segue
because it happens in both ways,
forward directions and back directions.
Another reason why we like this is
because view controllers are pretty much like Lego blocks.
They should be able to be rearranged independently
of each other to create new structures.
In this case, an iPhone SE's overall user experience is going
to be different for your app than it would be
on an iPad 12 inch, iPadPro 12 inch.
If your view controllers are independent,
it's easy to do that.
Now, last year at WWDC, I gave a presentation
on topics including playgrounds and modernizing the UI.
And interoperability with Objective-C and Swift.
And since then, we've added things to the playgrounds,
things that we couldn't do last year.
So, I want to show you some of those things.
With that, we're going to move to a demo.
So, this is the periodic table of elements app
that I showed you last year.
Since then, it's been updated a little bit.
For example, the colors have changed a bit.
Instead of using states of matter, gas, liquid,
now it's the kind of object like a transition metal, metal,
halogen, that kind of thing.
We've also added support to render it
out so it actually looks like a periodic table of elements
and that's a collection view doing that.
I want to show you some of the code.
One of the things that changed with this code base
since the last time that you saw it is
that the data model has moved over to Swift.
So now I have a class the represents an atomic element
like nitrogen or oxygen.
And I have a class that represents a collection
of those called, excuse me, periodic elements.
This class instantiates the elements,
the individual atomic elements, by reading in a property list.
Which I've also stored in my app here.
But now that I've moved the data model to Swift,
I can show you the data model in a playground.
Last year when I did this, I'm just going
to make a new playground first.
New file, playground.
Last year when I did this, I was copying,
pasting between my code and my playground.
And when you do this a lot, your playground can become really big
with a lot of code in it.
So, instead our playgrounds now have subfolders
for source code and resources.
I can take my model objects and put them
into the sources folder.
And then we compile them behind the scenes
and we implicitly import all of their public symbols
into the playground to make them available for you.
Which means if I take this resource file, my property list,
and include that in the resources folder
which makes it accessible to the playground,
I can come over to the playground here
and instantiate an instance of the atomic elements.
So, the ability to take these source files
and have them automatically imported into your project
when they're in the sources folder
or the resources folder is a pretty, I think powerful way
to keep your playgrounds themselves small
and concise while also having more content inside of them.
Now, above and beyond that, something else happened
since last year that affects this app in particular.
The International Union of Pure
and Applied Chemistry confirmed the discovery
of 4 additional elements.
So, now all of a sudden this Property List file
that I've got embedded inside my app doesn't seem
like such good idea anymore.
So, I thought well, we can fix that.
We'll just put it online.
So I set up a little Web server and put it a JSON file
at this time with all the elements.
And then the idea is that when my app launches,
initially it'll use the embedded built-in data file.
That way if there's no network connectivity,
there's still something for the customer to see.
And this is also a pattern we recommend that you do.
Then we'll do a background check,
and in the background we'll check to see
if there's an update to the file.
And if so, we'll put it in the caches folder
and we'll reference that inside the app.
So, I want to do the network request in the playground.
And for that, we have the class formerly known as NSURL Session
and now known as URL Session.
Which is shown here.
We grabbed the Session and the thing I like about the Session
when I call it out on line 24,
is I give it a completion handler.
Because this runs asynchronously in the background.
Go to the Network Request, let my code continue to run.
When the request either times out or the data comes in,
it gives us call back.
And in the call back on line 28,
you can see I'm printing out one element here.
But you actually don't see it over there.
That's a problem.
So, just think about what's happening here.
We have a playground that's running.
We asked it to do a background operation.
At some indeterminate time in the future,
we get a call back saying this is what we want to do.
But playgrounds typically execute like a script.
The first line all the way
through to the end and they stop.
Key word being typically.
But they don't have to.
We can get a runloop and have asynchronous operations
in our playgrounds.
To do that we import a new module called
And then you ask the playground for indefinite execution.
And then it never stops, it just keeps processing.
So, now if I scroll down to this section,
you can see I'm getting my callback.
Next I want to expand on last year.
Last I showed a drawing,
a rounded corner core graphics image.
This time I don't just want to draw an image and preview it.
I want to do a whole table view controller in the playground.
And we have some new features
in the playgrounds that let us do this.
Let me just open up some space in my app.
Call it View.
So, I'm going to make a subclass of UI Table View Controller.
This is all that it is, a selected area.
I'm implementing the 2 methods at every Table View
or Table View Controller are required to have.
Number of objects, sorry, rather number of items,
self-wrote index that and returnings themselves.
And I'd like to see that live.
So, this time what we do is we instantiate the view controller
and then using that playground support module
that I've already imported, the one that's right here on line 4,
I grab the playground's Live View.
And then we take that view and we render it
into the assistant editor who's no longer an Assistant Editor
but rather our Live View.
See? It's a Live Table View.
It scrolls, it's active.
Is not just a static image.
It's a real interactive table view.
If you wanted to try some of our new delegate methods
or even the existent delegate methods, you could throw them
into the playground and now interact with them.
No need to put this into the simulator anymore.
So, now I want to take the two things and put them together.
Specifically, I have this background network request
And after I retrieve the data, I want to reload my table view.
So, let me show you that.
First of all, comment this.
And just to prove to you that it's totally loading the data
from that site, I'm going to change this
so I have an empty array of elements.
So, I have an empty array of elements.
I do the network request.
When the network request comes back
and it's call back, it's a sign.
The new array of elements that I just pulled in.
And then we reload the Table View.
Is that what you would expect?
It's not what I expected the first time that I saw that.
Oh, that must be a bug in Xcode.
I'll just go file a bug report at Apple.com.
But it's actually not.
It's a bug in my code, believe it or not.
It's a bug in my code and here's what's happening.
We have this background operation coming in.
The background operation has a background operation.
It's not on the main thread.
UI is in the main thread.
So, in my closure, the completion handler,
I can't be updating the main thread from there,
which is what I'm trying to do when I reload my Table View.
When I tap on the cells, we are invalidating them
and that's why they do refresh
and you can see them at that time.
This is also something we can fix.
We have something new in Swift 3, which is a Swift API
for Grand Central Dispatch.
So, instead of that C API we had before,
I can now say a dispatch cue, which cues the main cue.
What kind of operation?
An asynchronous one.
We don't need that anymore.
And here's what I wanted to do on the main thread.
Now it bounces back to the main thread and it's there.
Dispatch cue, thank you, dispatch cue in Swift,
Live Views in Swift and indefinite execution.
Let's switch back to the slides.
So, just as a point of summary, there's the key points
from the previous presentation, or the preset demonstration.
Some other tips.
When I dragged those files into the sources folder
or the resources folder, they are copied.
They are not referenced.
Also, only methods that are marked as public, methods,
properties, and datatypes, etc that are marked
as public are going to be available
to you in the playground.
So, you might have to go add some public specifiers
because the default visibility specifier
in Swift is internal, not public.
I mentioned using a caches folder.
Now, we still have some devices out there
that are constrained storage wise.
And those customers try to updates to new versions
of the operating system and they find
out there's not enough space.
So, what we do when that happens, when the device is
under storage pressure, is we go through the caches folders
and we delete everything we can find
in all the applications caches folders.
This way we free up space.
We need your help with this.
If you are temporarily downloading data
and you're not putting into caches folder,
put it in the caches folder.
Next. I thought it'd be really neat to take,
it's doing that again.
To take the view controllers that use dependency injection
and rearrange them in a way that might suit a different platform.
So, I'm going switch back to the demo.
Turn on my Bluetooth.
And take the view controllers as is over the tvOS.
Well, there we go.
I have paired my Siri remote to my Mac
so I can use it with the simulator.
It's exactly the same code from before, same view,
same table view, all that stuff is there.
I can go over and I can see the periodic table like this.
And I get that neat TV effect when I slide over the cells
and its collection view.
It's the same layout code.
It's the same view controller.
It's all the same as what I had before.
I added this line of code so that
when I selected a collection view cell,
you would get that focused item.
And I used the OS Compiler directive
to say this is only for tvOS.
Other than that, it's the same code.
Which kind of brings me to another point
that if you ever think you are taking an app from one platform
and just dropping it on another platform
with no real other changes,
you're possibly doing something wrong and might want to check
out the human interface guidelines just
to see how you can make that platform and that app
on that platform more native and feel more like it belongs there.
Let me give you an example.
Let's say that you made an app that does some sort
of like cloud-based accounting.
And maybe you have an iPad version and on the iPad version,
people fill out invoices.
It makes no sense, in my opinion,
to fill out invoices on a 60-inch TV.
You just wouldn't do it.
Why would you do that?
This is not an app you would just take as is
and launch it on tvOS.
But, the iPad version of your app probably has some great
visualization about how the company is performing.
Maybe you've got some graphs or charts.
Those would be awesome on a TV.
You could have a version of your app that has a Live Status board
of how the company's doing.
And maybe in the company's boardroom or something it's just
on and it just animates through and shows an update.
It's the same data model.
It's the same data access.
But you pivoted that data in a way
that makes it more applicable for the platform
in which it's running.
And that's an idea I want you to think about.
How can we take the same data that we have and just pivot it
in a way that works for that platform?
In summary, modernizing your app is an ongoing process.
We want you to rely on the frameworks as much as you can.
If you can get rid of code that's in your app
and instead use code that we've got in our frameworks,
that's code let's code for you to maintain.
We'd like you to do that.
We want you to get started today with finding issues with Xcode,
with the Swift converter.
We want you to architect your app
with fewer inter-object dependencies so it's easier
for you to rearrange your app.
Finally, we want you to consider bringing your app
to other platforms of ours [laughter]
by pivoting your data model.
There are some related sessions you can check out.
There you go.
And other than that, you can check out our link
for this particular session.
And that is it for me.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.