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
To display the image in a scroll view we can use an image view like we normally do. As you'll see in this video however, because of the way a scroll view behaves, we have do to some additional work to size and position the view correctly.
Resources
-
0:00
Let's recap what we learned in the last video.
-
0:03
A scrollable view defines a content size which is the size of the scrollable area.
-
0:08
We can add sub views to the scrollable view and place them anywhere within.
-
0:13
Their positions within the scrollable area are defined by their frame.
-
0:17
That is the layout and
-
0:19
size rectangle that is defined relative to this super views coordinate system.
-
0:23
The area of the scrollView that is displayed
-
0:26
on the screen is defined by the balance rectangle of the scrollView.
-
0:30
By modifying the balance origin,
-
0:32
we can give the user the impression that they're scrolling around a mainView.
-
0:37
As you might have guessed, there's a decent bit of math involved in
-
0:40
doing all this, and redrawing the right views on screen.
-
0:43
But thankfully, we don't have to worry about any of that.
-
0:47
When you scroll a scrollView, it modifies a property known as content offset.
-
0:52
Content offset is a point at which the origin of the contentView,
-
0:56
that is the bounds rectangle, is offset from the origin of the scrollView.
-
1:01
Using this value, the scrollView can compute its new bounds and
-
1:04
redraw any of its subviews.
-
1:06
So now that we have an idea of the basics of scrollViews,
-
1:10
let's step away from theory and get back to our app.
-
1:13
So from the project navigator, let's add a new file to our project.
-
1:18
So this is going to be a ViewController.
-
1:24
Named PhotoZoomController.
-
1:28
Now I'm just adding an empty Swift file.
-
1:30
You can add the co-attach subclass, same thing.
-
1:34
So at the top, I'll import UIKit.
-
1:38
And then this a class named PhotoZoomController,
-
1:43
which is a subclass of the UIViewController.
-
1:49
So empty viewDidLoad method and should be good to go.
-
1:54
Okay, for this video,
-
1:55
let's just focus on displaying a photo inside this ViewController.
-
1:58
Now this is not going to be as straightforward as dropping in an image
-
2:01
view because we want to zoom in on this image.
-
2:04
So instead we're going to drop in a scrollView from the object library.
-
2:08
Now let's go over Main.storyboard.
-
2:10
We need a scene so that we can modify all this and design our layout.
-
2:15
We'll bring out a new View Controller.
-
2:19
We'll drop that in.
-
2:21
We already have that sub class so let's go ahead and assign it.
-
2:25
In the identity inspector,
-
2:27
change the backing custom class to PhotoZoomController.
-
2:31
Okay, next let's look for a Scroll View from the object library and drop that in.
-
2:39
The Scroll View is going to take up the entire view, so
-
2:42
let's add in four constraints all the way to the edges.
-
2:45
So from the pin menu, we'll just select 0 on all four and
-
2:50
we want to constrain it all the way to the edges, so add four constraints.
-
2:54
And then, remember just like last time you can see here that it's pinning to the safe
-
2:59
area at the top.
-
2:59
And that's okay for the top and bottom, but on the sides,
-
3:03
we want to assign or align the trailing areas.
-
3:07
Let's double-click, to the Superviews Trailing, rather than the Scroll View,
-
3:13
or rather than the safe area.
-
3:14
So let's click that again, Size Inspector.
-
3:17
So the trailing is pinned to the Superview.
-
3:19
Let's do the same for the leading, double-click and
-
3:23
this needs to be Superview.
-
3:26
Okay, the reason we're allowing the Scroll View to take up the entire screen area
-
3:30
is so that we can zoom into a picture and
-
3:33
let it take up more than its original size on the screen.
-
3:37
Since we want to display an image next, we need to add an Image View.
-
3:42
We're going to add this as a subview of the Scroll View, so drop it right in here.
-
3:46
And then in your document outline,
-
3:48
it should be listed as a subview of the Scroll View.
-
3:51
Now like with the Scroll View, we're going to let the Image View
-
3:54
take up the entire space provided by the Superview.
-
3:57
We're doing this so that when we zoom, the Image View can take up the entire
-
4:01
scrollable content area as defined by the Scroll View.
-
4:06
But we're only going to show a zoomed in section by
-
4:08
manipulating that content offset.
-
4:11
If we add the same constraints as we did to the Scroll View,
-
4:14
that is constrained to the edges with on spacing.
-
4:17
Let's go ahead and do that.
-
4:18
So from the pin menu, we'll just do 0 for all four.
-
4:21
And since it's a sub view of the Scroll View,
-
4:23
we don't have to worry about the safe areas this time.
-
4:26
It should just go ahead and pin this directly to the Superview.
-
4:29
Now when we do that you'll see that it is not enough information for
-
4:32
interface builder which still complains and makes everything red.
-
4:36
If you click in the document outline on the error symbol and
-
4:41
look at the issues, you'll see that it is missing constraints for the X position and
-
4:45
the Y position of the Scroll View.
-
4:47
Now, this is kind of misleading.
-
4:49
The problem here is that Image View is depending
-
4:52
on the size of the Scroll View to define a height and width.
-
4:57
But at the same time, the Scroll View is depending on the Image View for
-
5:00
its dimensions.
-
5:02
Remember, we can define any sort of size for the content size of the Scroll View.
-
5:07
And that's how the Scroll View determines how large it is,
-
5:09
and therefore how we can scroll around it.
-
5:12
But these two are now relying on each other for
-
5:15
those dimensions, so there's a lot of ambiguity.
-
5:18
The layout engine cannot resolve this properly.
-
5:21
Now, fixing this is not difficult.
-
5:23
If we added a center vertically and horizontally constrained
-
5:29
to the Image View, that should be enough information that provides the layout
-
5:33
engine with enough information to resolve positioning and size a run time.
-
5:38
But unfortunately this gets in our way.
-
5:40
So while layout is resolved here, an interface builder satisfied,
-
5:44
center accent, center wide constraints actually prevent us from building
-
5:49
the functionality we want.
-
5:51
Remember that if we're pinning the center of the Image View to the center of
-
5:54
the screen, we can't move it from side to side since the center's always pinned.
-
5:58
Don't worry though, there's another solution here.
-
6:01
So let's go ahead, so now that you've added the center x and
-
6:04
center y constraints and Interface Builder's happy.
-
6:07
The next thing we're going to do is navigate to the Size Inspector
-
6:10
of the Image View and look for the constraints we just added.
-
6:14
So you should see the constraints here, Align Center X and Align Center Y.
-
6:18
Double-click on Align Center X and
-
6:21
we'll go and select this box that says this is a place holder constraint.
-
6:26
This tells Interface Builder to go ahead and
-
6:28
remove the constraints when we build and run this code.
-
6:32
Now, this seems silly to do, but it allows the layout engine to figure out a base
-
6:36
layout, and then we can modify that base layout at run time.
-
6:40
So do that and then select the Image View in GoMac, and do this for
-
6:44
the center Y constraint as well.
-
6:47
Now, none of this would be an issue if we defined the layout in code.
-
6:50
But now you can see if you work with Interface Builder,
-
6:53
the problem is you need to satisfy a set of constraints.
-
6:57
You need to let Interface Builder be happy with your constraints at build time.
-
7:01
You can't rely on the constraints being correct at run time
-
7:04
only because we won't be able to build and run this app if we have these errors.
-
7:08
Well, we could, but everything would break.
-
7:10
By removing the constraints at build time,
-
7:12
we now need to make sure that we define the size of the Image View in code, so
-
7:17
that the layout engine can size and position all our views accordingly.
-
7:22
So what we'll do, first steps first,
-
7:26
we'll setup up some outlets to these views so that we can use them in code.
-
7:30
Now, before you do that, I forgot, we need to set the content mode of the Image View.
-
7:35
So that's in the Attributes inspector.
-
7:37
We need to send it to Aspect Fit.
-
7:40
This way the Image View maintains the aspect ratio of the picture and
-
7:44
it fits it to the screen width.
-
7:46
This is how the Photos App does it as well so we'll just mimic the behavior, okay.
-
7:50
Once you do that, switch over to the Assistant Editor.
-
7:53
You'll want the document outline up, because we want outlets for
-
7:57
both the Image View and the Scroll View.
-
8:00
And it's hard to get to the underlying Scroll View from here, obviously,
-
8:03
because it's under the Image View.
-
8:05
Now set this to Automatic, that doesn't work, so I'm gonna do Cmd + Shift + O.
-
8:11
This is the PhotoZoomController and then holding down the Option key press Enter.
-
8:16
All right let's add this Outlets.
-
8:19
So the first one I want is the Scroll View.
-
8:21
I'll just name this scrollView, Xcode 9 error.
-
8:26
Just make sure you build this.
-
8:29
Okay there we go, now Ctrl+drag, we'll call this scrollView.
-
8:34
And then we'll call this one the Image View, we'll call it photoImageView.
-
8:40
We'll also need a stored property in here to maintain a reference to the photo
-
8:44
instance.
-
8:45
Say var photo of type Photo.
-
8:48
As always, the reason here for the explicitly unwrapped optional is so
-
8:51
that we can initialize the class from a storyboard identifier
-
8:54
without assigning any initial values.
-
8:57
In viewDidLoad we can assign the photo to the Image Viewer and get it from there.
-
9:02
So let's see what this looks like.
-
9:04
We want to launch the Photo Zoom Controller
-
9:07
when we tap on the photo inside of the Page View Controller.
-
9:11
The Page View Controller encapsulates instances of the Photo View Controller.
-
9:16
So what we'll do is add a Tap Gesture Recognizer
-
9:20
to the Image View over here in the Photo Viewer Controller scene.
-
9:24
So from the object library, look for Tap Gesture Recognizer.
-
9:30
And drag and drop to the Image View.
-
9:33
Actually, this is easier on the document outline.
-
9:35
So pull up the Photo Viewer Controller scene.
-
9:38
Look for that Image View, and then add,
-
9:41
we'll just drop it here, add a Tap Gesture Recognizer.
-
9:45
Now still, with the document outline open,
-
9:47
you should have seen it added to the bottom right here.
-
9:51
We'll again switch over to the Assistant Editor, and
-
9:56
I'm going to switch over to the PhotoViewerController.
-
10:01
Oops I needed to press, so again Cmd+Shift +O,
-
10:04
PhotoViewerController, holding Option, hit Enter, there we go.
-
10:09
And now let's Ctrl+drag from the Tap Gesture Recognizer to create an action,
-
10:14
you know what I'm going to build this first so
-
10:17
I don't run into any errors again.
-
10:21
And I'll Ctrl+drag, and we'll say launch.
-
10:24
This is an action.
-
10:26
LaunchPhotoZoomController, and hit Connect, okay?
-
10:34
Because we're not defining a storyboard segue, we need to give this new
-
10:37
PhotoZoomController a storyboard identifier from the Identity Inspector so
-
10:42
that we can reference it and we can instantiate it in code.
-
10:46
We'll set this property to PhotoZoomController.
-
10:51
In the IBAction, we just defined, so
-
10:53
right here, let me get out Assistant Editor mode so you can see better.
-
10:58
PhotoViewerController, so
-
11:01
in here, we'll start by grabbing a reference to the storyboard.
-
11:04
So guard let storyboard = storyboard else return.
-
11:11
And then, using the storyboard, we'll just tack on a few more checks.
-
11:16
We'll instantiate the PhotoZoomController.
-
11:18
So we'll say, let zoomController = storyboard.instantiateViewController.
-
11:26
But you know what, we don't need that there.
-
11:33
Let's go ahead and just do it right below.
-
11:36
So we say, let zoomController =
-
11:38
storyboard.instantiateViewController(with- Identifier:.
-
11:44
Now we can either use the string or we can do something
-
11:49
like String(describing: PhotoZoomController.self)) and
-
11:56
that should give us the string we typed name.
-
12:01
We'll go ahead and use an explicitly unwrapped optional here to cast or
-
12:04
rather we'll force cast it to the right type.
-
12:07
If you don't wanna do that, you can add it on to the guard check here.
-
12:10
No big deal.
-
12:11
And then we'll assign the photo,
-
12:13
zoomController.photo = photo, that should be good.
-
12:19
Let's push the controller onto the navigation stack, so it appears modally.
-
12:23
NatigationController.present(zoomControl- ler,
-
12:30
animated: true, completion: nil).
-
12:34
We'll need to make one tiny change here, if you run the app now.
-
12:38
Well we'll need to make more than one tiny change here, but if you run the app now.
-
12:42
And tap on the photo view, nothing will happen.
-
12:45
And that's because by default, user interaction is disabled on certain views.
-
12:50
So if you click on this Image View in the PhotoViewerController,
-
12:53
go to the Attributes inspector,
-
12:55
you need to check the user interaction enabled box, okay.
-
13:00
One more change, one more small change, we'll go back to the PhotoZoomController.
-
13:08
And here inside viewDidLoad,
-
13:11
we'll say photoImageView.image = photo.image.
-
13:16
Okay, and let's give this a try, run the app
-
13:24
So here we have some photos, we'll click on one.
-
13:27
Now this launches the PageController, remember,
-
13:29
where we can thumb through photos.
-
13:30
And now if we tap on this photo, it should launch the PhotoZoomController.
-
13:35
And it seems like we have an exception, okay.
-
13:38
What is this about?
-
13:41
Does not contain a ViewController with identifier.
-
13:45
Let's see if this was because we tried that trick.
-
13:49
Got ahead of myself.
-
13:50
Let's see, so we were in the PhotoViewerController.
-
13:55
Let's see if the issue here is that we used this.
-
13:59
Instead, we're just going to use the name, PhotoZoomController.
-
14:05
I might have made a typo in main.storyboard.
-
14:08
So let's go there and check, as well.
-
14:10
And you might have even noticed it while I was doing it.
-
14:14
We forgot to set the storyboard ID, interesting.
-
14:16
Did I do that elsewhere?
-
14:18
No, okay.
-
14:19
So we'll paste it in, hit Enter.
-
14:21
So you can use that string describing
-
14:24
pattern if you want to rather than just stringly typing the name.
-
14:27
Okay, so now we have a photo.
-
14:29
We'll click it, or tap it, we'll tap in the middle and there we go.
-
14:32
This launches the photo that we want.
-
14:35
You'll see, however, that it fills up the entire view and
-
14:38
does not look at all like how the built-in Photos app launches a photo.
-
14:42
Well this has to do with Scroll Views.
-
14:44
But let's take a break here.
-
14:46
In the next video we'll explore how we can fix this issue.
You need to sign up for Treehouse in order to download course files.
Sign up