TOMMY MACWILLIAM: Let's take
a look at our next iOS app.
In this video, we're going to be making
an app that applies filters to photos,
much like Instagram does.
So let's get started.
We'll open up Xcode.
And again, we're going
to create a new project.
So we're going to click New Project.
Again, we're going to have a single view
app, so we'll just leave that selected.
Click Next.
For our product name, we can
call it whatever we want.
I'm going to call mine Fiftygram.
Organization Name
identifier, that's the same.
Language, Swift.
And we don't need to check
off anything else here.
I'll click Next.
As usual, it'll ask me
where I want to save it.
And I'll click Create.
All right, so last time we
created our Pokédex app,
we also selected single
view application.
So all of the generated
code here is the same.
So we won't need to go through that.
Let's just jump right
into making our app.
So let's tackle the view layer first.
Let's open up our storyboard.
And just like before, we have
an empty view controller here.
And let's think about
what we want in our app.
So we want to display
some sort of title bar.
In that title bar, there'll be a button
where you can pull up an image selector
and select a photo from your photo list.
Then it'll display that photo so
you can see what it looks like.
And then we'll have a few
different just buttons
that you can press to apply
filters to that photo.
So let's add each of those components.
Let's do the title bar first.
Just like before, we
can come up to Editor.
First, we want to select the scene.
Then come up to Editor.
Embed in navigation controller.
We'll click that.
And then just like before,
we've got our title bar now.
And let's just give our app a title.
So we can click Title bar.
Come over to Attributes
and give it a title.
So now let's put the button
to select the photo actually
in that navigation item.
So to do that, we're going to
come up here at the top right.
Get that search box again.
And now let's add what's
called a bar button item.
So this is something that you can
only add to one of those toolbars.
So let's drag that up, and let's
put it up here on the right.
You can see that it kind of
highlights where it wants to go.
And so now we have our bar item.
So let's just give it a
title of Choose Photo.
And we don't really need
to do anything else here.
Let's just keep it at the
default styling for now.
And we don't need an image
or anything like that.
So that looks pretty good.
Next, let's create a container
where we can display an image.
So let's come up to the top right.
Let's just start typing
image and see what we get.
And there's this control
called an ImageView.
And an ImageView does exactly what
it sounds like it's going to do.
It's a container that can display
some image data in your app.
So let's go ahead and drag this over.
And we can just sort of
position it however we want.
Let's try to drag it.
Let's try to get it to something
that's, you know, roughly a square.
And then if we want to select that,
you can come over here to the right
and look at some properties here.
The one that we really care
about is called Aspect Fit.
And you'll see here, there's a whole
bunch of different content modes here.
But this basically specifies
what happens to your image
if you give an image that's larger
or smaller than the image of you
that you've defined.
And in our case, what we want to do
is just sort of resize everything
to show a scaled-down
version of the same photo,
but keeping that same aspect ratio.
And that's what Aspect Fit does.
It says let's take the photo, let's make
it fit into this ImageView container
that we've defined, but let's
preserve the aspect ratio
so it doesn't sort of
stretch in a weird way.
So we've got that selected.
And nothing else we
really need to change.
And so last, let's
just add a few buttons.
So just like before, let's
come up here to the top right,
and let's just start typing
button, see what we get.
And that's the top result.
It's just a simple button.
So let's drag this down and put
it underneath our ImageView.
Again here, we've got a
bunch of different options
that you can choose
to style your button.
In this case, let's just make
ours a little bit bigger.
So let's bump up the font size.
So let's-- I don't know.
That looks pretty good.
And I happen to know that the first
filter that we're going to write
is a sepia filter.
So we can just double click this
button and change the text to sepia.
So OK.
That's looking pretty good.
So let's just quickly run our app, just
to make sure that nothing looks crazy.
All right.
And now that our app is started out,
that's sort of what we'd expect.
There is no image that we've actually
loaded into the ImageView yet.
And we can click this
button and nothing happens.
And we can click this button
up here and nothing happens.
So let's tackle this app in two parts.
Let's first write the code to
open up a user's list of photos.
Let them pick one and then
display that in the ImageView.
Then after that, let's write the
code to apply filters to that image.
So first, we want to
make sure that everything
is hooked up with outlets and actions.
So let's first create
what's called an IB action.
That's going to be a function
or a method that's called
when you tap on that bar button item.
So we don't really need anything
in the viewDidLoad right now.
So we can delete that.
And now let's create an IB action.
And we're going to
call that Choose Photo.
And so again, just like
before, with IB outlets,
we have this little circle indicated
that it's not connected to anything.
So let's come back to our storyboard.
And now we're going to Control click
and drag from that Choose Photo button
over to View Controller.
And here, you'll see,
in this sent actions--
this is a list of IB actions that
we've defined in our View Controller.
So I'm just going to
select Choose Photo.
And now they're connected.
Remember, we can verify
that they're connected just
by clicking the bar button
item, coming over to Outlets,
and seeing that, yep, when
it's tapped, the default action
that's going to trigger is
this Choose Photo method
inside of our View Controller.
So now that we've created our
action, let's create an outlet.
And we want to connect this
outlet to our ImageView
because we know that we're going to
want to access that ImageView somehow
in order to set its contents.
So we're going to say IB outlet.
And we're going to call it ImageView.
And the type is UI ImageView.
So now it's come back to our storyboard.
We're going to control drag from our
View Controller to our ImageView.
And when I let go, one of my
choices for outlets is ImageView.
So I'm going to click on that.
OK.
So now I've just wired
up these two things.
So we have an outlet that lets us
access that view from our code.
And we have an action,
which lets the storyboard
respond to some kind of event and
call a method that I've defined.
OK, so let's first write the code
to show the users photo gallery.
So to do that, we're going
to use a built-in iOS class
called UI Image Picker Controller.
Kind of a mouthful.
But it's nice because it gives
us a bunch of functionality
for free, sort of displaying all the
user's photo, or responding to a tab,
and so on.
So the first thing I'm
going to say is I'm going
to say my UI Image Picker Controller.
And I want to make sure that I
can access the user's photos.
So I can say is source available.
And then we're going
to say photo library.
So this is just to
make sure that we have
permission to access the user's photo
and that they're not somehow denied.
And so after we've created
that, let's create our picker.
So that's going to be a UI
Image Picker Controller.
Notice here that there's
some options here.
But we don't really need
to take any of them.
We can just create a blank
UI Image Picker Controller.
And now there's a couple of properties
we want to set on this picker.
The first one is the delegate.
And remember that a delegate
is a way for one class
to delegate behavior
to some other class.
And so in this case, we
have this picker class.
And a user's going to tap on an image.
And we want to delegate how to
respond to that to the class
that I'm using right here.
So we're going to say that
picker.delegate is self.
So the object that we want to
use to respond to those events
is this View Controller.
But now we get this error message
here that says, in order to do this,
we need to make sure that my class
is a Image Picker Controller delegate
and a Navigation Controller delegate.
So just like we did with Codable,
all we have to do is add those here--
one, two.
And that's going to enable us to define
some methods that are called in a bit.
So that's one.
Then we also want to
specify the photo source.
Remember we just checked to see if
the photo library was available.
So let's say that my source
type here is a photo library.
OK, so now our picker is all set
up, has all the options we need.
So the last thing to do
is to actually display it.
And in order to display it, we're going
to use that Navigation Controller.
So remember that we've already created
one because we already embedded
our app into a Navigation Controller.
And by doing that, it means that
we can say Navigation Controller.
And then there's a method on
Navigation Controller called Present.
And it takes a few different arguments.
So the first one is the View
Controller that we want to present.
So in this case, we want
to present the picker,
whether or not we want
it to be animated.
Sure.
And finally, another
closure, or another callback,
that's a function that's going
to be called after this finishes.
So in this case, we don't really need
anything to happen after it finishes.
And you notice that the
type of this parameter
isn't optional because
it ends in question mark.
So let's just say nil, because
we don't need to do anything.
So let's build the app.
Looks like our build succeeded.
And so let's try giving it a run.
OK, so here's our app again.
And now when I press
Choose Photo, there we go.
So the iOS simulator has a
couple images preloaded on it.
And then I happened
to add one of my own.
So when I tap on this
image, the picker goes away.
And now we can actually use that image.
Nothing happened yet
because we haven't defined
what's going to happen
after user selects an image.
So let's do that now.
So to do that, we want to implement
one of the methods that's defined
by UI Image Picker Controller Delegate.
So the method that we
want to use happens
to be called Image Picker
Controller Did Finish.
So I'm just going to
start typing did finish
and just let the auto
complete do the work here.
So this is a method
that's defined for us
and is going to get
called by iOS for us.
And it takes a couple of arguments.
The first one is the picker or the
instance of the picker that called it.
And the second one is this dictionary.
So it looks like it's a dictionary
from some key to anything.
But this is actually going to be
where the information about what
the user selected is contained.
So let's take a look.
So we want to create a
new variable called Image.
And now let's take a
look at this dictionary.
So if you consult the documentation,
you'll see that the key we want to use
is this imagePickerControlle
r.InfoKey.originalImage.
And so this is going to
give you back an image.
And so what we want to do is
just cast this into a UI image.
So this just makes sure that
the photo the user selected,
we want to get that in an
object called UI Image.
And finally, just to make
sure that this cast succeeds,
let's put this inside of an if let.
And then let's build.
And we're good to go.
OK.
So what do we want to
do with this image?
What we want to do is display
it inside of that ImageView.
And that's actually pretty easy.
All we have to say is ImageView.image
is equal to that image
that we just got back.
So let's run this.
So we run our app.
Let's click on Choose photo.
Let's pick My Favorite Photo.
So nothing's happening.
So it looks like I'm tapping this,
but nothing's actually happening.
So let's add a quick
Print statement to see
if my delegate method is being called.
So let's say Print selected photo.
Run again.
Choose photo.
And we'll click on this.
And it looks like it is.
So if you come down to
the bottom here, you
can see this is where my print
statements are going to be.
Every time I tap this, I'm
getting a selected photo added.
So it looks like my delegate is working
and my method is hooked up correctly.
But what's happening here is now
that I've implemented this method,
I need to manually dismiss this picker.
In other words, we want
this View Controller
to say, hey, I'm all done with
that Image Picker Controller.
I've got the data I needed.
So let's dismiss that View Controller.
So to do that is pretty simple.
We can just say,
navigationController.dismiss.
And this is automatically just going
to take whatever View Controller is
on the top of the screen and hide it.
So same parameter as before.
Animated-- sure, why not.
Completion-- you don't really
need to do anything with this.
So let's now try and run it.
So there is our app.
Let's click Choose photo.
Tap on a photo.
And there we go.
That's pretty good.
So this time, we've dismissed
the View Controller.
And then just like we
expected, here's our image.
And we've automatically done
the aspect fit correctly.
So we don't end up with some
crazy, weird stretching.
So that's the first half of our app.
We've opened up this
user's photo gallery.
We've allowed them to pick an image.
So now we want to apply a filter to it.
So let's take a look at Apple's
documentation for filters.
In general, the iOS documentation
is really, really good.
It's really thorough.
It has a bunch of examples.
And what we're going to be looking at is
this part of the API called Core Image.
And you can see there's a
sort of really long guide
here that you can sort of walk through
a bunch of how this stuff works.
So feel free to check that out.
But in general, all
we're going to need to do
is take a look at what filters
are built into iOS for us.
So if we scroll down
here, you can see there's
sort of a whole category color effect.
So there's a whole bunch
of things built in here.
There's different photo effects.
So we're actually going
to get a lot for free
because iOS has provided
these frameworks
and libraries to do filtering for us.
And we can just take a
look at the documentation.
We said before we wanted a sepia one.
So if I just search sepia
and click it, there we go.
It even tells us sort of like
what this is going to look like.
So with this knowledge, let's
try writing a filter in our app.
So the first thing we need to do
is, again, hook up another action.
We want to make sure that when the user
taps on one of those filter buttons,
we can run some code.
So first, let's create another action.
So let's say IB action.
And we'll call this one applySepia.
And then let's jump
back to our storyboard.
Just like we did before, let's
control drag from our button
to our View Controller.
And let's click on applySepia.
OK, great.
Now we have the code hooked up.
So now let's write our filter.
If you read through that
Core Image documentation,
you'll notice that the first
thing it tells you to do
is create what's called a context.
This is basically an object
that some underlying API
calls are going to use
under the hood in order
to perform those image operations.
So to do that, we can
just say CIContext.
And we'll just have that.
So now that we've connected
our actions, let's
write the code to create the filter.
So the first thing we want
to do is create an object
to represent the filter.
So let's say let filter equals CIFilter.
Looks like the constructor
can give it a name.
And let's call it CISepiaTone.
And where did I get that?
I got that right from
the documentation here.
Looks like CISepiaTone is
the name of the filter.
And then it looks like this filter
takes a couple of parameters.
And from reading the
documentation, I found out
that the way to set those
parameters is by calling
this method called filter.setvalue.
So let's do that.
So let's say filter.setValue.
Two parameters here.
The first is the value.
And the second is this key that
says what value am I setting.
And again, I just found these keys
from reading the documentation.
But this first one looks
like it's called Intensity.
And it has a default value of 1.
So let's just try giving it a 0.5.
And then the key--
I can use autocomplete here--
I happen to know that it's
the input intensity key.
And there we go.
So I've set the value of the intensity.
The other thing we need to do is set
the image that we want to filter.
So to do that, I'm going to
use the same method that I
got from the documentation, setValue.
And what we want to set is a CI image.
And so we know that-- from
down here are our types--
that we have a UI image.
And we need a CI image.
So these are just two
different classes that Apple
provides to work with images.
But it's pretty easy
to convert among them.
So you'll see in this
filter, we're going
to be converting among a
few different image types,
but it's pretty straightforward.
So the first thing we want to do is
take our UI image and make a CI image.
So to do that, we can
just say, CI image.
And then we can give that an
image that is a UI image, which
is our image that we loaded.
And key we want to use-- again, we can
just sort of use autocomplete here--
is our input image key.
And again, the documentation told
me that, so that's input image.
And from reading around,
you can find that key.
So it looks like we have an error here.
Let's take a look.
It looks like this CI image constructor
is going to return an Optional.
So let's just use an exclamation
point to unwrap that.
Again, you can use if let and guard
let if you want to be a little safer.
But for now, let's
just sort of skip that.
So now we've configured my filter.
So to actually run the
filter, all I have to say
is let my output equal
filter.outputImage.
And so this is going to be the resulting
image from applying this filter.
But remember, I gave in a CI image.
And so the type of this output image--
you can see from the autocomplete--
is also a CI image.
So the last thing I need
to do to display this photo
is just convert it back into UI image.
And to do that, we're going to use this
context object that we created earlier.
So we're going to say
imageView.image is equal to self--
or equal to a UI image.
And from the constructors,
we actually want
to use this intermediate format
because that's going to make sure
that our image retains the right size.
So this intermediate format is called
a CG image, or a core graphics image.
Again, just sort of a
third type that Apple
who tries to work with
images, and they each
have their own different
functionalities.
But don't worry too much about that.
All you need to know
is that to create one,
you can just say this self.context.
And then this Create CG image.
Looks like it takes a
couple of parameters.
The first one is a CI image.
Great.
We've got one of those.
And the second one is just the
bounds of that image, so just how
large is this image so that
it can draw it correctly.
And luckily, we know that as well
there's a property on every CI image
called Extent, and that just
defines the bounds for that image.
So OK, so it looks like
our compiler is letting
us know there's errors, probably
some stuff related to optional.
Let's take a look.
Yep.
It looks like there's a few
things here that are optional.
So we can add an
exclamation point there.
Try building.
Add a couple other
exclamation points, just
depending on what types things need.
And there we go.
And again, I wouldn't
recommend really doing this.
I'd recommend really unwrapping
these using guards and if lets.
It's just a little bit quicker to
use the exclamation points here.
So let's run this.
OK, so we have our app.
Let's select a photo.
And let's try pressing
that sepia button.
All right, it looks like we've just
applied a filter to that image.
But what happens if we press it again?
It looks like every time
we press it, that image
is getting darker and darker, which
isn't necessarily something we want.
So what we actually have
to do is make sure we
save that original image that the
user selected from their gallery.
So let's do that.
Let's create a new variable here.
And we'll just call it Original.
And the type of this is a UI image.
And this just says
we're going to use this
to keep track of that original image.
So now, rather than using the what's
currently displayed in the ImageView,
let's just use that original image.
And similarly here, let's just make
sure we save a copy of this as Original.
But now let's be a little
careful here, since what
happens if the user presses
the button for a filter
before they've selected a photo.
Right now, we know our app is
going to crash because we just have
this exclamation point on Original.
So let's not do this.
Let's make Original an optional, which
it could be there in the event someone
selected a photo.
But it could not be,
because the app just loaded.
So now, inside of
applySepia, let's just say
that we want to make sure
that this original is not nil.
And if it is, just return
and don't do anything.
So now we can see compiler errors
go away because we've added a guard.
And we're good to go.
So now if we run the app again,
we're going to choose a photo.
Select this.
We're going to run the sepia filter.
And now, every time we press it,
the image isn't changing anymore.
So OK, so that's one filter.
Let's try seeing what else we can
do with the API and add a few more.
So first, let's jump back to
our storyboard and add a couple
other buttons.
From browsing the
documentation earlier, I
found a couple other
filters I thought were cool.
One was called Noir, and
one was called Vintage.
So let's just copy this button.
We first want to change
the text to noir.
And we also want to remove
that IB action we added before.
If you come over here to
the connections inspector,
you can see that because I copy pasted,
that same action is actually still
there.
So to remove it, we can
just click the little x.
And now there's no action
associated with that button.
So let's do the same thing
for our third filter.
So this one is called Vintage.
Just double check to make sure
there's no actions associated there,
and there aren't.
So that's our view.
Pretty simple.
So let's jump back into
our View Controller
and create a couple of actions,
just like we did before.
We'll call one applyNoir.
And we'll call the
other one applyVintage.
Now that we've got the
actions defined, let's
wire them up, just like we did before.
We're going to control click
from the button to the scene.
Select applyNoir for the second
and applyVintage for the third.
And so now we've wired everything up.
So the last thing to do is just to write
the code for applying these filters.
It's going to look pretty similar
to the code that we just wrote.
So maybe let's start
by copying and pasting,
but then factor out some common code
into a helper method after that.
So first, let's copy this.
And the first thing we want
to do is change the filter.
So this is using a sepia filter.
But let's jump back to the documentation
and see what we should use instead.
Looks like there's
this filter Noir here.
If I click it, you can see this
is the name that we want to use.
So let's paste that in first.
And then from the
documentation, it looks
like there's only one parameter here.
So there's no mention
of intensity anywhere.
So let's remove that intensity, since
it doesn't really make sense here.
Now, everything else here
is pretty much the same.
We want to set the image.
We want to get the output and then
convert that output into something
that our ImageView can display.
So now let's just factor out the end
of this into its own helper method.
So let's call that method Display.
And it's going to take one
parameter, and that's just
the filter to use to display.
So let's say filter, which a CI filter.
It's not going to return
anything because it's just going
to change the image in the ImageView.
And now let's take these last
three lines here and put them here,
or rather put them here.
So the first thing you'll notice is
that filter here isn't an optional.
It's just a regular CI filter.
So we can remove the
question mark there.
And now we can just call
this display method.
So we'll call display on the
filter that we just created.
Similarly, we can use it up here.
So we can say display on this filter.
We can delete those lines of
code that were exactly the same.
Make sure we're unwrapping the optional.
And we'll build.
Looks like we got an error.
So let's see what that is.
Yep, same as before.
We just want to make sure that
we're unwrapping things in a way
that we expect.
Looks like that builds.
And so last, let's use the same
code and apply our Vintage filter.
So let's jump back to the
documentation and scroll down a bit
and see this one looks pretty good.
It's called CI Photo Effect Process.
So let's just change this
string, so that, build.
And so you can see here
that when I'm making sure
that this optional actually exists, I'm
not actually using this value at all.
So I can actually change this
card to be a little simpler,
and I can just say if optional is nil.
And that's going to
have the same effect.
Because really what we care
about is just making sure
that optional isn't nil before we
attempt to apply filters to it.
So we can do that here as well.
All right, so let's build our app--
or sorry, not optional-- original.
So we'll fix those.
There we go.
So now we compile.
And so now let's try running our app.
OK, so let's first choose a photo.
Same photo as before.
Make sure our sepia filter still works.
And it does.
And now we can try out these other ones.
So that's our Noir filter.
That's our Vintage filter.
And we can apply these
in any order you want.
And just like Instagram, you get
some cool effects on your photos.
And so that's everything
for our Instagram app.
In our last video, we'll be
taking a look at one other app
and looking at how to
save data on the device.
