(soft music)
- [Woman] Stanford University.
- [Instructor] Welcome everyone
to the very last lecture,
lecture number 14 of Stanford CS193p
during Spring quarter of 2020.
For all of you it's all final project
all the rest of the way.
However, I do want to talk about
one last feature in SwiftUI
which is the integration with UIKit.
Remember that UIKit is the old way
of developing applications for iOS.
And when SwiftUI came
out a few months ago,
it does pretty much
everything that UIKit does
but there's a few UIKit features in there
that aren't in SwiftUI,
and you'd like to be able to use them,
and so this API makes it
really easy to do that.
Also you might be an existing
developer of an iOS app
and you've got a bunch of UIKit code,
of course you wanna use that
as you transition over to SwiftUI,
and this lets you do that as well.
Now for me to talk about this feature,
I have to explain a little
bit about how UIKit works.
I'm not gonna teach you any UIKit itself,
but conceptually it's important
to understand some basics about it.
One thing is there's no MVVM in UIKit,
instead it's something called
MVC, Model-View-Controller.
And in the Model-View-Controller
architecture,
Views are kind of grouped together
and controlled by something
called a Controller.
This Controller is sort of the granularity
at which you present Views on screen.
And by presenting I
mean like SwiftUI sheet
or popover or the destination
of a NavigationLink.
Those are presenting Views on screen.
Well, in SwiftUI there's no difference.
We don't have any Controllers,
so Views are just Views,
and we present them whenever we want.
It's quite nice.
But in UIKit, it's different
when you're putting Views on screen.
You have to essentially
present a Controller,
and the Controller controls Views.
Now because of this,
because you've got this Controller thing,
the integration between SwiftUI and UIKit
really needs two integration points.
Now these two integration
points are very similar,
but one is a UIViewRepresentable,
that's a SwiftUI View that
represents a UIKit View,
and then there's also
UIViewControllerRepresentable,
a SwiftUI View that represents
one of these Controllers
and all the Views that
that Controller controls.
We can see these things look the same
but it's just necessary to have both
because of UIKit's underlying
dichotomy between their Views
like Buttons and
TextFields, those are Views,
and their ViewControllers
which are these groupings of Views.
And the main work that's involved
in implementing either of
these two Representables
is dealing with setting of the vars
to configure the View
or the ViewController.
And especially with the ViewControllers,
they often want to call you back
and say this happened in my ViewController
and what should I do about
it and things like that.
I say here, quote, "callback
functions" on the slide,
but it's not really
callback functions per se.
And I'm gonna dive into
that a little bit more now
so you can understand
how this mechanism of a View is on screen
or a ViewController is one
thing and things are happening,
how do we interact with
it, works in UIKit.
The main concept there
that you need to understand
is something called delegation.
Remember UIKit is object-oriented?
Not functional programming,
it's object-oriented.
So it's very different.
It's not declarative, it's not reactive,
none of those things.
And it uses heavily a
concept called delegation.
We briefly mentioned this when
we talked about FileManager.
FileManager has a delegate.
But these objects, these
Controllers and their Views,
have a delegate that
they literally delegate
functionality to.
Some of the functionalities
of Views and ViewController,
they offer to allow some
other object to control that.
Now they do this by having
a var in themselves.
The Views or ViewControllers
have a var in themselves
called a delegate.
And this delegate var its
type is some protocol.
Now we know protocols well of course
as functional programmers.
In object-oriented programming,
protocols are used not as much,
and the protocols are slightly different
in that the methods and
vars in the protocol,
some of them can be optional.
Not optional like the enum Optional,
but optional like you don't
have to implement them.
You're gonna see when
we do our demos today,
we're going to do a couple of
different delegate protocols
and we're only gonna implement
one or two methods in each one
even though there might be
10 or 15 methods available.
You might not need to use all of them.
This delegate var has this object
which can be pretty much anything
as long as it implements this protocol.
And this protocol has a
lot of things in there
like I will do this and should I do this
and I just did do this.
Those kinds of notifications are happening
between the View or
ViewController and this delegate.
So when we do our integration
between SwiftUI and UIKit,
we have to somehow provide this delegate
so that this View or
ViewController can function
'cause a lot of times
they need that delegate
to do anything of any substance.
So I decided in the two
demos that I'm doing today,
I'm gonna do one ViewRepresentable demo
and one ViewControllerRepresentable.
Both of them are gonna have a delegate
so you can see how that integrates.
So let's talk about these
Representables, these two
SwiftUI Views, UIViewRepresentable and
UIViewControllerRepresentable,
they're super similar
and they both have these
five main components.
The first one is just
a function that makes,
creates the underlying
View or ViewController,
the so-called makeUIView
or makeUIViewController.
And you go pass this context
which I'll talk about in a minute,
and you just return the
View or the ViewController
that is Representable,
is basically representing
with some SwiftUI Views.
So that's obvious we need that.
Another obvious thing we need
is something to get this UIKit
to participate in the reactive
nature of SwiftUI, right?
When a Binding changes or
ObservableObject changes
or some internal state
changes, something like that,
we do some Intent, changes our Model,
it ripples back to us
and we redraw in the UI,
we are redrawing our Views,
updating them all the time in SwiftUI.
This doesn't really exist in UIKit.
So in your Representable
you have to implement
a function called updateUIView
or updateUIViewController,
and it will pass the ViewController
that you created with makeUIView
back to you along again with this context
which I'll talk about in a second,
and you now update that UIView.
So this is getting called
whenever your SwiftUI View
is part of an update pass,
right, it's being updated,
and so you have to able
to update the View.
You might set vars,
you might pass values, whatever, in there,
you can have Binding.
I mean UIViewRepresentable
and UIViewControllerRepresentable,
they are SwiftUI Views,
of course they can have @Bindings
and maybe you're getting some
values out of these things
that are bound back to
your other SwiftUI Views
and you're setting it in your UIKit View.
So that's what's happening here.
Now this third thing is a
function called makeCoordinator
that's gonna return what's
called a Coordinator.
Now that Coordinator is a don't care.
It can be anything you want, right?
It has no requirements on it.
I'm not even sure it has any
constrains and gains on it.
The main thing about this
Coordinator though is
it's something that's gonna be passed
back to you via that context above.
Alright, so this Coordinator is the thing
that's gonna serve as the delegate
of our UIView or
UIViewController if it needs one.
And it's really nothing
more than a collection
of the functionality that
you would need in a delegate
to make your View or ViewController work.
Really the whole UIView
and UIViewControllerRepresentable system
doesn't even care what this thing is.
It just wants to give you an opportunity
to have some object that
is being the delegate.
Now this context I keep
talking about up here,
this pass to the makeUIView
and updateUIView,
it has three things in it.
One is the Coordinator you created,
the thing that's gonna be the delegate
for your View or ViewController.
It also includes your
SwiftUI's environment
like are you in dark
mode, all those things.
And it contains a transaction.
So this is an animation transaction.
I told you earlier in the quarter
we're not really gonna
talk about transactions,
but it's essentially encapsulating
what sort of animation
environment is going on
while the makeUIView or
updateUIView is happening.
So what this context is.
Mostly out of the context,
you're gonna be grabbing environment stuff
or you're gonna get your Coordinator
so you can talk to the delegate
basically of your View and ViewController.
I don't wanna make it sound like
your Coordinator can only be the delegate,
but that's the primary,
probably the primary thing
you'll use the Coordinator for.
And then finally there
is a tear down phase
when your SwiftUI View is going away.
Of course you wanna be able to clean up
your ViewController or your
View it just passed back to you
along with your coordinator,
your delegate or whatever
so you could clean that
up as well if you have to.
You can do whatever is necessary there.
That's it.
Pretty much that's how you make it,
you update when the passes happen through,
you've got a little Coordinator,
you can have your delegate,
and then you can dismantle
it when things are all done.
So the demos I'm gonna
do today, the two demos,
one, we're gonna go back to Enroute,
and in the FilterFlights,
we choose the destination
airport right now from a Picker,
and we're gonna make it so we can choose
that destination airport from a map,
an actual map, or an
Apple Maps-like thing.
Right in our UI we can
pick the airport that way.
And that's going to be
essentially a UIViewRepresentable
because the map, it's just gonna be
a UIKit map called an MKMapView.
It's actually not even in UIKit.
It's in something called MapKit
which is really part of
the whole UIKit ecosystem.
And then the second feature we're gonna do
is we're gonna go into EmojiArt
and make it so that the background image
instead of being dragged
in or copy and paste,
we're gonna add another
way to do it which is
you can take a picture with your camera
or just get a photo out
of your photo library
and use that as the background.
Now this is going to use a
UIViewControllerRepresentable
because getting a picture from the camera
involves putting up one
of these MVC things,
these Controllers that
controls all the Views
that are in the UI that gets
a picture from the camera
and same thing with the photo library.
So this is a little slightly
different kind of integration,
this ViewController integration.
Now I'm just gonna give a caveat
before we start this demo.
I'll probably mention it
again in the demo itself.
This is not an awesomely implemented
feature for us in EmojiArt
because we take the picture
from the camera or the photo library
and we just drop it into our filesystem
and store it as a URL in our EmojiArt
like we've been doing so far.
That's actually not
really appropriate here.
You would probably wanna
put the image itself,
the actual JPEG or whatever image data
into your EmojiArt Model, right?
Store it there so that if you took
that EmojiArt document someday
and you moved it to another device,
it would actually work,
whereas really the solution we have
where we just drop it in the filesystem
and grab a URL to it,
it's obviously pointing to something
that's in the sandbox of
this app, that's kinda bogus.
But we're not worried about that,
we're not worried about the background.
We're worried about how
do we put the UIKit UI,
the Controller that gets
images from the camera
or the photo library,
how do we get that to work in our SwiftUI
so we can get the image out of it.
So that's it.
So let's dive right in.
Enjoy the very last demo of the year.
The first of the two demos
here is going to be showing
how to integrate a UIKit
View into a SwiftUI View.
And the UIKit View we're
gonna do is MapView.
Actually it's in its own
little framework called MapKit,
but it is a UIKit-style View.
And what I essentially wanna
do is here in my FilterFlights
where we're choosing our destination,
origin, and our airline, et cetera,
for the destination,
I'm still gonna allow you
to pick your destination via Picker,
but I also want you to be
able to pick it via a MapView
that we create by using the UIKit MapView.
Let's put this in its own Section here.
And we'll put the other
things in their own Section.
I could imagine that quite easily
wanting to use this map
also for the origin later.
We're gonna focus on just doing it
for the destination for this demo.
So what is this MapView thing right here?
It's going to be one of these
UIViewRepresentables we talked about.
Let's go create that.
File, New, File.
It is a SwiftUI View
but since it's going to
be a UIViewRepresentable,
it's not going to have var body,
that's gonna be implemented
by UIViewRepresentable for us.
Let's go here.
I'm gonna call this my MapView.
And when we're doing our MapView,
we're going to have to import
more than just SwiftUI,
we're gonna have to also import UIKit
and also import MapKit.
A lot of imports to do right there.
This struct called MapView,
it is a View but it's
even more than a View,
it's a UIViewRepresentable.
This is a different protocol right here
that inherits from the View protocol,
but it has a bunch of other stuff in it
that allows us to do this
integration with UIKit obviously.
And we see that we do not conform
to the protocol UIViewRepresentable
'cause we have to put those functions
that I was showing you in the slides
like func makeUIView
given a certain context
that returns the UIKit
View that we want to do.
And what are we doing here?
We're doing MKMapView.
This is the UIKit View
we're trying to build.
In fact I'm gonna build it here.
Okay, that View equals MKMapView.
And then I'm gonna return the mkMapView.
I'll be doing a little more
to create it here in a second.
And we also have that func updateUIView
which gives us the UIView back,
this MKMapView and also that context.
And inside here we can
do whatever we need to do
to keep this MapView up to date
as our SwiftUI goes through
its normal reactive redrawing mechanisms.
And then we have this func makeCoordinator
which returns something
that is a don't care
which I'm gonna call Coordinator,
this type right here.
I'm gonna make that a little nested class
inside my struct here called Coordinator.
And it's going to be
my MapView's delegate.
This MapView has one of those things
we were talking about, a delegate.
So to be a delegate, it
has to also be an NSObject,
and it has to implement the
MKMapViewDelegate protocol.
All the delegates in
UIKit always are classes
that inherit from this
base class NSObject.
So I'm gonna do the same thing here
in our SwiftUI compatibility
as it's done in normal UIKit,
make a delegate that
inherits from NSObject
and implements these methods.
Now it turns out none of the
methods in here are required,
but we're actually going to need
a couple of them to make this work,
but that's why I'm not
getting any complaints here
that this protocol is not
implemented or something like that
because it has 10 or 12 methods
all of which are not required.
So how are we gonna make our Coordinator?
I'm just gonna literally say
make me one of these Coordinators
that I just defined down here.
This is just a class.
I'm just making a class
and I'm returning it here.
You don't have to say return
but that's what I'm doing.
This is the basic structure
of a UIViewRepresentable right here.
The only thing is we wanna
hook up our Coordinator
which is this delegate to our MapView.
We're gonna do that right
here by saying mkMapView,
your delegate is our Coordinator.
And we get the Coordinator
out of our context.
This is not where we
create the Coordinator.
It gets created down here inside this.
So if we wanted, we just
say context.coordinator.
Let's go back to our FilterFlights.
And if we build actually, this succeeds.
It's able to build because we
made a thing called MapView.
This is a normal SwiftUI View
called MapView right here.
And if we run,
go to our Filter,
there it is,
there's our map right there.
You can't see much.
It's still too small.
So why don't we make this a lot bigger?
Maybe have it be, I mean at least
maybe three or 400 points high.
The width is fine but you wanna make sure
that it's at least three
or 400 points high.
So I'm gonna go down here and
say .frame(minHeight: 400).
Let's see what that looks like.
Go to Filter.
Whoa!
That looks a lot better
when you can see it.
And there is our map.
Now of course none of these airports
that we have here are
showing up on the map,
no kind of markers or anything for it.
That's because we don't do
anything to put them on there.
So let's learn a little bit about MapKit.
How does one put little markings on here
to represent these airports?
Well, MapKit is actually pretty flexible
and it uses protocols.
It has a protocol called MKAnnotation.
And any object that
implements MKAnnotation
can be put on a map.
So all you need to do to
put an object on the map
is implement MKAnnotation for that object.
So what kind of objects do we
want on our map right here?
Well, we want Airports.
So here's our Airport.
This is our Core Data object
that does the Airport.
I'm just going to add an extension to it
that implements this
MKAnnotation protocol.
And we need to import MapKit here
because MKAnnotation is inside MapKit.
Now what is required of
this MKAnnotation protocol?
Let's go ahead and even
use our "Fix it" here.
We can see the type
Airport does not confirm.
But we can add protocol stubs
that will add the required
methods here, "Fix".
There it is.
Just this one var.
If you implement just this one var,
then you too can be an MKAnnotation.
And what is this var coordinate?
What's its type?
What's it returning?
It's a CLLocationCoordinate2D.
This is latitude and longitude.
And luckily, Airports, we look
at our data model over here.
Let's do the version here.
You can see we have a
latitude and longitude
that we got from FlightAware.
So we are absolutely ready
over here to be one of these.
I'm just going to return,
creating a CLLocationCoordinate2D
from a latitude which is our latitude,
this is just a var in the database,
and longitude, this is our other var.
That's it.
Look, it compiles and allows
us to be an MKAnnotation.
Now MKAnnotation,
again it's Objective-C
version of a protocol.
This is the only required var
but it has a couple other vars
that I'm gonna throw in here.
And one of these vars is called title.
It's an Optional String.
And it's something that
if you click on something in the map,
it'll show you this title.
So I'm gonna have this be
the name of the Airport.
If that's not set, we'll
use the KSFO or whatever.
And then it also has
another one called subtitle
which it'll also show in the call-out
that comes if you click on something.
And then we'll put the location
like San Jose, California
or whatever in there.
So Airports are now MKAnnotations,
and that means that they can
be put anywhere on this map.
If we drop Airport on this map,
it's gonna have some sort of
little representation of it.
I'm gonna show you how to
do that in a second as well.
We need to have our MapView right here
have some sort of var
that you can pass into it.
I'm gonna make it a let even
which is the annotations.
It's just an Array of
those MKAnnotation things.
So this could be Airport,
anything else that
implements MKAnnotation.
And when we make our View,
in addition to setting the delegate here,
I'm gonna add these
annotations to the map.
That's it.
That's all we have to do
to make them be on the map.
Now the only problem is the map,
it has no idea how to draw them.
It doesn't know are you
drawing some sort of pin
or some sort of a marker or a custom View
or what are you doing to draw it?
And how these annotations
get drawn on a map
is actually controlled by this delegate.
So we're gonna see our first ever
delegate method from UIKit.
And the one we want here is called
mapView ViewFor annotation.
Let's see if we can find it.
Look at all of these.
These are all MapView
delegate functions right here,
didSelect something, didDeselect,
the region we're showing
in the map change.
I'm looking for the one
which is ViewFor annotation.
It's right here.
And this is just returning
something called an MKAnnotationView
which is a UIKit View
that shows annotations.
And all it's doing is
getting the right one
for a given annotation,
in our case these are Airports,
so just creating a View for the Airports.
Now we're not here to learn MapKit
so I'm just going to show you
how one does this to
create this little View.
And I'm gonna use a pin.
I can also do markers
which are kind of a new look of the map.
You ask the mapView to dequeue
a ReusableAnnotationView
with the identifier,
and we can use any
identifier we want here,
I'm gonna just call mine
"MapViewAnnotation".
And if that comes back
nil, I can't reuse one.
This is reusing one of
those pins or whatever.
Now I'm gonna create my
own, MKPinAnnotationView.
That's the one that looks like a pin.
And it takes the annotation
like our airport or whatever
and it also wants that
reuseIdentifier again,
the same thing here.
I should probably create
a constant for that
but I'll just quickly type it in here.
And that is creating a
little PinAnnotationView.
The newer one is called
a MarkerAnnotationView.
We could use that but
I'm gonna use the pin.
I kinda like the call-outs on the pin.
And speaking of which I'm also gonna call
something on that View
called canShowCallout,
set that to be true.
I'm gonna return this View.
And this is how the
MapView knows what View
am I supposed to use to
draw a certain annotation.
Now the last thing we
need to do of course is
when we create our MapView,
we need to pass the
annotations we want in,
and this has to be an
Array of MKAnnotations.
Well, Airports are MKAnnotations,
so this could be an
Array of Airports, right?
So let's do our airports.sorted.
airport is this thing
we're fetching all of.
Right here we got a FetchedResults.
And I'm just taking these
FetchedResults and sorting them
so that we'd get our annotations sorted.
It really doesn't matter.
I could also say Array(airports).
I'm doing this because
this takes an Array,
and airport is a FetchedResult,
so again which is a collection
but not quite an Array.
So just sorting them
turns them into an Array.
I could just say Array(airports) as well.
So let's see if this is working.
Hopefully now when we
go to our filter, wow!
I see all these airports
right here now appear here.
You can see them overseas.
And they're really cool,
these pins if you tap on them,
it shows you that title
and subtile that we set.
Title and subtitle.
Title and subtitle.
Big advancement already right there.
The only thing here is when
I go and choose Boston,
I want this to kinda zoom in on Boston.
And also if I were to click
on Houston, Texas here,
I want this to change to Houston.
So we have a little bit of work to do
to hook up this selection up here
to whatever is selected and zoomed in on
in our map right here.
So let's start doing the zoom in.
How am I gonna zoom in on
whatever is selected here?
That's pretty straightforward
to do actually.
Remember that every time our
SwiftUI update passes happen,
we get a chance to update this View.
So I'm gonna put a little
var here called my selection
which is gonna be an MKAnnotation.
I'll make it Optional
'cause you don't have to select
an annotation if you want.
But if you do, then when I'm updating,
every time I draw, I'm gonna
zoom in to show this selection.
Say here if I can let
annotation equal the selection,
so you have chosen one,
then I'm gonna do this thing
in the MapView called setRegion.
And setRegion takes an MKCoordinateRegion.
An MKCoordinateRegion takes a center
which is gonna be that
annotation's coordinates, right?
That's this thing we
implemented right here
for airport to CLLocationCoordinate2D.
That's saying where is this annotation?
That is the selection right there.
And it also takes a span,
and this span is an
object we're gonna create.
This is how much to zoom in essentially.
And we can also say that yes,
we want it animated as well.
So what is this span?
When I zoom in on something,
I want it to be kind of a town-sized View.
So I'm gonna call this span town.
It's gonna be the span
that's approximately a town.
So what is a span that's
approximately a town?
Well, it's an MKCoordinateSpan
which just specifies the latitudeDelta,
we're gonna do .1 degrees,
and the longitude, .1.
So .1 degrees of latitude and longitude
is about the size of a town.
It's a little smaller than a city
but it's bigger than like
a little neighborhood.
This is kind of a good size to zoom in on
if you're zooming in on some annotation.
And this is gonna happen every time
that this MapView gets
updated in the SwiftUI,
reactive UI updating passes.
We're going to re-zoom
to show our selection.
So let's go over here
and set our selection.
Here's where we'll create our MapView.
We'll add this extra
argument, the selection.
And what is the selection?
Well, it's just our
draft's destination, right?
This draft is our FlightSearch.
This is what we're kind of filtering for.
And the destination is the
destination Airport inside draft.
It's the exact same thing
we're showing as our
Pickers thing right here.
Let's see.
Hit Filter.
Hopefully, yeah!
San Francisco.
This is San Francisco International.
Let's pick something else.
How about Boston?
Oop, zoom in over, there's Boston.
Maybe pick Bob Hope, Burbank.
Back to Bob Hope, there it is.
Now we can still zoom back out right here
and pick other airports.
The problem is though when we pick them,
it's not updating here.
When I'm picking this, it's nice.
It's telling me it's
San Diego International.
But when I pick it here,
I mean choose this, go to that place.
Now how do we make this go both ways?
This is working well
when we choose Boston.
It goes into here and sets the map.
But how do we make it work the other way
when we click over here
on Newark or whatever,
Connecticut Airport,
it makes that be our destination?
Well, how do we do that in
Views in general, right?
If we have a View and
we can interact with it
and it wants to communicate information
back out to some other View,
what do we do?
We use a Binding.
And this MapView, even though
it's UIViewRepresentable,
it's a View and so we could
have this be a Binding.
This is now a Binding to selection.
Now we will down here go ahead
and have our selection
be updated and all that.
But we have a little bit of
problem first which is that
here we are passing our
selection as an Airport.
So this is not a Binding
to an MKAnnotation, right?
That's what this says,
Binding to MKAnnotation.
This is an Airport.
So how do we do that?
Can we just do dollar sign right there?
No, we can't because draft is @State.
If draft were an ObservedObject,
we could do this.
This would work if this
were an ObservedObject.
But draft is not, draft is State.
And the Binding, the
dollar sign of a State
binds to the entire struct.
You can't bind it to
variables inside the struct.
So how the heck are we
going to pass a Binding
to this Airport inside of this State?
Well, let's create a var
that's called destination.
And it's gonna be a type
Binding to MKAnnotation.
Now as soon as I start
using MKAnnotation here,
better go up here and say import MapKit
or the compiler is not gonna
know what the heck we're doing.
So this is the destination Binding.
And how can we use this
Binding right here?
Just say pass the destination there.
And this when I compile
is gonna work down here.
See, we no longer are
gonna have this thing
where it can't infer
or any errors down here
because I am passing a
Binding to an MKAnnotation
which is exactly what this thing wants,
a Binding to an MKAnnotation.
All we need to do though
is figure out and implement this var.
How do we return a Binding
to an MKAnnotation here?
We need to somehow make an MKAnnotation
that hooks up to this draft's destination.
How are we gonna do that?
Well, we're just going to use
one of Binding's constructors.
You can see it's got a few here.
I'm gonna use this one.
This is almost kind of a
primitive sort of Binding
and it just lets you specify a closure
to get the value of the Binding
and a closure to set
the value of the Binding
which is really getting at the heart
of how Bindings work, right?
Bindings are just getting and setting
some other values somewhere else.
So it makes total sense that
there's a version of Binding
which is just closure to
get and a closure to set.
Now all we need to do is
implement these to get and set
the value of the destination
in our draft State.
Could not be simpler, so let's
Go do this.
It's just a little easier to see
what's going on before we do this.
Let's do the get first.
The get is saying this
is takes no arguments
and returns an MKAnnotation.
Of course because this is a
Binding to an MKAnnotation
so we won't be able to get it there.
So let's do that.
Quite simple here.
We're just going to return
our draft's destination.
Now this is an Airport,
an Airport is definitely an MKAnnotation,
so this is perfectly legal.
I'm also gonna put self in here.
You can return this value.
That's super simple.
What about the set side?
The set is a closure that
takes an MKAnnotation,
an optional MKAnnotation.
It doesn't return anything.
It just sets the value.
Let's have that annotation that it takes
be this argument right here.
We'd like to say something like
self.draft.destination
equals that annotation.
Woo-hoo, we're done!
We'll just do kinda the opposite of this.
But this is not gonna work.
And the reason this doesn't work
is that the type of this is MKAnnotation,
Optional MKAnnotation.
That's its type.
So here we're trying to make an Airport
equal to an MKAnnotation.
That's not gonna work
because an Airport is an MKAnnotation
but not all MKAnnotations are Airports.
So you can't just make
this assignment right here.
This is where we have to do the as,
the type casting that you read about
and we've talked about
briefly a couple of times
maybe in the context of talking about
the old Objective-C Any type.
So I need to capture, I'm gonna
say if I can let an Airport
equal this annotation as an Airport,
and that is conditionally trying
to see if this MKAnnotation
is in fact an Airport
and not something else
that we implement MKAnnotation on.
And if that's true,
then I can set my destination
to be this airport.
So in this way, we are
just creating a Binding
that binds to and from
our draft's destination.
And we pass that Binding
right off to our MapView right here.
So far our MapView is not doing anything
to set the selection.
It's still just looking at the selection.
But let's make sure we
haven't broken anything.
We shouldn't 'cause we're still Binding
to our draft destination.
So when we go over here,
see, it's still San Francisco.
If we change this to Boston,
the whole SwiftUI View updating happens
and we get to see Boston,
so that's working.
But we haven't done anything yet
on the other side of the Binding
so that when we pick an airport here,
it set the selection.
So we need to have our
MapView set the selection.
How do we know when a
pin has been touched on?
In MapView we do that with the delegate.
So there's another delegate method here,
mapView didSelect, where is that?
There it is, mapView didSelect.
This delegate method is called
whenever one of these
pin Views is touched on.
And this is the pin View that
was touched on right here,
MKAnnotationView, in this our
case it's a pin annotation.
So we can get the annotation
that was clicked on
by just asking the annotation View
what annotation do you have?
It's asking this pin thing
what's your annotation?
Now we have the annotation,
we kinda wanna do
something along the lines
of self.selection equals that annotation
except that self.selection is
not here in our Coordinator.
It's up here in our UIViewRepresentable.
This is nested class but
it can't see the vars
of this outer class right here.
So we have to bind this
selection from here
out to this Binding out here.
So let's just create
another Binding right here,
var selection, it's an MKAnnotation.
And then when we create our Coordinator,
let's pass the selection, $selection
which is the dollar sign
version of our Binding,
and we know that dollar of a
Binding is the Binding itself
or a Binding to the same thing
that the Binding binds to.
But this doesn't quite work
because this is a class, not a struct,
so we don't get this free constructor.
We have to create our own init selection
that takes a Binding to
an MKAnnotation Optional.
And then again we can't say
self.selection equals selection.
This is an @Binding,
so we have to say _selection.
The actual struct is the
thing that's passing here.
So now our selection binds both ways.
When the delegate notices
this one's been clicked on,
it sets it in the selection Binding here
which binds to this selection right here
which binds back out to
this Binding out here,
and that's going to set
our draft destination.
Let's see that in action.
Go to our filter.
Binding correctly this way,
we got San Francisco right here.
Let's go find a different airport.
How about Oakland right there?
Woo!
Look at that!
It zoomed in on it and
it picked it up here.
It can pick the other way.
Let's go Boston.
Here's Boston.
Zoom out again.
Over here to Newark.
Woo, it did it again.
And if we click Done,
it has picked this Newark.
We get to see Newark Airport.
So that is it.
It's all I wanted to show you
on this ViewRepresentable side.
The next demo we're gonna do
is ViewControllerRepresentable.
But this one really shows
you kind of all the things
you need to know about
Binding into a UIKit View
including, if you have to,
building your own custom Binding
with get and set to be
able to bind in there.
But all other times you won't
even need a custom Binding.
You'll have an ObservedObject
you can bind to
or you can bind directly
to a State or whatever
that you can then pass
into what's going on here.
And you might well have to pass
it to the delegate and back
like we have here 'cause the
delegate is usually the one
handling most of the activity
going on in the UIKit View.
Alright, so that is it for Enroute
and the ViewRepresentable.
Let's move over to our other
demo which is an EmojiArt demo.
So in this demo we're going to be wrapping
a whole bunch of Views,
a Controller that's
controlling all bunch of Views
that we're gonna present using a sheet,
and this sheet that we're gonna present
lets you take a picture from the camera
or the photo library.
We'll start with the photo library here
and use that as the background
image of our EmojiArt,
and then we'll move on to the camera
'cause there's a couple
of things to think about
with the camera that
are a little different.
So where are we gonna put the UI
that lets us take a picture
and put it as our background
and then drop it right up
in the navigationBarItems?
We already have one right there
for our pasteboard button on the right.
So let's put on the other
side, the leading side,
some buttons or something that
lets us choose this stuff.
We'll call this my pickImage.
And my pickImage is just going
to be a little var down here,
pickImage, some View.
We could even make this private of course
and just pickImage.
And let's us make it be
an Image, systemName.
How about the image "photo"?
That's a good system image I
think for picking a photo here.
And I'm gonna make this larger.
I'm not really sure
whether we should be
making that larger there
but it's good for demos,
easy to see what's going on up there.
And I'm also gonna set the
color to be the .accentColor,
the same thing a Button would use.
And in fact I could use a Button here,
but just for variety,
let's do an onTapGesture.
This Image is going to appear in our UI.
Let's take a look at it.
There it is right there.
And when I click on this,
I want it to bring up this UIKit UI
that lets us choose from the
photo library in this case.
And later we'll add another
button for the camera.
So how do I make a sheet appear?
We know exactly how to do that, right?
We just set some sort of boolean variable.
I'm gonna call my boolean variable
showImagePicker = true.
I need a little private @State for that.
We'll start out as false of course.
And then we're just going
to .sheet isPresented,
put a Binding to that showImagePicker,
and then we're going to have to provide
whatever sheet it is that we present.
In this case it's gonna be ImagePicker.
This is gonna be a
UIViewControllerRepresentable
that puts up the photo picker.
Let's go ahead and do this, File, New.
Again it is a SwiftUI View,
but since it's a Representable,
we don't need the var body and all that.
We'll call it my ImagePicker.
And here it is.
As usual we're doing the integration,
we need both SwiftUI and UIKit.
And this is just a
struct called ImagePicker
which is a UIViewControllerRepresentable.
And it has exactly the same functions
as we had in the other ones,
so let's just type them right in
since we already know how to do this.
There's the makeUIViewController
that has the context.
Now the kind of ViewController
that we're going to make here
is called a UIImagePickerController.
This is just a MVC, a Controller in UIKit
that we can use to choose a photo
from our photo library or our camera.
Let's let picker equal
one of these things.
I'm gonna create one.
And return it.
And we're gonna have to configure it
a little bit inside here.
So we'll leave space for that.
Then of course we have our
updateUIViewController.
This takes an ImagePickerController.
Here's where we would update things.
I don't actually think we
have any updating to do
because we just present
this ViewController
and we're gonna have a delegate for it
but otherwise it's gonna do its thing.
We of course need makeCoordinator.
And we'll do the exact same thing,
we'll call it Coordinator,
and we'll have it be a
nested class Coordinator.
It has be an NSObject
because it's gonna be
the UIImagePickerController's delegate.
And just for good measure,
and I'm not gonna explain why
because we're not really that much
learning about UIImagePickerController
although we are gonna
learn a lot by doing this,
it also has to implement
another protocol here
called UINavigationControllerDelegate.
This is just a protocol
that this ImagePicker
requires its delegate to implement.
We're not gonna use any methods from it
but we still have to put it on there.
There are a couple methods in here,
and we really need them both.
One is the imagePickerController didCancel
and the other is the imagePickerController
didFinishPickingMedia.
So let's put them both in here.
This cancel one right here is called
when the person cancels
of course the Controller.
Remember we're putting up an
entire sheet worth of UI here
and it's gonna have a
"Cancel" button on it.
And then this is the one where
we actually pick an image
either from the camera or
from the photo library.
So here's where the action
is really happening in our delegate,
and this is our PickerControllerDelegate,
so let's set our picker's delegate
to be our context.coordinator, right?
We're setting this instance of
this that we're gonna create.
And we'll create that here, Coordinator.
It doesn't take any
arguments as yet anyway.
Now we got it all set up.
This is just exactly like
we set up the MapView.
We got the delegate and all that.
But one other thing we wanna
do in configuration here,
to start is I'm gonna set
the picker's source type
to be photo library.
So the picker is going
to try and pick an image
from the photo library to start.
Eventually again we'll do the camera
but for now just the photo library.
Alright, we go back to
our DocumentView up here.
We've got our ImagePicker.
It should compile now.
Let's run, to our "House".
Now when we tap on this, an onTapGesture,
hopefully it will put up
our nice photo library user interface.
And there it is.
Woo-hoo!
Okay, there's our photos.
Now if I pick one like this
thing here, nothing happens,
but if I hit "Cancel", nothing happens.
That's because back here
in our pickImage over here,
we put this sheet up, the ImagePicker up,
but we don't do anything when
it gets canceled or picked,
so of course this thing
never gets put away,
this showImagePicker
never gets set to false,
and clearly we're not grabbing the image.
But we've got this thing presenting,
so now let's just do
the little bit of work
to get this Image out of here.
Now how am I gonna get
that Image out of here?
One way to do it will be the
same way we did the MapView,
put an @Binding here and bind to the Image
and then the Image will be chosen
and we would have to pass it
down here to the delegate,
and it could set the Binding here
and it would transport back.
That is one way to do it.
Just to show you there are
other ways to do things,
I'm gonna do this a little differently.
I'm going to add a var
here to my Representable
which is called handlePickedImage,
and it's going to be closure,
UIImage, return void.
And I'm going to call this
closure when you pick your Image.
Now I'm gonna give you nil if you canceled
or for some reason I
couldn't get the Image.
And I'm gonna give you an Image
if you actually picked it.
Now we know that this handlePickedImage
is actually happening down here.
So we have to do the same
thing we did with our map,
and this is very common to have to do,
we're gonna have to do the same function
down here in our Coordinator,
var handlePickedImage UIImage, and Void,
and pass that to our Coordinator,
handlePickedImage, call
in handlePickedImage.
And again this is a class
so we don't get this free
constructor right here,
so we have to say init handlePickedImage
with this UIImage that returns a Void,
self.handlePickedImage
equals handlePickedImage.
I'm really getting used to
typing handlePickedImage.
Of course we are grabbing on
to this and holding on to it
so this is going to
have to be an @escaping.
We know that anytime we have
a function that we pass in,
when we hold on to it, we
have to let people know
that that's what we're doing
that we're grabbing on to it there.
Now that we have this
handlePickedImage here,
let's just, when we cancel,
call our handlePickedImage(nil)
so we know that you canceled.
And here I'm gonna do my
handlePickedImage with the image.
Now how do I get the image?
When imagePickerControllerDelegate
gets called,
this didFinishPickingMediaWithInfo,
we get this Dictionary,
and this Dictionary has info
like how big the image was
and what the actual image was.
And this Dictionary has a bunch of keys.
One of the info's keys is .originalImage,
and that gives us the
original image as a UIImage.
Of course this Dictionary
has Any's as the values.
This is an old Objective-C API here.
So we need to say as? UIImage
and see if we can cast it.
And we're doing as?.
This will return nil if for some reason
this is not a UIImage,
and that's okay 'cause the
handlePickedImage will have nil
and that'll be just like canceling,
no different from canceling.
Now one thing I'm gonna
do while we're here,
I don't think we did this all quarter,
which is typealiasing.
I had to type this type
all bunch of times in here.
And when you have kind of a complex type
like that's not a String
or some name of a struct,
a lot of times we'll create a typealias.
For example I might call this
thing my PickedImageHandler
and it equals UIImage returning Void.
And then everywhere where I use this,
I'm going to put this instead.
So here, and here, and here.
I couldn't put that.
It's kind of good for my API.
It's a little more descriptive
of what this function was,
and it also cleans up things,
and I don't have to worry about
typing this over and over,
and I might make a mistake
when I'm doing it typing it over and over,
so like I might forget my question mark
or something like that,
this prevents that as well.
It's quite common to make typealiases
for functions arguments
especially if they're pass
around a lot of places.
All we have to do now is
set this handlePickedImage
when we do our ImagePicker over here.
So here's our ImagePicker.
Let's do it.
The image given back to us here.
Now we know that we have to check to see
if the image does not equal nil,
and if it does not equal nil,
then we've got to actually
use it as our background.
And in any case though
we're going to set our
showImagePicker to false.
We want to dismiss this
sheet that we presented.
Now what are we gonna do
if the image is not nil?
This is where we're gonna
do that kind of bogus thing
that I was talking about there
where we're gonna throw this
thing into the file system,
this image into the file
system and grab a URL to it
and set that as our background URL.
Again that would tie this whole thing,
this document that we're
creating to this device
'cause we're pointing
into its own sandbox.
Also by the way I had to go
back in my EmojiArt's extension
and play a little bit with this image URL.
I had to actually check for file URLs
because one thing to
note is that as you're,
especially doing your development
and running new versions of your app,
the Application Support directory
which is where I decided to
put this thing, this image,
it changes the URL, it
changes from launch to launch.
So here I just have a thing where
whenever I see a file URL
as our background image,
I'm going in re-looking it
up in Application Support
to get the new version.
So that's a little bit annoying.
I think I also had to
go over in our Document
and make sure that whenever I fetch
the background image data,
I call that imageURL again.
I was originally only
calling when we set it,
but now I'm calling it every time
to make sure this file
URL gets properly set.
So that's just a lot of
kind of fratzing around
to make this demo go really smooth
because I can now type this
one line of code right here
which is self.document.backgroundURL
equals that image storeInFilesystem,
and store in the file system,
just this little extension
down here that I wrote.
And you know how to do all this
now after the last lecture,
getting the directory,
appendingPathComponent.
I use this jpegData function
on UIImage to get the data
and then I write to that URL,
put it into the filesystem,
and I return the URL that I wrote it to.
Really simple, makes this
a one-liner right here.
One other thing I'm gonna do here
which we haven't talked about this either,
I'm little worried that this closure
might be called at an inopportune time.
For example, I'm not really allowed
to modify myself as a View
while I'm in the middle
of processing events.
You kinda have to wait for
everything to settle back down.
So to be safe, this closure right here,
I'm not gonna do this backgroundURL
which I know is gonna update my UI.
I'm going to do it in a
DispatchQueue.main.async.
Now why would I be doing this?
There's no issue here
with multi-threading.
None of this is happening
on a background thread.
This has nothing to do
actually with multi-threading.
It's just that if I ask my main
queue to go do this closure,
it's gonna put it on its list,
and even though I'm running
in the main queue right here,
it's gonna finish doing all
the things it's doing here,
maybe updating my Views,
and when it's done,
it's gonna go to its queue
and grab the next one
which hopefully will
be mine and execute it.
So it's essentially just saying do this
after everything settles down.
And if you start getting
warnings when you run,
that say, oh, you can't update,
you're not allowed to modify the View
while it's in the middle
of constructing its body or whatever,
then you might think of
delaying something like this.
And we might have wanted to
do that in Enroute as well.
Okay, in Enroute when you
click the Airports in the map,
maybe that selection of the Airports
should have been done this
way too just to be safe.
I didn't see it have any problems
but anytime you're having
callbacks right here
that are happening especially from UIKit
'cause UIKit's in a little
different world than SwiftUI,
that's sometimes a little
safety buffer you can put in it.
Not gonna hurt anything.
We're just essentially doing
a user Intent here, right?
This is an Intent, an Intent
to change the background URL.
Perfectly fine to delay
that a short moment
as we wait for the dispatch
queue to settle down
and come back around.
Let's see if this works.
Right, here's our "House".
Let's see if we can change
the background from the house
to be something from our photo library.
Let's click on the photo library.
Here it is.
Let's go pick this nice little green.
Woo-hoo it works!
And we can zoom in and out and everything.
And we could even make sure
that it's really saving things here.
Let's go back, run again, "House".
Woo-hoo, it's still in there!
I'm zoomed in.
Alright.
So this is great.
This is working for the photo library.
Very simple, very powerful.
It looks really nice in our code.
Here our SwiftUI code is all SwiftUI-ish.
There's really not a
lot of weird stuff here.
And even the ImagePicker,
pretty straightforward.
This is pretty much a
minimal amount of code
to get a UIImagePicker working.
Now what about the camera?
Making this UIImagePickerController
pick for the camera
is really simple.
It's actually just changing
this photo library right
here to be .camera.
However, I really wanna use all this code
for both the photo library and the camera.
So let's make this be a var here.
So we'll say sourceType.
We'll make this into a var here.
And this type of this enum
is UIImagePickerController.SourceType.
That's the type of this enum essentially.
And we just instantly made it
so that this ImagePicker can either pick
from the camera or from the photo library.
And back here of course
now we have an error.
We need to provide the sourceType.
Let's create a little
imagePickerSourceType @State.
And we'll set it equal to
UIImagePickerController.SourceType.photoLibrary.
It doesn't matter what we choose here
because what we're gonna do is
we're gonna have this photo,
one, set this self.imagePickerSourceType
equal to the photoLibrary,
and then we're gonna
have a whole other button
right next to it, camera,
and it's gonna have its
SourceType be the camera.
I'll put these two things in an HStack
so they'll be sitting side by side
in that upper left corner, up there.
And that's all we should
have to do, right?
Let's take a look.
Alright, here's our "House".
We got them there.
So let's see is there a photo library,
it still seems to be working.
We can choose different images.
Works great.
How about the camera?
Whoa!
Our app crashed.
What is going on here?
Let's look in the console.
Of course every time you have crashes,
you wanna go look in the
console see what happened.
And sure enough here it is,
"Source type 1 not available."
Oh yeah.
And the simulator doesn't have a camera
and so it crashes our app.
So the camera UIImagePickerController,
if you look at this documentation,
it's very, very clear about the fact
that you should not ask
for this SourceType camera
unless you've checked first to
make sure there is a camera.
So we'll just do that here.
If
UIImagePickerController.isSourceTypeAvailable?
And we're talking about the camera here.
Then we'll put this camera here.
Now when we run on our simulator,
we now have a camera.
Woo, it worked great!
Okay, but we wanna see the
camera in action, don't we?
So let's try running it on a device.
I have a device attached
here, a Gold iPad.
Let's run it on that and see what happens.
Alright, we got a couple
of documents here.
Let's go to our "Barn" document
and let's bring up the camera.
See the little camera picture there?
Bring that up.
Oh no, it crashed again!
What?
Look back here, oh no!
And what does this say?
It says "this app's Info.plist
must contain NSCameraUsage
"with a key with a String
value to explain to the user
"how the app uses this data."
That's right.
We can't just go willy-nilly,
choosing people's cameras and microphones
and other things on their device.
We need to ask for permission.
And part of asking permission
is explaining why you
want to use this thing.
So it's saying here go to
the Info.plist and add this.
Let's do exactly that, Info.plist.
The way we add an entry to the Info.plist
is we right-click and say
"Add Row", here it is,
then we just scroll down.
This is a privacy thing
so it's gonna be down
under privacy somewhere.
Here it is, privacy, yep, the camera.
See there, privacy, camera.
A lot of privacy things here.
Camera is the one we want.
And we're just gonna explain
why we want the camera,
why we want to use the camera,
and we're gonna say "EmojiArt lets you set
"the background of your
documents using the camera."
It's our explanation as to why
EmojiArt wants their camera right there.
Let's try it again now.
We'll go to "Barn" again, hit the camera.
Now notice it's asking us
do you want to allow the
user to use the camera?
And it's got our little
explanation in there.
It's great.
So we'll say Allow.
And then let's go take a picture here.
Maybe that now.
We'll say yeah, we wanna use this photo.
Woo!
It's our background image.
So the camera was really
just as easy to do
as the photo library.
We just had to make sure the camera
is actually available on our device
and then provide that
little info to the user
about why we wanna use it.
So that is it for our
UIViewControllerRepresentable demo.
Now you know how to put both Views
and ViewControllers into your app.
And that's all I have
for you this quarter.
So now it's your turn to go
show us all that you've learned
with your awesome final projects.
And I look forward to
helping you down the stretch
with any questions you have about that.
And thanks for listening
to all these lectures.
And good luck with your final project.
- [Woman] For more, please
visit us at standford.edu.
