iOS Training from beginner to advanced
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.
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!
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.
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.
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.
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.
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:
viewForZoomingInScrollView
maximumZoomScale
propertyminimumZoomScale
propertyThe 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.
Hi there I would love to see detailed post on how to transition between different tableviews. I’m confused about how to code to segue from on tableview to another in an app. e.g. if screen 1 was dog cat snake and you wanted to go to screen 2 labrador,husky poodle but cat would show lion tiger etc how you would code for that.
Or indeed if you could create a collection view which would then segue to a tableview?
That is a great question!!!!
I’ll try to make up a example of that for a January Swift Swift. in the meantime I’ll give you my thinking for it in the simplest form.
First you’ll need an object for the animals, so make this class or something like it:
For your first tableview, you would have an array of type
Animal
, and use the index from the array intableView:didSelectRowAtIndexPath:
to access the animal’s name to display on the table view. When an animal gets selected, your segue or however you are getting to your second controller would take the selected index, and send the selected animal object to your second view controller as a propertymyAnimal:Animal
. In your second view controller you would use themyAnimal.subspecies[index]
property to get your names intableView:didSelectRowAtIndexPath:
where index =indexPath.row
to show the subspecies of animals. That would be the simplest way.There are few other ways too if you used SQL or coreData, but that gets complicated.
Hope that helps
(edited for clarity a few times)
Hi thanks for that! although I’m still a little lost to be honest :(
I created a github repository of the project I’ve created so far https://github.com/edlen/Animal-App-tableview.git but it doesn’t have the code for going between the screens I was wondering would you mind taking a look at it and maybe letting me know how I could proceed? If there was anyway you could even suggest how to segue from dog to labrador to the info screen on labrador I’d really appreciate it!
Unfortunately I’m swamped until the new year and my book launches. I’ll write an example in January of what you are trying to do then.
No problem it was kinda cheeky of me to ask! I’ll keep an eye out for the example in jan- looking forward to the book!
Thank you for understanding.
Hi
Great tutorial, but how do u simulate zooming the image? Have tried option/click and I can see the zoom control but image does not appear to be zooming..
Actually got it, cheers
PS A tutorial on page scroll in swift would be great.
guess you found out it requires the delegate.
Yep, am a new to iOS, live n learn
I’m old to iOS and I’m still learning. Keep at it.
Would it possible to change the horizontal scroll to contain a series image views, then update the main vertical scroll zoomed image view when the a imag view is touched in the horizontal view?
Not sure where we might update the zoomed image view as the image view is set in view didload and scroll in viewDidLayoutSubviews…..
If I am understanding what you want. You might want to read a bit about table views and collection views. these will do the job much easier than coding all of that yourself. Collection views is available in the newsletter downloads site right now, but will be released publicly soon. Sign up for the newsletter and you will get directions on how to get there.
Thanks will do, had a quick look but I am not certain if u can pinch/zoom in out of images using a collection view?
Hey, thank you for your tutorial on UIScrollViews, but trying your code my program crashes at that point I press one of these buttons. It tells me “libc++abi.dylib: terminating with uncaught exception of type NSException” and “Thread 1: signal SIGABRT”.
I dunno how to handle this.. Where’s the problem? I exactly used your code for the Button Scroll…
Thanks in advance :)
I’m not sure. possible culprit is the target string is not spelled exactly same as the function name. Make sure you have the colon in the target string.
Ok, I figured it out. I made a mistake in the button.addTarget()-Function. ^^
Thought it might be something like that. You get that in a storyboard and programmatically when the button and target are wired up to an unknown identifier.
Pingback: EVERYTHING YOU WANT – Swift resources | swiftioscodetutorial
Pingback: How MakeAppPie.com is Updating to Swift 2.0 | Making App Pie
What’s up with the -16 leading and trailing constraints you added? Why did you do that? I have an idea, but I’d like to hear it from you first. :)
It’s one way to defeat margins and have the view flush against the view. In Xcode7 that number should be -20. The other way is to check off the constrain to margins box in the pin menu.
Having trouble following this example. Do you have the code on github or in a zip file you can post?
I’m afraid not. I found maintaining live code was too much work.
I made a ***sample project*** according to the tutorial and code above. Hope it saves time for people from Google haha. If all of us can upload our Xcode projects at the comment section for every tutorial, we make the world a better place.
https://dl.dropboxusercontent.com/u/2834495/ScrollViewDemo%20for%20makeapppie.zip
Thanks Eric. I’d love to have time to get the Git working but this will help greatly.