내용에 맞게 UIScrollView의 크기를 자동으로 조정하는 방법


130

UIScrollView스크롤하는 컨텐츠의 높이 (또는 너비)를 자동으로 조정 하는 방법이 있습니까?

다음과 같은 것 :

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

답변:


306

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

9
나는 이것을 viewDidLayoutSubviews자동 레이아웃이 완료되도록 iOS7에서 잘 작동했지만 iOS6을 테스트하면 어떤 이유로 든 자동 레이아웃이 작업을 완료하지 못하여 높이 값이 잘못되었으므로 이제는 제대로 viewDidAppear작동합니다. 누군가가 이것을 필요로 할 것이라고 지적하기 위해. 감사합니다
Hatem Alimam

1
iOS6 iPad에서는 오른쪽 하단 모서리에 신비한 UIImageView (7x7)가있었습니다. (보이는 && 알파) UI 구성 요소 만 수락하여 필터링했습니다. 그 imageView가 scrollBars와 관련이 있는지 궁금합니다. 내 코드는 아닙니다.
JOM

5
스크롤 표시기가 활성화되면주의하십시오! SDK는 UIImageView를 자동으로 생성합니다. 이를 설명해야합니다. 그렇지 않으면 콘텐츠 크기가 잘못됩니다. ( stackoverflow.com/questions/5388703/… 참조 )
AmitP 2016 년

이 솔루션은 스위프트 4에 완벽하게 나를 위해 작동하는지 확인할 수 있습니다
에단 험프리스

실제로, 우리는 CGRect.zero에서 통합을 시작할 수 없으며, 일부 하위 뷰를 음의 좌표로 홈을 배치하는 경우에만 작동합니다. 0이 아닌 첫 번째 서브 뷰에서 유니온 서브 뷰 프레임을 시작해야합니다. 예를 들어, 프레임 (50, 50, 100, 100)이있는 UIView 인 하나의 하위보기 만 있습니다. 콘텐츠 크기는 (50, 50, 100, 100)이 아닌 (0, 0, 150, 150)입니다.
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))]; BTW, 크기와 높이를 요청하는 것을 잊지 마십시오. 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

40

자동 레이아웃을 사용하는 경우 해결책 :

  • 설정 translatesAutoresizingMaskIntoConstraintsNO관련된 모든 뷰에.

  • 스크롤보기 외부의 제약 조건으로 스크롤보기를 배치하고 크기를 조정하십시오.

  • 제한 조건을 사용하여 스크롤보기 내에 서브 뷰를 배치하십시오. 제한 조건이 스크롤보기의 네 모서리 모두에 연결되고 스크롤보기에 의존하여 크기를 확보하지 않아야합니다.

출처 : https://developer.apple.com/library/ios/technotes/tn2154/_index.html


10
Imho 이것은 모든 것이 자동 레이아웃으로 발생하는 세상의 실제 솔루션입니다. 다른 솔루션과 관련하여 : 모든 뷰의 높이와 때로는 너비를 계산할 때 어떤 점이 중요합니까?
Fawkes

이것을 공유해 주셔서 감사합니다! 그 대답이 받아 들여 져야합니다.
SePröbläm

2
내용 높이를 기준으로 스크롤보기의 높이를 조정하려는 경우 작동하지 않습니다. (예를 들어, 일정량의 콘텐츠가 나올 때까지 스크롤하지 않으려는 화면 중앙에 팝업).

이것은 질문에 대답하지 않습니다
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

방법 self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;의 시작과 self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;끝 에서 왜 했 습니까?
João Portela

답변 감사합니다 richy :) +1.
Shraddha

1
@ richy 오른쪽 스크롤 표시기는 보이는 경우 하위 뷰이지만 표시 / 숨김 논리가 잘못되었습니다. 두 개의 BOOL 로컬을 추가해야합니다 : restoreHorizontal = NO, restoreVertical = NO. 수평으로 표시되면 숨기고 restoreHorizontal을 YES로 설정하십시오. 세로도 마찬가지입니다. 다음으로 for / in 루프 삽입 후 if 문 : restoreHorizontal 인 경우이를 수직으로 표시하도록 설정하십시오. 귀하의 코드는 두 지표를 모두 강제로 표시합니다
불법 이민자

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.contentSize contentRect.size = {contentRect = contentRect.union (view.frame)}에 self.scrollView.subviews 보려면
그린 ..

메인 스레드에서 호출해야합니까? 그리고 didLayoutSubViews에서?
Maksim Kniazev

8

@leviatan의 답변에 대한 스위프트 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스크롤 표시기가 숨김으로 설정된 후에 계산이 수행됩니다.

또한 선택적 기능 매개 변수가 있으며 매개 변수를 함수에 전달하여 오프셋 값을 추가 할 수 있습니다.


이 솔루션에는 너무 많은 가정이 포함되어 있는데 왜 컨텐츠 크기를 설정하는 방법으로 스크롤 표시기를 표시합니까?
Kappe

5

@leviathan의 훌륭하고 최상의 솔루션. FP (기능 프로그래밍) 접근 방식을 사용하여 신속하게 번역합니다.

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

숨겨진보기를 무시하려면 축소하기 위해 전달하기 전에 하위보기를 필터링 할 수 있습니다.
Andy

스위프트 3에 업데이트 :self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
존 로저스

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

또한 하나 이상의 하위 뷰에 origin == CGPoint.zero가 있는지 또는 특정 (예 : 가장 큰) 하위 뷰의 원점을 0에 할당하는지 확인해야합니다.
Paul 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

사실입니다. 이 비 효율성을 피하는 한 가지 방법은 [self isDragging] 및 [self isDecelerating]을 확인하고 둘 다 거짓 인 경우에만 레이아웃을 수행하는 것입니다.
Greg Brown

1

이와 같이 동적 컨텐츠 크기를 설정하십시오.

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

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

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.