Thay đổi kích thước UITableView để phù hợp với nội dung


170

Tôi đang tạo một ứng dụng sẽ có một câu hỏi trong UILabelvà một câu trả lời trắc nghiệm được hiển thị trong đó UITableView, mỗi hàng hiển thị nhiều lựa chọn. Câu hỏi và câu trả lời sẽ khác nhau, vì vậy tôi cần điều này UITableViewđể năng động về chiều cao.

Tôi muốn tìm một sizeToFitcông việc xung quanh bàn. Trong đó khung của bảng được đặt theo chiều cao của tất cả nội dung của nó.

Bất cứ ai có thể tư vấn làm thế nào tôi có thể đạt được điều này?


Đối với bất kỳ ai muốn làm điều tương tự trên OSX, hãy xem câu hỏi này: stackoverflow.com/questions/11322139/
mẹo

1
@MiMo hi, bạn có thể giải thích điều gì sai với cách tiếp cận "sizeToFit" không? :)
iamdavidlam

Tôi muốn tìm một "sizeToFit" hoạt động xung quanh . Vì vậy, bạn đã hoặc chưa thử - sizeToFitphương pháp của UIView ?
Slipp D. Thompson

Câu trả lời:


155

Thật ra tôi đã tìm thấy câu trả lời cho mình.

Tôi chỉ cần tạo một mới CGRectcho tableView.framevới heightcáctable.contentSize.height

Mà bộ chiều cao của UITableViewđến heightnội dung của nó. Vì mã sửa đổi giao diện người dùng, đừng quên chạy nó trong luồng chính:

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
Có thể gặp vấn đề với phương pháp này. Nếu bạn có một danh sách lớn, có thể chiều cao của khung sẽ cắt một số ô của bạn. Bạn có thể thấy điều này xảy ra nếu bạn bật bật và cuộn xuống phía dưới để xem một số ô bật ra khỏi tầm nhìn.
whyoz

Trường hợp chính xác bạn đã xác định chiều cao? Bạn đã thực hiện một cuộc gọi để tải lạiData và thay đổi kích thước của nó sau đó?
James

27
Đâu là nơi tốt nhất để đặt mã này? Tôi đã thử nó trong viewDidLoad và có lẽ nó không hoạt động vì các phương thức ủy nhiệm của chế độ xem bảng chưa được kích hoạt. Phương pháp đại biểu nào là tốt nhất?
Kyle Clegg

4
Tôi đã làm điều này là viewDidAppear và dường như nó đã hoạt động. Lưu ý rằng chế độ xem bảng có thể cao hơn một chút so với nội dung của nó vì nó để lại một chút không gian cho phần đầu trang và chân trang
Jameo

2
Tôi đã thấy rằng viewDidAppear là một nơi tuyệt vời để đặt những thứ bạn muốn xảy ra sau khi tất cả các bảng ban đầu Xem các cuộc gọi cellForRowAtIndexPath đã kết thúc
Rigdonmr

120

Giải pháp Swift 5 và 4.2 không có KVO, DispatchQueue hoặc tự cài đặt các ràng buộc.

Giải pháp này dựa trên câu trả lời của Gulz .

1) Tạo một lớp con của 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) Thêm một UITableViewvào bố cục của bạn và đặt các ràng buộc ở tất cả các phía. Đặt lớp của nó thành ContentSizedTableView.

3) Bạn sẽ thấy một số lỗi, vì Storyboard không tính đến lớp con của chúng tôi intrinsicContentSize. Khắc phục sự cố này bằng cách mở trình kiểm tra kích thước và ghi đè nội tạiContentSize thành giá trị giữ chỗ. Đây là một ghi đè cho thời gian thiết kế. Trong thời gian chạy, nó sẽ sử dụng ghi đè trong ContentSizedTableViewlớp của chúng tôi


Cập nhật: Thay đổi mã cho Swift 4.2. Nếu bạn đang sử dụng phiên bản trước, hãy sử dụng UIViewNoIntrinsicMetricthay vìUIView.noIntrinsicMetric


