Coding Swift

Discovering automatic adjustment of ContentInset

This first post is mainly for people who are on the struggle that I've had for some time.
When you are building apps on the iOS platform sometimes you have to use a ScrollView to display some content for example a horizontal slider with images.

When I was building an app with a Navigation Controller followed by a couple of views, I stumbled on a stupid issue. When I put a simple empty Scrollview in a View Controller and programatically added some pictures to the empty Scrollview, it looked as if my images where pushed to the bottom! But it was not just any kind of amount, it was exactly the height of my navigation bar!

In the following section I will give you a basic example how to reproduce this situation. After that I will supply you with the solution I have found for this problem. This post if fully written in Swift so I assume you can read and write it.

The situation

First of all go ahead and make a new project that is Universal (for both iPad and iPhone). In the Main Storyboard you will need to wipe everything and place a Navigation Controller followed by a normal blank View Controller that contains a horizontal-like Scrollview. You can set the autolayout so that it has a fixed height as well as de space to the borders of the View Controller.

The next thing we're going to do is create a custom View Controller class, link it to the empty View Controller and place an IBOutlet of the Scrollview in there so that you can fill it.

Now comes the real coding stuff. We are going to add an image to the Scrollview by code as if you would retrieve multiple from the server, but now just with 1 local image called "settings.png"

import UIKIT

class ScrollViewController: UIViewController {
    @IBOutlet weak var scroller: UIScrollView!
    override func viewDidLoad() {
        //add a border arround the scrollview
        self.scroller.layer.borderColor = UIColor.redColor().CGColor
        self.scroller.layer.borderWidth = 1.0
        //make the image and imageview
        let image = UIImage(named: "settings.png") //this can be any image
        var imageview = UIImageView(frame: CGRect(x: 0, y: 0, width: image!.size.width, height: image!.size.height))
        imageview.image = image
        //add a border arround imageview to see where it is put in the scrollview
        imageview.layer.borderColor = UIColor.blackColor().CGColor
        imageview.layer.borderWidth = 1.0
        //add the imageview to the scrollview

The last step to run this is by setting the Navigation Controller as the Initial View Controller in your Storyboard and set the View Controller as his root controller.
When you have this all set up you should be able to run the project and get a view that looks like the following image.

At first I didn't know what I was doing wrong, I started calculating the height of the navigation bar and position the ImageViews more to the top by setting the 'y' var negative by adding the following code:

    	//make the image and imageview
        let image = UIImage(named: "settings.png") //this can be any image
        let navHeight = self.navigationController!.navigationBar.frame.size.height
        let statusHeight = UIApplication.sharedApplication().statusBarFrame.size.height
        var imageview = UIImageView(frame: CGRect(x: 0, y: 0 - statusHeight - navHeight, width: image!.size.width, height: image!.size.height))
        imageview.image = image

But this ended up in a terrible trial and error, I started getting images to much to the top when in landscape because an iPhone in landscape doesn't have a status bar while an iPad does. So I was adding more bad things to the scrollview than good. I had to find a better and easier sollution.

Finding the solution

When I went to look at the API of the UIScrollView (which you can find here), I saw 2 interesting attributes, contentOffset and contentInset.

The contentInset attribute defines the place where the content of the scrollview wil begin. And the contentOffset attributes means the same but where it will end.

What I did is I went to check those values the moment my view was fully loaded with my code at the first stage (so without y: 0 - navHeight - statusHeight).

What I ended up with was that the top of my contentInset automatically is turned to 64. I figured out why. The view that you've put your scrollview in doesn't know that you've already laid constraints so that your scrollview always will be under the top layout. So basically the view automatically will push the contentInset to the bottom when it detects that there is like a navigationbar added to the view.

Solution in short

The solution to this simple but stupid problem is quite simple as well.
At your main storyboard, select the view controller that contains the scrollview. In the attributes inspector you wil see a checkbox that says 'Adjust Scroll View Insets'. When you uncheck this one, your view controller wil not touch the contentInset of your scrollviews anymore.

Now when I use in the original code, the content of my scrollview will stay at the same height.