How To Use UIScrollView to Scroll and Zoom Content in Swift

How To Use UIScrollView to Scroll and Zoom Content in Swift
Learn how to use UIScrollViews for paging, zooming, scrolling, and more!

Learn how to use UIScrollViews for paging, zooming, scrolling, and more!

Note from Ray: This is a Swift update to a popular Objective-C tutorial on our site, released as part of the iOS 8 Feast. Update by Michael Briscoe, Original post by Tutorial Team member Matt Galloway. Enjoy!

UIScrollView is one of the most versatile and useful controls in iOS. It is the basis for the very popular UITableView and is a great way to present content larger than a single screen. In this UIScrollView tutorial you’ll learn all about using this control:

  • How to use a UIScrollView to view a very large image.
  • How to keep the UIScrollView’s content centered while zooming.
  • How to embed a complex view hierarchy inside a UIScrollView.
  • How to use UIScrollView’s paging feature, in conjunction with UIPageControl, to allow scrolling through multiple pages of content.
  • How to make a “peeking” UIScrollView that gives a glimpse of the previous/next page as well as the current page.
  • And much more!

This tutorial assumes some familiarity with Swift and iOS programming. If you are a complete beginner, you may wish to check out some of the other tutorials on this site first.

This tutorial also assumes that you know how to use Interface Builder to add new objects to a view and connect outlets between your code and the Storybard. You’ll want to be familiar with Storyboards before proceeding, so definitely take a look at the Storyboards tutorial on this site if you’re new to Storyboards and/or Interface Builder.

Getting Started

Fire up Xcode and create a new project. Choose File\New\Project…, then choose the iOS\Application\Single View Application template. Enter ScrollViews for the product name, choose Swift for the language, and set devices to iPhone.

Create Project

Click Next and choose a location to save your project.

Then download the resources for this project and drag & drop the extracted files into the project root. Make sure that the “Copy items if needed” checkbox is ticked.

Copy Resources

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:

Storyboard Overall

To build the tableview menu, do the following:

  1. Open Main.storyboard and delete the scene that’s already in there by selecting the view controller (click on it on the storyboard) and then deleting it.
  2. For this tutorial you’re going to disable Auto Layout. From the Utilities panel, choose the File Inspector, and uncheck Use Auto Layout. From the alert that appears, make sure “Keep size class data for:” is set to iPhone. Then choose Disable Size Classes.
  3. Disable Size Classes

  4. Next, add a Table View Controller by dragging one from the Object Library on to the storyboard.
  5. Now select the table you added and choose Editor\Embed In\Navigation Controller.
  6. 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).
  7. Static Cells

  8. Click on the arrow next to Table View in the Document Outline showing the storyboard hierarchy and then select Table View Section. In the inspector, set the number of rows in the section to 4.
  9. UIScrollView table view section

  10. 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: 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 table view does absolutely nothing at the moment – but you can fix that!

Scroll View Run 1

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. Open ViewController.swift, and change the class declaration at the top of the file so it also conforms to the UIScrollViewDelegate protocol:

class ViewController: UIViewController, UIScrollViewDelegate {

Inside the class declaration, add the following outlet property:

@IBOutlet var scrollView: UIScrollView!

You’ll wire this up to the actual scroll view in the next step.

Open the storyboard and drag a View Controller from the objects library onto the canvas. Select the new view controller and in the Identity Inspector, set its class to ViewController.

View controller class type

This view controller will show the image scroll demo. Control-click and drag from the Image Scroll row of the table view to the new view controller. In the popup menu that appears, select Push under Selection Segue. This will wire things up so the view controller is pushed onto the navigation stack when the user selects that first row.

Drag a Scroll View from the object library to completely fill the view controller.

Add Scroll View

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.

Scroll View Outlets

Now you’re going to get down and dirty with the code. Open ViewController.swift and add a new property to the class:

var imageView: UIImageView!

This will hold the image view that the user will be scrolling around.

Now it’s time to get into the most interesting part of setting up the scroll view. Replace viewDidLoad with the following code:

override func viewDidLoad() {
  super.viewDidLoad()
 
  // 1
  let image = UIImage(named: "photo1.png")
  imageView = UIImageView(image: image)
  imageView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size:image.size)
  scrollView.addSubview(imageView)
 
