[Objective-C + Cocoa] UIScrollView and contentSize

So here’s a simple one. For whatever reason, a UIScrollView instance only behaves correctly if you programmatically set its contentSize when you use it. This is fairly silly because in most cases the contentSize is simply the total size of the UIScrollView’s subview(s). Why the UIScrollView class doesn’t provide at least the option of automatically determining its own contentSize based upon its current subviews is beyond me, but here is some simple code to approximate this behavior:

@interface UIScrollView(auto_size)
- (void) adjustHeightForCurrentSubviews: (int) verticalPadding;
- (void) adjustWidthForCurrentSubviews: (int) horizontalPadding;
- (void) adjustWidth: (bool) changeWidth andHeight: (bool) changeHeight withHorizontalPadding: (int) horizontalPadding andVerticalPadding: (int) verticalPadding;
@end

@implementation UIScrollView(auto_size) 
- (void) adjustWidth: (bool) changeWidth andHeight: (bool) changeHeight withHorizontalPadding: (int) horizontalPadding andVerticalPadding: (int) verticalPadding {
    float contentWidth = horizontalPadding;
    float contentHeight = verticalPadding;
    for (UIView* subview in self.subviews) {
        [subview sizeToFit];
        contentWidth += subview.frame.size.width;
        contentHeight += subview.frame.size.height;
    }
    
    contentWidth = changeWidth ? contentWidth : self.superview.frame.size.width;
    contentHeight = changeHeight ? contentHeight : self.superview.frame.size.height;
    
    NSLog(@"Adjusting ScrollView size to %fx%f, verticalPadding=%d, horizontalPadding=%d", contentWidth, contentHeight, verticalPadding, horizontalPadding);
    self.contentSize = CGSizeMake(contentWidth, contentHeight);
}

- (void) adjustHeightForCurrentSubviews: (int) verticalPadding {
    [self adjustWidth:NO andHeight:YES withHorizontalPadding:0 andVerticalPadding:verticalPadding];
}

- (void) adjustWidthForCurrentSubviews: (int) horizontalPadding {
    [self adjustWidth:YES andHeight:NO withHorizontalPadding:horizontalPadding andVerticalPadding:0];
}
@end

This code allows a UIScrollView to internally determine its contentSize based upon its current subviews; all you have to do is call one of the three interface methods at an appropriate time (like from within your parent view-controller’s ‘viewDidLoad:‘ implementation). Note that while auto-sizing based upon both width and height is supported, you will only get a correct result for width if all of the UIScrollView’s subviews span the entire height of the view, and you will only get a correct result for height if all of your subviews span the entire width of the view. For instance, if you add a thumbnail image to the UIScrollView and then drag a UILabel next to it then both of them will count towards the computed height even though they are logically on the same row.

You can work around this limitation either by using the ‘…padding’ parameters to adjust the final contentSize, or by adding a UIView that spans the width of the UIScrollView and placing both your thumbnail image and UILabel as subviews of that UIView instead of the UIScrollView. The latter option of using a nested UIView to contain the content of the row is a better/more maintainable way to build an interface anyways (and also building a UI in Android basically requires you to follow this pattern, so best to get used to it). But I did try various approaches to solve this problem automatically in the code, such as keeping track min and max x/y coordinates of every subview in the UIScrollView, but this gave inconsistent results between the initial time the view was displayed and subsequent times.

This entry was posted in coding, objective-c and tagged , , . Bookmark the permalink.

8 Responses to [Objective-C + Cocoa] UIScrollView and contentSize

  1. rv reviews says:

    You completed various fine points there. I did a search on the subject and found most persons will consent with your blog.

  2. electric cigarette says:

    Thanks for that awesome posting. It saved MUCH time :-)

  3. bt says:

    Great work,webmaster,nice design!

  4. 6kN31C5Lr16 says:

    I want to post quick hello and want to say appriciate for this good article.

  5. vikas says:

    Can any one tell me the every co-ordinate of scroll indicator in UITableView in iPhone?

  6. john says:

    Really nice one , thanks for sharing this.

  7. Bob says:

    I think it would be better to find the outmost coordinates of the subviews and then set your contentSize based on that.

    Instead of:

    for (UIView* subview in self.subviews)
    {
    contentWidth += subview.frame.size.width;
    contentHeight += subview.frame.size.height;
    }

    try

    for (UIView* subview in self.subviews)
    {
    contentLeft = contentLeft subview.frame.origin.x + subview.frame.size.width ?
    contentRight : subview.frame.origin.x + subview.frame.size.width;

    contentTop = contentTop subview.frame.origin.y + subview.frame.size.height ?
    contentBottom : subview.frame.origin.y + subview.frame.size.height;
    }

    contentSize.width = horizontalPadding + ( contentRight - contentLeft );
    contentSize.height = verticalPadding + ( contentBottom = contentTop );

Leave a Reply