Bummer! This is just a preview. You need to be signed in with a Basic account to view the entire video.
Start a free Basic trial
to watch this video
Right now, when we tap on a cell, nothing happens. In this video, let's apply the filter selected from the options available to the main image that we're holding on to. There are a couple constraints here and in this video let's see how we can work around them.
-
0:00
Right now when we tap on a cell, nothing happens.
-
0:03
So we need to provide an implementation for did select item at index path so
-
0:08
we can determine which filter we want to apply to the main image.
-
0:13
Let's navigate to photo filter controller.swift.
-
0:16
When you tap on a cell, that fires off a UICollectionView delegate method.
-
0:21
So at the bottom, right after the data source implementations,
-
0:25
let's create an extension of the photo filter controller class, and
-
0:30
conform it to UICollectionViewDelegate.
-
0:35
This is a delegate protocol, so we also need to assign the class acting as
-
0:39
a delegate, and we'll do that in viewDidLoad().
-
0:42
We'll say filtersCollectionView.delegate = self.
-
0:45
We're going to keep this really simple.
-
0:48
Using the indexPath, we'll figure out which filter the user's tapping on.
-
0:52
Now, the method again here is didSelectItemAt indexPath.
-
0:57
So first we need the filter, so let filter = PhotoFilter.
-
1:02
Dot default filters, oops, PhotoFilter.defaultFilters and
-
1:08
then we'll use the indexPath.row.
-
1:12
We need to keep track of this filter.
-
1:15
So, let's create another stored property.
-
1:17
And we'll go all the way up to the top.
-
1:20
Right after the photo, we'll say var selectedFilter,
-
1:23
and this is a type CIFilter.
-
1:28
That's optional, obviously, because initially,
-
1:30
we don't have a selected filter.
-
1:33
Once we get the filter now and the didSelectItem method,
-
1:36
we can assign it to this property.
-
1:37
So selectedFilter = filer.
-
1:40
This part is easy.
-
1:42
Now we need to apply this filter to the main image and this gets tricky again.
-
1:47
Why, well we need to apply the filter back to the main image which,
-
1:51
as you remember is pretty large.
-
1:54
Yes, it's just one filter and one image, but it is a large image and
-
1:58
it is going to take quite some time.
-
2:00
We're operating under some constraints here, and
-
2:02
it's worth listing out the constraints so that we have a good sense of what the are.
-
2:06
One, since we're building off a previous app, one of the features already in place,
-
2:11
is the ability to pinch and zoom into an image.
-
2:15
This means that the final image we save has to be a large one.
-
2:19
There's some wiggle room here because it doesn't have to be extremely large.
-
2:23
We also need to display the image without the filter initially and
-
2:26
then apply the filter once the user selects one.
-
2:30
I think the most straightforward way to solve this given our constraints
-
2:33
is an approach as follows.
-
2:35
When we pick an image, we'll hang onto a large image, but
-
2:38
we'll create a second copy that's cropped to fit the screen with on the filter view.
-
2:43
Now this is a significantly small image, and applying a filter should be faster, so
-
2:47
we'll display this one and use it temporarily.
-
2:51
Then, when we move on to the next screen, where we add tags and
-
2:54
a caption, while the user is working on that,
-
2:57
we can apply the filter to the main image in the background.
-
3:01
This sort of resembles what we did with the operations based approach.
-
3:05
We held on to the photo and we just used a much smaller one that was sized to
-
3:10
fit the screen to crop it and display the photo.
-
3:13
Now this does involve a fair bit of work re implementing this, but
-
3:16
we already know how to do this.
-
3:17
We already spent some time learning about resizing an image and
-
3:20
applying filter in the background using an operation.
-
3:23
So don't worry, I won't make you write all that code all over again.
-
3:26
I'll provide it in just a second.
-
3:28
Now before we get to that though, let's do a bit of refactoring, so we can crop
-
3:32
the image to fit the screen, while at the same time holding on to the main image.
-
3:37
Earlier when we wrote the code to resize the image by redrawing it into the rect
-
3:41
with a defined size, we did that right there in the lazy property.
-
3:46
So instead of writing those exact lines of code again,
-
3:48
we'll refactor that into a method in an extension of UI image.
-
3:53
So, let's add a new file to our helpers group, and
-
3:57
we'll call this UIImage+Resizing.
-
4:04
In here, we will start by importing the UI kit and
-
4:10
this is going to be a method that we call directly on an image.
-
4:13
So we'll add it as an extension of UI image.
-
4:16
We'll call this method resized to size
-
4:21
CGSize and this returns a UI image, an optional UI image instance.
-
4:27
Now, I'm following the swift naming conventions here.
-
4:29
Resized indicates that this is going to be a copy of the image, and
-
4:34
we're not going to mutate that original image.
-
4:37
And here we'll define a rect.
-
4:38
So, let rect = CGRect.
-
4:42
And we'll use the initializer that takes a size and a origin.
-
4:45
So you'll say .zero and pass in the size.
-
4:49
After that, we'll say UIGraphicsBeginImageContext,
-
4:57
with size rect.size.
-
5:00
This code should be familiar to you.
-
5:01
And then we'll say self.draw in rect,
-
5:05
we'll attempt to get the scaled image out.
-
5:10
So guard let scaledImage = UIGraphicsGetImageFromCurrentImageContext
-
5:17
else return nil.
-
5:20
This method returns an optional instance, so now if it doesn't work,
-
5:23
we'll go ahead and return nil.
-
5:25
We'll end the image context, and we'll return the scaledImage.
-
5:32
Okay, once we have this method, let's navigate to photo filter controller,
-
5:36
that's swift.
-
5:37
Scroll all the way up to the top.
-
5:39
Right now we have a reference to the photo, and
-
5:42
we're displaying that photo in the image view.
-
5:44
Instead of displaying this photo, let's define a lazy store property containing
-
5:48
the photo resized to fit the screen using our new method.
-
5:52
So we'll say lazy var displayPhoto: UIImage?
-
6:00
And we'll call this closure, and most of the work here is defining this size.
-
6:05
So first we'll unwrap the property because it's an optional.
-
6:10
If we don't have a photo, well, then we don't have a resize image.
-
6:13
So we can return nil.
-
6:15
Next, we need to get the current imageWidth.
-
6:19
Again, this should all be familiar.
-
6:20
So image.size.width and
-
6:24
the imageHeight, image.size.height.
-
6:30
We'll also get the screenWidth.
-
6:33
So UIScreen.main.bounds.
-
6:38
We need to figure out what the scaled ratio is between the screens width and
-
6:42
the images width.
-
6:42
So do let scaledRatio.
-
6:44
And this is to ensure that it fits across the entire width of the screen.
-
6:49
So screenWidth divided by imageWidth.
-
6:54
And using this now, we can get the scaled height.
-
6:56
So let scaledHeight = scaledRatio
-
7:02
times imageHeight, all right.
-
7:07
Okay, what is the issue here?
-
7:09
Main.bounds.width for the screen width.
-
7:12
There we go.
-
7:13
Okay, now let's define that size object.
-
7:15
So let size = CGSize.
-
7:20
For the width, we'll say screenWidth, for the height, we'll say scaledHeight.
-
7:25
And now we can return the image.resized
-
7:30
to the new size, which is that extension method we just wrote.
-
7:35
Okay, so let's modify some more code.
-
7:38
In here, we're displaying the main photo, inside viewed load,
-
7:41
let's now assign the display photo over to that photo image view.
-
7:46
Now, we're not going to remove the reference to the larger image though,
-
7:49
because we need to start an operation with that down the road.
-
7:52
Since the display photo is smaller, we can now apply a filter and
-
7:55
get a much quicker turnaround time.
-
7:57
We're going to hang on to the larger photo and apply the filter to that later.
-
8:01
At this point, we've gone through two different methods of applying a filter to
-
8:04
a photo, so I'm not going to make you implement it again for the third time.
-
8:09
In the node section, you'll find a link to an image filtration operation.swift.
-
8:14
This is a slightly modified version of the operation we wrote earlier, and
-
8:18
you can read the code to see what's going on.
-
8:20
Let's go ahead and add it to our project.
-
8:23
So in the helpers group, add a new file.
-
8:25
ImageFiltrationOperation, and then copy/paste that code in.
-
8:32
Okay, now back in photo filter controller.
-
8:36
Once we've obtained the filter back down here, let's start a filtration operation.
-
8:41
So first we need a filtration image.
-
8:43
Let image = FfiltrationImage.
-
8:47
And we're going to pass through the display photo.
-
8:51
Using this image, we can define an operation,
-
8:56
say let operation = ImageFiltrationOperation.
-
9:00
And like before, this class takes a filtration image and a filter.
-
9:06
When the operation completes,
-
9:07
we'll just modify the image displayed by the photo image view.
-
9:12
So we'll say operation.completionBlock equal, we need to assign a closure.
-
9:18
As always we need to check if the operation has been cancelled and
-
9:22
return if it has without doing any work.
-
9:24
Now here, once the method completes, once the operation completes,
-
9:30
we're simply going to jump back on to the main thread and
-
9:35
we're going to change the image assigned to the photo image view.
-
9:39
So say self.photoImageView.image =
-
9:43
operation.filtrationImage.image.
-
9:49
I'm now going to reassign this new image to the display photo property,
-
9:53
and that's because the next time I tap on a filter,
-
9:56
I want to apply this second filter back on the original image.
-
10:00
What I don't want is to apply the filter to an image that already has
-
10:04
a filter on it.
-
10:05
And if that happened, I would get a distorted view of what this filter looks
-
10:09
like and not the actual filtration operation.
-
10:12
Okay, to finish this up, let's go back to the top,
-
10:20
and we need to define our operation queue.
-
10:25
Right, that's the last step, so
-
10:28
let queue = OperationQueue.
-
10:33
And now back in the delegate method after the completion log,
-
10:38
we can say queue.addOperation(operation).
-
10:44
Okay, let's see what this looks like.
-
10:46
So now, we should quickly get that collection view with a bunch of filters
-
10:50
right below once we select an image.
-
10:58
Okay, perfect.
-
11:00
And now, once we tap on a cell,
-
11:02
we should apply the filter back to this display photo.
-
11:06
And there you go.
-
11:07
Look at that.
-
11:09
Let's try this black and white one.
-
11:11
Perfect, everything should work with minimal delay.
-
11:13
Now, there are several improvements you could make over here.
-
11:18
If you quickly tapped on every cell, we'd be firing off a bunch of operations for
-
11:21
absolutely no reason.
-
11:23
Keeping only one operation active for
-
11:25
the most recently tapped cell would be a less wasteful way of going about this.
-
11:30
So let's take a step back here.
-
11:31
We did a lot of work to get these filters working, and
-
11:34
you learned a bunch of new concepts including to new image formats.
-
11:37
But we still have plenty of work to do.
-
11:39
We still need to apply the filter back to the main image, the one that we store.
-
11:45
Right now we're hanging onto it with the photo property right at the top over here,
-
11:49
not doing anything with it.
-
11:51
And then on top of that this course is also about intermediate core data and
-
11:54
we haven't touched any of that yet.
-
11:57
Before we save the photos that we're creating,
-
11:59
we need to let the user input some information about the photos.
-
12:02
We'll tackle that over the next few videos.
You need to sign up for Treehouse in order to download course files.
Sign up