  // 2
  scrollView.contentSize = image.size
 
  // 3
  var doubleTapRecognizer = UITapGestureRecognizer(target: self, action: "scrollViewDoubleTapped:")
  doubleTapRecognizer.numberOfTapsRequired = 2
  doubleTapRecognizer.numberOfTouchesRequired = 1
  scrollView.addGestureRecognizer(doubleTapRecognizer)
 
  // 4
  let scrollViewFrame = scrollView.frame
  let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
  let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
  let minScale = min(scaleWidth, scaleHeight);
  scrollView.minimumZoomScale = minScale;
 
  // 5
  scrollView.maximumZoomScale = 1.0
  scrollView.zoomScale = minScale;
 
  // 6
  centerScrollViewContents()
}

This might look complicated, so let’s break it down step-by-step. You’ll see it’s really not too bad.

  1. 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 superview. Finally, add the image view as a subview of the scroll view.
  2. 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.
  3. Here you’re setting up a gesture recognizer for the double-tap to zoom in. You don’t need a UIPinchGestureRecognizer for zooming, because UIScrollView has one already built-in!
  4. 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.
  5. 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.
  6. This calls a helper method to center the image within the scroll view. Where’s the helper method? It’s coming up next!

Add the implementation of centerScrollViewContents to the class:

func centerScrollViewContents() {
  let boundsSize = scrollView.bounds.size
  var contentsFrame = imageView.frame
 
  if contentsFrame.size.width < boundsSize.width {
    contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
  } else {
    contentsFrame.origin.x = 0.0
  }
 
  if contentsFrame.size.height < boundsSize.height {
    contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
  } else {
    contentsFrame.origin.y = 0.0
  }
 
  imageView.frame = contentsFrame
}

The point of this function is to get around a slight annoyance with UIScrollView: 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, it would be nice if the image sat in the center of the view. This function accomplishes that by positioning the image view such that it is always in the center of the scroll view’s bounds.

Finally, add the implementation of scrollViewDoubleTapped to the class to handle the double-tap gesture:

func scrollViewDoubleTapped(recognizer: UITapGestureRecognizer) {
  // 1        
  let pointInView = recognizer.locationInView(imageView)
 
  // 2
  var newZoomScale = scrollView.zoomScale * 1.5
  newZoomScale = min(newZoomScale, scrollView.maximumZoomScale)
 
  // 3
  let scrollViewSize = scrollView.bounds.size
  let w = scrollViewSize.width / newZoomScale
  let h = scrollViewSize.height / newZoomScale
  let x = pointInView.x - (w / 2.0)
  let y = pointInView.y - (h / 2.0)
 
  let rectToZoomTo = CGRectMake(x, y, w, h);
 
  // 4
  scrollView.zoomToRect(rectToZoomTo, animated: true)
}

This function is called when the tap gesture recognizer detects a double tap. Here’s a step-by-step guide to what’s happening here:

  1. 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.
  2. Next, you calculate a zoom scale that’s zoomed in 150%, but capped at the maximum zoom scale you specified in viewDidLoad.
  3. Then you use the location from step #1 to calculate a CGRect rectangle that you want to zoom in on.
  4. Finally, you need to tell the scroll view to zoom in, and here you animate it to look pretty too.

For more information on the different gesture recognizers available in iOS, check out our UIGestureRecognizer tutorial.

Now, remember how you set up ViewController as a UIScrollViewDelegate? Well, now you’re going to implement a couple of needed functions in that protocol. Add the following method to the class:

func viewForZoomingInScrollView(scrollView: UIScrollView!) -> UIView! {
  return imageView
}

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.

Finally, add this delegate method to the class:

func scrollViewDidZoom(scrollView: UIScrollView!) {
  centerScrollViewContents()
}

