내용에 맞게 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

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

  • 설정 translatesAutoresizingMaskIntoConstraints에 NO관련된 모든 뷰에.

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

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

출처 : 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.