如何动态设置UIScrollView的内容大小

39

我有一个关于UIScrollview的问题。

情况是这样的,我有一个名为ChartsView的UIView,我通过重写drawRect()方法自己重新绘制它。绘图内容是动态生成的,因此我直到运行时才知道其大小。问题是如何/在哪里可以动态设置其超级视图(scrollView)的内容大小?有任何想法吗?

    - (void)viewDidLoad
    {
        [super viewDidLoad];

// not this way. it's fixed size.....
        ChartsView *chartsView = [[ChartsView alloc]initWithFrame:CGRectMake(0, 0, 320, 800)]; 

        self.scrollView.contentSize = chartsView.frame.size;

        [self.scrollView addSubview:chartsView];

    }
12个回答

62

再来一种简单的方法

- (void)viewDidLoad
{
    float sizeOfContent = 0;
    UIView *lLast = [scrollView.subviews lastObject];
    NSInteger wd = lLast.frame.origin.y;
    NSInteger ht = lLast.frame.size.height;

    sizeOfContent = wd+ht;

    scrollView.contentSize = CGSizeMake(scrollView.frame.size.width, sizeOfContent);
}

1
不错的技巧。它可以用于任何 UIScrollView。对此点赞。 - Akshit Zaveri
我只想补充一件事。你们中的一些人可能喜欢将这段代码放在viwDidAppear:中,因为你的UIView可能会在viewDidAppear之前绘制,但在viewDidLoad被调用之后;这将使你能够访问视图的最新高度。 - mvb
20
lastObject 返回在subviews数组中索引最后的对象,而不是屏幕上最下面的子视图。事实上,lastObject 与视图的位置无关,使用它是错误的。 - AmitP
返回导航控制器时出现了问题。 - Gajendra K Chauhan
1
由于某些原因,这个程序在我的nib文件中是正确的,但它从未返回正确的子视图。最终我放弃了调用最后一个子视图,并硬编码了我想要的子视图。 - user3344977
为什么lastObject会是正确的高度?把所有对象的高度加起来不是更好吗? - Trip

25

并不保证scrollView的subviews数组中的最后一个对象是滚动视图中y轴起点最高的对象。subviews数组是按Z-Index排列的(即在subviews数组中,最后一个对象将是堆叠最高且将成为滚动视图子视图中最顶部的子视图)。因此,更准确的方法是使用基本排序函数来迭代subviews并获取具有最高y-origin的对象。

Swift 4

extension UIScrollView {
    func updateContentView() {
        contentSize.height = subviews.sorted(by: { $0.frame.maxY < $1.frame.maxY }).last?.frame.maxY ?? contentSize.height
    }
}

使用方法(在 viewDidLayoutSubviews 或每当您的内容大小更新时):

myScrollView.updateContentView()

在调用updateContentView()之前,请记得添加以下这行代码:self.view.layoutIfNeeded() - Holofox
1
这确实是我找到的唯一合法解决方案。经常重复的方法(如下所示),其中将内容高度限制为滚动视图的父视图的高度不起作用。我对此进行了修改,并将其转换为UIView的扩展,以便我可以查询人们倾向于嵌套在UIScrollViewController下面的scrollContent视图,并找出在任何给定时间包含其子视图所需的高度。 - Oscar

7

swift (2.0)

@IBOutlet weak var btnLatestButton: UIButton!

override func viewDidLoad() {
    super.viewDidLoad()

    let height = btnLatestButton.frame.size.height
    let pos = btnLatestButton.frame.origin.y
    let sizeOfContent = height + pos + 10
    scrollview.contentSize.height = sizeOfContent

}

当您的滚动视图中只有固定数量的视图时,可以使用上述方法。iOS Rocks 方法也可用,但对我来说不好,创建了更多的空间,而我并不需要。
    let lastView : UIView! = scrollview.subviews.last
    let height = lastView.frame.size.height
    let pos = lastView.frame.origin.y
    let sizeOfContent = height + pos + 10
    scrollview.contentSize.height = sizeOfContent

4
感谢IOS Rocks和CularBytes提供的线索。 我发现上述解决方案存在两个问题(对我来说):
1. 我始终将一个容器UIView作为UIScrollView的唯一子项,因此必须更新查找该子UIView中最后一个视图的方式(见下文) 2. 我正在偏移我的UIScrollView中的内容,因此需要计算最后一个UIView的绝对位置(即相对于窗口而不是父视图)
另外,我正在使用自动布局来固定UIScrollView的边缘,因此不需要担心宽度(与CularBytes一样)。
以下是我想出的并且可行的方法(在Swift 3.0中):
func setScrollViewContentSize() {

    var height: CGFloat
    let lastView = self.myScrollView.subviews[0].subviews.last!
    print(lastView.debugDescription) // should be what you expect

    let lastViewYPos = lastView.convert(lastView.frame.origin, to: nil).y  // this is absolute positioning, not relative
    let lastViewHeight = lastView.frame.size.height

    // sanity check on these
    print(lastViewYPos)
    print(lastViewHeight)

    height = lastViewYPos + lastViewHeight

    print("setting scroll height: \(height)")

    myScrollView.contentSize.height = height
}

