15

UIScrollView Tutorial: Getting Started [FREE]

 4 years ago
source link: https://www.raywenderlich.com/5758454-uiscrollview-tutorial-getting-started
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Update note : Ron Kliffer updated this tutorial for Xcode 11, Swift 5 and iOS 13. Ray Wenderlich wrote the original.

UIScrollView is one of the most versatile and useful controls in iOS. It’s the basis for the very popular UITableView and it’s a great way to present content that’s larger than a single screen.

In this UIScrollView tutorial, you’ll create an app similar to the default iOS Photos app and learn all about UIScrollView . You’ll learn how to:

UIScrollView
UIScrollView
UIScrollView
UIPageViewController

This tutorial assumes you understand how to use Interface Builder to add objects and connect outlets between your code and storyboard scenes. If you’re not familiar with Interface Builder or storyboards , work through ourstoryboards tutorial before starting this one.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project, then open it in Xcode. Build and run using an iPhone 8 simulator to see what you’re starting with. Continue using the iPhone 8 simulator for the rest of the tutorial.

starter_1.gif

You’ll see that you have several photos available in your project. You can select a photo to see it full-sized, but sadly, the device’s limited size prevents you from seeing the whole photo.

What you really want is to fit the image to the device’s screen by default and zoom to see details, just like the Photos app does.

Can you fix it? Yes, you can!

Scrolling and Zooming a Large Image

To kick off this UIScrollView tutorial, you’ll set up a scroll view that lets the user pan and zoom an image.

Open Main.storyboard and drag a Scroll view from the Object Library onto the document outline , right below View on the Zoomed Photo View Controller scene. Then, move Image View inside your newly-added Scroll View . Your document outline should now look like this:

scrollview_1-383x320.png

See that red arrow? Xcode is complaining that your Auto Layout rules are wrong.

To fix them, select Scroll View and click the Pin button at the bottom of the storyboard window. Add four new constraints: top, bottom, leading and trailing. Set each constraint’s constant to 0 and uncheck Constrain to margins . The result should look like this:

scrollview_4-249x320.png

Now, select Image View and add the same four constraints on it. Make sure to pin the constraints to the Scroll View and not the Frame Layout Guide , like this:

scrollview_5-295x320.png

Build and run.

scrollview_gif_1.gif

Thanks to the scroll view, you can now swipe to see the full-size image! But what if you want to zoom in and out? Or what if you want to see the picture scaled to fit the device’s screen?

You’ll need to write code for those cases!

Panning and Zooming Your Image

Open ZoomedPhotoViewController.swift and add the following outlets inside the class declaration:

@IBOutlet weak var scrollView: UIScrollView! 
@IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!

Back in Main.storyboard , set the scrollView outlet to Scroll View and set the Scroll View’s delegate to Zoomed Photo View Controller . Also, connect the new constraint outlets to the appropriate constraints in the document outline , like this:

scrollview_6-1-650x349.png

Back in ZoomedPhotoViewController.swift , add the following to the end of the file:

extension ZoomedPhotoViewController: UIScrollViewDelegate {
  func viewForZooming(in scrollView: UIScrollView) -> UIView? {
    return imageView
  }
}

This makes ZoomedPhotoViewController conform to UIScrollViewDelegate and implements viewForZooming(in:) . The scroll view calls this method to determine which of its subviews to scale when the user pinches the image. Here, you tell it to scale imageView .

Setting the Zoom Scale

Next, add the following inside the class, right after viewDidLoad() :

func updateMinZoomScaleForSize(_ size: CGSize) {
  let widthScale = size.width / imageView.bounds.width
  let heightScale = size.height / imageView.bounds.height
  let minScale = min(widthScale, heightScale)
    
  scrollView.minimumZoomScale = minScale    
  scrollView.zoomScale = minScale
}

