By jwerre


2010-05-31 14:54:47 8 Comments

Is there a way to make a UIScrollView auto-adjust to the height (or width) of the content it's scrolling?

Something like:

[scrollView setContentSize:(CGSizeMake(320, content.height))];

20 comments

@gokhanakkurt 2016-04-14 12:39:11

Following extension would be helpful in Swift.

extension UIScrollView{
    func setContentViewSize(offset:CGFloat = 0.0) {
        // dont show scroll indicators
        showsHorizontalScrollIndicator = false
        showsVerticalScrollIndicator = false

        var maxHeight : CGFloat = 0
        for view in subviews {
            if view.isHidden {
                continue
            }
            let newHeight = view.frame.origin.y + view.frame.height
            if newHeight > maxHeight {
                maxHeight = newHeight
            }
        }
        // set content size
        contentSize = CGSize(width: contentSize.width, height: maxHeight + offset)
        // show scroll indicators
        showsHorizontalScrollIndicator = true
        showsVerticalScrollIndicator = true
    }
}

Logic is the same with the given answers. However, It omits hidden views within UIScrollView and calculation is performed after scroll indicators set hidden.

Also, there is an optional function parameter and you're able to add an offset value by passing parameter to function.

@leviathan 2013-06-24 19:37:38

The best method I've ever come across to update the content size of a UIScrollView based on its contained subviews:

Objective-C

CGRect contentRect = CGRectZero;

for (UIView *view in self.scrollView.subviews) {
    contentRect = CGRectUnion(contentRect, view.frame);
}
self.scrollView.contentSize = contentRect.size;

Swift

var contentRect = CGRect.zero

for view in mainScrollView.subviews {
   contentRect = contentRect.union(view.frame)
}
mainScrollView.contentSize = contentRect.size

@Hatem Alimam 2014-05-07 08:56:42

I was running this in viewDidLayoutSubviews so the autolayout would finish, in iOS7 it worked well, but testing os iOS6 the autolayout for some reason didn't finish the work, so I had some wrong height values, so I switched to viewDidAppear now works fine.. just to point out maybe someone would need this. thanks

@JOM 2014-05-08 09:20:32

With iOS6 iPad there was mysterious UIImageView (7x7) at bottom right corner. I filtered that away by accepting only (visible && alpha) UI components, then it worked. Wondering if that imageView was related to scrollBars, definitely not my code.

@AmitP 2014-10-26 15:58:51

Please be careful when Scroll Indicators are enabled! a UIImageView will be automatically created for each one by the SDK. you must account for it or else your content size will be wrong. ( see stackoverflow.com/questions/5388703/… )

@Ethan Humphries 2018-05-03 13:50:08

Can confirm that this solution works perfectly for me in Swift 4

@Evgeny 2019-07-23 13:03:31

Actually, we cannot start unioning from CGRect.zero, it works only if you home some subviews in negative coordinates. You should start union subview frames from the first subview, not from zero. For instance, you have just one subview which is an UIView with frame (50, 50, 100, 100). Your content size will be (0, 0, 150, 150), not (50, 50, 100, 100).

@mm282 2018-05-22 12:26:48

For swift4 using reduce:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
   return $0.union($1.frame)
}).size

@Paul B 2019-01-15 14:12:32

Also worth checking if at least one subview has origin == CGPoint.zero or just assign a particular (largest for example) subview's origin to zero.

@linus_hologram 2019-08-05 12:40:43

This only works for people who place their views with CGRect(). If you are using constraints this is not working.

@fredericdnd 2017-05-15 17:06:00

Here's a Swift 3 adaptation of @leviatan's answer :

EXTENSION

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

USAGE

scrollView.resizeScrollViewContentSize()

Very easy to use !

@Hardik Shah 2017-07-08 08:16:17

Very useful. After so many iteration i found this solution and it is perfect.

@arvidurs 2017-09-17 03:41:45

Lovely! Just implemented it.

@iluvatar_GR 2017-03-22 17:02:51

Because a scrollView can have other scrollViews or different inDepth subViews tree, run in depth recursively is preferable. enter image description here

Swift 2

extension UIScrollView {
    //it will block the mainThread
    func recalculateVerticalContentSize_synchronous () {
        let unionCalculatedTotalRect = recursiveUnionInDepthFor(self)
        self.contentSize = CGRectMake(0, 0, self.frame.width, unionCalculatedTotalRect.height).size;
    }

