UICollectionView การปรับขนาดเซลล์ด้วยเค้าโครงอัตโนมัติ


207

ฉันกำลังพยายามปรับขนาดตัวเอง UICollectionViewCellsทำงานกับเค้าโครงอัตโนมัติ แต่ฉันไม่สามารถทำให้เซลล์ปรับขนาดตัวเองให้เข้ากับเนื้อหาได้ ฉันมีปัญหาในการทำความเข้าใจว่าขนาดของเซลล์มีการอัปเดตอย่างไรจากเนื้อหาของสิ่งที่อยู่ภายใน contentView ของเซลล์

นี่คือการตั้งค่าที่ฉันได้ลอง:

  • กำหนดเองUICollectionViewCellด้วย a UITextViewใน contentView
  • เลื่อนเพื่อที่UITextViewถูกปิดการใช้งาน
  • ข้อ จำกัด ในแนวนอนของ contentView คือ: "H: | [_textView (320)]" กล่าวUITextViewคือถูกตรึงที่ด้านซ้ายของเซลล์ด้วยความกว้างที่ชัดเจน 320
  • ข้อ จำกัด ในแนวตั้งของ contentView คือ: "V: | -0 - [_ textView]" กล่าวคือUITextViewถูกตรึงไว้ที่ด้านบนของเซลล์
  • UITextViewมีชุดความสูงข้อ จำกัด ที่จะคงที่ซึ่งเป็นUITextViewรายงานที่จะพอดีกับข้อความ

นี่คือสิ่งที่ดูเหมือนกับพื้นหลังของเซลล์ตั้งค่าเป็นสีแดงและUITextViewพื้นหลังตั้งค่าเป็นสีน้ำเงิน: เซลล์พื้นหลังสีแดง UITextView พื้นหลังสีน้ำเงิน

ฉันใส่โครงการที่ผมได้เล่นกับบน GitHub ที่นี่


คุณกำลังใช้วิธี sizeForItemAtIndexPath หรือไม่?
Daniel Galasko

@DanielGalasko ฉันไม่ ความเข้าใจของฉันคือคุณสมบัติเซลล์การปรับขนาดตัวเองใหม่ใน iOS 8 ไม่ต้องการให้คุณทำเช่นนั้นอีกต่อไป
rawbee

ไม่แน่ใจว่าคุณได้รับข้อมูลจากที่ใด แต่คุณยังต้องไปดู WWDC excerpt asciiwwdc.com/2014/sessions/226
Daniel Galasko

แต่อีกครั้งมันขึ้นอยู่กับกรณีการใช้งานของคุณตรวจสอบให้แน่ใจว่าคุณใช้งานประมาณ
ItemSize

1
2019 - จำเป็นต้องดูที่: stackoverflow.com/a/58108897/294884
Fattie

คำตอบ:


309

อัปเดตสำหรับ Swift 5

preferredLayoutAttributesFittingAttributesเปลี่ยนชื่อเป็นpreferredLayoutAttributesFittingและใช้การปรับขนาดอัตโนมัติ


อัปเดตสำหรับ Swift 4

systemLayoutSizeFittingSize เปลี่ยนชื่อเป็น systemLayoutSizeFitting


อัปเดตสำหรับ iOS 9

หลังจากเห็นการแก้ปัญหา GitHub ของฉันภายใต้ iOS 9 ในที่สุดฉันก็มีเวลาที่จะตรวจสอบปัญหาอย่างเต็มที่ ตอนนี้ฉันได้ปรับปรุง repo เพื่อรวมตัวอย่างของการกำหนดค่าต่างๆสำหรับเซลล์ที่ปรับขนาดด้วยตนเอง ข้อสรุปของฉันคือเซลล์ปรับขนาดตัวเองนั้นยอดเยี่ยมในทางทฤษฎี แต่ยุ่งในทางปฏิบัติ คำเตือนเมื่อดำเนินการกับเซลล์การปรับขนาดด้วยตนเอง

TL; DR

ลองดูโครงการ GitHubของฉัน


เซลล์การปรับขนาดด้วยตนเองรองรับเฉพาะผังการไหลเพื่อให้แน่ใจว่าสิ่งที่คุณกำลังใช้อยู่

มีสองสิ่งที่คุณต้องตั้งค่าเพื่อให้เซลล์ปรับขนาดด้วยตนเองทำงานได้

1. ชุดestimatedItemSizeบนUICollectionViewFlowLayout

เค้าโครงเลย์เอาต์จะเป็นแบบไดนามิกในธรรมชาติเมื่อคุณตั้งค่าestimatedItemSizeคุณสมบัติ

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

2. เพิ่มการรองรับการปรับขนาดบนคลาสย่อยของเซลล์

มาใน 2 รสชาติ; Auto-เค้าโครงหรือpreferredLayoutAttributesFittingAttributesแทนที่กำหนดเอง

สร้างและกำหนดค่าเซลล์ด้วยเค้าโครงอัตโนมัติ

ฉันจะไม่เข้าไปดูรายละเอียดเกี่ยวกับสิ่งนี้เนื่องจากมีโพสต์ SO ที่ยอดเยี่ยมเกี่ยวกับการกำหนดค่าข้อ จำกัด สำหรับเซลล์ เพียงระวังว่าXcode 6 ทำสิ่งต่าง ๆ ด้วย iOS 7 ดังนั้นถ้าคุณรองรับ iOS 7 คุณจะต้องทำสิ่งต่าง ๆ เช่นตรวจสอบให้แน่ใจว่าตั้งค่า autoresizingMask บน contentView ของเซลล์และขอบเขต contentView ถูกตั้งค่าเป็นขอบเขตของเซลล์เมื่อ โหลดเซลล์แล้ว (เช่นawakeFromNib)

สิ่งที่คุณต้องระวังก็คือเซลล์ของคุณจะต้องมีข้อ จำกัด อย่างจริงจังมากกว่าเซลล์ Table View ตัวอย่างเช่นหากคุณต้องการให้ความกว้างเป็นแบบไดนามิกเซลล์ของคุณจะต้องมีข้อ จำกัด ด้านความสูง ในทำนองเดียวกันหากคุณต้องการให้ความสูงเป็นแบบไดนามิกคุณจะต้อง จำกัด ความกว้างให้กับเซลล์ของคุณ

นำไปใช้preferredLayoutAttributesFittingAttributesในเซลล์ที่กำหนดเองของคุณ

เมื่อฟังก์ชั่นนี้เรียกว่ามุมมองของคุณได้รับการกำหนดค่าด้วยเนื้อหาแล้ว (เช่นcellForItemถูกเรียก) สมมติว่าข้อ จำกัด ของคุณได้รับการตั้งค่าอย่างเหมาะสมคุณอาจมีการใช้งานเช่นนี้:

//forces the system to do one layout pass
var isHeightCalculated: Bool = false

override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    //Exhibit A - We need to cache our calculation to prevent a crash.
    if !isHeightCalculated {
        setNeedsLayout()
        layoutIfNeeded()
        let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
        var newFrame = layoutAttributes.frame
        newFrame.size.width = CGFloat(ceilf(Float(size.width)))
        layoutAttributes.frame = newFrame
        isHeightCalculated = true
    }
    return layoutAttributes
}

หมายเหตุบน iOS 9 พฤติกรรมเปลี่ยนไปเล็กน้อยซึ่งอาจทำให้เกิดปัญหากับการใช้งานของคุณหากคุณไม่ระวัง (ดูเพิ่มเติมที่นี่ ) เมื่อคุณใช้งานpreferredLayoutAttributesFittingAttributesคุณจำเป็นต้องตรวจสอบให้แน่ใจว่าคุณเปลี่ยนเฟรมของคุณลักษณะเลย์เอาต์ของคุณเพียงครั้งเดียว หากคุณไม่ทำเช่นนี้โครงร่างจะเรียกการใช้งานของคุณไปเรื่อย ๆ และจะล้มเหลวในที่สุด ทางออกหนึ่งคือการแคชขนาดที่คำนวณได้ในเซลล์ของคุณและทำให้เป็นโมฆะเมื่อใดก็ตามที่คุณใช้เซลล์นั้นซ้ำหรือเปลี่ยนแปลงเนื้อหาตามที่ฉันได้ทำกับisHeightCalculatedคุณสมบัติ

สัมผัสกับเค้าโครงของคุณ

ณ จุดนี้คุณควรมีเซลล์แบบไดนามิก 'ทำงาน' ในคอลเล็กชันของคุณ ฉันยังไม่พบวิธีแก้ปัญหากล่องที่เพียงพอในระหว่างการทดสอบของฉันดังนั้นอย่าลังเลที่จะแสดงความคิดเห็นหากคุณมี มันยังรู้สึกเหมือนUITableViewชนะการต่อสู้เพื่อปรับขนาด IMHO

คำเตือน

ต้องระวังมากว่าถ้าคุณกำลังใช้เซลล์ต้นแบบในการคำนวณ estimatedItemSize - นี้จะแตกถ้าคุณใช้ XIB ชั้นเรียนขนาด เหตุผลของเรื่องนี้ก็คือว่าเมื่อคุณโหลดมือถือของคุณจาก XIB Undefinedระดับขนาดของมันจะได้รับการกำหนดค่าด้วย สิ่งนี้จะใช้งานไม่ได้กับ iOS 8 และใหม่กว่าตั้งแต่บน iOS 7 ขนาดของคลาสจะถูกโหลดตามอุปกรณ์ (iPad = ปกติ - ใด ๆ , iPhone = กะทัดรัด - อะไรก็ได้) คุณสามารถตั้งค่าประมาณItemSizeโดยไม่ต้องโหลด XIB หรือคุณสามารถโหลดเซลล์จาก XIB เพิ่มลงใน collectionView (สิ่งนี้จะตั้งค่า traitCollection) ดำเนินการเลย์เอาต์แล้วออกจาก superview หรือคุณอาจทำให้เซลล์แทนที่traitCollectiongetter และคืนค่าคุณสมบัติที่เหมาะสม มันขึ้นอยู่กับคุณ.

แจ้งให้เราทราบหากฉันไม่ได้รับสิ่งใดหวังว่าฉันจะช่วยและโชคดีในการเขียนโค้ด



2
ฉันพยายามที่จะใช้สิ่งเดียวกันกับเค้าโครงผังงาน แต่เมื่อฉันส่งคืนเฟรมใหม่ด้วยขนาดที่อัปเดตเค้าโครงดูเหมือนจะไม่คำนวณตำแหน่ง y ใหม่ซึ่งยังคงยึดตามความสูงในขนาดโดยประมาณ สิ่งที่ขาดหายไป?
anna

@ คุณจะเรียกโมฆะโมฆะออกจากแผนผังหรือไม่
Daniel Galasko

1
อ่าพบความแตกต่างคุณตั้งค่าความสูงโดยประมาณให้สูง (400) ในขณะที่ฉันกำลังทำขั้นต่ำ (44) นั่นช่วยได้ แต่ขนาดเนื้อหายังดูเหมือนว่าจะไม่ได้รับการตั้งค่าอย่างถูกต้องจนกว่าคุณจะเลื่อนไปจนสุดดังนั้นจึงยังใช้งานไม่ได้มาก Btw ฉันเปลี่ยน UITextView เป็น UILabel พฤติกรรมเดียวกัน
anna

17
ดูเหมือนว่าจะใช้งานไม่ได้กับ iOS 9 GM การตั้งค่าestimatedItemSizeนำไปสู่การล่ม - ดูเหมือนว่าจะมีข้อผิดพลาดบางอย่างมากในการที่เลย์เอาต์อัตโนมัติพยายามประมวลผล UICollectionViewCell
Wes Campaigne

3
หากพบปัญหาที่คล้ายคลึงกันมีใครพบวิธีแก้ไขบ้างหรือไม่
โทนี่

47

ใน iOS10 มีค่าคงที่ใหม่ที่เรียกว่าUICollectionViewFlowLayout.automaticSize(เดิมUICollectionViewFlowLayoutAutomaticSize) ดังนั้น:

self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)

คุณสามารถใช้สิ่งนี้:

self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

มันมีประสิทธิภาพที่ดีขึ้นโดยเฉพาะเมื่อเซลล์ในมุมมองคอลเลกชันของคุณมีวิดเจ็ตคงที่

การเข้าถึงรูปแบบการไหล:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
   }
}

Swift 5 อัปเดต:

override func viewDidLoad() {
   super.viewDidLoad()

   if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
      flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
}

8
คุณจะตั้งค่านี้ที่ไหน ใน viewdidload? คุณจะจัดการกับ flowlayout ได้อย่างไร?
UKDataGeek

1
ฉันจัดการเพื่อใช้สิ่งนี้ - แต่มันมีพฤติกรรมแปลก ๆ ที่ทำให้มันอยู่ตรงกลางเซลล์ถ้ามีเซลล์เดียว นี่คือคำถามล้นสแต็กเกี่ยวกับมัน - ให้ฉันนิ่งงัน: stackoverflow.com/questions/43266924/ …
UKDataGeek

1
คุณสามารถเข้าถึงผังการไหลผ่านคอลเลกชันชมcollectionViewLayoutและเพียงตรวจสอบว่าเป็นประเภทUICollectionViewFlowLayout
d4Rk

4
มันทำให้เซลล์ของฉันเล็กมากไร้ประโยชน์
924

44

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

ในขั้นตอนที่ 1 เมื่อใช้เค้าโครงอัตโนมัติเพียงเพิ่ม UIView พาเรนต์เดียวไปยังเซลล์ ทุกอย่างภายในเซลล์จะต้องเป็นมุมมองย่อยของผู้ปกครอง นั่นตอบปัญหาทั้งหมดของฉัน แม้ว่า Xcode จะเพิ่มสิ่งนี้สำหรับ UITableViewCells โดยอัตโนมัติ แต่จะไม่ (แต่ควร) สำหรับ UICollectionViewCells ตามเอกสาร :

ในการกำหนดค่าลักษณะที่ปรากฏของเซลล์ของคุณให้เพิ่มมุมมองที่จำเป็นในการนำเสนอเนื้อหาของรายการข้อมูลเป็นมุมมองย่อยไปยังมุมมองในคุณสมบัติ contentView อย่าเพิ่มการดูย่อยลงในเซลล์โดยตรง

จากนั้นข้ามขั้นตอนที่ 3 โดยสิ้นเชิง มันไม่จำเป็น


1
ที่น่าสนใจ; นี่เป็นเฉพาะสำหรับ Xibs แต่อย่างไรก็ตามขอขอบคุณจะลองและยุ่งเหยิงกับโครงการ Github และดูว่าฉันสามารถทำซ้ำ อาจแบ่งคำตอบเป็น Xib กับโปรแกรม
Daniel Galasko

มีประโยชน์มาก ขอบคุณ!
ProblemSlover

2
iOS 9, Xcode 7 เซลล์จะถูกนำออกมาในกระดานเรื่องราวตั้งค่าคลาสย่อยที่กำหนดเอง พยายามสร้างคุณสมบัติที่เรียกว่าcontentViewXcode บ่นว่ามันขัดแย้งกับคุณสมบัติที่มีอยู่ พยายามเพิ่มการชมย่อยself.contentViewและตั้งข้อ จำกัด จากนั้นแอปขัดข้อง
Nicolas Miari

@ NicolasMiari ฉันไม่รู้เกี่ยวกับวิธีการทำสิ่งนี้โดยทางโปรแกรมโซลูชันของฉันเป็นเพียงแค่การเพิ่ม uiview เดียวใน XIB ที่ทุกอย่างเข้าไปข้างใน คุณไม่จำเป็นต้องสร้างทรัพย์สินหรืออะไรเลย
Matt Koala

ฉันกำลังเผชิญกับปัญหาเดียวกันกับ @NicolasMiari เมื่อฉันตั้งค่าประมาณ ContentSize สำหรับเซลล์ที่สร้างต้นแบบในสตอรีบอร์ดด้วยข้อ จำกัด อัตโนมัติใน iOS 9 / Xcode 7 แอปขัดข้องด้วยข้อยกเว้นการเข้าถึงที่ไม่ดีและไม่มีสแต็คที่มีประโยชน์
wasabi

34

ใน iOS 10+ นี่เป็นกระบวนการ 2 ขั้นตอนที่ง่ายมาก

  1. ตรวจสอบให้แน่ใจว่าเนื้อหาของเซลล์ทั้งหมดของคุณอยู่ใน UIView เดียว (หรือภายในลูกหลานของ UIView เช่น UIStackView ซึ่งช่วยลดความยุ่งยากในการ autolayout เป็นจำนวนมาก) เช่นเดียวกับการปรับขนาด UITableViewCells แบบไดนามิกลำดับชั้นของมุมมองทั้งหมดจำเป็นต้องมีการกำหนดค่าข้อ จำกัด จากคอนเทนเนอร์ด้านนอกสุดไปจนถึงมุมมองด้านในสุด ซึ่งรวมถึงข้อ จำกัด ระหว่าง UICollectionViewCell และ childview ในทันที

  2. สั่งการเลย์เอาต์ของ UICollectionView ให้มีขนาดโดยอัตโนมัติ

    yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

อยากรู้อยากเห็นว่าการห่อเนื้อหาเซลล์ของคุณใน UIView ทำให้เค้าโครงอัตโนมัติทำงานได้ดีขึ้นได้อย่างไร รหัสคิดว่ามันจะทำงานได้ดีขึ้นด้วยลำดับชั้นที่ตื้นขึ้น?
James Heald

1
ฉันไม่ได้พูดถึงประสิทธิภาพความเรียบง่ายเท่านั้น เซลล์ที่ปรับขนาดด้วยตนเองจะทำงานเฉพาะเมื่อคุณมีข้อ จำกัด ที่ทอดข้ามไปมาระหว่างซ้ายและขวาและระหว่างบนและล่าง ความสำเร็จที่ง่ายที่สุดเมื่อมีการห่อทุกมุมมองไว้ในคอนเทนเนอร์เดียว ถ้าคอนเทนเนอร์นั้นเป็น UIStackView จะง่ายกว่าถ้าเป็นลูกหลานอื่นของ UIView
Marmoy

คุณช่วยบอกวิธีการตั้งค่าความสูงอย่างคงที่ได้ไหม (เช่นความสูง 25 และความกว้างจะเปลี่ยนโดยอัตโนมัติ) สำหรับ UICollectionViewFlowLayoutAutomaticSize
Svetoslav Atanasov

การใช้สวิฟท์ 4.1, Xcode บอกฉันได้รับการเปลี่ยนชื่อเป็นUICollectionViewFlowLayout.automaticSize UICollectionViewFlowLayoutAutomaticSize
LinusGeffarth

14
  1. เพิ่ม flowLayout บน viewDidLoad ()

    override func viewDidLoad() {
        super.viewDidLoad() 
        if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.estimatedItemSize = CGSize(width: 1, height:1)
        }
    }
  2. นอกจากนี้ตั้งค่า UIView เป็น mainContainer สำหรับเซลล์ของคุณและเพิ่มมุมมองที่จำเป็นทั้งหมดภายใน

  3. อ้างถึงบทช่วยสอนที่ยอดเยี่ยมและเหลือเชื่อนี้สำหรับการอ้างอิงเพิ่มเติม: UICollectionView พร้อมเซลล์ปรับขนาดอัตโนมัติโดยใช้การชำระอัตโนมัติใน iOS 9 และ 10


11

แก้ไข 11/19/19: สำหรับ iOS 13 เพียงใช้ UICollectionViewCompositionalLayout พร้อมความสูงโดยประมาณ อย่าเสียเวลาจัดการกับ API ที่เสียนี้

หลังจากดิ้นรนกับสิ่งนี้มาระยะหนึ่งฉันสังเกตเห็นว่าการปรับขนาดไม่สามารถใช้กับ UITextViews หากคุณไม่ปิดใช้งานการเลื่อน:

let textView = UITextView()
textView.scrollEnabled = false

เมื่อฉันพยายามเลื่อนเซลล์มุมมองการรวบรวมไม่ราบรื่น คุณมีทางออกสำหรับสิ่งนี้หรือไม่?
Sathish Kumar Gurunathan

6

contentView anchor ลึกลับ:

ในกรณีที่แปลกประหลาดอย่างนี้

    contentView.translatesAutoresizingMaskIntoConstraints = false

จะไม่ทำงาน เพิ่มจุดยึดสี่จุดอย่างชัดเจนใน contentView และทำงานได้

class AnnoyingCell: UICollectionViewCell {
    
    @IBOutlet var word: UILabel!
    
    override init(frame: CGRect) {
        super.init(frame: frame); common() }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder); common() }
    
    private func common() {
        contentView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            contentView.leftAnchor.constraint(equalTo: leftAnchor),
            contentView.rightAnchor.constraint(equalTo: rightAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
    }
}

และตามปกติ

    estimatedItemSize = UICollectionViewFlowLayout.automaticSize

ใน YourLayout: UICollectionViewFlowLayout

ใครจะรู้? อาจช่วยใครซักคน

เครดิต

https://www.vadimbulavin.com/collection-view-cells-self-sizing/

สะดุดที่ปลายมี - ไม่เคยเห็นที่ใดในบทความ 1000s ทั้งหมดในนี้


3

ฉันทำเซลล์ความสูงของการดูคอลเล็กชัน นี่คือrepo ศูนย์กลางคอมไพล์

และขุดหาเหตุผลว่าทำไมที่ต้องการเลย์เอาต์ -AttributesFittingAttributes เรียกว่ามากกว่าหนึ่งครั้ง จริงๆแล้วมันจะถูกเรียกอย่างน้อย 3 ครั้ง

รูปภาพบันทึกของคอนโซล: ป้อนคำอธิบายรูปภาพที่นี่

1stLayoutAttributesFittingAttributes ที่ต้องการ :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath:    0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

layoutAttributes.frame.size.height สถานะปัจจุบัน57.5

อันดับที่ 2 LayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);

ความสูงของเฟรมเซลล์เปลี่ยนเป็น534.5ตามที่เราคาดไว้ แต่มุมมองคอลเลกชันยังคงมีความสูงเป็นศูนย์

อันดับที่ 3 LayoutAttributesFittingAttributes :

(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016> 
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5); 

(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);

ท่านสามารถเข้าดูสูงมุมมองก็เปลี่ยนคอลเลกชัน 0-477

ลักษณะการทำงานคล้ายกับการจัดการกับการเลื่อน:

1. Before self-sizing cell

2. Validated self-sizing cell again after other cells recalculated.

3. Did changed self-sizing cell

ตอนแรกฉันคิดว่าวิธีนี้จะโทรครั้งเดียวเท่านั้น ดังนั้นฉันเขียนดังนี้:

CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;

สายนี้:

frame.size.height = frame.size.height + self.collectionView.contentSize.height;

จะทำให้เกิดการวนรอบของการเรียกระบบไม่สิ้นสุดและแอปขัดข้อง

ขนาดใดก็ได้ที่เปลี่ยนไปมันจะตรวจสอบความถูกต้องของเซลล์ทั้งหมดที่ต้องการ LAYoutAttributesFittingAttributes ซ้ำแล้วซ้ำอีกจนกระทั่งทุกตำแหน่งของเซลล์ (เช่นเฟรม) ไม่มีการเปลี่ยนแปลงอีกต่อไป


2

นอกจากคำตอบข้างต้นแล้ว

เพียงตรวจสอบให้แน่ใจว่าคุณได้ตั้งค่าคุณสมบัติของItemSizeของUICollectionViewFlowLayoutเป็นขนาดบางส่วนแล้วและอย่าใช้sizeForItem: atIndexPathวิธีการมอบหมาย

แค่นั้นแหละ.


1

ถ้าคุณใช้เมธอด UICollectionViewDelegateFlowLayout:

- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath

เมื่อคุณเรียกcollectionview performBatchUpdates:completion:ความสูงขนาดจะใช้แทนsizeForItemAtIndexPath preferredLayoutAttributesFittingAttributes

กระบวนการแสดงผลของperformBatchUpdates:completionจะผ่านวิธีการpreferredLayoutAttributesFittingAttributesแต่จะไม่สนใจการเปลี่ยนแปลงของคุณ


ฉันเห็นพฤติกรรมไม่เหมาะสมนี้ แต่เฉพาะเมื่อขนาดโดยประมาณเป็นศูนย์ คุณแน่ใจหรือว่าคุณตั้งค่าขนาดโดยประมาณ
dgatwood

1

หากใครก็ตามที่อาจช่วยได้

ฉันมีความผิดพลาดที่น่ารังเกียจถ้าestimatedItemSizeมีการตั้งค่า แม้ว่าผมจะกลับ numberOfItemsInSection0 ดังนั้นเซลล์เองและเลย์เอาต์อัตโนมัติของพวกเขาจึงไม่ได้เป็นสาเหตุของความผิดพลาด ... คอลเลกชันที่ดูเพิ่งเกิดข้อผิดพลาดแม้ว่าจะว่างเปล่าเพียงเพราะestimatedItemSizeถูกตั้งค่าสำหรับการปรับขนาดด้วยตนเอง

ในกรณีของฉันฉันจัดระเบียบโครงการของฉันใหม่จากคอนโทรลเลอร์ที่มี collectionView ไปยัง collectionViewController และทำงานได้

ไปคิด


1
ลองโทรหลังcollectionView.collectionViewLayout.invalidateLayout() collectionView.reloadData()
chengsam

สำหรับ ios10 และด้านล่างเพิ่มรหัสนี้ `แทนที่ func viewWillLayoutSubviews () {super.viewWillLayoutSubviews () ถ้า #available (iOS 11.0, *) {} อื่น {mainCollectionView.collectionViewLayout.invalidateLayout ()}}`
Marosdeema ที่

1

สำหรับทุกคนที่ลองทุกอย่างโดยไม่มีโชคนี่เป็นสิ่งเดียวที่ทำให้ฉันทำงานได้ สำหรับฉลากหลายเซลล์ในเซลล์ลองเพิ่มสายเวทย์นี้:

label.preferredMaxLayoutWidth = 200

ข้อมูลเพิ่มเติม: ที่นี่

ไชโย!


0

ตัวอย่างวิธีการข้างต้นไม่ได้รวบรวม นี่คือรุ่นที่แก้ไข (แต่ยังไม่ได้ทดสอบว่าทำงานได้หรือไม่)

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
{
    let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes

    var newFrame = attr.frame
    self.frame = newFrame

    self.setNeedsLayout()
    self.layoutIfNeeded()

    let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    newFrame.size.height = desiredHeight
    attr.frame = newFrame
    return attr
}

0

อัปเดตข้อมูลเพิ่มเติม:

  • หากคุณใช้flowLayout.estimatedItemSizeแนะนำให้ใช้ iOS8.3 เวอร์ชั่นใหม่กว่า ก่อน iOS8.3 [super layoutAttributesForElementsInRect:rect];มันจะผิดพลาด ข้อความแสดงข้อผิดพลาดคือ

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • ประการที่สองในเวอร์ชั่น iOS8.x flowLayout.estimatedItemSizeจะทำให้การตั้งค่าส่วนแทรกแตกต่างกันไม่ทำงาน (UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:ฟังก์ชั่นเช่น:


0

การแก้ปัญหาประกอบด้วย 4 ขั้นตอนสำคัญ:

  1. การเปิดใช้งานการปรับขนาดเซลล์แบบไดนามิก

flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

  1. เปิดใช้งานเลย์เอาต์อัตโนมัติและปักหมุดcontentViewที่ขอบของเซลล์อย่างชัดเจนเนื่องจากcontentViewป้องกันการปรับขนาดด้วยตนเองเนื่องจากไม่ได้เปิดใช้งานเลย์เอาต์อัตโนมัติ
class MultiLineCell: UICollectionViewCell{

    private var textViewHeightContraint: NSLayoutConstraint!

    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .cyan
        contentView.translatesAutoresizingMaskIntoConstraints = false
        contentViewWidthAnchor = contentView.widthAnchor.constraint(equalToConstant: 0)

        NSLayoutConstraint.activate([
            contentView.leadingAnchor.constraint(equalTo: leadingAnchor),
            contentView.topAnchor.constraint(equalTo: topAnchor),
            contentView.trailingAnchor.constraint(equalTo: trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: bottomAnchor),
        ])
    } 
    ...
}
  1. ตั้งค่า contentView.widthAnchor.constraint จากcollectionView(:cellForItemAt:)เพื่อจำกัดความกว้างของ contentView เป็นความกว้างของ collectionView

มันเกิดขึ้นดังนี้ เราจะตั้งค่ามือถือของmaxWidthตัวแปรความกว้าง CollectionView ที่collectionView(:cellForItemAt:)และในmaxWidth's didSetวิธีการที่เราจะตั้ง widthAnchor.constant เพื่อ maxWidth

class ViewController: UIViewController, UICollectionViewDataSource {
    ...

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MultiLineCell
        cell.textView.text = dummyTextMessages[indexPath.row]
        cell.maxWidth = collectionView.frame.width
        return cell
    }

    ...
}
class MultiLineCell: UICollectionViewCell{
    ....

    var maxWidth: CGFloat? {
        didSet {
            guard let maxWidth = maxWidth else {
                return
            }
            contentViewWidthAnchor.constant = maxWidth
            contentViewWidthAnchor.isActive = true
        }
    }

    ....
}

เนื่องจากคุณต้องการเปิดใช้งานการปรับขนาด UITextView ด้วยตนเองจึงมีขั้นตอนเพิ่มเติมดังนี้:

4. คำนวณและตั้งค่า heightAnchor.constant ของ UITextView

ดังนั้นเมื่อใดก็ตามที่ความกว้างของ contentView มีการตั้งค่าเราจะปรับความสูงของ UITextView พร้อมในการdidSetmaxWidth

ภายใน UICollectionViewCell:

var maxWidth: CGFloat? {
    didSet {
        guard let maxWidth = maxWidth else {
            return
        }
        contentViewWidthAnchor.constant = maxWidth
        contentViewWidthAnchor.isActive = true

        let sizeToFitIn = CGSize(width: maxWidth, height: CGFloat(MAXFLOAT))
        let newSize = self.textView.sizeThatFits(sizeToFitIn)
        self.textViewHeightContraint.constant = newSize.height
    }
}

ขั้นตอนเหล่านี้จะทำให้คุณได้รับผลลัพธ์ที่ต้องการ นี่คือส่วนสำคัญ runnable สมบูรณ์

ขอบคุณVadim Bulavinสำหรับการโพสต์บล็อกที่ชัดเจนและกระจ่างแจ้งของเขา - Collection View Cells Self-Sizing: ทีละขั้นตอนการสอน


-2

ฉันพยายามใช้estimatedItemSizeแต่มีข้อบกพร่องมากมายเมื่อใส่และลบเซลล์หากestimatedItemSizeไม่เท่ากับความสูงของเซลล์ ฉันหยุดการตั้งค่าestimatedItemSizeและปรับใช้เซลล์ไดนามิกโดยใช้เซลล์ต้นแบบ นี่คือวิธีที่ทำ:

สร้างโปรโตคอลนี้:

protocol SizeableCollectionViewCell {
    func fittedSize(forConstrainedSize size: CGSize)->CGSize
}

ใช้โปรโตคอลนี้ในแบบกำหนดเองของคุณUICollectionViewCell:

class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {

    @IBOutlet private var mTitle: UILabel!
    @IBOutlet private var mDescription: UILabel!
    @IBOutlet private var mContentView: UIView!
    @IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
    @IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!

    func fittedSize(forConstrainedSize size: CGSize)->CGSize {

        let fittedSize: CGSize!

        //if height is greatest value, then it's dynamic, so it must be calculated
        if size.height == CGFLoat.greatestFiniteMagnitude {

            var height: CGFloat = 0

            /*now here's where you want to add all the heights up of your views.
              apple provides a method called sizeThatFits(size:), but it's not 
              implemented by default; except for some concrete subclasses such 
              as UILabel, UIButton, etc. search to see if the classes you use implement 
              it. here's how it would be used:
            */
            height += mTitle.sizeThatFits(size).height
            height += mDescription.sizeThatFits(size).height
            height += mCustomView.sizeThatFits(size).height    //you'll have to implement this in your custom view

            //anything that takes up height in the cell has to be included, including top/bottom margin constraints
            height += mTitleTopConstraint.constant
            height += mDescriptionBottomConstraint.constant

            fittedSize = CGSize(width: size.width, height: height)
        }
        //else width is greatest value, if not, you did something wrong
        else {
            //do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
        }

        return fittedSize
    }
}

ตอนนี้ให้คอนโทรลเลอร์ของคุณสอดคล้องUICollectionViewDelegateFlowLayoutและมีฟิลด์นี้:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}

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

ในที่สุดUICollectionViewDelegateFlowLayout's collectionView(:layout:sizeForItemAt:)จะต้องมีการใช้งาน:

class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    private var mDataSource: [CustomModel]

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {

        //bind the prototype cell with the data that corresponds to this index path
        mCustomCellPrototype.bind(model: mDataSource[indexPath.row])    //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind

        //define the dimension you want constrained
        let width = UIScreen.main.bounds.size.width - 20    //the width you want your cells to be
        let height = CGFloat.greatestFiniteMagnitude    //height has the greatest finite magnitude, so in this code, that means it will be dynamic
        let constrainedSize = CGSize(width: width, height: height)

        //determine the size the cell will be given this data and return it
        return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
    }
}

และนั่นคือมัน การคืนขนาดของเซลล์collectionView(:layout:sizeForItemAt:)ด้วยวิธีนี้ทำให้ฉันไม่ต้องใช้estimatedItemSizeและการแทรกและลบเซลล์ก็ทำงานได้อย่างสมบูรณ์

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