iphone dev 入门实例6:How To Use UIScrollView to Scroll and Zoom and Page
http://www.raywenderlich.com/10518/how-to-use-uiscrollview-to-scroll-and-zoom-content
Getting Started
Fire up Xcode and create a new project with the iOS\Application\Single View Application template. Enter ScrollViews for the product name, enter the company identifier you used when creating your App ID, leave the class prefix blank, set device family to iPhone, and make sure that Use Storyboards and Use Automatic Reference Counting are checked (but leave the other checkboxes unchecked):
Click Next and choose a location to save your project.
Then download the resources for this project and extract the contents of the ZIP file into a temporary folder.
Drag & drop the extracted files into the project root, and make sure that the “Copy items into destination group’s folder (if needed)” checkbox is ticked if you are dragging and dropping from a temporary folder.
Since this tutorial is going to illustrate four different things that you can do with scroll views, the project will have a tableview menu with four options from which to select. Each option will open a new view controller to show off a certain aspect of scroll views.
This is what your storyboard will look like when you’re finished:
To build the tableview menu, do the following:
- Open MainStoryboard.storyboard and delete the scene that’s already in there by selecting the view controller (click on it on the story board) and then deleting it.
- Then, add a table view controller by dragging one from the Object Library on to the story board.
- Now select the table you added and then click Editor\Embed In\Navigation Controller.
- Select the table view within the table view controller, and set the content type to Static Cells in the attributes inspector (as shown in image below).
- Finally, set the number of rows in the table view section (if you don’t see it, tap on the arrow next to “Table View” in the left sidebar showing the storyboard hierarchy and then select “Table View Section”) to 4, and for each row in the table view, set its style to basic and edit the labels to read:
- Image scroll
- Custom view scroll
- Paged
- Paged with peeking
Note: For item #5 above, as you change each table row’s style to “Basic”, the table row will get an additional sub-component, the label. You might need to again expand the row items to be able to see the sub-component items and to edit them.
Save the storyboard, and build and run. You should see your table view, similar to the image below. Sadly, the tableview does absolutely nothing at the moment – but we can fix that!
Scrolling and Zooming a Large Image
The first thing you’re going to learn is how to set up a scroll view that allows the user to zoom into an image and pan around.
First, you need to set up the view controller. Select ViewController.h, add an outlet for a UIScrollView called scrollView and declare that the view controller is going to be a UIScrollView delegate, like so:
#import <UIKit/UIKit.h> |
Then, synthesize scrollView at the top of ViewController.m, just below the @implementation line:
@synthesize scrollView = _scrollView; |
Back in the storyboard, drag a view controller from the objects list onto the storyboard and set its class to ViewController.
Right-click on the first table row (either on the left sidebar or on the main storyboard view) and a connections popup will appear. Drag from the Push outlet under Storyboard Segues to the new view controller.
Alternatively, you can control click on the table row and drag to the new view controller and a popup should appear with the segue options. You can select Push from there.
Drage a scroll view from the object library to completely fill the view controller.
Wire up the scroll view to the view controller by attaching it to the scrollView outlet and setting the view controller as the scroll view’s delegate.
Now you’re going to get down and dirty with ViewController.m. First you need to declare some properties and methods in the class continuation category at the top of the file (above the @implementation line).
@interface ViewController () |
Note: The class continuation category is a special category which is only allowed in the implementation file of the class and has no specified name. It allows you to specify properties and methods (and even instance variables) that are private.
That should give you a sneak peek of what you’re going to be doing in a moment. But first, synthesize the property you just added.
@synthesize imageView = _imageView; |
And now it’s time to get into the most interesting part of setting up the scroll view. Replace viewDidLoad and viewWillAppear: with the following code:
- (void)viewDidLoad { |
This might look complicated, so let’s break it down step-by-step. You’ll see it’s really not too bad.
- First, you need to create an image view with the photo1.png image you added to your project and you set the image view frame (it’s size and position) so it’s the size of the image and sits at point 0,0 within the parent. Finally, the image view gets added as a subview of your scroll view.
- You have to tell your scroll view the size of the content contained within it, so that it knows how far it can scroll horizontally and vertically. In this case, it’s the size of the image.
- Here you’re setting up two gesture recognizers: one for the double-tap to zoom in, and one for the two-finger-tap to zoom out. If you’re unfamiliar with how these work, then I suggest reading this tutorial.
- Next, you need to work out the minimum zoom scale for the scroll view. A zoom scale of one means that the content is displayed at normal size. A zoom scale below one shows the content zoomed out, while a zoom scale of greater than one shows the content zoomed in.
To get the minimum zoom scale, you calculate how far you’d need to zoom out so that the image fits snugly in your scroll view’s bounds based on its width. Then you do the same based upon the image’s height. The minimum of those two resulting zoom scales will be the scroll view’s minimum zoom scale. That gives you a zoom scale where you can see the entire image when fully zoomed out.
- You set the maximum zoom scale as 1, because zooming in more than the image’s resolution can support will cause it to look blurry. You set the initial zoom scale to be the minimum, so that the image starts fully zoomed out.
- We’ll come back to this in a bit. For now, just understand that this will center the image within the scroll view.
You might ask why we don’t do all of the above in viewDidLoad, and you’d be right to ask. The reason you can’t is that the view size isn’t definitely known until viewWillAppear:, and since you use the size of scrollView when calculating the minimum zoom, things might go wrong if we do it in viewDidLoad.
Now let’s look at those methods that I said you’d implement – centerScrollViewContents, scrollViewDoubleTapped: and scrollViewTwoFingerTapped:. Add the following code above viewDidLoad:
- (void)centerScrollViewContents { |
The point of this method is to get around a slight annoyance with UIScrollView, which is: if the scroll view content size is smaller than its bounds, then it sits at the top-left rather than in the center. Since you’ll be allowing the user to zoom out fully, you’d rather like the image to sit in the center of the view, wouldn’t you? :] This method accomplishes that by positioning the image view such that it is always in the center of the scroll view’s bounds.
Next up is scrollViewDoubleTapped:. Add this just above viewDidLoad again:
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer { |
This method is called when the tap gesture recognizer fires. Remember, you set that up to recognize double-tap events. Here’s a step-by-step guide to what scrollViewDoubleTapped: does:
- First, you need to work out where the tap occurred within the image view. You’ll use this to zoom in directly on that point, which is probably what you’d expect as a user.
- Next, you calculate a zoom scale that’s zoomed in 150%, but capped at the maximum zoom scale you specified in viewDidLoad.
- Then you use the location from step #1 to calculate a CGRect rectangle that you want to zoom in on.
- Finally, you need to tell the scroll view to zoom in, and here you animate it, as that will look pretty.
The final method to implement is scrollViewTwoFingerTapped:. Once again, add this aboveviewDidLoad:
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer { |
This is similar to the way you zoomed in, except you don’t bother calculating anything about where the user tapped in the view, because it doesn’t particularly matter. Trust me, it’ll look good if you just do it like this. :]
Now, remember how you set up ViewController as a UIScrollView delegate? Well, now you’re going to implement a couple of needed methods for a UIScrollView delegate. The first is viewForZoomingInScrollView. Add the following above viewDidLoad:
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView { |
This is the heart and soul of the scroll view’s zooming mechanism. You’re telling it which view should be made bigger and smaller when the scroll view is pinched. So, you tell it that it’s your imageView.
The second method you’ll need to implement is scrollViewDidZoom:, which is a notification when the scroll view has been zoomed. Here you need to re-center the view – if you don’t, the scroll view won’t appear to zoom naturally, instead, it will sort of stick to the top-left. Add this above viewDidLoad:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView { |
Now take a deep breath, give yourself a pat on the back and build and run your project. Tap on Image scroll and if everything went smoothly, you’ll end up with a lovely image that you can zoom, pan and tap. w00t!
Scrolling and Zooming a View Hierarchy
What if you want more than an image in your scroll view? What if you’ve got some complex view hierarchy which you want to be able to zoom and pan around? Well, there’s a scroll view for that! What’s more, it’s just a small step beyond what you’ve done already.
Create a new file with the iOS\Cocoa Touch\UIViewController subclass template. Name the classCustomScrollViewController and make sure that Targeted for iPad and With XIB for user interface are not checked. Click Next and save it with the rest of the project.
Open CustomScrollViewController.h and replace the contents with this:
#import <UIKit/UIKit.h> |
Next, go to MainStoryboard.storyboard and just as before, add a view controller that’s wired up with a push segue from the 2nd row of the table. This time, set the view controller’s class to be the class just created, CustomScrollViewController.
Also add a scroll view and connect it to the outlet created and set the view controller as its delegate, just as before.
Then, open CustomScrollViewController.m and set up the class continuation category at the top (above the @implementation line) like this:
@interface CustomScrollViewController () |
And add the property synthesizers below the @implementation line:
@synthesize scrollView = _scrollView; |
You’ll probably notice the lack of gesture recognizer callbacks. That is simply to make this part of the tutorial more straightforward. Feel free to add them in afterwards as an additional exercise.
The only other difference compared to the previous view controller is that instead of a UIImageView, we’ve got a UIView and it’s called containerView. That should be a little hint as to how this is all going to work.
Now, implement viewDidLoad and viewWillAppear: like so.
- (void)viewDidLoad { |
You might be feeling a sense of deja-vu here, as it’s very familiar code. In fact, viewWillAppear: is almost identical to the previous code, except for the line where you set the zoomScale. Here, we set the zoomScale to 1 instead of minScale so that we’d have the content view at normal size instead of it fitting the screen. Since we aren’t going to implement the zoom handlers, if the view fits the screen, you will not be able to test the scroll view by panning the view around.
viewDidLoad, however, sets up a view hierarchy with a single root view, which is your instance variable, containerView. Then that single view is added to the scroll view. That is the key here – just one view can be added to the scroll view if you’re going to be zooming in, because as you’ll recall, you can only return one view in the delegate callback, viewForZoomingInScrollView:.
Again, implement centerScrollViewContents and the two UIScrollView delegate methods, substituting imageView with containerView from the original versions. (You can add the code aboveviewDidLoad, as before.)
- (void)centerScrollViewContents { |
Now build and run your project. This time, select Custom view scroll and watch in amazement as you can pan around a beautifully hand-crafted scene. (If you add in the gesture recognizers from the previous code, you’ll even be able to zoom in and out.)
Paging with UIScrollView
In the third part of this tutorial, you’ll be creating a scroll view that allows paging. This means that the scroll view locks onto a page when you stop dragging. You’ll see this in action in the Apple App Store app when you view screenshots of an app, for instance.
Create a new file with the iOS\Cocoa Touch\UIViewController subclass template. Name the classPagedScrollViewController and make sure that Targeted for iPad and With XIB for user interface are not checked. Click Next and save it with the rest of the project.
Open PagedScrollViewController.h and set it up like so:
#import <UIKit/UIKit.h> |
Next, go to MainStoryboard.storyboard and just like before, add a view controller that’s wired up with a push segue from the 3rd row of the table. This time, set the view controller’s class to be the class just created, PagedScrollViewController.
Also, add and wire up a scroll view to the outlet created, and set the view controller as its delegate, just like before.
This time, turn on Paging Enabled for the scroll view via the Attributes Inspector.
Also, add a Page Control element at the bottom of the view (if you made the scroll view fit the screen, you might need to adjust the height of the scroll view first) and make it fill the width. Wire it up to the pageControl outlet. Be careful not to add it as a subview of the scroll view, but rather as a sibling of the scroll view. It should look like this in Interface Builder:
Finally, set the background color of the main view to be black, so that the page control will be visible (it is white by default, and white on white really doesn’t work!).
Now open PagedScrollViewController.m and once again set up the class continuation category:
@interface PagedScrollViewController () |
And synthesize instance variables:
@synthesize scrollView = _scrollView; |
You’ll notice a lot of differences this time. There’s no container view, there are two arrays and there are three new methods. I’ll get to the methods in due course but for now, let me explain the arrays:
- pageImages: This will hold all the images to display – 1 per page.
- pageViews: This will hold instances of UIImageView to display each image on its respective page. It’s a mutable array, because you’ll be loading the pages lazily (i.e. as and when you need them) so you need to be able to insert and delete from the array.
Next, implement viewDidLoad and viewWillAppear: as follows:
- (void)viewDidLoad { |
Breaking that down, this is what happens:
- First, you set up the page images. You’ve added five photos to the project and so you just make an array containing all of them.
- Then, you tell the page control how many pages there are, and that it should start on page 0.
- Next, you set up the array that holds the UIImageView instances. At first, no pages have been lazily loaded and so you just fill it with the right amount of NSNull objects that are needed – one for each page. You’re using [NSNull null] because it’s a lightweight singleton object that can be added to an array to signify a placeholder. Later on, you’ll use the fact that there is an NSNull in there to know if that page is loaded or not.
- The scroll view, as before, needs to know its content size. Since you want a horizontal paging scroll view (it could just as easily be vertical if you want), you calculate the width to be the number of pages multiplied by the width of the scroll view. The height of the content is the same as the height of the scroll view.
- You’re going to need some pages shown initially, so you call loadVisiblePages, which you’ll implement shortly.
It’s time to implement the three methods defined earlier – loadVisiblePages, loadPage: andpurgePage:.
Add the following above viewDidLoad:
- (void)loadPage:(NSInteger)page { |
Breaking that down:
- First, you check if you’ve already loaded the view. If you haven’t, then the object in the pageViews array will be an NSNull (remember, [NSNull null] is a special singleton which is why == works).
- If you made it into the if statement, then you need to create a page. So first, work out the frame for this page. It’s calculated as being the same size as the scroll view, positioned at zero y offset, and then offset by the width of a page multiplied by the page number in the x (horizontal) direction.
- This creates a new UIImageView, sets it up and adds it to the scroll view.
- Finally, you replace the NSNull in the pageViews array with the view you’ve just created, so that if this page was asked to load again you would now not go into the if statement and instead do nothing since the view for the page has already been created.
Next, add this above viewDidLoad:
- (void)purgePage:(NSInteger)page { |
This method purges a page that was previously created via loadPage:. It first checks that the object in the pageViews array for this page is not an NSNull. If it’s not, it removes the view from the scroll view and updates the pageViews array with an NSNull again to indicate that this page is no longer there.
Why bother lazy loading, you ask? Well, in this example, it won’t matter too much if you load all the pages at the start, since there’s only five and they won’t be large enough to eat up too much memory. But imagine you had 100 pages and each image was 5MB in size. That would take up 500MB of memory if you loaded all the pages at once! Your app would bomb out, exceeding the amount of memory available. Lazy loading means that you’ll only have a certain number of pages in memory at any given time.
The two methods we defined above are tied together via a method called loadVisiblePages. Add the following above viewDidLoad:
- (void)loadVisiblePages { |
Here you work out what page the scroll view is currently on, update the page control and then load or purge the relevant pages. The calculation of what page you’re on looks a bit scary, but it’s not too bad. You can convince yourself it’s correct by plugging some numbers in. (Note that the floor() function will round a decimal number to the next lowest integer.)
You choose to load the current page and the page on either side of it. This is so that as the user starts scrolling, they can see the next page before it becomes the central one. You could load the previous and next two pages if you wanted, but this would increase memory usage and serves no useful purpose.
The final thing to do is to implement part of the UIScrollView delegate. This time you just need to implement scrollViewDidScroll:. Add this to PagedScrollViewController.m above viewDidLoad:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { |
All this does is ensure that as the scroll view is scrolled, the relevant pages are always loaded (and that unnecessary pages are purged).
Build and run the project, select Paged and marvel at the wonderful paged scroll view you’ve just created!
Viewing Previous/Next Pages
For the final addition to this project, I’m going to show you how you can make a scroll view that looks a lot like the screenshot viewer when browsing apps on the App Store app. You get to see parts of the previous and next pages, and it’s a great technique because the user can immediately see there’s extra content they can scroll through.
Create a new file with the iOS\Cocoa Touch\UIViewController subclass template. Name the classPeekPagedScrollViewController and maker sure that Targeted for iPad and With XIB for user interface are not checked. Click Next and save it with the rest of the project.
Open PeekPagedScrollViewController.h and set it up like so:
#import <UIKit/UIKit.h> |
Next, open PeekPagedScrollViewController.m. Set up the class continuation category as before:
@interface PeekPagedScrollViewController () |
And synthesize the instance variables:
@synthesize scrollView = _scrollView; |
Then implement viewDidLoad and viewWillAppear:, which are the same as in the previous section’s paged scroll view example.
- (void)viewDidLoad { |
Then, implement loadVisiblePages, loadPage:, purgePage:, and the scrollViewDidScroll: UIScrollView delegate method, which are also identical to the previous section, except for a slight difference in loadPage, explained below.
- (void)loadVisiblePages { |
The only difference in the above methods from those implemented in the earlier sections is the addition of the following line of code in loadPage:.
frame = CGRectInset(frame, 10.0f, 0.0f); |
This line sets the frame of the image view to be slightly inset horizontally, such that the pages don’t touch. It makes it look pretty, similar to the screenshots viewer in the App Store app.
Now go to MainStoryboard.storyboard, and just as in the previous example, add a view controller that’s wired up with a push segue from the 4th row of the table. This time, set the view controller’s class to be the class just created, PeekPagedScrollViewController.
Also add and wire up a paging enabled scroll view to the outlet created, and set the view controller as its delegate, just as before. And again, add a page control element and wire it up. And don’t forget to set the main view background to black so that the paging control shows up.
Make the scroll view smaller than the screen – my suggestion is 240×312 – and center it on the screen. It should end up looking like this:
Next, turn off Clip Subviews for the scroll view. This will allow it to draw outside of its view, which is important for the peeking of pages.
Build and run, choose Paged with peeking, and there you have it! Well done!
Detecting Touches Outside a Scroll View
You may have just noticed with the peeking pages that you now can’t click outside of the scroll view region. That’s not ideal now, is it? But we can fix it!
The problem is that the scroll view only gets the touches if they occur within its bounds, and now that the bounds are smaller than the area it draws into (because Clip Subviews is off), it will miss some touches. We’re going to fix it by wrapping the scroll view in a container view whose job it is to intercept touches and hand them off to the scroll view.
Create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class ScrollViewContainer, and make it a subclass of UIView. Click Next and save it along with the rest of the project.
Open ScrollViewContainer.h and replace its contents with the following:
#import <UIKit/UIKit.h> |
You’re going to need to tell the view which scroll view it’s wrapping, that’s why you’ve added an outlet for a UIScrollView.
Open ScrollViewContainer.m and replace its contents with the following:
#import "ScrollViewContainer.h" |
That’s simple, right? I bet you thought there’d be lines and lines of code. Well, not today. All this does is hand control over to the scroll view for any touches that occur within the container view’s bounds.
Now you need to actually use the new container view you’ve created.
Go to MainStoryboard.storyboard and back to the view controller for this example. Select the scroll view and then click Editor\Embed In\View. This should have created a view that the scroll view now sits within.
Make this new view the width of the screen and the height of the scroll view. Then set its class toScrollViewContainer. Also wire up the scrollView outlet of the container to the scroll view. It should then look like this:
If you set the background for the main view to black so that the page control would show up, then don’t forget to set the background for the container view to black as well. Since it sits on top of the main view, the container view will, by default, have a white background.
Build and run. Choose Paged with peeking. Notice you can now tap outside the scroll view’s bounds as you desire. w00t! How cool is that and all done with just a few lines of code!
iphone dev 入门实例6:How To Use UIScrollView to Scroll and Zoom and Page的更多相关文章
- iphone dev 入门实例7:How to Add Splash Screen in Your iOS App
http://www.appcoda.com/how-to-add-splash-screen-in-your-ios-app/ What’s Splash Screen? For those who ...
- iphone dev 入门实例5:Get the User Location & Address in iPhone App
Create the Project and Design the Interface First, create a new Xcode project using the Single View ...
- iphone dev 入门实例4:CoreData入门
The iPhone Core Data Example Application The application developed in this chapter will take the for ...
- iphone dev 入门实例3:Delete a Row from UITableView
How To Delete a Row from UITableView I hope you have a better understanding about Model-View-Control ...
- iphone dev 入门实例2:Pass Data Between View Controllers using segue
Assigning View Controller Class In the first tutorial, we simply create a view controller that serve ...
- iphone dev 入门实例1:Use Storyboards to Build Table View
http://www.appcoda.com/use-storyboards-to-build-navigation-controller-and-table-view/ Creating Navig ...
- iphone Dev 开发实例10:How To Add a Slide-out Sidebar Menu in Your Apps
Creating the Xcode Project With a basic idea about what we’ll build, let’s move on. You can create t ...
- iphone Dev 开发实例9:Create Grid Layout Using UICollectionView in iOS 6
In this tutorial, we will build a simple app to display a collection of recipe photos in grid layout ...
- iphone Dev 开发实例8: Parsing an RSS Feed Using NSXMLParser
From : http://useyourloaf.com/blog/2010/10/16/parsing-an-rss-feed-using-nsxmlparser.html Structure o ...
随机推荐
- codeforces 192a
link: http://codeforces.com/contest/330/problem/A brute force. /* ID: zypz4571 LANG: C++ TASK: 191a. ...
- python--迭代--7
原创博文,转载请标明出处--周学伟http://www.cnblogs.com/zxouxuewei/ 一.什么是迭代 在Python中,如果给定一个list或tuple,我们可以通过for循环来遍历 ...
- java窗口添加背景
1.import javax.swing.ImageIcon; 2.import javax.swing.JFrame; 3.import javax.swing.JLabel; 4.import j ...
- leetcode 140. Word Break II ----- java
Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each ...
- php 倒计时程序
<html> <head> <meta http-equiv="Content-Type" content="text/html; ch ...
- PHP太怪了,in_array() ,strpos,
PHP中在某个字符中查找另外一个字符串,是否存在,用的是strpos,此函数用法,经常很多人用反了,正确的用法是strpos(string,search),strstr等,前面是原字符串,后面是要在原 ...
- http协议传输二进制数据以及对输入流(php://input)和http请求的理解
1.index.php <?php $data=file_get_contents('./a.jpg'); $opts = array('http' => array( 'method' ...
- Don’t Assume – Per Session Buffers
MySQL has a number of global buffers, i.e. your SGA. There are also a number of per session/thread b ...
- asp.net如何确定是哪些控件验证失败
Page.Validate("FeedBack"); if (!Page.IsValid) { foreach (IValidator validator in Validator ...
- docker加速器
https://cr.console.aliyun.com/#/docker/booster 阿里云开发者账号注册后,获得一专属加速器地址. 转发自: http://www.imike.me/2016 ...