1
Câu trả lời rất tốt, nó làm việc cho tôi, cảm ơn. Một điều mặc dù - trong trường hợp của tôi, tôi cần phải thay đổi lớp của tableViewIntrinsicTableViewtrong kịch bản này. Tôi không cần phải nhúng chế độ xem bảng của mình trong một cái khác UIView.
dvp.petrov

Uh, đúng vậy. Tôi cập nhật câu trả lời của tôi. Bước 2 có thể được đọc như bạn nói. Tất nhiên tôi có nghĩa là để thiết lập tableViewđể IntrinsicTableView. Ngoài ra một gói thêm UIViewkhông cần thiết. Tất nhiên, bạn có thể sử dụng bất kỳ chế độ xem cha mẹ hiện có nào :)
fl034

1
Swift 4 Điều này làm việc với tôi rất tốttableView.sizeToFit()
Souf ROCHDI

Vâng đó có thể là một giải pháp tốt, nếu nội dung của bạn là tĩnh. Với cách tiếp cận của tôi, bạn có thể sử dụng Bố cục tự động để nhúng bảng Xem nội dung động trong các chế độ xem khác và giữ cho nó luôn ở độ cao đầy đủ, mà không cần suy nghĩ về những nơi sizeToFit()cần được gọi
fl034

2
Thật tuyệt vời, cảm ơn!. Cuối cùng, khớp cha mẹ và bọc nội dung cho xem bàn ...
Amber K

79

Giải pháp Swift

Thực hiện theo các bước sau:

1- Đặt giới hạn chiều cao cho bảng từ bảng phân cảnh.

2- Kéo giới hạn chiều cao từ bảng phân cảnh và tạo @IBOutlet cho nó trong tệp điều khiển xem.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Sau đó, bạn có thể thay đổi chiều cao cho bảng động bằng cách sử dụng mã này:

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

Cập nhật

Nếu hàng cuối cùng bị cắt cho bạn, hãy thử gọi viewWillLayoutSubview () trong hàm di động willDisplay:

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

1
Đã làm cho tôi!! thnx
Pushpa Y

5
Giải pháp này luôn cắt bỏ hàng cuối cùng trên Xcode 8.3 cho tôi.
Jen C

@Jec C: Không phải cho tôi
KMC

Làm việc cho tôi quá !!
Antony Raphel

1
Tôi đã phải đặt cái này trong phương thức cellForRowAtIndexPath :. Nếu tôi đặt nó trong phương thức viewWillLayoutSubview, chiều cao của kích thước nội dung được tính dựa trên chiều cao ước tính, không phải chiều cao nội dung thực tế.
Manu Mateos

21

Tôi đã thử điều này trong iOS 7 và nó hoạt động với tôi

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

3
Nếu UITableView của bạn là động (có nghĩa là tải thông tin đến và đi từ đó), bạn sẽ cần đặt cái này ở nơi khác, không phải trong viewDidLoad. Đối với tôi, tôi đặt nó trong cellForRowAtIndexPath. Cũng phải thay đổi "tableView" thành tên của thuộc tính IBOutlet của tôi để tránh lỗi "bảng thuộc tính không tìm thấy" cũ.
jeremytripp

thx, có vấn đề với việc thay đổi kích thước bàn trong popover, nó đã hoạt động
Zaporozhchenko Oleksandr

19

Thêm trình quan sát cho thuộc tính contentSize trên chế độ xem bảng và điều chỉnh kích thước khung cho phù hợp

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

sau đó trong cuộc gọi lại:

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

Hy vọng điều này sẽ giúp bạn.


2
Bạn nên thêm một điều nữa. Bạn nên loại bỏ người quan sát cho chế độ xem bảng. Bởi vì nó sẽ sụp đổ nếu tế bào bị hủy.
Mahesh Agrawal

12

Tôi đã có chế độ xem bảng bên trong chế độ xem cuộn và phải tính toán chiều cao của bảng và thay đổi kích thước cho phù hợp. Đó là những bước tôi đã thực hiện:

