Last week we talked about cameras, one thing I’d like in my camera app is the ability to take a picture then zoom and scroll around the photo. For that, we need to use the UIScrollview
. UIScrollView
is a very flexible and important control in UIKit
. It is the basis for many the controls we all know and love including UItableView. IT’s what does all the pinch zoom and pan on many applications like maps. I love UItableView
and UIPicker
, but more than once I’d like that does the same thing moving sideways. besides zooming I’ll cover that too. Most importantly, in iOS8 we can’t get around auto layout, which messes up just about everything that traditional approaches to UIScrollView
does. I’ll show you a good way of handling things.
Set up the views
we are going to use two UIScrollViews
for this in auto layout. Make a new project named SwiftPizzaScrollview with a single view in Swift and universal. Drag two UIScrollView
s on the screen and anchor one to the bottom and one to the top. Make 10 point vertical space between them, and equal heights. Update the frames then change the equal height to a 1:4 proportion. IF that made no sense to you, follow the video below:
Once you have the layout completed, open the aassistant editor and set to automatic so you see the viewController.Swift class. Control drag from the top UIScrollView
to the class and make an outlet named buttonScroll
. then control drag from the bottom UIScrollView
and make a outlet named imageScroll.
@IBOutlet weak var imageScroll: UIScrollView! @IBOutlet weak var buttonScroll: UIScrollView!
Setting up the Button Scroll
The first scroll we will look at is the button scroll. Scroll views work by having view added to them and the contentSize
property being bigger than the size of the scroll view. We’ll need a big view for this. For this one, I’ll make a programmatic row of buttons that select a color. Under the outlets, add this code to make a function to create the buttons:
func colorButtonsView(buttonSize:CGSize, buttonCount:Int) -> UIView { //creates color buttons in a UIView let buttonView = UIView() return buttonView() }
This makes a view, but doesn’t do much, let’s start by defining a few properties for the view. We’ll make the background black, and set the origin to 0,0.
Add this after the buttonView
declaration:
buttonView.backgroundColor = UIColor.blackColor() buttonView.frame.origin = CGPointMake(0,0)
Next we need to size the view. However to size the view we have to calculate its size based on the size of the buttons and the padding added to it.
let padding = CGSizeMake(10, 10) buttonView.frame.size.width = (buttonSize.width + padding.width) * CGFloat(buttonCount) buttonView.frame.size.height = (buttonSize.height + 2.0 * padding.height)
Line 1 I made a constant for the padding I can use in my calculations. The width of the view in line 2 would be the size of the button and one side of padding multiplied by the number of buttons. The height in line 3 is the height of the button with a top and bottom padding.
Our strategy to add the buttons is to loop through ans add buttons each loop. Along the way we will change the x origin of the new button, and change the color using a HSB hue in UIColor(hue:saturation:brightness:alpha)
We’ll need some initial values before we start the loop:
//add buttons to the view var buttonPosition = CGPointMake(padding.width * 0.5, padding.height) let buttonIncrement = buttonSize.width + padding.width let hueIncrement = 1.0 / CGFloat(buttonCount) var newHue = hueIncrement
Line two starts us at a place with some padding. Line 3 is the distance between buttons on the x axis. line 4 is the distance between colors we will show. Since colors are represented between 1.0 and 0 this will give the number of colors. We initialize a value for the hue we will use.
Next we start the loop. Add the loop like this:
for i in 0...(buttonCount - 1) { var button = UIButton.buttonWithType(.Custom) as UIButton button.frame.size = buttonSize button.frame.origin = buttonPosition buttonPosition.x = buttonPosition.x + buttonIncrement button.backgroundColor = UIColor(hue: newHue, saturation: 1.0, brightness: 1.0, alpha: 1.0) newHue = newHue + hueIncrement button.addTarget(self, action: "colorButtonPressed:", forControlEvents: .TouchUpInside) buttonView.addSubview(button) }
We make a button, set it size and origin, then increment the origin for the next loop. Similarly, we set the background color of the hue to a color, then increment the hue to the next color. We add an action to the button and the add the button to the view. when the loop finished we have a complete view we can return and do what we need with it.
Only thing missing is the target. Add this under the last method:
func colorButtonPressed(sender:UIButton){ buttonScroll.backgroundColor = sender.backgroundColor }
Which just take the background color and makes the buttonScroll
that color.
Making the sliding button scroll view
To make the scroll view run, we need the following code in the viewDidLoad
:
//scrolling pageview let scrollingView = colorButtonsView(CGSizeMake(100.0,50.0), buttonCount: 10) buttonScroll.contentSize = scrollingView.frame.size buttonScroll.addSubview(scrollingView) buttonScroll.showsHorizontalScrollIndicator = true buttonScroll.indicatorStyle = .Default
Line 1 gets a view. once we have the view we have it’s size which we make the contentSize
of the scrollingView
. If you skip this step the scroll view will not work. We add the view to the buttonScroll
and we are set tot go. The last two lines add a scroll indicator for cosmetic purposes.
Build and run. You get a color picker that side scrolls.
Using a Xib
We used for this example a programmatically generated view. It’s not the only way to go. While I didn’t use this, you could use a xib if you wanted various controls in the code. Set up a view controller with a xib. Set an absolute height and width for the xib’s view to make it easy to get a contentView size. Then declare the view controller (again this isn’t part of our app)
let scrollingViewController = UIViewController(nibName: "buttonScrollViewController", bundle: nil)
Instead of line 1 above in the viewDidLoad
use this:
let scrollingView = scrollingViewController.view
This will allow you to put any control in the scroll view, and use the xib’s view controller to control it.
Scrolling a Picture
We’ll need a photo for this. We’ll use this one of a pizza:
Download the image and click on Images.Xassets in Xcode. Drag the image into the assets from your finder.
Just like the scrolling buttons we just have to make a view, add it to the scroll view and set the content size. Next make an UIImageView
in the declaration just below the outlets:
var imageView = UIImageView(image: UIImage(named: "pizza"))
And then we add this to the viewDidLoad
imageScroll.contentSize = imageView.frame.size imageScroll.addSubview(imageView)
Build and run. The photo is 640×640 so depending on the simulator you pick you will get different scrolling behaviors. Use a small device like the iPhone 4s and you will get more scrolling.
Zooming in a scroll view with Auto Layout
If you look across the web, you will find a lot of articles on zooming with UIScrollView
. If you use Auto Layout, you will quickly and frustratingly learn auto layout has problem with the way most prope write for UIScrollView
. Some turn off auto layout to get around this. The problem is a moving target. A Zoom needs several things:
- Add the UIScrollViewDelegate
- Set the delegate
- Implement the delegate method
viewForZoomingInScrollView
- Set the
maximumZoomScale
property - Set the
minimumZoomScale
property - Set the current zoom
The maximumZoomScale
and minimumZoomScale
properties of the UIScrollView
cannot be equal on order for zoom to work. It’s these scales which messes everything up. Most people set their zoom by making a minimum zoom as a ratio of the bounds of the scroll view and the content view. This usually get coded in viewDidLoad
, except auto layout does not set the bounds of the scroll view that early. It’s one of the last things to happen. So all of that code is looking for something either that is the wrong size, or doesn’t exist yet.
The way around this is overriding didLayoutSubViews
. This happens after any changes to the bounds, and all the bounds are actually set. This takes care of both auto layout not knowing anything on the initial load, and any time we change orientation. Both cases are caught. There is one caution to this. The content size will change in the process of a rotation. We will need to reset it every time we go change the scale. To make this easy, I made a reference property for the class
var imageSize = CGSizeMake(0,0)
You could keep calling the imageView’s frame if you wanted, but it seems verbose. Instead in viewDidLoad
I assign it and the delegate, and set the content size in didLayoutSubviews
. Change what we had for the imageScroll
code in viewDidLoad
to this:
//zooming photo imageSize = imageView.frame.size imageScroll.delegate = self imageScroll.addSubview(imageView) imageScroll.showsHorizontalScrollIndicator = false imageScroll.showsVerticalScrollIndicator = false
Much of this code we discussed already. I added the scroll indicator properties explicitly instead of its defaults.
now add the following just below viewDidLoad
:
override func viewDidLayoutSubviews() { imageScroll.maximumZoomScale = 5.0 imageScroll.contentSize = imageSize let widthScale = imageScroll.bounds.size.width / imageSize.width let heightScale = imageScroll.bounds.size.height / imageSize.height imageScroll.minimumZoomScale = min(widthScale, heightScale) imageScroll.setZoomScale(max(widthScale, heightScale), animated: true ) }
Line 2 sets the maximum zoom scale. I used an arbitrary value of five times the size of the image. I then set the content size. As we’ve already said, the scroll view needs this, but we are keeping the size under control by using a constant, since the content view changes when we rotate the view, and can mess up our calculations. Next we figure the scale by taking the bounds of the scroll View, which by now are correct since auto layout is done with them. We see if the widthScale
or heightScale
is smaller, and use that scale as the one for our minimum zoom scale. We then set the scale for the scroll view as the minimum zoom scale. You can do a lot with this code and modify it a lot of different ways for different scale effects. This is the basics.
Our last step is to set up the delegate, which is a quick one. It just wants the view we are zooming to return to the delegate
//MARK: Delegates func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { return imageView }
Build and run. You get a working scroll view.
You can do a lot from this point. The scrolling anchor point for example is the origin at (0,0) some people like zooming from a center point. The big thing you need to do is get your sizing information after auto layout does what it does.
Leave a Reply