The scroll view will call this method after the user finishes zooming. 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.

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!

Scroll View Run 2

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\Source\Cocoa Touch Class subclass template. Name the class CustomScrollViewController and set the subclass to UIViewController. Make sure “Also create XIB file” is not checked and the language is set to Swift. Click Next and save it with the rest of the project.

Open CustomScrollViewController.swift and replace the contents with this:

import UIKit
 
class CustomScrollViewController: UIViewController, UIScrollViewDelegate {
  @IBOutlet var scrollView: UIScrollView!
 
}

Next, open Main.storyboard and just as before, add a View Controller that’s wired up with a push segue from the 2nd row of the table. Set the view controller’s class to be the class you 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.swift and add this property just below your scrollView outlet:

var containerView: UIView!

The difference compared to the previous view controller is that instead of a UIImageView, you’ve got a UIView called containerView. That should be a little hint as to how this is all going to work.

Now, implement viewDidLoad like so.

override func viewDidLoad() {
  super.viewDidLoad()
 
  // Set up the container view to hold your custom view hierarchy
  let containerSize = CGSize(width: 640.0, height: 640.0)
  containerView = UIView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size:containerSize))
  scrollView.addSubview(containerView)
 
  // Set up your custom view hierarchy
  let redView = UIView(frame: CGRect(x: 0, y: 0, width: 640, height: 80))
  redView.backgroundColor = UIColor.redColor();
  containerView.addSubview(redView)
 
  let blueView = UIView(frame: CGRect(x: 0, y: 560, width: 640, height: 80))
  blueView.backgroundColor = UIColor.blueColor();
  containerView.addSubview(blueView)
 
  let greenView = UIView(frame: CGRect(x: 160, y: 160, width: 320, height: 320))
  greenView.backgroundColor = UIColor.greenColor();
  containerView.addSubview(greenView)
 
  let imageView = UIImageView(image: UIImage(named: "slow.png"))
  imageView.center = CGPoint(x: 320, y: 320);
  containerView.addSubview(imageView)
 
  // Tell the scroll view the size of the contents
  scrollView.contentSize = containerSize;
 
  // Set up the minimum & maximum zoom scales
  let scrollViewFrame = scrollView.frame
  let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width
  let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height
  let minScale = min(scaleWidth, scaleHeight)
 
  scrollView.minimumZoomScale = minScale
  scrollView.maximumZoomScale = 1.0
  scrollView.zoomScale = 1.0
 
  centerScrollViewContents()
}

viewDidLoad sets up a view hierarchy with a single root view, which is your instance variable, containerView. Then you add that single view 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. You set the zoomScale to 1 instead of minScale so that the content view is at normal size, instead of it fitting the screen.

Again, implement centerScrollViewContents and the two UIScrollViewDelegate functions, substituting imageView with containerView from the original versions.

func centerScrollViewContents() {
  let boundsSize = scrollView.bounds.size
  var contentsFrame = containerView.frame
 
  if contentsFrame.size.width < boundsSize.width {
    contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0
  } else {
    contentsFrame.origin.x = 0.0
  }
 
  if contentsFrame.size.height < boundsSize.height {
    contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0
  } else {
    contentsFrame.origin.y = 0.0
  }
 
  containerView.frame = contentsFrame
}
 
func viewForZoomingInScrollView(scrollView: UIScrollView!) -> UIView! {
  return containerView
}
 
func scrollViewDidZoom(scrollView: UIScrollView!) {
  centerScrollViewContents()
}

Note: You’ll probably notice the lack of the UITapGestureRecognizer. That is simply to make this part of the tutorial more straightforward. Feel free to add it in afterwards as an additional exercise.

Now build and run your project. This time, select Custom View Scroll and watch in amazement as you can pan and zoom around a beautifully hand-crafted UIView scene!

Scroll View Run 3

Paging with UIScrollView

In the third section 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 App Store app when you view screenshots of an app, for instance.

