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

有没有一种方法可以使 UIScrollView自动调整到它所滚动的内容的高度(或宽度) ?

比如:

[scrollView setContentSize:(CGSizeMake(320, content.height))];
187934 次浏览

它实际上取决于内容: content.frame.height 可能会给你你想要的?取决于内容是一个单独的事物,还是一个事物的集合。

UIScrollView 不会自动知道其内容的高度。您必须自己计算高度和宽度

比如说

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


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

但这只有在视图一个接一个的情况下才有效。如果你有一个相邻的视图,你只需要添加一个高度,如果你不想设置滚动条的内容大于它的实际大小。

大小取决于装载在其中的内容以及剪辑选项。如果它是一个文本视图,那么它还取决于包装、文本的行数、字体大小等等。几乎不可能计算你自己。好消息是,它是在视图加载之后在 viewWillAppear 中计算出来的。在此之前,它的所有未知和内容大小将是相同的帧大小。但是,在 viewWillAppear 方法和之后(比如 viewDidAppear) ,内容大小将是实际的。

我把这个加到 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;
}

包装 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 实例。

您可以通过计算哪个子节点“到达更远的地方”来获得 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))];

通过这种方式,项目(子视图)不必直接堆叠在另一个之下。

我遇到过的最好的方法是根据包含的子视图更新 UIScrollView的内容大小:

目标 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

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

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

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

  • 在滚动视图中使用约束来布局子视图,确保约束绑定到滚动视图的所有四条边,不要依赖滚动视图来获得它们的大小。

来源: Https://developer.apple.com/library/ios/technotes/tn2154/_index.html

我根据@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 的填充

我还发现利维坦的答案最有效。然而,它正在计算一个奇怪的高度。当通过子视图进行循环时,如果 scrollview 设置为显示滚动指示符,那么这些指示符将位于子视图数组中。在这种情况下,解决方案是在循环之前暂时禁用滚动指示器,然后重新建立它们以前的可见性设置。

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

我认为这是更新 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
}
}

或者就这么做:

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

(这个解决方案是我在这个页面中作为评论添加的。在获得19个赞成票之后,我决定添加这个解决方案作为对社区有益的正式回答!)

为什么不用一行代码呢? ?

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

以下的扩展将有助于 斯威夫特

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中的隐藏视图,并且在滚动指示符设置为隐藏后执行计算。

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

对于那些懒得去改变它的人来说,这是一个公认的迅速的答案:)

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

像这样设置动态内容大小。

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

来自@leviathan 的伟大和最好的解决方案。只是转换为快速使用 FP (函数式编程)方法。

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

由于 scrollView 可以有其他 scrollView 或不同的 inDepth 子视图树,所以最好以递归方式深入运行。 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)
}
}

用法

scrollView.recalculateVerticalContentSize_synchronous()

以下是根据@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()

非常容易使用!

对于 Swift 4使用 reduce:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
return $0.union($1.frame)
}).size
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)
}


}

我创建了 ScrollView的一个子类来处理 intrinsicContentSize,它对我来说非常有用

public final class ContentSizedScrollView: UIScrollView {
override public var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}


override public var intrinsicContentSize: CGSize {
layoutIfNeeded()
return self.contentSize
}
}

现在您可以使用这个类创建一个 scrollview,并在所有方面设置约束

确保子视图绑定到 scrollView 的所有四条边

如果您使用的是 自动布局,那么只需将边框元素的边缘设置为与滚动视图相等即可。

例如,我想让我的水平滚动视图自动适应我的水平内容:

斯威夫特

let bottomConstrint = NSLayoutConstraint.init(item: (bottommost UI element),
attribute: .bottom,
relatedBy: .equal,
toItem: (your UIScrollView),
attribute: .bottom,
multiplier: 1.0,
constant: 0)
bottomConstrint.isActive = true

如果你像我一样使用 快餐,只需:

  scrollView.addSubview( (bottommost element) )
(bottommost element).snp.makeConstraints { make in
/*other constraints*/
make.bottom.equalToSuperview()
}

我将创建一个 UIScrollView的子类,具体如下:

class ContentSizedScrollView: UIScrollView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}


override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}

这将根据内容的高度自动调整大小。

class ContentSizedScrollView: UIScrollView {


override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}


override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}


// In UIViewController


import SnapKit


...


var scrollView: ContentSizedScrollView!


override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = .init(width: view.bounds.width, height: stackView.bounds.height)
}




// Here some example of content composing inside of UIStackView
func setupContent() {
scrollView = ContentSizedScrollView()
blockView.addSubview(scrollView)
scrollView.snp.makeConstraints { make in
make.top.equalTo(19)
make.left.equalToSuperview()
make.right.equalToSuperview()
make.bottom.equalTo(-20)
}
scrollView.contentInset = .init(top: 0, left: 0, bottom: 26, right: 0)
scrollView.showsVerticalScrollIndicator = false
scrollView.clipsToBounds = true
scrollView.layer.cornerRadius = blockView.layer.cornerRadius / 2


stackView = UIStackView()
scrollView.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.width.equalToSuperview().offset(-10)
}
stackView.axis = .vertical
stackView.alignment = .center


textTitleLabel = Label()
stackView.addArrangedSubview(textTitleLabel)
textTitleLabel.snp.makeConstraints { make in
make.width.equalToSuperview().offset(-30)
}
textTitleLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold)
textTitleLabel.textColor = Color.Blue.oxfordBlue
textTitleLabel.textAlignment = .center
textTitleLabel.numberOfLines = 0
stackView.setCustomSpacing(10, after: textTitleLabel)
}