0) thêm UIView vào scrollView của bạn (có thể sẽ hoạt động mà không có bước này nhưng tôi đã làm điều đó để tránh mọi xung đột có thể xảy ra) - đây sẽ là chế độ xem chứa cho chế độ xem bảng của bạn. Nếu bạn thực hiện bước này, sau đó đặt đường viền khung nhìn sang chế độ xem của bảng.

1) tạo một lớp con của 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) đặt lớp của chế độ xem bảng trong Storyboard thành IntrinsicTableView: ảnh chụp màn hình: http://joxi.ru/a2XEENpsyBWq0A

3) Đặt heightConstraint cho chế độ xem bảng của bạn

4) kéo IBoutlet của bảng vào ViewContoder của bạn

5) kéo IBoutlet của ràng buộc chiều cao của bảng của bạn vào ViewContoder của bạn

6) thêm phương thức này vào ViewContoder của bạn:

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

Hi vọng điêu nay co ich


Xem câu trả lời của tôi dựa trên lớp con của bạn nhưng không cần bất kỳ cài đặt hạn chế chiều cao nào.
fl034

@ fl034 cung cấp liên kết?
Gulz

@ fl034 Tôi không nghĩ bạn có thể tránh sử dụng các ràng buộc khi các thành phần của bạn được nhúng vào Chế độ xem cuộn) Trường hợp của tôi là với ScrollView
Gulz

Tôi cũng có chế độ xem bảng của mình bên trong chế độ xem cuộn và nó hoạt động hoàn hảo :)
fl034

hoạt động với tôi trên iOS 13 / Xcode 11 Bạn đã chấm dứt 3 ngày đau khổ thưa ông, cảm ơn bạn
Mehdi S.

8

Trong trường hợp bạn không muốn theo dõi thay đổi kích thước nội dung của chế độ xem bảng, bạn có thể thấy lớp con này hữu ích.

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
Đối với swift 3, override public var intrinsicContentSize: CGSize { //... return someCGSize }
intinsicContentSize

không làm việc cho tôi vẫn đang ẩn cuối bảng
Gulz 2/218


4

Swift 3, iOS 10.3

Giải pháp 1: Chỉ cần đưaself.tableview.sizeToFit()vàocellForRowAt indexPath chức năng. Hãy chắc chắn để đặt chiều cao xem bàn cao hơn sau đó bạn cần. Đây là một giải pháp tốt nếu bạn không có chế độ xem dưới chế độ xem bảng. Tuy nhiên, nếu bạn có, ràng buộc xem bảng dưới cùng sẽ không được cập nhật (Tôi đã không cố gắng sửa nó vì tôi đã đưa ra giải pháp 2)

Thí dụ:

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

Giải pháp 2: Đặt ràng buộc chiều cao của bảng xem trong bảng phân cảnh và kéo nó vào ViewContoder. Nếu bạn biết chiều cao trung bình của ô của bạn và bạn biết mảng của bạn chứa bao nhiêu phần tử, bạn có thể làm một cái gì đó như thế này:

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

*BIÊN TẬP:

Sau tất cả các giải pháp tôi đã thử và mỗi người trong số họ đang sửa một cái gì đó, nhưng không hoàn toàn, đây là câu trả lời giải thích và khắc phục hoàn toàn vấn đề này.


Đây là giải pháp rất đơn giản, nó hiệu quả với tôi, Cảm ơn bạn @Dorde Nilovic
R. Mohan

1

Cả hai câu trả lời của MimoAnooj VM đều tuyệt vời nhưng có một vấn đề nhỏ nếu bạn có một danh sách lớn, có thể chiều cao của khung sẽ cắt một số ô của bạn.

Vì thế. Tôi đã sửa đổi câu trả lời một chút:

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

Có một cách tốt hơn để làm điều đó nếu bạn sử dụng AutoLayout: thay đổi ràng buộc xác định chiều cao. Chỉ cần tính chiều cao của nội dung bảng của bạn, sau đó tìm ràng buộc và thay đổi nó. Đây là một ví dụ (giả sử rằng ràng buộc xác định chiều cao của bảng của bạn thực sự là ràng buộc về chiều cao với mối quan hệ "Bằng"):

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

Bạn có thể cải thiện điều này. Thêm một ràng buộc bổ sung vào chế độ xem bảng của bạn, giả sử, chiều cao <= 10000. Sử dụng kéo điều khiển từ Trình tạo giao diện vào tệp .h của bạn để biến ràng buộc này thành một thuộc tính của trình điều khiển xem của bạn. Sau đó, bạn có thể tránh lặp đi lặp lại thông qua các ràng buộc và tìm kiếm. Chỉ cần đặt trực tiếpmyTableHeightConstraint.constant = tableView.contentSize.height
RobP

1

Điều này hoạt động với tôi bằng cách sử dụng Bố cục tự động, với chế độ xem bảng chỉ với một phần.

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
    }