Create a new file with the iOS\Source\Cocoa Touch Class subclass template. Name the class PagedScrollViewController and set the subclass to UIViewController. Make sure Also create XIB file is not checked and that the language is set to Swift. Click Next and save it with the rest of the project.

Open PagedScrollViewController.swift and replace the contents of the file with the following:

import UIKit
 
class PagedScrollViewController: UIViewController, UIScrollViewDelegate {
  @IBOutlet var scrollView: UIScrollView!
  @IBOutlet var pageControl: UIPageControl!
}

Next, go to Main.storyboard and just like before, add a View Controller that’s wired up with a push segue from the 3rd row of the table. Set the view controller’s class to be the class you just created, PagedScrollViewController.

Set the background color of the main view to be black, so that the page control that you will be adding will be visible – it is white by default, and white on white really doesn’t work!

Add a Page Control element to the bottom of the view and make it fill the width. Wire it up to the pageControl outlet.

Also, add and wire up a Scroll View to the outlet created, and set the view controller as its delegate, just like before. Adjust the size of the Scroll View to fill the view controller, but leave some room for the Page Control.

This time, turn on Paging Enabled for the scroll view via the Attributes Inspector.

Now open PagedScrollViewController.swift and add these properties after the outlets:

var pageImages: [UIImage] = []
var pageViews: [UIImageView?] = []

You’ll notice some differences this time. There’s no container view, and there are two 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 an array of optionals, because you’ll be loading the pages lazily (i.e. as and when you need them) so you need to be able to handle nil values from the array.

Next, implement viewDidLoad as follows:

override func viewDidLoad() {
  super.viewDidLoad()
 
  // 1
  pageImages = [UIImage(named: "photo1.png"),
    UIImage(named: "photo2.png"),
    UIImage(named: "photo3.png"),
    UIImage(named: "photo4.png"),
    UIImage(named: "photo5.png")]
 
  let pageCount = pageImages.count
 
  // 2
  pageControl.currentPage = 0
  pageControl.numberOfPages = pageCount
 
  // 3
  for _ in 0..<pageCount {
    pageViews.append(nil)
  }
 
  // 4
  let pagesScrollViewSize = scrollView.frame.size
  scrollView.contentSize = CGSize(width: pagesScrollViewSize.width * CGFloat(pageImages.count),
                                  height: pagesScrollViewSize.height)
 
  // 5
  loadVisiblePages()
}

Breaking that down, this is what’s happening:

  1. 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.
  2. The page index starts at zero, so you set the page control to the first page and tell it how many pages there are.
  3. 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 nil objects as placeholders – one for each page. Later on, you’ll use optional binding to check if that page is loaded or not.
  4. 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.
  5. You’re going to need some pages shown initially, so you call loadVisiblePages, which you’ll implement soon.

Add the following method to the class:

func loadPage(page: Int) {
  if page < 0 || page >= pageImages.count {
    // If it's outside the range of what you have to display, then do nothing
    return
  }
 
  // 1
  if let pageView = pageViews[page] {
    // Do nothing. The view is already loaded.
  } else {
    // 2
    var frame = scrollView.bounds
    frame.origin.x = frame.size.width * CGFloat(page)
    frame.origin.y = 0.0
 
    // 3
    let newPageView = UIImageView(image: pageImages[page])
    newPageView.contentMode = .ScaleAspectFit
    newPageView.frame = frame
    scrollView.addSubview(newPageView)
 
    // 4
    pageViews[page] = newPageView
  }
}

Remember each page is a UIImageView stored in an array of optionals. When the view controller loads, the array is filled with nil. This method will load the content of each page:

  1. First, you’re using optional binding to check if you’ve already loaded the view. If pageView contains a UIImageView then do nothing and ignore the rest of this function.
  2. If pageView is nil, 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.
  3. This creates a new UIImageView, sets it up and adds it to the scroll view.
  4. Finally, you replace the nil 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 the following method to the class:

