내용에 맞게 UITableView 크기 조정


170

에 질문이 UILabel있고에 객관식 답변이 표시 되는 앱을 만들고 있습니다. UITableView각 행 에는 객관식 선택이 표시됩니다 . 질문과 답변은 다양하므로 UITableView동적으로 높이 가 필요합니다 .

sizeToFit테이블에 대한 해결 방법 을 찾고 싶습니다 . 표의 프레임이 모든 내용의 높이로 설정되는 경우.

아무도 내가 이것을 달성 할 수있는 방법에 대해 조언 할 수 있습니까?


OSX에서 비슷한 일을하려는 사람은 다음 질문을 참조하십시오. stackoverflow.com/questions/11322139/…
Michael Teper

1
@MiMo 안녕하세요, "sizeToFit"접근 방식의
문제점을 설명해 주

""sizeToFit "해결 방법을 찾고 싶습니다 . UIView의 - sizeToFit방법 을 사용해 보셨습니까 ?
슬립 D. 톰슨

답변:


155

실제로 나는 스스로 답을 찾았습니다.

난 그냥 새를 만들 CGRect에 대한 tableView.frameheighttable.contentSize.height

즉,의 높이를 설정 UITableView받는 height내용의. 코드가 UI를 수정하므로 기본 스레드에서 UI를 실행하는 것을 잊지 마십시오.

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });

3
이 방법으로 문제가 발생할 수 있습니다. 큰 목록이 있으면 프레임의 높이로 인해 일부 셀이 잘릴 수 있습니다. 수신 거부를 사용하도록 설정 한 경우 아래쪽으로 스크롤하면 일부 셀이 튀어 나오는 것을 볼 수 있습니다.
whyoz

높이를 정확히 어디에 지정 했습니까? reloadData를 호출하여 사후 크기를 조정 했습니까?
James

27
이 코드를 넣는 가장 좋은 장소는 어디입니까? viewDidLoad에서 시도했지만 tableview 대리자 메소드가 아직 실행되지 않았기 때문에 작동하지 않았을 것입니다. 어떤 델리게이트 방법이 가장 좋습니까?
Kyle Clegg 2

4
나는 이것이 viewDidAppear이며, 효과가있는 것으로 보인다.
테이블 뷰

2
나는 viewDidAppear은 모든 초기있는 tableView의 cellForRowAtIndexPath 호출을 완료 한 후 발생 할 물건을 넣을 수있는 좋은 장소입니다 것으로 나타났습니다
rigdonmr

120

KVO, DispatchQueue 또는 제약 조건 설정이없는 Swift 5 및 4.2 솔루션.

이 솔루션은 Gulz의 답변을 기반으로합니다. 합니다.

1)의 서브 클래스를 생성합니다 UITableView:

import UIKit

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

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

2) UITableView레이아웃에 a 를 추가하고 모든면에 제약 조건을 설정하십시오. 그것의 클래스를 설정ContentSizedTableView .

3) 스토리 보드는 하위 클래스 intrinsicContentSize를 고려 하지 않기 때문에 약간의 오류가 표시 됩니다. 크기 관리자를 열고 intrinsicContentSize를 자리 표시 자 값으로 재정 의하여이 문제를 해결하십시오. 이것은 디자인 타임에 대한 재정의입니다. 런타임에 ContentSizedTableView클래스 에서 재정의를 사용합니다.


업데이트 : Swift 4.2 코드가 변경되었습니다. 이전 버전을 사용 UIViewNoIntrinsicMetric하는 경우 대신UIView.noIntrinsicMetric


1
아주 좋은 대답, 그것은 나를 위해 일했습니다. 감사합니다. 한 가지 생각 - 나는의 클래스를 변경하는 데 필요한 내 경우는 tableView일하기 IntrinsicTableView스토리 보드에. 테이블 뷰를 다른 테이블 내에 포함시킬 필요가 없었습니다 UIView.
dvp.petrov

그래, 당신 말이 맞아. 내 답변을 업데이트했습니다. 당신이 말한 것처럼 2 단계를 읽을 수 있습니다. 물론 나는로 설정 tableView했다 IntrinsicTableView. 또한 추가 포장 UIView이 필요하지 않습니다. 기존의 모든 부모 뷰를 사용할 수 있습니다 :)
fl034

1
스위프트 4 이것은 나를 위해 잘 작동tableView.sizeToFit()
Souf ROCHDI

예, 콘텐츠가 정적 인 경우 좋은 해결책이 될 수 있습니다. 내 접근 방식을 사용하면 자동 레이아웃을 사용하여 동적 뷰가 포함 된 tableView를 다른 뷰에 임베드하고 sizeToFit()호출해야 할 위치에 대한 생각없이 항상 전체 높이를 유지할 수 있습니다.
fl034

2
멋진 감사합니다!. 마지막으로 부모와 일치하고 tableview에 대한 내용을 포장 ...
Amber K

