Cách xác định chiều cao của UICollectionView bằng FlowLayout


118

Tôi có một UICollectionViewvới một UICollectionViewFlowLayoutvà tôi muốn tính toán kích thước nội dung của nó (để lấy lại lợi nhuận intrinsicContentSizecần thiết cho việc điều chỉnh chiều cao của nó thông qua Tự động thanh toán).

Vấn đề là: Ngay cả khi tôi có chiều cao cố định và bằng nhau cho tất cả các ô, tôi không biết mình có bao nhiêu "hàng" / dòng trong đó UICollectionView. Tôi cũng không thể xác định số lượng mục đó trong nguồn dữ liệu của mình, vì các ô đại diện cho các mục dữ liệu có độ rộng khác nhau, do đó, số lượng mục tôi có trong một dòng của UICollectionView.

Vì tôi không thể tìm thấy bất kỳ gợi ý nào về chủ đề này trong tài liệu chính thức và googling không mang lại cho tôi thêm bất kỳ sự trợ giúp nào, mọi trợ giúp và ý tưởng sẽ được đánh giá rất cao.

Câu trả lời:


255

Ái chà! Vì lý do nào đó, sau nhiều giờ nghiên cứu, giờ đây tôi đã tìm thấy một câu trả lời khá dễ dàng cho câu hỏi của mình: Tôi đã hoàn toàn tìm kiếm sai chỗ, đào bới tất cả các tài liệu tôi có thể tìm thấy UICollectionView.

Giải pháp đơn giản và dễ dàng nằm ở bố cục bên dưới: Chỉ cần gọi collectionViewContentSizethuộc tính của bạn myCollectionView.collectionViewLayoutvà bạn sẽ có được chiều cao và chiều rộng của nội dung CGSize. Nó là dễ dàng như vậy.


1
whoa, tôi muốn UITableView có một cái gì đó tương tự
Chris Chen

1
Ignatus, @jbandi, Chris và cộng sự, bạn có thể mở rộng về điều này không? Khi tôi thử nghiệm với hướng cuộn ngang với khoảng cách giữa các mục lớn (để các mục sẽ được bố trí theo chiều ngang), chế độ xem bộ sưu tập trả về kích thước nội dung nội tại không hợp lệ (-1, -1) và collectionViewContentSize trả về kích thước hiệu quả giới hạn của chế độ xem bộ sưu tập - không tính đến việc chỉ có một hàng được bao quanh bởi không gian. Nói tóm lại, nó không thực chất. Cảm ơn!
Chris Conover

8
Sự khác biệt giữa collectionViewContentSize và contentSize của dạng xem cuộn là gì?
rounak

Khi bạn gọi contentSize của dạng xem bộ sưu tập trong viewDidLoad, nó sẽ trả về khung của dạng xem bộ sưu tập, collectionViewContentSize trả về kích thước nội dung chính xác.
Fury

1
Nó có thể giúp cho ai: Tôi đã phải thực hiện "layoutIfNeeded ()" trước khi điều này để tôi có thể làm cho nó hoạt động.
asanli

37

Trong trường hợp bạn đang sử dụng Bố cục tự động , thì bạn có thể tạo một lớp con củaUICollectionView

Nếu bạn sử dụng mã bên dưới thì bạn không phải chỉ định bất kỳ ràng buộc chiều cao nào cho chế độ xem bộ sưu tập vì nó sẽ thay đổi dựa trên nội dung của chế độ xem bộ sưu tập.

Dưới đây là cách triển khai:

@interface DynamicCollectionView : UICollectionView

@end

@implementation DynamicCollectionView

- (void) layoutSubviews
{
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.bounds.size, [self intrinsicContentSize]))
    {
        [self invalidateIntrinsicContentSize];
    }
}

- (CGSize)intrinsicContentSize
{
    CGSize intrinsicContentSize = self.contentSize;

    return intrinsicContentSize;
}

@end

