15
votes

In advance, I apologize as I would say that I'm a beginner with iOS programming.

I want to use UIScrollView because the content to display exceed the height of the screen. Therefore, I would like only a vertical scroll and not a horizontal scrolling.

I'm using the Storyboard to draw the views with AutoLayout.

Here's are the screenshot of my UIViewController with the UIScrollView :

enter image description hereenter image description here

Then I change the Label to a big text like that

override func viewDidLoad() {
    super.viewDidLoad()

    self.label1Label.text = "Seize jours après la chute du président Blaise Compaoré, le Burkina Faso a un nouveau chef d'EtatA l'issue d'ultimes tractions, civils et militaires se sont accordés, lundi 17 novembre, sur le nom du diplomate Michel KafandoSeize jours après la chute du président Blaise Compaoré, le Burkina Faso a un nouveau chef d'EtatA l'issue d'ultimes tractions, civils et militaires se sont accordés, lundi 17 novembre, sur le nom du diplomate Michel Kafando"
    self.label1Label.numberOfLines = 0
    self.label1Label.sizeToFit()

My problem is that if I don't set manually a width for my contentView (inside the UIScrollView), the scrolling is horizontal, not vertical. (Look Screenshot below):

enter image description here

I've tried to set contentSize as I've seen in many google post but without success :

self.scrollView.contentSize = CGSizeMake(400.0, 600.0)

If I set a width manually for my contentview (i.e : 320pts), the scrolling will be vertical (GOOD) but then depending on the iphone size, it won't cover the whole width of the screen as shown in the following screenshot :

enter image description here

The question is : what is the correct implementation to use UIScrollView to have a contentView that respect the autolayout constraint (full screen : 0top - 0bottom - 0left - 0right) and the scrolling to be only vertical.

Thanks a lot for your help!

7

7 Answers

42
votes

Mike Woelmer shows how to do this correctly with Interface Builder on the Atomic Object blog.

http://spin.atomicobject.com/2014/03/05/uiscrollview-autolayout-ios/

I also have my own code only (no storyboard implementation) on github.

https://github.com/nadthevlad/AutolayotScrollview

You don't want to set the height or width of your content or views. Instead you want to use Autolayouts pins and constraints to set how the scrollview behaves.

  1. Create your UIScrollView *scrollView.

  2. You want to create one UIView *contentView which you will put the rest of your view elements into.

    [self.view addSubview:scrollView];
    [scrollView addSubview:contentView];
    [contentView addSubview:_label1];
    [contentView addSubview:_label2];
    [contentView addSubview:_label3];
    [contentView addSubview:_label4];
    [contentView addSubview:_label5];
    [contentView addSubview:_label6];
    
  3. Pin the 4 edges of scrollView to the 4 edges of self.view

  4. Pin the top and bottom edges of contentView to the top and bottom of scrollView.

  5. This is the tricky part. To set the horizontal sizing, you want the leading (right) and trailing(left) edges of the contentView to be pinned to the leading and trailing edges self.view instead of scrollView. Even though contenView is a sub view of scrollView its horizontal constraints are going to reach outside of the scrollView and connect to self.view.

  6. Pin any other view elements to contentView as you normally would.

22
votes

The trick to permitting a UIScrollView to scroll in only one direction is to make the content size of the UIScrollView for the restricted dimension the same as the size of the UIScrollView's frame in the same dimension. So in this case, scrollview.contentSize.width should equal scrollview.frame.size.width.

With that in mind, try the following:

  1. Ensure you have constraints set up in the same way as described in this answer

  2. Add the following code to your view controller:

    override func viewWillLayoutSubviews()
    {
        super.viewWillLayoutSubviews();
        self.scrollView.contentSize.height = 3000; // Or whatever you want it to be.
    }
    

Personally, I'm really not a fan of Auto Layout. If you are interested in trying this without Auto Layout - ie. just with code instead of constraints - you could turn off Auto Layout for the view controller's view and change your viewWillLayoutSubviews method to look like this:

override func viewWillLayoutSubviews()
{
    super.viewWillLayoutSubviews();

    self.scrollView.frame = self.view.bounds; // Instead of using auto layout
    self.scrollView.contentSize.height = 3000; // Or whatever you want it to be.
}
7
votes

This how I always do it for scrolling vertically from storyboard with constraints :

1-drag a viewcontroller

2-drag a scrollview in the controller's main view with constraints L=0 , T =0 , T =0 and B = 0 to its superview (controller's main view).

