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


Answers:


306

我见过的最好的方法是UIScrollView根据其所包含的子视图来更新a的内容大小:

目标C

CGRect contentRect = CGRectZero;

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

迅速

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

9
我正在运行此程序,viewDidLayoutSubviews因此自动布局将完成,在iOS7中运行良好,但是由于某些原因在OS6上测试了OS6的自动布局未能完成工作,因此我有一些错误的高度值,因此我切换到viewDidAppear现在可以正常使用了。只是指出也许有人会需要这个。感谢
Hatem Alimam 2014年

1
在iOS6 iPad的右下角有一个神秘的UIImageView(7x7)。我只接受(可见的&& alpha)UI组件将其过滤掉,然后它起作用了。想知道那个imageView是否与scrollBars有关,绝对不是我的代码。
2014年

5
启用滚动指示器时,请小心!SDK会自动为每个UIImageView创建一个UIImageView。您必须考虑它,否则您的内容大小将是错误的。(见stackoverflow.com/questions/5388703/...
AmitP

可以确认该解决方案在Swift 4中非常适合我
Ethan Humphries,

实际上,我们无法从CGRect.zero开始合并,仅当您将某些子视图放置在负坐标中时,它才起作用。您应该从第一个子视图开始而不是从零开始并集子视图框架。例如,您只有一个子视图,即一个带有框架(50,50,100,100)的UIView。您的内容大小将为(0,0,150,150),而不是(50,50,100,100)。
Evgeny

74

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 2010年

将scrollViewHeight初始化为高度会使滚动条大于其内容。如果这样做,则不要在滚动条内容的初始高度上添加可见视图的高度。尽管也许您需要一个初始的差距或其他东西。
emenegro 2010年

21
或者只是做:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
2013年

@saintjab,谢谢,我只是将其添加为正式答案:)
Gal

40

如果使用自动布局的解决方案:

  • 设置translatesAutoresizingMaskIntoConstraintsNO所有涉及的视图。

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

  • 使用约束在滚动视图中布置子视图,确保约束与滚动视图的所有四个边缘相关联,并且不依赖于滚动视图来获取其大小。

资料来源:https : //developer.apple.com/library/ios/technotes/tn2154/_index.html


10
恕我直言,这是世界上真正的解决方案,所有事情都通过自动版式进行。关于其他解决方案:在计算每个视图的高度(有时甚至是宽度)时,您是认真的...?
福克斯2015年

感谢您分享这一点!那应该是公认的答案。
SePröbläm

2
当您希望滚动视图的高度基于其内容高度时,此方法不起作用。(例如,在屏幕上居中显示一个弹出窗口,直到您要滚动一定数量的内容为止)。
LeGom

这不能回答问题
Igor Vasilev

很好的解决方案。但是,我还要再加上一个项目符号...“如果子堆栈视图应该垂直增长,则不要限制其高度。只需将其固定在滚动视图的边缘即可。”
安德鲁·基尔纳

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
精彩!正是我所需要的。
理查德

2
令我感到惊讶的是,这种方法不是课程的一部分。
若昂里斯本

为什么self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;在方法的开始和self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;结束时进行操作?
若昂里斯本

谢谢richy :) +1的答案。
Shraddha'2

1
@richy你是对的滚动指示器是可见的子视图,但是显示/隐藏逻辑是错误的。您需要添加两个BOOL本地变量:restoreHorizo​​ntal = NO,restoreVertical = NO。如果显示showHorizo​​ntal,请将其隐藏,将restoreHorizo​​ntal设置为YES。垂直方向也一样。接下来,在for / in循环之后插入if语句:if restoreHorizo​​ntal,设置为显示它,垂直显示相同。您的代码只会强制显示两个指标
非法移民

13

对于任何懒惰的人来说,这都是迅速接受的答案:)

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

并为Swift3更新:var contentRect = CGRect.zero以查看self.scrollView.subviews {contentRect = contentRect.union(view.frame)} self.scrollView.contentSize = contentRect.size
绘制。.

是否必须在主线程上调用它?在didLayoutSubViews中?
Maksim Kniazev '18

8

这是@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

7

以下扩展将对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并在滚动指示器设置为隐藏后执行计算。

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


此解决方案包含太多假设,为什么您要使用设置内容大小的方法来显示滚动指示器?
卡佩

5

@leviathan的最佳解决方案。只需使用FP(函数编程)方法转换为swift。

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

如果要忽略隐藏视图,则可以在传递子视图之前对子视图进行过滤以进行缩减。
安迪

已为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 CG de Vilhena

3

我基于@emenegro的解决方案提出了另一个解决方案

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

基本上,我们找出哪个元素在视图中最下方,并在底部添加10px的填充


3

因为scrollView可以具有其他scrollViews或不同的inDepth子视图树,所以最好以递归方式深度运行。 在此处输入图片说明

迅捷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)
    }
}

用法

scrollView.recalculateVerticalContentSize_synchronous()

2

或者只是做:

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

(此解决方案是我在此页中添加的评论。在对此评论进行19次投票后,我决定将该解决方案作为正式答案添加,以使社区受益!)


2

对于swift4使用reduce:

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

同样值得检查的是,至少一个子视图的原点== CGPoint.zero还是仅将特定(例如最大)子视图的原点分配为零。
Paul B

这仅适用于使用CGRect()放置其视图的人。如果使用约束,则此方法无效。
linus_hologram

1

大小取决于其中加载的内容以及剪切选项。如果是textview,则还取决于换行,多少行文本,字体大小等等。您几乎无法计算自己。好消息是,它是在加载视图之后并在viewWillAppear中计算的。在此之前,这都是未知的,内容大小将与帧大小相同。但是,在viewWillAppear方法中以及之后(例如viewDidAppear),内容大小将是实际的。


1

包装Richy的代码,我创建了一个自定义UIScrollView类,该类可以完全自动执行内容大小调整!

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

使用方法:
只需将.h文件导入您的视图控制器,然后声明一个SBScrollView实例,而不是普通的UIScrollView实例。


2
layoutSubviews看起来像是此类与布局相关的代码的正确位置。但是在UIScrollView中,每次滚动时更新内容偏移量都会调用layoutSubview。而且由于滚动时内容大小通常不会改变,因此非常浪费。
斯文

确实如此。避免这种效率低下的一种方法可能是检查[self isDragging]和[self isDecelerating]并仅在两者均为false时才执行布局。
格雷格·布朗


0

这实际上取决于内容:content.frame.height可能会为您提供所需的内容?取决于内容是单个事物还是事物的集合。


0

我还发现leviathan的答案是最好的。但是,它正在计算一个奇怪的高度。遍历子视图时,如果将滚动视图设置为显示滚动指示器,则这些滚动指示器将在子视图数组中。在这种情况下,解决方案是在循环之前临时禁用滚动指示器,然后重新建立其先前的可见性设置。

-(void)adjustContentSizeToFit 是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;
}

0

我认为这是更新UIScrollView内容视图大小的一种好方法。

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

0

为什么不一行代码呢?

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

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

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.