func purgePage(page: Int) {
  if page < 0 || page >= pageImages.count {
    // If it's outside the range of what you have to display, then do nothing
    return
  }
 
  // Remove a page from the scroll view and reset the container array
  if let pageView = pageViews[page] {
    pageView.removeFromSuperview()
    pageViews[page] = nil
  }
}

This function purges a page that was previously created via loadPage(). It first checks that the object in the pageViews array for this page is not nil. If it’s not, it removes the view from the scroll view and updates the pageViews array with nil again to indicate that this page is no longer there.

Why bother lazy loading and purging pages, you ask? Well, in this example, it won’t matter too much if you load all the pages at the start, since there are 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 quickly exceed the amount of memory available and be killed by the operating system. Lazy loading means that you’ll only have a certain number of pages in memory at any given time.

The two functions you defined above are tied together via a function called loadVisiblePages(). Add the implementation of that method to the class:

func loadVisiblePages() {
  // First, determine which page is currently visible
  let pageWidth = scrollView.frame.size.width
  let page = Int(floor((scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)))
 
  // Update the page control
  pageControl.currentPage = page
 
  // Work out which pages you want to load
  let firstPage = page - 1
  let lastPage = page + 1
 
  // Purge anything before the first page
  for var index = 0; index < firstPage; ++index {
    purgePage(index)
  }
 
  // Load pages in our range
  for index in firstPage...lastPage {
    loadPage(index)
  }
 
  // Purge anything after the last page
  for var index = lastPage+1; index < pageImages.count; ++index {
    purgePage(index)
  }
}

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 or even three 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.swift:

func scrollViewDidScroll(scrollView: UIScrollView!) {
  // Load the pages that are now on screen
  loadVisiblePages()
}

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!

Scroll View Run 4

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\Source\Cocoa Touch Class subclass template. Name the class PeekPagedScrollViewController and set the subclass to UIViewController. Make sure Also create XIB file is not checked and that the language is set to Swift. Click Next and save it with the rest of the project.

Open PeekPagedScrollViewController.swift and replace its contents with the following:

import UIKit
 
class PeekPagedScrollViewController: UIViewController, UIScrollViewDelegate {
  @IBOutlet var scrollView: UIScrollView!
  @IBOutlet var pageControl: UIPageControl!
 
  var pageImages: [UIImage] = []
  var pageViews: [UIImageView?] = []
}

Then implement viewDidLoad, which is the same as in the previous section’s paged scroll view example.

override func viewDidLoad() {
  super.viewDidLoad()
 
  // Set up the image you want to scroll & zoom and add it to the scroll view
  pageImages = [UIImage(named: "photo1.png"),
    UIImage(named: "photo2.png"),
    UIImage(named: "photo3.png"),
    UIImage(named: "photo4.png"),
    UIImage(named: "photo5.png")]
 
  let pageCount = pageImages.count
 
  // Set up the page control
  pageControl.currentPage = 0
  pageControl.numberOfPages = pageCount
 
  // Set up the array to hold the views for each page
  for _ in 0..<pageCount {
    pageViews.append(nil)
  }
 
  // Set up the content size of the scroll view
  let pagesScrollViewSize = scrollView.frame.size
  scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * CGFloat(pageImages.count), pagesScrollViewSize.height)
 
  // Load the initial set of pages that are on screen
  loadVisiblePages()
}

Then, implement loadVisiblePages, loadPage:, purgePage:, and the scrollViewDidScroll: UIScrollView delegate function, which are also identical to the previous section, except for a slight difference in loadPage, explained below.

func loadVisiblePages() {
  // First, determine which page is currently visible
  let pageWidth = scrollView.frame.size.width
  let page = Int(floor((scrollView.contentOffset.x * 2.0 + pageWidth) / (pageWidth * 2.0)))
 
  // Update the page control
  pageControl.currentPage = page
 
  // Work out which pages you want to load
  let firstPage = page - 1
  let lastPage = page + 1
 
  // Purge anything before the first page
  for var index = 0; index < firstPage; ++index {
    purgePage(index)
  }
 
  // Load pages in our range
  for index in firstPage...lastPage {
    loadPage(index)
  }
 
  // Purge anything after the last page
  for var index = lastPage+1; index < pageImages.count; ++index {
    purgePage(index)
  }
}
 