12
không làm việc cho tôi ... Tôi đang sử dụng CollectionView của tôi bên trong một UITableViewCell và muốn tableview để phát triển chiều cao như xem bộ sưu tập phát triển về chiều cao sử dụng iOS8 UITableViewAutomaticDimension Nhưng collectionview ở lại của tôi nhỏ :(
Georg

Tuyệt vời. Giải pháp ngắn gọn và hấp dẫn cho UICollectionView của tôi bên trong UIScrollView!
dotToString

2
Một điều quan trọng là vô hiệu hóa cuộn và thanh cuộn trong chế độ xem bộ sưu tập
Georg

1
Cùng một vấn đề như Georg, đó là khoảng 50px chiều cao khi nó phải được hơn 400
xtrinch

3
Có, tôi đặt chế độ xem bộ sưu tập thành "không phải thanh cuộn, không có thanh trượt" rồi tải lại, sau đó nội dung của chế độ xem bảng được đặt. Ngoài ra, collectionview là một lớp con trả về kích thước nội dung của nó dưới dạng nội tạiContentSize. Hy vọng rằng sẽ giúp!
Georg

25

Tại viewDidAppearbạn có thể lấy nó bằng cách:

float height = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;

Có thể khi bạn tải lại dữ liệu thì cần tính độ cao mới với dữ liệu mới thì bạn có thể lấy bằng cách: thêm trình quan sát để lắng nghe khi CollectionViewtải lại xong dữ liệu của bạn tại viewdidload:

[self.myCollectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

Sau đó, thêm hàm dưới đây để có chiều cao mới hoặc làm bất cứ điều gì sau khi tải lại xong collectionview:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    //Whatever you do here when the reloadData finished
    float newHeight = self.myCollectionView.collectionViewLayout.collectionViewContentSize.height;    
}

Và đừng quên xóa trình quan sát:

[self.myCollectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];

11

câu trả lời user1046037 bằng Swift ...

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize() {
            invalidateIntrinsicContentSize()
        }
    }

    override func intrinsicContentSize() -> CGSize {
        return self.contentSize
    }
}

Nó hoạt động hoàn hảo nhưng Nếu bạn có một cái gì đó như UILabeltrên, nội dung từ collectionViewchuyển qua phần tử khác ở trên, bạn có giải pháp khác không?
KolaCaine

11

Mã Swift 3 cho câu trả lời người dùng1046037

import UIKit

class DynamicCollectionView: UICollectionView {

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
            self.invalidateIntrinsicContentSize()
        }

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}

Tôi mới hơn, và tôi không chắc chắn về vị trí và cách sử dụng cái này trong ViewController của tôi, Bạn có thể vui lòng chỉnh sửa với câu trả lời .....?
Abirami Bala

1
chỉ cần đặt lớp này thành chế độ xem bộ sưu tập của bạn trong Storyboard
Mohammad Sadiq Shaikh

7

Swift 4

class DynamicCollectionView: UICollectionView {
  override func layoutSubviews() {
    super.layoutSubviews()
    if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
      self.invalidateIntrinsicContentSize()
    }
  }

  override var intrinsicContentSize: CGSize {
    return collectionViewLayout.collectionViewContentSize
  }
}

1
Tôi thấy rằng nó không làm việc trong iPhone 6/7 cộng và XR, XS-MAX
Rahul K Rajan

7

Swift 4.2

class DynamicCollectionView: UICollectionView {
    override func layoutSubviews() {
        super.layoutSubviews()
        if bounds.size != intrinsicContentSize {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        return self.contentSize
    }
}

4

Đã quá muộn để trả lời câu hỏi này nhưng gần đây tôi đã xem xét vấn đề này. Câu trả lời trên
của @ lee giúp tôi rất nhiều để có câu trả lời cho mình. Nhưng câu trả lời đó chỉ giới hạn ở mục tiêu-c và tôi đã làm việc nhanh chóng . @lee Với việc tham khảo câu trả lời của bạn, hãy cho phép tôi để người dùng nhanh chóng giải quyết vấn đề này một cách dễ dàng. Đối với điều đó, vui lòng làm theo các bước dưới đây:

Khai báo một CGFloatbiến trong phần khai báo:

var height : CGFloat!

Tại viewDidAppearbạn có thể lấy nó bằng cách:

height = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

Có thể khi reloaddữ liệu của bạn cần tính độ cao mới với dữ liệu mới thì bạn có thể lấy nó bằng cách: addObserverđể lắng nghe khi CollectionViewtải lại xong dữ liệu của bạn tại viewWillAppear:

override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(true)
        ....
        ....
        self.shapeCollectionView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.Old, context: nil)
    }

Sau đó thêm hàm dưới đây để có chiều cao mới hoặc làm bất cứ điều gì sau khi collectionviewtải lại xong:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        let newHeight : CGFloat = self.myCollectionView.collectionViewLayout.collectionViewContentSize().height

        var frame : CGRect! = self.myCollectionView.frame
        frame.size.height = newHeight

        self.myCollectionView.frame = frame
    }

Và đừng quên xóa trình quan sát:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)
    ....
    ....
    self.myCollectionView.removeObserver(self, forKeyPath: "contentSize")
}

Tôi hy vọng điều này sẽ giúp bạn giải quyết vấn đề của mình nhanh chóng.


@ShikhaSharma: Bạn có thể thêm mã removeObserver trong phương thức "ViewDidDisappear", sẽ chỉ xóa trình quan sát sau khi chế độ xem xác nhận rằng nó đã biến mất. Vui lòng kiểm tra câu trả lời được cập nhật.
Ờ. Vihar

đang gặp lỗi này: An -observeValueForKeyPath: ofObject: change: context: đã nhận được thông báo nhưng không được xử lý.
Shikha Sharma

@ShikhaSharma: Xin lỗi, tôi nhầm lẫn khi chỉ định phương pháp. Cố gắng đặt mã removeobserver trong viewWillDisappear.
Ờ. Vihar

1

Câu trả lời này dựa trên @Er. Câu trả lời của Vihar. Bạn có thể dễ dàng triển khai giải pháp bằng cách sử dụng RxSwift

     collectionView.rx.observe(CGSize.self , "contentSize").subscribe(onNext: { (size) in
        print("sizer \(size)")
    }).disposed(by: rx.disposeBag)

0

Phiên bản Swift của @ user1046037:

public class DynamicCollectionView: UICollectionView {

    override public func layoutSubviews() {
        super.layoutSubviews()
        if !bounds.size.equalTo(intrinsicContentSize) {
            invalidateIntrinsicContentSize()
        }
    }

    override public var intrinsicContentSize: CGSize {
        let intrinsicContentSize: CGSize = contentSize
        return intrinsicContentSize
    }
}

0

Swift 4.2, Xcode 10.1, iOS 12.1:

Vì lý do nào đó, collectionView.contentSize.heightxuất hiện nhỏ hơn chiều cao đã phân giải của chế độ xem bộ sưu tập của tôi. Đầu tiên, tôi đang sử dụng ràng buộc bố cục tự động liên quan đến 1/2 chiều cao của superview. Để khắc phục điều này, tôi đã thay đổi ràng buộc liên quan đến "vùng an toàn" của chế độ xem.

Điều này cho phép tôi đặt chiều cao ô để lấp đầy chế độ xem bộ sưu tập của mình bằng cách sử dụng collectionView.contentSize.height:

private func setCellSize() {
    let height: CGFloat = (collectionView.contentSize.height) / CGFloat(numberOfRows)
    let width: CGFloat = view.frame.width - CGFloat(horizontalCellMargin * 2)

    let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    layout.itemSize = CGSize(width: width, height: height)
}

Trước

hạn chế chiều cao so với superview kết quả giả lập xấu

Sau

hạn chế chiều cao so với khu vực an toàn kết quả giả lập tốt


0

giả sử collectionView của bạn được đặt tên là collectionView, bạn có thể lấy chiều cao như sau.

let height = collectionView.collectionViewLayout.collectionViewContentSize.height

0

Ngoài ra, bạn nên gọi điện reloadDatatrước khi có được chiều cao:

theCollectionView.collectionViewLayout.collectionViewContentSize;
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.