Tôi gọi chức năng này khi thiết lập Bố cục tự động (Mẫu ở đây sử dụng SnapKit, nhưng bạn có ý tưởng):

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

Tôi muốn UITableView chỉ cao bằng chiều cao kết hợp của các ô; Tôi lặp qua các ô và tích lũy tổng chiều cao của các ô. Vì kích thước của chế độ xem bảng là CGRect.zero tại thời điểm này, tôi cần đặt giới hạn để có thể tôn trọng các quy tắc Bố cục tự động được xác định bởi ô. Tôi đặt kích thước thành một giá trị tùy ý phải đủ lớn. Kích thước thực tế sẽ được tính toán sau bởi hệ thống Bố cục tự động.


1

Tôi đã làm theo một cách khác một chút, Thật ra TableView của tôi nằm trong scrollview nên tôi phải đưa ra giới hạn chiều cao là 0.

Sau đó, tại thời gian chạy tôi đã thực hiện các thay đổi sau,

       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

Là một phần mở rộng của câu trả lời của Anooj VM, tôi đề nghị những điều sau chỉ làm mới kích thước nội dung khi nó thay đổi.

Cách tiếp cận này cũng vô hiệu hóa cuộn đúng cách và hỗ trợ danh sáchxoay lớn hơn . Không cần phải gửi_async vì các thay đổi của ContentSize được gửi trên luồng chính.

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

phiên bản objc của Musa almatri

(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

Bạn có thể dùng thử Tùy chỉnh này AGTableView

Để đặt ràng buộc chiều cao của bảng xem Sử dụng bảng phân cảnh hoặc lập trình. (Lớp này tự động tìm nạp một ràng buộc về chiều cao và đặt chiều cao của chế độ xem nội dung thành chiều cao của bạn có thể xem được).

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

Lưu ý Nếu có vấn đề để đặt Chiều cao thì yourTableView.layoutSubviews().


0

Dựa trên câu trả lời của fl034 . Nhưng đối với người dùng 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

Việc triển khai Swift 5 của tôi là đặt ràng buộc cao của bảngView thành kích thước của nội dung của nó ( contentSize.height). Phương pháp này giả định rằng bạn đang sử dụng bố trí tự động. Mã này nên được đặt bên trong cellForRowAtphương thức tableView.

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

-1

Nếu bạn muốn bảng của bạn động, bạn sẽ cần sử dụng giải pháp dựa trên nội dung bảng như chi tiết ở trên. Nếu bạn chỉ đơn giản muốn hiển thị một bảng nhỏ hơn, bạn có thể sử dụng chế độ xem vùng chứa và nhúng UITableViewCont kiểm soát vào đó - UITableView sẽ được thay đổi kích thước theo kích thước vùng chứa.

Điều này tránh được rất nhiều tính toán và các cuộc gọi để bố trí.


1
Đừng dùng từ abovevì câu trả lời có thể bị xáo trộn.
Nik Kov

-3

Giải pháp Mu cho việc này trong swift 3 : Gọi phương thức này trongviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.