79

스위프트 솔루션

다음과 같이하세요:

1- 스토리 보드에서 테이블의 높이 제한을 설정하십시오.

2- 스토리 보드에서 높이 구속 조건을 끌어 뷰 컨트롤러 파일에서 @IBOutlet을 만듭니다.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- 그런 다음이 코드를 사용하여 테이블 동적 분석의 높이를 변경할 수 있습니다.

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
}

최신 정보

마지막 행이 잘린 경우 willDisplay 셀 함수에서 viewWillLayoutSubviews ()를 호출하십시오.

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}

1
나를 위해 일했다!! thnx
Pushpa Y

5
이 솔루션은 항상 Xcode 8.3의 마지막 행을 차단했습니다.
Jen C

@Jec C : 나도 마찬가지
KMC

나도 일 했어 !!
Antony Raphel

1
나는 이것을 cellForRowAtIndexPath : 메소드에 넣어야했다. viewWillLayoutSubviews 메소드에 넣을 경우 계산 된 contentSize 높이는 실제 컨텐츠 높이가 아닌 예상 높이를 기반으로합니다.
Manu Mateos

21

나는 iOS 7에서 이것을 시도했고 그것은 나를 위해 일했다.

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}

3
UITableView가 동적 인 경우 (정보로드가 즉시로드 됨), viewDidLoad가 아닌 다른 곳에 배치해야합니다. 나를 위해 cellForRowAtIndexPath에 넣었습니다. 또한 오래된 "속성 tableView를 찾을 수 없음"오류를 피하기 위해 "tableView"를 내 IBOutlet 특성의 이름으로 변경해야했습니다.
jeremytripp

들으, 팝 오버 내부의 tableview의 크기를 조정에 문제가 있었다, 그것은 일
Zaporozhchenko 올렉산드르에게

19

테이블보기에서 contentSize 속성에 대한 관찰자를 추가하고 그에 따라 프레임 크기를 조정하십시오.

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

콜백에서 :

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

이것이 도움이되기를 바랍니다.


2
한 가지 더 추가해야합니다. 테이블 뷰에 대한 관찰자를 제거해야합니다. 셀 할당이 해제되면 충돌하기 때문입니다.
Mahesh Agrawal

12

스크롤보기 안에 테이블보기가 있었고 tableView의 높이를 계산하고 그에 따라 크기를 조정해야했습니다. 내가 취한 단계는 다음과 같습니다.

0) scrollView에 UIView를 추가하십시오 (아마도이 ​​단계없이 작동하지만 가능한 충돌을 피하기 위해 수행했습니다)-이것은 테이블 뷰의 컨테이너 뷰입니다. 이 단계를 수행하면 뷰 경계를 테이블 뷰의 경계로 바로 설정하십시오.

1) UITableView의 하위 클래스를 만듭니다.

class IntrinsicTableView: UITableView {

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

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

}

2) 스토리 보드의 테이블 뷰 클래스를 IntrinsicTableView로 설정하십시오. http://joxi.ru/a2XEENpsyBWq0A

3) heightConstraint를 테이블 뷰로 설정하십시오.

4) 테이블의 IBoutlet을 ViewController로 드래그하십시오.

5) 테이블 높이 제약 조건의 IBoutlet을 ViewController로 드래그하십시오.

6)이 메소드를 ViewController에 추가하십시오.

override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
    }

도움이 되었기를 바랍니다


하위 클래스를 기반으로하지만 높이 제한 설정이 필요없는 내 대답을 참조하십시오.
fl034

@ fl034는 링크를 제공합니까?
Gulz

@ fl034 나는 당신의 요소가 스크롤 뷰에 삽입 할 때 당신이 내 경우가있는 ScrollView 함께) 제약 조건을 사용하지 수 있다고 생각하지 않습니다
Gulz

또한 스크롤보기 안에 내 테이블보기가 있으며 완벽하게 작동합니다. :)
fl034

iOS 13 / Xcode 11에서 작동합니다. 3 일 동안 고생했습니다. 감사합니다
Mehdi S.

8

테이블 뷰의 컨텐츠 크기 변경을 직접 추적하지 않으려는 경우이 서브 클래스가 유용 할 수 있습니다.

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {

    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }

            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}

1
신속한 3의 경우, intrinsicContentSize는 다음과 같은 변수가되었습니다.override public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod

나를 위해 일하지 않습니다. 아직도 테이블의 바닥을 숨기고
Gulz


4

스위프트 3, iOS 10.3

해결 방법 1 : 그냥 넣어self.tableview.sizeToFit()cellForRowAt indexPath 기능. 테이블 뷰 높이를 필요한 높이보다 높게 설정하십시오. tableview 아래에 뷰가없는 경우 좋은 솔루션입니다. 그러나 가지고 있다면 하단 테이블 뷰 제약 조건이 업데이트되지 않습니다 (솔루션 2를 생각해 냈으므로 수정하지 않았습니다)

예:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }

    return UITableViewCell()
}

해결 방법 2 : 스토리 보드에서 테이블 뷰 높이 제약 조건을 설정하고 ViewController로 끕니다. 셀의 평균 높이를 알고 배열에 포함 된 요소 수를 알고 있으면 다음과 같이 할 수 있습니다.

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*편집하다:

모든 솔루션을 한 후 나는 시도하고 그들의 모든 뭔가를 해결했다,하지만 완전히 이 문제를 완전히 설명하고 수정 대답이다.


이것은 매우 간단한 해결책이며, 나를 위해 일했습니다. 감사합니다 @Dorde Nilovic
R. Mohan

1

Mimo의 대답Anooj VM의 대답은 모두 훌륭하지만 목록이 큰 경우 작은 문제가 있습니다. 프레임 높이가 일부 셀을 차단 할 수 있습니다.

그래서. 나는 대답을 약간 수정했다.

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}

1

AutoLayout을 사용하는 경우 훨씬 더 좋은 방법이 있습니다. 높이를 결정하는 구속 조건을 변경하십시오. 테이블 내용의 높이를 계산 한 다음 구속 조건을 찾아 변경하십시오. 다음은 예입니다 (테이블 높이를 결정하는 구속 조건이 실제로 "같음"관계의 높이 구속 조건이라고 가정).

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}

이것을 향상시킬 수 있습니다. tableview에 높이 <= 10000과 같은 추가 제약 조건을 추가합니다. Interface Builder에서 .h 파일로 control-drag를 사용하여이 제약 조건을 뷰 컨트롤러의 속성으로 만듭니다. 그러면 제약 조건과 검색을 반복하지 않아도됩니다. 바로 설정myTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

이것은 하나의 섹션 만있는 테이블보기와 함께 자동 레이아웃을 사용하는 경우 효과적입니다.

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

자동 레이아웃을 설정할 때이 함수를 호출합니다 (여기의 샘플은 SnapKit을 사용하지만 아이디어를 얻습니다).

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

UITableView가 셀의 결합 된 높이만큼 커지기를 원합니다. 나는 세포를 반복하고 세포의 총 높이를 축적합니다. 이 시점에서 테이블 뷰의 크기가 CGRect.zero이므로 셀에서 정의한 자동 레이아웃 규칙을 준수 할 수 있도록 경계를 설정해야합니다. 크기를 충분히 큰 임의의 값으로 설정했습니다. 실제 크기는 나중에 자동 레이아웃 시스템에 의해 계산됩니다.


1

나는 약간 다른 방식으로, 실제로 내 TableView는 scrollview 안에 있었으므로 높이 제한을 0으로 지정해야했습니다.

그런 다음 런타임에 다음과 같이 변경했습니다.

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }

0

Anooj VM의 답변을 확장하여 다음을 제안합니다. 콘텐츠 크기가 변경 될 때만 콘텐츠 크기 새로 고치는 .

이 방법은 스크롤을 제대로 비활성화 하고 더 큰 목록회전을 지원합니다 . contentSize 변경 내용이 기본 스레드에서 전달되므로 dispatch_async가 필요하지 않습니다.

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}


- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }

- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}

0

Musa almatri의 objc 버전

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}

0

이 사용자 정의를 시도해 볼 수 있습니다 AGTableView

스토리 보드를 사용하거나 프로그래밍 방식으로 TableView 높이 구속 조건을 설정하려면 이 클래스는 자동으로 높이 제약 조건을 가져오고 컨텐츠 뷰 높이를 tableview 높이로 설정합니다.

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!

    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }

    override open func layoutSubviews() {
        super.layoutSubviews()

        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }

    func associateConstraints() {
        // iterate through height constraints and identify

        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

참고 높이 설정에 문제가있는 경우 yourTableView.layoutSubviews().


0

fl034의 답변을 바탕으로 합니다. 그러나 Xamarin.iOS 사용자의 경우 :

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }

0

내 Swift 5 구현은 tableView의 hight constraint를 내용의 크기 ( contentSize.height)로 설정합니다. 이 방법은 자동 레이아웃을 사용한다고 가정합니다. 이 코드는 cellForRowAttableView 메소드 안에 있어야합니다 .

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true

-1

테이블을 동적으로 만들려면 위에서 설명한대로 테이블 내용을 기반으로하는 솔루션을 사용해야합니다. 더 작은 테이블을 표시하려는 경우 컨테이너 뷰를 사용하여 UITableViewController를 포함시킬 수 있습니다. 컨테이너 크기에 따라 UITableView의 크기가 조정됩니다.

이것은 많은 계산을 피하고 레이아웃을 호출합니다.


1
above답이 뒤섞 일 수 있으므로 단어를 사용하지 마십시오 .
Nik Kov

-3

신속한 3에 대한 뮤 솔루션 :이 메소드를 호출하십시오.viewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.