This method calculates the zoom scale for the scroll view. A zoom scale of 1 indicates that the content displays at its normal size. A zoom scale of less than 1 shows a zoomed-out version of the content, and a zoom scale greater than 1 shows the content zoomed in.

To get the minimum zoom scale, you first calculate the required zoom to fit the image view snugly within the scroll view, based on its width. You then calculate the same for the height. You take the minimum of the width and height zoom scales, and set this value for both minimumZoomScale and zoomScale of scrollView .

You’ll initially see the entire image fully zoomed-out, and after zooming in, the user will be able to zoom out to this level, too.

Since the maximumZoomScale defaults to 1, you don’t need to set it. If you set it to greater than 1, the image may appear blurry when the user zooms in on it. If you set it to less than 1, you won’t be able to zoom in to the full image’s resolution.

Finally, you need to update the minimum zoom scale each time the controller updates its subviews. Add the following right before the previous method to do this:

override func viewWillLayoutSubviews() {
  super.viewWillLayoutSubviews()
  updateMinZoomScaleForSize(view.bounds.size)
}

Build and run; you should get the following result:

scrollview_gif_2.gif

Now, when you tap on an image, it fits on the screen. You can pan around it and zoom in or out. Awesome!

Note : If you’re unfamiliar with how to perform pinch gestures on the simulator, try holding the Option key while clicking and dragging.

However, there’s still one problem: The image is pinned to the top of the scroll view. It’d sure be nice to center it instead, right?

Centering Your Image

Still in ZoomedPhotoViewController.swift , add the following inside the class extension, right after viewForZooming(in:)

//1
func scrollViewDidZoom(_ scrollView: UIScrollView) {
  updateConstraintsForSize(view.bounds.size)
}

//2
func updateConstraintsForSize(_ size: CGSize) {
  //3
  let yOffset = max(0, (size.height - imageView.frame.height) / 2)
  imageViewTopConstraint.constant = yOffset
  imageViewBottomConstraint.constant = yOffset
  
  //4 
  let xOffset = max(0, (size.width - imageView.frame.width) / 2)
  imageViewLeadingConstraint.constant = xOffset
  imageViewTrailingConstraint.constant = xOffset
    
  view.layoutIfNeeded()
}

Now, go over this step-by-step:

  1. The scroll view calls scrollViewDidZoom(_:) each time the user zooms. In response, you simply call updateConstraintsForSize(_:) and pass in the view’s bounds size.
  2. updateConstraintsForSize(_:) gets around an annoyance with UIScrollView : If the scroll view’s content size is smaller than its bounds, it places the contents at the top-left of the screen, rather than the center.
  3. You center the image vertically by subtracting the height of imageView from the view ‘s height and dividing the result in half. This value adds padding to the top and bottom imageView constraints.
  4. Similarly, you calculate an offset for the leading and trailing constraints of imageView , based on the width of the view.

Pat yourself on the back, then build and run your project! Select an image and you’ll end up with a lovely, centered image that you can zoom and pan. :]

scrollview_gif_3.gif

Scrolling Vertically

Now, suppose you want to change PhotoScroll to display the image at the top and add comments below it. Depending on how long the comment is, you may end up with more text than your device can display. UIScrollView to the rescue!

Note : In general, Auto Layout considers the top, left, bottom and right edges of a view to be the visible edges. However, UIScrollView

scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the edges within a scroll view actually refer to the edges of its Content view.

To size the scroll view’s frame with Auto Layout, you need to either make the constraints regarding the width and height of the scroll view explicit, or you must tie the edges of the scroll view to views outside of its own subtree.

You can read more in this technical note from Apple.

Next, you’ll learn how to fix the width of a scroll view, which is really its content size width, using Auto Layout.

Scroll View and Auto Layout

Open Main.storyboard and lay out a new scene.

First, add a new View Controller . In the Size inspector , replace Fixed with Freeform for the Simulated Size , then enter a width of 340 and a height of 800 .

scrollview_7-480x192.png

