UIScrollView Tutorial: Getting Started

In this UIScrollView tutorial, you’ll create an app similar to the default iOS Photos app to learn all about paging, scrolling and more with UIScrollView. By Ron Kliffer.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Managing the Keyboard

Unlike UITableViewController, which automatically handles moving content out of the way of the keyboard, you have to manage the keyboard manually when you use a UIScrollView directly.

Do this by making PhotoCommentViewController observe the keyboard Notification objects that iOS sends whenever the keyboard hides and displays.

To do this, open PhotoCommentViewController.swift and add the following methods:

//1
func adjustInsetForKeyboardShow(_ show: Bool, notification: Notification) {
  guard 
    let userInfo = notification.userInfo,
    let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] 
      as? NSValue 
    else {
      return
  }
    
  let adjustmentHeight = (keyboardFrame.cgRectValue.height + 20) * (show ? 1 : -1)
  scrollView.contentInset.bottom += adjustmentHeight
  scrollView.verticalScrollIndicatorInsets.bottom += adjustmentHeight
}
  
//2
@objc func keyboardWillShow(_ notification: Notification) {
  adjustInsetForKeyboardShow(true, notification: notification)
}
@objc func keyboardWillHide(_ notification: Notification) {
  adjustInsetForKeyboardShow(false, notification: notification)
}

Here’s what you’re doing with this code:

  1. In adjustInsetForKeyboardShow(_:notification:), you extract the keyboard height from the notification‘s userInfo and set the scroll view’s insets accordingly.
  2. You call adjustInsetForKeyboardShow(_:) when the user hides or shows the keyboard, indicating the direction to move the scroll view.

Next, you need to set up the observers for the keyboard changes. Add the following code at the bottom of viewDidLoad():

NotificationCenter.default.addObserver(
  self,
  selector: #selector(keyboardWillShow(_:)),
  name: UIResponder.keyboardWillShowNotification,
  object: nil)

NotificationCenter.default.addObserver(
  self,
  selector: #selector(keyboardWillHide(_:)),
  name: UIResponder.keyboardWillHideNotification,
  object: nil)

Dismissing the Keyboard

To dismiss the keyboard, add this method to PhotoCommentViewController.swift:

@IBAction func hideKeyboard(_ sender: AnyObject) {
  nameTextField.endEditing(true)
}

This method will resign the first responder status of the text field, which, in turn, dismisses the keyboard.

Finally, open Main.storyboard, and drag a Tap Gesture Recognizer onto the View in the Photo Comment View Controller scene. Then, wire it to the hideKeyboard(_:) IBAction in PhotoCommentViewController.

To make it more user friendly, the keyboard should also dismiss when the user presses the return key. Right-click on nameTextField and wire Primary Action Triggered to hideKeyboard(_:).

Build and run.

Keyboard shows and hides itself correctly

Now, navigate to the Photo Comment View Controller scene. Tap the text field and then tap somewhere else on the view. The keyboard should properly show and hide itself relative to the other content on the screen. Likewise, tapping the return key does the same.

Paging With UIPageViewController

In the third section of this UIScrollView tutorial, you’ll create a scroll view that allows paging, meaning that the scroll view locks onto a page when you stop dragging. You can see this in action in the App Store app when you view screenshots of an app.

Getting Set Up

Go to Main.storyboard and drag a Page View Controller onto the canvas. Open the Identity inspector and enter PageViewController for the Storyboard ID.

In the Attributes inspector, the Transition Style is set to Page Curl by default. Change it to Scroll and set the Page Spacing to 8.

Transition style settings

In the Photo Comment View Controller scene’s Identity inspector, specify a Storyboard ID of PhotoCommentViewController. This lets you refer to the storyboard from your code.

Open PhotoCommentViewController.swift and add this property after the others:

var photoIndex: Int!

This references the index of the photo to display. The page view controller will use this property.

Creating and Implementing Your Page View Controller

Now, create a new file with the iOS ▸ Source ▸ Cocoa Touch Class template. Name the class ManagePageViewController and set the subclass to UIPageViewController.

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

import UIKit

class ManagePageViewController: UIPageViewController {
  var photos = ["photo1", "photo2", "photo3", "photo4", "photo5"]
  var currentIndex: Int!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // 1
    if let viewController = viewPhotoCommentController(currentIndex ?? 0) {
      let viewControllers = [viewController]
      
      // 2
      setViewControllers(viewControllers,
                         direction: .forward,
                         animated: false,
                         completion: nil)
    }    
  }
  
  func viewPhotoCommentController(_ index: Int) -> PhotoCommentViewController? {
    guard 
      let storyboard = storyboard,
      let page = storyboard
        .instantiateViewController(withIdentifier: "PhotoCommentViewController")
        as? PhotoCommentViewController 
      else {
        return nil
    }
    page.photoName = photos[index]
    page.photoIndex = index
    return page
  }
}

Here’s what this code does:

  1. viewPhotoCommentController(_:) creates an instance of PhotoCommentViewController through the storyboard. You pass the name of the image as a parameter so the displayed view matches the image you selected in the previous screen.
  2. You set up the UIPageViewController by passing it an array that contains the single view controller you just created.

Now that you’ve taken care of that, you need to implement UIPageViewControllerDataSource. Add the following class extension to the end of this file:

extension ManagePageViewController: UIPageViewControllerDataSource {
  func pageViewController(
    _ pageViewController: UIPageViewController,
    viewControllerBefore viewController: UIViewController) 
      -> UIViewController? {
    if let viewController = viewController as? PhotoCommentViewController,
      let index = viewController.photoIndex,
      index > 0 {
        return viewPhotoCommentController(index - 1)
    }
    
    return nil
  }
  
  func pageViewController(
    _ pageViewController: UIPageViewController,
    viewControllerAfter viewController: UIViewController) 
      -> UIViewController? {
    if let viewController = viewController as? PhotoCommentViewController,
      let index = viewController.photoIndex,
      (index + 1) < photos.count {
        return viewPhotoCommentController(index + 1)
    }
    
    return nil
  }
}

UIPageViewControllerDataSource allows you to provide content when the page changes. You provide view controller instances for paging both forward and backward. In both cases, you use photoIndex to determine which image is currently displayed.

Both methods use the viewController parameter to indicate the currently-displayed view controller. Using the photoIndex, it creates and returns a new controller.

You also need to set the dataSource. Add the following to the end of viewDidLoad():

dataSource = self

There are only a couple things left to do to get your page view running. First, you'll fix the flow of the app.

Fixing Your App's Flow

Switch back to Main.storyboard and select your newly-created Page View Controller scene. In the Identity inspector, specify ManagePageViewController for its class.

Delete the push segue showPhotoPage you created earlier. Then control-drag from Photo Cell in Scroll View Controller to Manage Page View Controller Scene and select a Show segue. In the Attributes inspector for the segue, specify its name as showPhotoPage, as you did before.

Open CollectionViewController.swift and change the implementation of prepare(for:sender:) to the following:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if let cell = sender as? UICollectionViewCell,
    let indexPath = collectionView?.indexPath(for: cell),
    let managePageViewController = segue.destination as? ManagePageViewController {
    managePageViewController.photos = photos
    managePageViewController.currentIndex = indexPath.row
  }
}

Build and run.

Scrolling to page between images

You can now scroll sideways to page between different detail views.