    private func recursiveUnionInDepthFor (view: UIView) -> CGRect {
        var totalRect = CGRectZero
        //calculate recursevly for every subView
        for subView in view.subviews {
            totalRect =  CGRectUnion(totalRect, recursiveUnionInDepthFor(subView))
        }
        //return the totalCalculated for all in depth subViews.
        return CGRectUnion(totalRect, view.frame)
    }
}

Usage

scrollView.recalculateVerticalContentSize_synchronous()

@othierry42 2016-11-27 14:11:33

Great & best solution from @leviathan. Just translating to swift using FP (functional programming) approach.

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect(), { 
  CGRectUnion($0, $1.frame) 
}.size

@Andy 2016-12-12 02:19:15

If you want to ignore hidden views you can filter subviews before passing to reduce.

@John Rogers 2018-04-09 00:39:26

Updated for Swift 3: self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size

@Monika Patel 2016-07-16 08:03:23

Set dynamic content size like this.

 self.scroll_view.contentSize = CGSizeMake(screen_width,CGRectGetMaxY(self.controlname.frame)+20);

@Josh 2016-07-16 07:33:47

Here is the accepted answer in swift for anyone who is too lazy to convert it :)

var contentRect = CGRectZero
for view in self.scrollView.subviews {
    contentRect = CGRectUnion(contentRect, view.frame)
}
self.scrollView.contentSize = contentRect.size

@drew.. 2017-07-02 23:50:12

and updated for Swift3: var contentRect = CGRect.zero for view in self.scrollView.subviews { contentRect = contentRect.union(view.frame) } self.scrollView.contentSize = contentRect.size

@Maksim Kniazev 2018-05-16 21:48:46

Does this has to be called on main thread? and in didLayoutSubViews?

@Ali 2016-03-29 08:12:21

why not single line of code??

_yourScrollView.contentSize = CGSizeMake(0, _lastView.frame.origin.y + _lastView.frame.size.height);

@Gal 2015-12-15 08:21:44

Or just do:

int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];

(This solution was added by me as a comment in this page. After getting 19 up-votes for this comment, I've decided to add this solution as a formal answer for the benefit of the community!)

@Furqan Khan 2015-09-12 11:50:44

I think this can be a neat way of updating UIScrollView's content view size.

extension UIScrollView {
    func updateContentViewSize() {
        var newHeight: CGFloat = 0
        for view in subviews {
            let ref = view.frame.origin.y + view.frame.height
            if ref > newHeight {
                newHeight = ref
            }
        }
        let oldSize = contentSize
        let newSize = CGSize(width: oldSize.width, height: newHeight + 20)
        contentSize = newSize
    }
}

@Mr.G 2016-06-26 13:05:55

does this work ??

@clayzar 2014-06-04 18:11:53

I also found leviathan's answer to work the best. However, it was calculating a strange height. When looping through the subviews, if the scrollview is set to show scroll indicators, those will be in the array of subviews. In this case, the solution is to temporarily disable the scroll indicators before looping, then re-establish their previous visibility setting.

-(void)adjustContentSizeToFit is a public method on a custom subclass of UIScrollView.

-(void)awakeFromNib {    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self adjustContentSizeToFit];
    });
}

-(void)adjustContentSizeToFit {

    BOOL showsVerticalScrollIndicator = self.showsVerticalScrollIndicator;
    BOOL showsHorizontalScrollIndicator = self.showsHorizontalScrollIndicator;

    self.showsVerticalScrollIndicator = NO;
    self.showsHorizontalScrollIndicator = NO;

    CGRect contentRect = CGRectZero;
    for (UIView *view in self.subviews) {
        contentRect = CGRectUnion(contentRect, view.frame);
    }
    self.contentSize = contentRect.size;

    self.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
    self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
}

@Chris 2014-04-05 16:12:53

I came up with another solution based on @emenegro's solution

NSInteger maxY = 0;
for (UIView* subview in scrollView.subviews)
{
    if (CGRectGetMaxY(subview.frame) > maxY)
    {
        maxY = CGRectGetMaxY(subview.frame);
    }
}
maxY += 10;
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, maxY)];

Basically, we figure out which element is furthest down in the view and adds a 10px padding to the bottom

@Adam Waite 2014-03-27 22:17:59

Solution if you're using auto layout:

  • Set translatesAutoresizingMaskIntoConstraints to NO on all views involved.

  • Position and size your scroll view with constraints external to the scroll view.

  • Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.

Source: https://developer.apple.com/library/ios/technotes/tn2154/_index.html

@Fawkes 2015-06-14 21:40:21

Imho this is the real solution in the world where everything happens with auto layout. Regarding the other solutions: What the... are you serious in calculating every view's heigh and sometimes width?

@SePröbläm 2016-03-26 14:52:16

Thank you for sharing this! That should be the accepted answer.

@LeGom 2017-09-27 13:24:00

This doesn't work when you want to height of the scrollview to be based on its content height. (for instance a popup centered in the screen where you don't want to scroll until a certain amount of content).

@Igor Vasilev 2018-11-22 17:11:16

This does not answer the question

@Andrew Kirna 2019-07-19 20:32:57

Great solution. However, I'd add one more bullet..."Don't restrict the height of a child stack view if it should grow vertically. Just pin it to the edges of the scroll view."

@paxx 2013-05-21 18:43:15

You can get height of the content inside UIScrollView by calculate which child "reaches furthers". To calculate this you have to take in consideration origin Y (start) and item height.

float maxHeight = 0;
for (UIView *child in scrollView.subviews) {
    float childHeight = child.frame.origin.y + child.frame.size.height;
    //if child spans more than current maxHeight then make it a new maxHeight
    if (childHeight > maxHeight)
        maxHeight = childHeight;
}
//set content size
[scrollView setContentSize:(CGSizeMake(320, maxHeight))];

By doing things this way items (subviews) don't have to be stacked directly one under another.

@Thomas C. G. de Vilhena 2014-01-31 12:37:06

You can also use this one liner inside the loop: maxHeight = MAX(maxHeight, child.frame.origin.y + child.frame.size.height) ;)

@richy 2011-06-14 01:02:49

I added this to Espuz and JCC's answer. It uses the y position of the subviews and doesn't include the scroll bars. Edit Uses the bottom of the lowest sub view that is visible.

+ (CGFloat) bottomOfLowestContent:(UIView*) view
{
    CGFloat lowestPoint = 0.0;

    BOOL restoreHorizontal = NO;
    BOOL restoreVertical = NO;

    if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
    {
        if ([(UIScrollView*)view showsHorizontalScrollIndicator])
        {
            restoreHorizontal = YES;
            [(UIScrollView*)view setShowsHorizontalScrollIndicator:NO];
        }
        if ([(UIScrollView*)view showsVerticalScrollIndicator])
        {
            restoreVertical = YES;
            [(UIScrollView*)view setShowsVerticalScrollIndicator:NO];
        }
    }
    for (UIView *subView in view.subviews)
    {
        if (!subView.hidden)
        {
            CGFloat maxY = CGRectGetMaxY(subView.frame);
            if (maxY > lowestPoint)
            {
                lowestPoint = maxY;
            }
        }
    }
    if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
    {
        if (restoreHorizontal)
        {
            [(UIScrollView*)view setShowsHorizontalScrollIndicator:YES];
        }
        if (restoreVertical)
        {
            [(UIScrollView*)view setShowsVerticalScrollIndicator:YES];
        }
    }

    return lowestPoint;
}

@Richard 2011-08-19 21:15:08

Wonderful! Just what I needed.

@bobbypage 2011-08-23 02:18:09

perfect, thanks so much!

@João Portela 2011-10-21 10:10:21

I'm surprised a method like this one not part of the class.

@João Portela 2011-10-21 10:11:39

Why did you do self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO; in the beginning and self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES; in the end of the method?

@richy 2012-02-05 23:52:59

it's not needed is it

@Shraddha 2012-02-09 10:55:52

Thanks richy :) +1 for the answer.

@zakdances 2012-05-17 21:15:50

Where does this code go? What is the "self" referring to?

@richy 2012-06-15 00:35:15

self is the owner of the scroll view

@richy 2012-06-15 00:41:18

Added showing and hiding of scroll indicator because scrollView.subviews can count the indicators as subviews. Not sure what causes it to count them sometimes.

@illegal-immigrant 2013-02-22 15:40:21

@richy you're right scroll indicators are subviews if visible, but showing/hiding logic is wrong. You need to add two BOOL locals: restoreHorizontal=NO,restoreVertical=NO. If showsHorizontal, hide it, set restoreHorizontal to YES. Same for vertical. Next, after for/in loop insert if statement: if restoreHorizontal, set to show it, same for vertical. Your code will just force to show both indicators

@richy 2013-02-25 22:59:13

good point taras.roshko! updating code also added checks for responding to set show scroll indicators so it can be used with a normal UIView

@AmitP 2013-02-19 20:15:26

Wrapping Richy's code I created a custom UIScrollView class that automates content resizing completely!

SBScrollView.h

@interface SBScrollView : UIScrollView
@end

SBScrollView.m:

@implementation SBScrollView
- (void) layoutSubviews
{
    CGFloat scrollViewHeight = 0.0f;
    self.showsHorizontalScrollIndicator = NO;
    self.showsVerticalScrollIndicator = NO;
    for (UIView* view in self.subviews)
    {
        if (!view.hidden)
        {
            CGFloat y = view.frame.origin.y;
            CGFloat h = view.frame.size.height;
            if (y + h > scrollViewHeight)
            {
                scrollViewHeight = h + y;
            }
        }
    }
    self.showsHorizontalScrollIndicator = YES;
    self.showsVerticalScrollIndicator = YES;
    [self setContentSize:(CGSizeMake(self.frame.size.width, scrollViewHeight))];
}
@end

How to use:
Simply import the .h file to your view controller and declare a SBScrollView instance instead of the normal UIScrollView one.

@Sven 2013-05-21 18:56:21

layoutSubviews looks like it is the right place for such layout related code. But in a UIScrollView layoutSubview gets called every time the content offset is updated while scrolling. And since the content size usually doesn’t change while scrolling this is rather wasteful.

@Greg Brown 2013-06-18 01:44:45

That's true. One way to avoid this inefficiency might be to check [self isDragging] and [self isDecelerating] and only perform layout if both are false.

@PapaSmurf 2011-05-27 03:25:51

The size depends on the content loaded inside of it, and the clipping options. If its a textview, then it also depends on the wrapping, how many lines of text, the font size, and so on and on. Nearly impossible for you to compute yourself. The good news is, it is computed after the view is loaded and in the viewWillAppear. Before that, it's all unknown and and content size will be the same as frame size. But, in the viewWillAppear method and after (such as the viewDidAppear) the content size will be the actual.

@emenegro 2010-05-31 15:24:48

UIScrollView doesn't know the height of its content automatically. You must calculate the height and width for yourself

Do it with something like

CGFloat scrollViewHeight = 0.0f;
for (UIView* view in scrollView.subviews)
{
   scrollViewHeight += view.frame.size.height;
}

[scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];

But this only work if the views are one below the other. If you have a view next to each other you only have to add the height of one if you don't want to set the content of the scroller larger than it really is.

@jwerre 2010-05-31 16:25:54

Do you think something like this would work as well? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))]; BTW, don't forget to ask for the size then height. scrollViewHeight += view.frame.size.height

@emenegro 2010-06-01 07:05:14

Initializing the scrollViewHeight to the height makes the scroller larger than its content. If you do this don't add the height of the views visible on the initial height of the scroller content. Although maybe you need an initial gap or something else.

@Gal 2013-07-10 13:33:35

Or just do: int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame)‌​, y))];

@saintjab 2015-12-14 11:42:54

@Gal, your answer works perfectly for me. Thanks

@Gal 2015-12-15 08:23:12

@saintjab, thanks, I just added it as a formal answer :)

@Andiih 2010-05-31 15:12:46

it depends on the content really : content.frame.height might give you what you want ? Depends if content is a single thing, or a collection of things.

@jwerre 2010-05-31 16:29:47

It's a collection of things

Related Questions

Sponsored Content

27 Answered Questions

[SOLVED] UIScrollView Scrollable Content Size Ambiguity

26 Answered Questions

14 Answered Questions

92 Answered Questions

25 Answered Questions

[SOLVED] Center content of UIScrollView when smaller

26 Answered Questions

[SOLVED] How do I sort an NSMutableArray with custom objects in it?

9 Answered Questions

4 Answered Questions

[SOLVED] How to center content in a UIScrollView with contentOffset

18 Answered Questions

[SOLVED] How can I use Autolayout to set constraints on my UIScrollview?

0 Answered Questions

Sponsored Content