func loadPage(page: Int) {
  if page < 0 || page >= pageImages.count {
    // If it's outside the range of what you have to display, then do nothing
    return
  }
 
  // Load an individual page, first checking if you've already loaded it
  if let pageView = pageViews[page] {
    // Do nothing. The view is already loaded.
  } else {
    var frame = scrollView.bounds
    frame.origin.x = frame.size.width * CGFloat(page)
    frame.origin.y = 0.0
    frame = CGRectInset(frame, 10.0, 0.0)
 
    let newPageView = UIImageView(image: pageImages[page])
    newPageView.contentMode = .ScaleAspectFit
    newPageView.frame = frame
    scrollView.addSubview(newPageView)
    pageViews[page] = newPageView
  }
}
 
func purgePage(page: Int) {
  if page < 0 || page >= pageImages.count {
    // If it's outside the range of what you have to display, then do nothing
    return
  }
 
  // Remove a page from the scroll view and reset the container array
  if let pageView = pageViews[page] {
    pageView.removeFromSuperview()
    pageViews[page] = nil
  }
}
 
func scrollViewDidScroll(scrollView: UIScrollView!) {
  // Load the pages that are now on screen
  loadVisiblePages()
}

The only difference in the above functions from those implemented in the earlier sections is the addition of the following line of code in loadPage:

frame = CGRectInset(frame, 10.0, 0.0)

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 Main.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. Set the view controller’s class to be the class you just created, PeekPagedScrollViewController.

As before, set the main view background to black, and add a Page Control element and wire it up. Also add and wire up a “Paging Enabled” Scroll View to the outlet created, and set the view controller as its delegate.

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:

Peek paged scroll view

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.

Clip subviews

Build and run, choose Paged with peeking, and there you have it! Well done!

Scroll View Run 5

Detecting Touches Outside a Scroll View

You may have just noticed with the peeking pages that you now can’t tap 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. You’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\Source\Cocoa Touch Class subclass template. Name the class ScrollViewContainer and make it a subclass of UIView. Make sure the language is set to Swift and click Next and save it with the rest of the project.

Open ScrollViewContainer.swift and replace its contents with the following:

import UIKit
 
class ScrollViewContainer: UIView {
  @IBOutlet var scrollView: UIScrollView!
 
  override func hitTest(point: CGPoint, withEvent event: UIEvent!) -> UIView? {
    let view = super.hitTest(point, withEvent: event)?
    if let theView = view {
      if theView == self {
        return scrollView
      }
    }
 
    return view
  }
}

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 Main.storyboard and back to the Peek Paged Scroll View Controller for this example. Select the scroll view and then choose 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 to ScrollViewContainer. Also wire up the scrollView outlet of the container to the scroll view. It should then look like this:

Embed in container

Also remember to set the background color for the container view to Clear Color, since it sits on top of the main view, and 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!

Scroll View Run 6

Where to Go From Here?

Here is an example project with all of the code from this tutorial.

You’ve delved into many of the interesting things that a scroll view is capable of. If you feel confident about what you’ve done here, you might want to attempt the following extras:

  • Create a vertically paging scroll view.
  • Embed a zoomable scroll view within a paging scroll view so that each page can be zoomed and panned individually.
  • Embed a series of vertically paging scroll views within a horizontally paging scroll view to give the illusion of a 2D grid.

Now go make some awesome apps safe in the knowledge that you’ve got mad scroll view skills!

If you run into any problems along the way or want to leave feedback about what you’ve read here, join in the discussion in the comments below.

How To Use UIScrollView to Scroll and Zoom Content in Swift is a post from: Ray Wenderlich

The post How To Use UIScrollView to Scroll and Zoom Content in Swift appeared first on Ray Wenderlich.

8
Like
Save

Comments

Write a comment

*