3-drag a UIVIew into the scroll view to work as its content view with constraints L-0,T=0,T=0,B=0 to its superview(the scrollview)

  • well here its time for setting Content size of scroll view , which controls scrolling both Horizontally and vertically as follows.

4-for stoping horizontal scrolling , make content view equal width to scrollview with constraint.

5-make height of content view equal hight of controller's view , This is temporary till we fill it with scrollable content .

6-add controls in the content view till finish .

7-and the most important delete the constraint you made in point 5 of the hight of content view and instead of it make constraint from the control on bottom of content view to have bottom alignment to it . This helps in making content view hight starts from first control on top and down till last control (it is all depends on this step).

8-just run and results should be as expected , otherwise please let me know .

Hope this helps!

2
votes

You can define ScrollView normally and constrain the properties of ScrollView normally

        let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.backgroundColor = .white
    self.view.addSubview(scrollView)

    scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0.0).isActive = true
    scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0.0).isActive = true
    scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0.0).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0).isActive = true

But the trick is in how you define your Label (or other view) that may be wider than the screen. Instead of using

myLabel.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -10.0).isActive = true

You should use

myLabel.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true

This second line will stop that view from causing horizontal scrolling. Do that with all your subviews and you won't scroll horizontally. (It will still scroll vertically as planned.)

Also, don't forget to set the bottom constraint on your last subview to make sure your UIScrollview scrolls vertically.

labelX.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -10.0).isActive = true
0
votes

I use this implementation for only horizontal/vertical scrollviews, although it is in Objective-C, I will try to explain the logic if something is unclear to you.

//Getting iDevice's screen width
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;

//Setting the right content size - only height is being calculated depenging on content.
CGSize contentSize = CGSizeMake(screenWidth, HEIGHT_OF_YOUR_CONTENT);
self.scrollView.contentSize = contentSize;

Now the width of the contentSize property of the scrollview is being attached to the actual width of the device's screen, so the scrollview will be scrolling only in vertical direction.

0
votes

Thanks to @NadtheVlad, I finally saw the light. Some further fiddling taught me that contentView just needs to be pinned to scrollView's Top, Bottom and Width. No need to reference self.view.

With EasyPeasy, this is simply:

scrollView <- Edges()
contentView <- [Top(), Bottom(), Width().like(scrollView)]
0
votes

I am late.. if you give the width constrains of label equal to view then you can achieve same kind of thing

 aboutCaption.widthAnchor.constraint(equalTo: self.view.widthAnchor , multiplier: 0.9).isActive = true

here is my sample code

class AboutVC: UIViewController{

let scrollView = UIScrollView()

lazy var aboutLbl : UILabel = {
    let lbl = UILabel()
    lbl.text = "About"
    lbl.font = .boldSystemFont(ofSize: 19)
    return lbl
}()

lazy var aboutCaption : UILabel = {
    let lbl = UILabel()
    lbl.text = "Hi. My name is Uzumaki Naruto. I am a professional MMA Coach with 15+ years of experience. I specialize in ninjutsu, shadow clones and various other techniques. I am in the TOP50 coaches in the Sports category. Been with GIGIMOT for 5 years already. Not a single bad review. "
    lbl.numberOfLines = 0
    lbl.font  = UIFont(name: "Helvetica Neue Thin", size: 16)!
    lbl.textColor = .systemGray2
    return lbl
}()

override func viewDidLoad() {
    super.viewDidLoad()
    configureUI()
    //        tableView.dataSource = self
    //        tableView.delegate = self
}

func configureUI() {
    self.view.backgroundColor = UIColor.white
    
    //Add and setup scroll view
    self.view.addSubview(self.scrollView)
    scrollView.backgroundColor = .white
    self.scrollView.translatesAutoresizingMaskIntoConstraints = false;
    self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true;
    self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true;
    self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true;
    self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true;
    
    scrollView.addSubview(aboutLbl)
    
    aboutLbl.anchor(top: scrollView.topAnchor, left: scrollView.leftAnchor , paddingTop: 10 , paddingLeft: 10)
    
    
    scrollView.addSubview(aboutCaption)
    aboutCaption.anchor(top: aboutLbl.bottomAnchor, left: scrollView.leftAnchor, bottom: scrollView.bottomAnchor , right: scrollView.rightAnchor, paddingTop: 10, paddingLeft: 10, paddingBottom: 10, paddingRight: 0)
    aboutCaption.widthAnchor.constraint(equalTo: self.view.widthAnchor , multiplier: 0.9).isActive = true
    
}}