You’ll notice the layout of the controller gets narrower and longer, simulating the behavior of a long, vertical piece of content. The Simulated Size helps you visualize the display in Interface Builder. It has no runtime effect.

Uncheck Adjust Scroll View Insets in the Attributes inspector for your newly-created view controller.

scrollview_8-355x320.png

Add a Scroll view that fills the entire space of the view controller.

Add leading and trailing constraints with constant values of 0 to the view controller. Make sure to uncheck Constrain to margins . Add top and bottom constraints from Scroll view to the Top and Bottom Layout guides, respectively. They should also have constants of 0 .

Add a view as a child of the Scroll view and resize it to fit the entire space of the Scroll view .

Rename its storyboard Label to Container View . Like before, add top, bottom, leading and trailing constraints, with constants of 0 , and uncheck Constrain to margins . Again, make sure to pin the constraints to the Scroll view and not the Frame Layout Guide .

To fix the Auto Layout errors, you need to specify the Container View’s size. You want it to be as wide as the scroll view, so attach an equal-width constraint from the Container View to the Scroll view . For Container View’s height, define a height constraint of 500 .

Note : Auto Layout rules must comprehensively define a scroll view’s contentSize . This is the key step in correctly sizing a scroll view when using Auto Layout.

Add an Image View inside Container View .

In the Attributes inspector , specify photo1 for the image, choose Aspect Fit for the mode and check Clip to Bounds .

scrollview_9-261x500.png

Add top, leading and trailing constraints to Container view like before, and add a height constraint of 300 .

Adding a Label to Your Image

Add a Label inside Container View below the image view. Specify the label’s text as What name fits me best? and add a centered horizontal constraint relative to Container View . Next, add a vertical spacing constraint of 0 with the UIImage .

Add a text field inside of Container View , below the new label. Add leading and trailing constraints to Container View with constant values of 8 and no margin. Next, add a vertical-space constraint of 30 relative to the label.

Your document outline should now look like this:

scrollview_10-315x320.png

Now, you need to connect a segue to your new View Controller .

To do so, first delete the existing show segue between the Photo Scroll scene and the Zoomed Photo View Controller scene. Don’t worry, you’ll add all the work you’ve done on Zoomed Photo View Controller back to your app later.

In the Photo Scroll scene, control-drag from PhotoCell to the new View Controller and add a show segue. Make the identifier showPhotoPage .

Build and run.

scrollview_11-281x500.png

Displaying Properly in Every Orientation

You can see that the layout displays correctly when the device is in vertical orientation. Try rotating to landscape orientation. Oops!

In landscape, there’s not enough vertical room to show all the content, yet the scroll view allows you to properly scroll to see the label and the text field. Unfortunately, since the image in the new View Controller is hard-coded, you won’t see the image you selected in the Collection view.

To fix this, you need to pass the image name to the View Controller when the segue executes.

Scrolling to See More

Go to File ▸ New ▸ File… , choose the iOS ▸ Source ▸ Cocoa Touch Class template and click Next . Name the class PhotoCommentViewController and set the subclass to UIViewController . Make sure that the language is set to Swift . Click Next and save it with the rest of the project.

Now, replace the contents of PhotoCommentViewController.swift with this code:

import UIKit

class PhotoCommentViewController: UIViewController {
  //1
  @IBOutlet weak var imageView: UIImageView!
  @IBOutlet weak var scrollView: UIScrollView!
  @IBOutlet weak var nameTextField: UITextField!
  
  //2
  var photoName: String?

  override func viewDidLoad() {
    super.viewDidLoad()
    //3
    if let photoName = photoName {
      self.imageView.image = UIImage(named: photoName)
    }
  }
}

Here’s what this code does, step-by-step:

  1. Adds IBOutlet s for the UI elements.
  2. Adds a photoName property to represent the presented image’s name.
  3. Sets the image on imageView using photoName .

