コンテンツに合わせてUIScrollViewのサイズを自動調整する方法


130

UIScrollViewスクロールするコンテンツの高さ(または幅)を自動調整する方法はありますか?

何かのようなもの:

[scrollView setContentSize:(CGSizeMake(320, content.height))];

回答:


306

UIScrollView含まれているサブビューに基づいてのコンテンツサイズを更新するために私が出会った中で最高の方法:

Objective-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ではうまく機能しましたが、何らかの理由でiOS6の自動レイアウトをテストしても機能しなかったため、高さの値が間違っていたため、正常に機能するように切り替えましたviewDidAppear。多分誰かがこれを必要とするだろうと指摘するだけです。感謝
Hatem Alimam 2014年

1
iOS6 iPadでは、右下隅に不思議なUIImageView(7x7)がありました。(可視&& alpha)UIコンポーネントのみを受け入れることでそれを除外し、それが機能しました。そのimageViewがscrollBarsに関連していたのかどうか疑問に思っています。
JOM 2014年

5
スクロールインジケーターが有効になっている場合は注意してください。UIImageViewは、SDKによってそれぞれ自動的に作成されます。あなたはそれを説明しなければなりません、さもなければあなたのコンテンツサイズは間違っているでしょう。(stackoverflow.com/questions/5388703/…を参照)
AmitP 2014年

このソリューションは、スウィフト4で私のために完璧に動作することを確認することができます
イーサン・ハンフリーズ

実際には、CGRect.zeroから結合を開始することはできません。負の座標でいくつかのサブビューをホームする場合にのみ機能します。ユニオンサブビューフレームは、ゼロからではなく、最初のサブビューから開始する必要があります。たとえば、フレーム(50、50、100、100)を持つUIViewであるサブビューが1つだけあります。コンテンツのサイズは(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

scrollViewHeightを高さに初期化すると、スクローラーがそのコンテンツより大きくなります。これを行う場合は、スクローラーコンテンツの初期の高さに表示されるビューの高さを追加しないでください。多分あなたは最初のギャップか何かが必要です。
emenegro 2010年

21
それとも実行しますint y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
ギャル

@saintjab、ありがとうございます。正式な回答として追加しました:)
Gal

40

自動レイアウトを使用している場合の解決策:

  • 関連するすべてのビューでに設定translatesAutoresizingMaskIntoConstraintsNOます。

  • スクロールビューの外部の制約を使用して、スクロールビューの位置とサイズを設定します。

  • 制約を使用して、スクロールビュー内のサブビューをレイアウトします。制約は、スクロールビューの4つのエッジすべてに結び付けられ、サイズを取得するためにスクロールビューに依存しないようにしてください。

出典:https : //developer.apple.com/library/ios/technotes/tn2154/_index.html


10
Imhoこれは、すべてが自動レイアウトで発生する世界での真の解決策です。他の解決策について:すべてのビューの高さを計算し、場合によっては幅を計算するのに真剣に取り組んでいますか?
Fawkes

これを共有してくれてありがとう!それが受け入れられる答えになるはずです。
SePröbläm

2
スクロールビューの高さをコンテンツの高さに基づくようにしたい場合、これは機能しません。(たとえば、一定量のコンテンツまでスクロールしたくない画面の中央にポップアップが表示されます)。
LeGom 2017

これは質問の答えにはなりません
Igor Vasilev

素晴らしいソリューション。ただし、もう1つ箇条書きを追加します。「子スタックビューを垂直方向に拡大する場合は、高さを制限しないでください。スクロールビューの端に固定してください。」
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
素晴らしい!ちょうど私が必要としたもの。
リチャード

2
このようなメソッドがクラスの一部ではないことに驚いています。
ジョアン・ポルテラ

なぜメソッドのself.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;最初とself.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;最後に行ったのですか?
ジョアン・ポルテラ

リッチに感謝:)答えを+1してください。
シュラッダー

1
@richyあなたが正しいスクロールインジケーターは表示されている場合はサブビューですが、ロジックの表示/非表示は間違っています。2つのBOOLローカルを追加する必要があります:restoreHorizo​​ntal = NO、restoreVertical = NO。Horizo​​ntalが表示されている場合は非表示にし、restoreHorizo​​ntalをYESに設定します。垂直でも同じです。次に、for / inループの後、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

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 2017

美しい!実装しただけです。
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、スクロールインジケーターが非表示に設定された後に計算が実行されます。

また、オプションの関数パラメーターがあり、関数にパラメーターを渡すことでオフセット値を追加できます。


このソリューションには仮定が多すぎますが、コンテンツサイズを設定するメソッドでスクロールインジケーターを表示するのはなぜですか?
Kappe

5

@leviathanの優れた最高のソリューション。FP(関数型プログラミング)アプローチを使用して迅速に変換するだけです。

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

このように処理することで、アイテム(サブビュー)を直接上下にスタックする必要がなくなります。


次の1つのライナーをループ内で使用することもできます。maxHeight = MAX(maxHeight、child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena 14年

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 subViewsツリーを含めることができるため、再帰的に実行することをお勧めします。 ここに画像の説明を入力してください

スウィフト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

reduceを使用するswift4の場合:

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

また、少なくとも1つのサブビューがorigin == CGPoint.zeroであるかどうかを確認するか、特定の(たとえば最大の)サブビューの原点をゼロに割り当ててください。
ポールB

これは、CGRect()でビューを配置する人にのみ機能します。制約を使用している場合、これは機能しません。
linus_hologram

1

サイズは、その中に読み込まれたコンテンツとクリッピングオプションによって異なります。テキストビューの場合は、折り返し、テキストの行数、フォントサイズなどにも依存します。自分で計算することはほとんど不可能です。良いニュースは、ビューが読み込まれた後、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ファイルをビューコントローラーにインポートし、通常のUIScrollViewインスタンスの代わりにSBScrollViewインスタンスを宣言します。


2
layoutSubviewsは、このようなレイアウト関連のコードに適した場所であるように見えます。ただし、UIScrollViewでは、スクロール中にコンテンツオフセットが更新されるたびに、layoutSubviewが呼び出されます。また、コンテンツのサイズは通常、スクロールしても変化しないため、かなり無駄です。
Sven

それは本当だ。この非効率を回避する1つの方法は、[self isDragging]と[self isDecelerating]を確認し、両方がfalseの場合にのみレイアウトを実行することです。
グレッグブラウン

1

このように動的コンテンツのサイズを設定します。

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

0

それは本当にコンテンツに依存します:content.frame.heightはあなたが望むものを与えるかもしれませんか?コンテンツが単一のものか、それともコレクションかによって異なります。


0

私はまた、リヴァイアサンの答えが最高に機能することを発見した。しかし、それは奇妙な高さを計算していました。サブビューをループするときに、スクロールビューがスクロールインジケーターを表示するように設定されている場合、それらはサブビューの配列に含まれます。この場合、解決策は、ループする前に一時的にスクロールインジケーターを無効にしてから、以前の表示設定を再確立することです。

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

なぜ1行のコードではないのですか?

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

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.