วิธีกำหนดความสูงของ UICollectionView ด้วย FlowLayout


118

ฉันมีUICollectionViewกับUICollectionViewFlowLayoutและฉันต้องการที่จะคำนวณขนาดเนื้อหา (สำหรับการกลับมาในintrinsicContentSizeที่จำเป็นสำหรับการปรับความสูงที่ผ่านการ autolayout)

ปัญหาคือแม้ว่าฉันจะมีความสูงคงที่และเท่ากันสำหรับทุกเซลล์ แต่ฉันก็ไม่รู้ว่าฉันมี "แถว" / บรรทัดในไฟล์UICollectionView. ฉันยังไม่สามารถระบุได้ว่าจะนับตามจำนวนรายการในแหล่งข้อมูลของฉันเนื่องจากเซลล์ที่แสดงรายการข้อมูลมีความกว้างแตกต่างกันดังนั้นจำนวนรายการที่ฉันมีในหนึ่งบรรทัดของไฟล์UICollectionView.

เนื่องจากฉันไม่พบคำแนะนำใด ๆ เกี่ยวกับหัวข้อนี้ในเอกสารอย่างเป็นทางการและ googling ก็ไม่ได้นำเสนออะไรเพิ่มเติมความช่วยเหลือและแนวคิดใด ๆ จะได้รับการชื่นชมเป็นอย่างมาก

คำตอบ:


254

โว้ว! ด้วยเหตุผลบางอย่างหลังจากการค้นคว้ามาหลายชั่วโมงตอนนี้ฉันพบคำตอบที่ค่อนข้างง่ายสำหรับคำถามของฉัน: ฉันค้นหาผิดที่โดยสมบูรณ์โดยดูเอกสารทั้งหมดที่ฉันสามารถหาUICollectionViewได้

ง่ายและโซลูชั่นที่ง่ายอยู่ในรูปแบบพื้นฐาน: โทรเพียงcollectionViewContentSizeบนทรัพย์สินและคุณจะได้รับความสูงและความกว้างของเนื้อหาที่เป็นmyCollectionView.collectionViewLayout CGSizeมันง่ายเหมือนกัน


1
โอ้โฮฉันหวังว่า UITableView จะมีบางอย่างที่คล้ายกัน
Chris Chen

1
Ignatus, @jbandi, Chris et al คุณช่วยขยายความได้ไหม เมื่อฉันทดสอบด้วยทิศทางการเลื่อนแนวนอนด้วยระยะห่างระหว่างรายการขนาดใหญ่ (เพื่อให้รายการจัดวางในแนวนอน) มุมมองคอลเลคชันส่งคืนขนาดเนื้อหาภายในที่ไม่ถูกต้อง (-1, -1) และ collectionViewContentSize ส่งคืนสิ่งที่มีประสิทธิภาพ ขอบเขตของมุมมองคอลเลกชัน - ไม่ได้คำนึงถึงว่ามีเพียงแถวเดียวที่ล้อมรอบด้วยช่องว่าง ในระยะสั้นมันไม่ได้เป็นเนื้อแท้มาก ขอบคุณ!
Chris Conover

8
collectionViewContentSize และ contentSize ของมุมมองเลื่อนต่างกันอย่างไร
rounak

เมื่อคุณเรียก contentSize ของมุมมองคอลเลกชันใน viewDidLoad จะส่งคืนเฟรมของมุมมองคอลเลกชันโดย collectionViewContentSize จะส่งคืนขนาดเนื้อหาที่ถูกต้อง
โกรธ

1
สำหรับใครที่สามารถช่วยได้: ฉันต้องทำ "layoutIfNeeded ()" ก่อนหน้านี้จึงจะสามารถใช้งานได้
asanli

37

ในกรณีที่คุณใช้การจัดวางอัตโนมัติคุณสามารถสร้างคลาสย่อยของUICollectionView

หากคุณใช้โค้ดด้านล่างคุณจะไม่ต้องระบุข้อ จำกัด ด้านความสูงใด ๆ สำหรับมุมมองคอลเลคชันเนื่องจากจะแตกต่างกันไปตามเนื้อหาของมุมมองคอลเลคชัน

ด้านล่างคือการใช้งาน:

@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
ไม่ได้ผลสำหรับฉัน ... ฉันใช้ CollectionView ของฉันใน UITableViewCell และต้องการให้ tableview เพิ่มขึ้นในขณะที่มุมมองคอลเลคชันเติบโตขึ้นตามความสูงโดยใช้ iOS8 UITableViewAutomaticDimension แต่มุมมองคอลเลคชันของฉันยังเล็กอยู่ :(
Georg

น่ากลัว วิธีแก้ปัญหาสั้น ๆ และน่ารักสำหรับ UICollectionView ของฉันภายใน UIScrollView!
dotToString

2
สิ่งสำคัญอย่างหนึ่งคือการปิดใช้งานการเลื่อนและแถบเลื่อนในมุมมองคอลเลคชัน
Georg

1
ปัญหาเดียวกับ Georg ความสูงประมาณ 50px เมื่อควรมากกว่า 400
xtrinch

3
ใช่ฉันตั้งค่า collectionview เป็น "not scrollabe, no scollbars" จากนั้นโหลดซ้ำจากนั้นจึงตั้งค่าเนื้อหาของ tableview นอกจากนี้ collectionview ยังเป็นคลาสย่อยที่ส่งคืนขนาดเนื้อหาเป็น intrinsicContentSize หวังว่าจะช่วยได้!
Georg

25

ที่viewDidAppearคุณจะได้รับโดย:

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

บางทีเมื่อคุณโหลดข้อมูลซ้ำแล้วจำเป็นต้องคำนวณความสูงใหม่ด้วยข้อมูลใหม่จากนั้นคุณสามารถรับได้โดย: เพิ่มผู้สังเกตการณ์เพื่อฟังเมื่อCollectionViewโหลดข้อมูลเสร็จแล้วที่viewdidload:

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

จากนั้นเพิ่มฟังก์ชั่นร้องเพื่อรับความสูงใหม่หรือทำอะไรก็ได้หลังจากโหลดคอลเลกชันดูเสร็จแล้ว:

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

และอย่าลืมลบผู้สังเกตการณ์:

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

11

user1046037 ตอบใน Swift ...

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

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

มันทำงานได้อย่างสมบูรณ์แบบ แต่ถ้าคุณมีสิ่งที่ต้องการUILabelข้างต้นเนื้อหาจากการcollectionViewส่งผ่านองค์ประกอบอื่น ๆ ข้างต้นคุณมีวิธีแก้ปัญหาอื่นหรือไม่?
KolaCaine

11

รหัส Swift 3 สำหรับคำตอบ user1046037

import UIKit

class DynamicCollectionView: UICollectionView {

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

    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

}

ฉันเป็นคนใหม่และฉันไม่แน่ใจว่าจะใช้สิ่งนี้ได้ที่ไหนและอย่างไรใน ViewController ของฉันโปรดแก้ไขคำตอบด้วย ..... ?
Abirami Bala

1
เพียงตั้งค่าชั้นเรียนนี้เป็นมุมมองคอลเลกชันของคุณใน Storyboard
Mohammad Sadiq Shaikh

7

สวิฟต์ 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
ฉันพบว่ามันใช้ไม่ได้ใน iPhone 6/7 plus และ 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

สายเกินไปที่จะตอบคำถามนี้ แต่เมื่อเร็ว ๆ นี้ฉันได้แก้ไขปัญหานี้แล้ว คำตอบข้างต้น
ของ @ lee ช่วยฉันได้มากในการรับคำตอบ แต่คำตอบที่ถูก จำกัด ไว้ที่วัตถุประสงค์ -cและผมกำลังทำงานอยู่ในที่รวดเร็ว @lee ด้วยการอ้างอิงถึงคำตอบของคุณอนุญาตให้ฉันปล่อยให้ผู้ใช้ที่รวดเร็วแก้ไขปัญหานี้ได้อย่างง่ายดาย โปรดปฏิบัติตามขั้นตอนด้านล่าง:

ประกาศCGFloatตัวแปรในส่วนการประกาศ:

var height : CGFloat!

ที่viewDidAppearคุณจะได้รับโดย:

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

บางทีเมื่อreloadข้อมูลของคุณจำเป็นต้องคำนวณความสูงใหม่ด้วยข้อมูลใหม่คุณสามารถรับได้โดย: addObserverเพื่อฟังเมื่อคุณCollectionViewโหลดข้อมูลเสร็จแล้วที่viewWillAppear:

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

จากนั้นเพิ่มฟังก์ชั่นร้องเพื่อรับความสูงใหม่หรือทำอะไรก็ได้หลังจากcollectionviewโหลดเสร็จแล้ว:

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
    }

และอย่าลืมลบผู้สังเกตการณ์:

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

ฉันหวังว่านี่จะช่วยให้คุณแก้ปัญหาได้อย่างรวดเร็ว


@ShikhaSharma: คุณสามารถเพิ่มโค้ด removeObserver ภายในเมธอด "ViewDidDisappear" ซึ่งจะลบผู้สังเกตการณ์หลังจากที่ข้อมูลพร็อพเพอร์ตี้ยืนยันว่าหายไปเท่านั้น โปรดตรวจสอบคำตอบที่อัปเดต
เอ่อ. วิหาร

ฉันได้รับข้อผิดพลาดนี้: An -observeValueForKeyPath: ofObject: change: context: ได้รับข้อความ แต่ไม่ได้รับการจัดการ
Shikha Sharma

@ShikhaSharma: ขออภัยฉันผิดพลาดในการระบุวิธีการ ลองใส่โค้ด removeobserver ใน viewWillDisappear
เอ่อ. วิหาร

1

คำตอบนี้อ้างอิงจาก @Er คำตอบของวิหาร คุณสามารถใช้โซลูชันได้อย่างง่ายดายโดยใช้ RxSwift

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

0

@ user1046037 เวอร์ชัน Swift:

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:

ด้วยเหตุผลบางประการcollectionView.contentSize.heightปรากฏว่ามีขนาดเล็กกว่าความสูงที่แก้ไขได้ของมุมมองคอลเลคชันของฉัน ก่อนอื่นฉันใช้ข้อ จำกัด การจัดวางอัตโนมัติที่สัมพันธ์กับ 1/2 ของความสูงของ superview เพื่อแก้ไขปัญหานี้ฉันได้เปลี่ยนข้อ จำกัด ให้สัมพันธ์กับ "พื้นที่ปลอดภัย" ของมุมมอง

สิ่งนี้ทำให้ฉันสามารถกำหนดความสูงของเซลล์เพื่อเติมเต็มมุมมองคอลเลกชันของฉันโดยใช้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)
}

ก่อน

ข้อจำกัดความสูงเมื่อเทียบกับ superview ผลการจำลองที่ไม่ดี

หลังจาก

ข้อจำกัดความสูงเมื่อเทียบกับพื้นที่ปลอดภัย ผลการจำลองที่ดี


0

สมมติว่า collectionView ของคุณตั้งชื่อเป็นcollectionViewคุณสามารถรับความสูงได้ดังนี้

let height = collectionView.collectionViewLayout.collectionViewContentSize.height

0

นอกจากนี้คุณควรโทรreloadDataก่อนที่จะได้รับความสูง:

theCollectionView.collectionViewLayout.collectionViewContentSize;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.