如何自动调整UIScrollView的大小以适应内容

140
有没有办法让UIScrollView自动调整到其滚动的内容的高度(或宽度)?
类似这样的功能:
[scrollView setContentSize:(CGSizeMake(320, content.height))];
25个回答

316

我发现更新基于UIScrollView所包含的子视图大小的最佳方法是:

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

13
我之前在 viewDidLayoutSubviews 中运行这段代码,以便自动布局完成。在iOS7中,它可以正常工作,但是在测试iOS6时,由于某种原因自动布局没有完成,导致我得到了错误的高度值。所以我改用了 viewDidAppear,现在代码可以正常工作了。这只是提醒,也许有人会需要这个信息。谢谢。 - Hatem Alimam
1
使用iOS6的iPad时,在右下角有一个神秘的UIImageView(7x7)。通过仅接受(visible && alpha)UI组件,我过滤掉了它,然后它就正常工作了。想知道这个imageView是否与scrollBars有关,但肯定不是我的代码问题。 - JOM
6
在启用滚动指示器时,请注意!SDK 将自动为每个指示器创建一个 UIImageView,如果您不对此进行处理,那么您的内容大小将会出错。(请参见 https://dev59.com/aW435IYBdhLWcg3wfQF9 ) - AmitP
可以确认这个解决方案在Swift 4中完美地运行。 - Ethan Humphries
实际上,我们不能从CGRect.zero开始联合,它只在您将一些子视图放置在负坐标中时才起作用。您应该从第一个子视图开始联合子视图框架,而不是从零开始。例如,您只有一个子视图,它是一个UIView,其框架为(50,50,100,100)。您的内容大小将为(0,0,150,150),而不是(50,50,100,100)。 - Evgeny
能否通过AutoLayout计算子视图的大小,即使它们带有偏移量?这种方法只计算框架而不计算之间的距离。 - Bios90

73

UIScrollView无法自动获取其内容的高度。必须自己计算高度和宽度。

可以使用以下方式进行计算

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

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

但是这仅适用于视图一个接一个地排列的情况。如果您的视图并排排列,如果您不想将滚动区域的内容设置得比实际更大,则只需添加其中一个视图的高度即可。


你认为这样做也可以吗? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];顺便提一下,别忘了先获取宽度再获取高度。 scrollViewHeight += view.frame.size.height - jwerre
将scrollViewHeight初始化为高度会使滚动条比其内容更大。如果您这样做,请不要将滚动条内容的初始高度上可见视图的高度加起来。虽然也许您需要一个初始间隙或其他东西。 - emenegro
21
或者只需执行以下代码:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))]; 这将使滚动视图的内容尺寸自适应其子视图的大小。 - Gal
@saintjab,谢谢,我刚刚将其添加为正式答案 :) - Gal

48

如果您正在使用自动布局,解决方案如下:

  • 在所有相关视图上将translatesAutoresizingMaskIntoConstraints设置为NO

  • 使用与滚动视图外部的约束来定位和调整滚动视图的大小。

  • 使用约束来布局滚动视图内的子视图,确保约束与滚动视图的四个边缘相连,并且不依赖于滚动视图来确定它们的大小。

来源:Technical Note TN2154 UIScrollView And Autolayout


14
在一切都使用自动布局的世界里,我认为这是真正的解决方案。至于其他解决方案:你在认真计算每个视图的高度,有时还包括宽度吗? - Fawkes
谢谢你分享这个!那应该是被接受的答案。 - SePröbläm
6
当你希望滚动视图的高度基于其内容高度时,这种方法就不起作用。(例如,在屏幕中央居中显示弹出窗口时,你不希望在内容少于一定量时进行滚动)。 - user509981
这并没有回答问题。 - Igor Vasilev
很好的解决方案。不过,我会再加一条建议:“如果一个子堆栈视图应该在垂直方向上增长,请不要限制其高度,只需将其固定在滚动视图的边缘即可。” - Andrew Kirna

35

我添加了这段代码到Espuz和JCC的答案中。它使用子视图的y位置,不包括滚动条。 编辑 使用可见的最低子视图的底部。

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

1
太棒了!正是我所需要的。 - Richard
2
我很惊讶这样的方法不是类的一部分。 - João Portela
谢谢Richy :) 给你的回答点个赞。 - Shraddha
1
@richy你是对的,滚动指示器如果可见则是子视图,但显示/隐藏逻辑是错误的。你需要添加两个BOOL本地变量:restoreHorizontal=NO,restoreVertical=NO。如果showsHorizontal,则将其隐藏,并将restoreHorizontal设置为YES。垂直方向同理。接下来,在for/in循环之后插入if语句:如果restoreHorizontal,则设置为显示它,垂直方向同理。你的代码将强制显示两个指示器。 - illegal-immigrant
好观点,taras.roshko!更新代码还添加了响应设置显示滚动指示器的检查,因此它可以与普通的UIView一起使用。 - richy
显示剩余3条评论

14

这是在 Swift 中接受的答案,适用于那些懒得转换的人 :)

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

或者更懒一点

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

更新为Swift3: var contentRect = CGRect.zero for view in self.scrollView.subviews { contentRect = contentRect.union(view.frame) } self.scrollView.contentSize = contentRect.size - drew..
1
这个需要在主线程上调用吗?还是在didLayoutSubViews中? - Yaroslav Dukal

9

以下扩展将对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
    }
}

逻辑与提供的答案相同。但是,它省略了UIScrollView中隐藏视图,并在滚动指示器设置隐藏后执行计算。

此外,还有一个可选的函数参数,您可以通过将参数传递给函数来添加偏移值。


这个解决方案包含了太多假设,为什么你在设置内容大小的方法中显示滚动指示器? - Kappe

9
这是对@leviatan答案的Swift 3版本改编:
扩展
import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

使用说明

scrollView.resizeScrollViewContentSize()

非常易于使用!

非常有用。经过这么多次迭代,我找到了这个解决方案,而且它完美无缺。 - Hardik Shah
太好了!刚刚实现了它。 - arvidurs

5

@leviathan提供的方案非常好,是使用FP(函数式编程)方法转换为Swift。

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

如果您想忽略隐藏的视图,可以在传递给reduce之前过滤子视图。 - Andrew
更新至Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size - John Rogers

4

您可以通过计算哪个子视图“到达最远”来获取UIScrollView内部内容的高度。要计算这个值,您需要考虑起始位置Y和项目高度。

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

通过这种方式,项目(子视图)不必直接堆叠在彼此下面。


你也可以在循环内使用这个一行代码:maxHeight = MAX(maxHeight, child.frame.origin.y + child.frame.size.height) ;) - Thomas C. G. de Vilhena

3
或者只需要执行以下操作:
int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];

这个解决方案是我在这个页面上添加的评论。在得到19个赞同之后,我决定将这个解决方案作为正式答案添加,以造福社区!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接