我在viewDidAppear()方法中调用此方法。

4

动态计算UIScrollView的contentSize(根据子视图的框架)

Swift:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        var contentRect = CGRect.zero

        for view in self.scrollView.subviews {
           contentRect = contentRect.union(view.frame)
        }

        self.scrollView.contentSize = contentRect.size
    }
}

Objective C:

 - (void)viewDidLayoutSubviews {
     [super viewDidLayoutSubviews];

     dispatch_async(dispatch_get_main_queue(), ^ {
                        CGRect contentRect = CGRectZero;

                        for(UIView *view in scrollView.subviews)
                           contentRect = CGRectUnion(contentRect,view.frame);

                           scrollView.contentSize = contentRect.size;
                       });
}

3
在Storyboard中使用滚动视图时,最好根据滚动视图中存在的视图数量计算内容大小,而不是以静态值编程方式给出内容大小。 以下是获取内容大小的步骤。
第1步: 在故事板中将ScrollView添加到视图中,并添加前导、尾随、顶部和底部约束(所有值都为零)。
第2步: 不要直接添加需要的视图到滚动视图中,首先向滚动视图添加一个视图(这将是我们所有UI元素的内容视图)。 添加以下约束到此视图。 前导、尾随、顶部和底部约束(所有值都为零)。 添加相等的高度、宽度到主视图(即包含滚动视图的视图)。 对于相等的高度,将优先级设置为低。 (这是设置内容大小的重要步骤)。 该内容视图的高度将根据添加到视图中的视图数量而定。 假设您添加的最后一个视图是一个标签,其Y位置为420,高度为20,则您的内容视图将为440。
第3步:根据需要,为添加到内容视图中的所有视图添加约束。

enter image description here

enter image description here

Ref:https://medium.com/@javedmultani16/uiscrollview-dynamic-content-size-through-storyboard-in-ios-fb873e9278e


在搜索了很多之后,我发现这是最好的答案。 - Ankit Rathi
我发现这个方法不起作用。你在这里的方法告诉iOS滚动内容与屏幕一样高,但这可能并不是真实情况。如果你在运行时减小了滚动视图的高度(为了给下面引入的新UI元素腾出空间),那么你就会在滚动内容底部留下空白。 如果你的滚动内容比屏幕更高怎么办?告诉iOS滚动内容只有屏幕那么高会在底部截断它。 或者我漏掉了什么? - Oscar

3
override func viewDidAppear(_ animated: Bool) {
    var height: CGFloat
    let lastView = self.scrollView.subviews[0].subviews.last!

    let lastViewYPos = lastView.convert(lastView.frame.origin, to: nil).y  
    let lastViewHeight = lastView.frame.size.height

    height = lastViewYPos + lastViewHeight
    scrollView.contentSize.height = height
}

欢迎来到 stackoverflow。请编辑你的答案,解释你的代码片段是如何解决问题的。更多信息请参见:[answer] - Djensen
虽然更详细的信息可能会更好,但现有的答案已经足够好并且有帮助。 - Totoro
谢谢,我会从现在开始添加更多细节。 - Asad Jamil

2

SWIFT 5

您还可以通过调整UIScrollView.contentLayoutGuide来以编程方式更新UIScrollView的contentView,以下是将contentview限制为yourView高度的示例:

self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.heightAnchor.constraint(equalTo: self.view.heightAnchor).isActive = true
self.scrollView.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true

self.scrollView.contentLayoutGuide.heightAnchor.constraint(equalTo: yourView.heightAnchor, multiplier: 1).isActive = true
self.scrollView.contentLayoutGuide.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1).isActive = true
self.scrollView.contentLayoutGuide.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.scrollView.contentLayoutGuide.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true

1
尝试这个 -
- (void)viewDidLoad
    {
        [super viewDidLoad];

// not this way. it's fixed size.....
        ChartsView *chartsView = [[ChartsView alloc]initWithFrame:CGRectMake(0, 0, 320, 800)]; 

        //self.scrollView.contentSize = chartsView.frame.size;
        [self.scrollView setContentSize:CGSizeMake(chartsView.frame.size.width, chartsView.frame.size.height)];
        [self.scrollView addSubview:chartsView];

    }

1

试一下

- (void)viewDidLoad
        {
            [super viewDidLoad];

    // not this way. it's fixed size.....
            ChartsView *chartsView = [[ChartsView alloc]initWithFrame:CGRectMake(0, 0, 320, 800)]; 

            self.scrollView.contentSize = chartsView.frame.size;
            [self.scrollView setNeedsDisplay];
            [self.scrollView addSubview:chartsView];

    }

仍然没有成功。我尝试找到一种解决方法,在绘制完成后设置Charts视图中ScrollView的内容大小。现在的问题是,我可以使用哪种API来获取ChartsView的实际大小? ((UIScrollView *)[self superview]).contentSize = CGSizeMake(320, 480); - Roger Lee

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