Back in the storyboard, open the Identity inspector for View Controller and select PhotoCommentViewController for the Class . Then, wire the IBOutlet s for the scroll view, image view and text field.

Open CollectionViewController.swift , and replace prepare(for:sender:) with this:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if let cell = sender as? UICollectionViewCell,
      let indexPath = collectionView?.indexPath(for: cell),
      let viewController = segue.destination as? PhotoCommentViewController {
    viewController.photoName = "photo\(indexPath.row + 1)"
  }
}

This sets the name of the photo your app will display on PhotoCommentViewController whenever the user taps one of the photos.

Build and run.

RrEvYfB.png!web

Your view nicely displays the content and, when needed, allows you to scroll down to see more. However, you’ll notice two issues with the keyboard: First, when entering text, the keyboard hides the text field. Second, there’s no way to dismiss the keyboard.

Ready to fix the glitches?

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.

scrollview_gif_4.gif

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 .

fEVBjqN.png!web

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.

scrollview_gif_5.gif

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

Displaying a Page Control Indicator

For the final part of this UIScrollView tutorial, you'll add a UIPageControl to your app.

Fortunately, UIPageViewController has the ability to automatically provide a UIPageControl .

To do so, your UIPageViewController must have a transition style of UIPageViewControllerTransitionStyleScroll and you must implement two special methods on UIPageViewControllerDataSource .

You previously set the Transition Style – great job! All you need to do now is add these two methods inside the UIPageViewControllerDataSource extension on ManagePageViewController :

func presentationCount(for pageViewController: UIPageViewController) -> Int {
  return photos.count
}
  
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
  return currentIndex ?? 0
}

Here's what this code does:

presentationCount(for:)
presentationIndex(for:)

After you've implemented the required delegate methods, you can add further customization with the UIAppearance API.

To try it out, go to AppDelegate.swift and replace application(_:didFinishLaunchingWithOptions:) with this:

func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?)
    -> Bool {
  let pageControl = UIPageControl.appearance()
  pageControl.pageIndicatorTintColor = UIColor.systemGray
  pageControl.currentPageIndicatorTintColor = UIColor.systemPurple

  return true
}

This will customize the colors of the UIPageControl .

Build and run.

scrollview_14-281x500.png

Putting It All Together

Almost there! The very last step is to add back the zooming view when the user taps an image.

Open PhotoCommentViewController.swift and add the following to the end of the class:

//1
@IBAction func openZoomingController(_ sender: AnyObject) {
  self.performSegue(withIdentifier: "zooming", sender: nil)
}
  
//2
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {  
  if let id = segue.identifier,
    let viewController = segue.destination as? ZoomedPhotoViewController,
    id == "zooming" {
    viewController.photoName = photoName
  }
}

Take a look at this code, step-by-step:

  1. The action for opening the zooming view simply calls performSegue(withIdentifier:sender:) with the zooming segue identifier.
  2. In prepare(for:sender:) , you send the destination controller's photoName value to the image the user selected.

In Main.storyboard , add a Show Detail segue from Photo Comment View Controller to Zoomed Photo View Controller . With the new segue selected, open the Identity inspector and set the Identifier to zooming .

Select the Image View in Photo Comment View Controller , open the Attributes inspector and check User Interaction Enabled . Drag a Tap Gesture Recognizer onto the image view and connect it to openZoomingController(_:) .

Now, when you tap an image in Photo Comment View Controller Scene , you'll go to the Zoomed Photo View Controller Scene where you can zoom the photo.

Build and run one more time.

scrollview_gif_6.gif

You did it! You've created an iOS Photos app clone. You can select and navigate through a collection view of images by swiping, and you can zoom the photo content.

Where to Go From Here?

Download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

You’ve delved into many of the interesting things that a scroll view is capable of doing. If you want to go further, there's an entire video series dedicated to scroll views.Take a look.

Now, go make some awesome apps, safe in the knowledge that you’ve got mad Scroll view skillz!

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK