How do I auto size a UIScrollView to fit its content

IphoneIosObjective CCocoa TouchUiscrollview

Iphone Problem Overview


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))];

Iphone Solutions


Solution 1 - Iphone

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

let contentRect: CGRect = scrollView.subviews.reduce(into: .zero) { rect, view in
    rect = rect.union(view.frame)
}
scrollView.contentSize = contentRect.size

Solution 2 - Iphone

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.

Solution 3 - Iphone

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

Solution 4 - Iphone

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;
}

Solution 5 - Iphone

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

Solution 6 - Iphone

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 !

Solution 7 - Iphone

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.

Solution 8 - Iphone

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

Solution 9 - Iphone

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.

Solution 10 - Iphone

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

Solution 11 - Iphone

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!)

Solution 12 - Iphone

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()

Solution 13 - Iphone

For swift4 using reduce:

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

Solution 14 - Iphone

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.

Solution 15 - Iphone

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.

Solution 16 - Iphone

why not single line of code??

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

Solution 17 - Iphone

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.

Solution 18 - Iphone

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;
}

Solution 19 - Iphone

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
    }
}

Solution 20 - Iphone

Set dynamic content size like this.

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

Solution 21 - Iphone

import UIKit

class DynamicSizeScrollView: UIScrollView {

    var maxHeight: CGFloat = UIScreen.main.bounds.size.height
    var maxWidth: CGFloat = UIScreen.main.bounds.size.width
    
    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size,self.intrinsicContentSize){
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        let height = min(contentSize.height, maxHeight)
        let width = min(contentSize.height, maxWidth)
        return CGSize(width: width, height: height)
    }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjwerreView Question on Stackoverflow
Solution 1 - IphoneleviathanView Answer on Stackoverflow
Solution 2 - IphoneemenegroView Answer on Stackoverflow
Solution 3 - IphoneAdam WaiteView Answer on Stackoverflow
Solution 4 - IphonerichyView Answer on Stackoverflow
Solution 5 - IphoneJoshView Answer on Stackoverflow
Solution 6 - IphonefredericdndView Answer on Stackoverflow
Solution 7 - IphonegokhanakkurtView Answer on Stackoverflow
Solution 8 - Iphoneothierry42View Answer on Stackoverflow
Solution 9 - IphonepaxxView Answer on Stackoverflow
Solution 10 - IphoneChrisView Answer on Stackoverflow
Solution 11 - IphoneGalView Answer on Stackoverflow
Solution 12 - Iphoneiluvatar_GRView Answer on Stackoverflow
Solution 13 - Iphonemm282View Answer on Stackoverflow
Solution 14 - IphonePapaSmurfView Answer on Stackoverflow
Solution 15 - IphoneAmitPView Answer on Stackoverflow
Solution 16 - IphoneAliView Answer on Stackoverflow
Solution 17 - IphoneAndiihView Answer on Stackoverflow
Solution 18 - IphoneclayzarView Answer on Stackoverflow
Solution 19 - IphoneFurqan KhanView Answer on Stackoverflow
Solution 20 - IphoneMonika PatelView Answer on Stackoverflow
Solution 21 - Iphoneuser6127890View Answer on Stackoverflow