จัดชิดซ้ายเซลล์ใน UICollectionView


98

ฉันใช้ UICollectionView ในโปรเจ็กต์ของฉันซึ่งมีหลายเซลล์ที่มีความกว้างต่างกันบนเส้น อ้างอิงจาก: https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

มันกระจายเซลล์ออกไปทั่วทั้งเส้นโดยมีช่องว่างที่เท่ากัน สิ่งนี้เกิดขึ้นตามที่คาดไว้ยกเว้นว่าฉันต้องการที่จะปรับให้มันถูกต้องและฮาร์ดโค้ดความกว้างของช่องว่างภายใน

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


คำตอบ:


176

โซลูชันอื่น ๆ ในเธรดนี้ทำงานไม่ถูกต้องเมื่อบรรทัดประกอบด้วยเพียง 1 รายการหรือมีความซับซ้อนมากเกินไป

จากตัวอย่างที่ Ryan ให้ไว้ฉันเปลี่ยนรหัสเพื่อตรวจจับบรรทัดใหม่โดยตรวจสอบตำแหน่ง Y ขององค์ประกอบใหม่ ประสิทธิภาพที่ง่ายและรวดเร็ว

รวดเร็ว:

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var leftMargin = sectionInset.left
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.origin.y >= maxY {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }

        return attributes
    }
}

หากคุณต้องการให้มุมมองเสริมคงขนาดไว้ให้เพิ่มสิ่งต่อไปนี้ที่ด้านบนของการปิดในการforEachโทร:

guard layoutAttribute.representedElementCategory == .cell else {
    return
}

วัตถุประสงค์ -C:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.
    CGFloat maxY = -1.0f;

    //this loop assumes attributes are in IndexPath order
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        if (attribute.frame.origin.y >= maxY) {
            leftMargin = self.sectionInset.left;
        }

        attribute.frame = CGRectMake(leftMargin, attribute.frame.origin.y, attribute.frame.size.width, attribute.frame.size.height);

        leftMargin += attribute.frame.size.width + self.minimumInteritemSpacing;
        maxY = MAX(CGRectGetMaxY(attribute.frame), maxY);
    }

    return attributes;
}

4
ขอบคุณ! ฉันได้รับคำเตือนซึ่งได้รับการแก้ไขโดยการทำสำเนาแอตทริบิวต์ในอาร์เรย์ let attributes = super.layoutAttributesForElementsInRect(rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }
tkuichooseyou

2
สำหรับใครก็ตามที่อาจสะดุดกับการมองหารุ่นที่มีการจัดตำแหน่งกึ่งกลางเหมือนที่ฉันเคยทำฉันได้โพสต์คำตอบของ Angel รุ่นแก้ไขแล้วที่นี่: stackoverflow.com/a/38254368/2845237
Alex Koshy

1
ลองใช้โซลูชันนี้ แต่ไม่สามารถเชื่อมโยงเค้าโครงกับ collectionView ในแผง Interface Builder Utilies จบลงด้วยการทำตามรหัส เบาะแสทำไม?
เอเคอร์

1
คุณจัดแนวนี้ไปทางซ้ายในระบบซ้ายไปขวาและทางขวาในระบบขวาไปซ้ายได้อย่างไร
Rick

1
สำหรับการใช้งานให้ทำ self.collectionView.collectionViewLayout = LeftAlignedCollectionViewFlowLayout ()
blackops

64

คำตอบสำหรับคำถามนี้มีแนวคิดดีๆมากมาย อย่างไรก็ตามส่วนใหญ่มีข้อบกพร่องบางประการ:

  • โซลูชันที่ไม่ตรวจสอบค่าyของเซลล์จะใช้ได้เฉพาะกับเลย์เอาต์บรรทัดเดียวเท่านั้น พวกเขาล้มเหลวสำหรับเค้าโครงมุมมองคอลเลกชันที่มีหลายบรรทัด
  • แนวทางแก้ไขที่ ทำตรวจสอบYค่าเหมือนคำตอบ Angel Garcia Olloqui ของ การทำงานเฉพาะในกรณีที่เซลล์ทุกคนมีความสูงเดียวกัน พวกเขาล้มเหลวสำหรับเซลล์ที่มีความสูงตัวแปร
  • โซลูชันส่วนใหญ่จะแทนที่ไฟล์ layoutAttributesForElements(in rect: CGRect)ฟังก์ชันเท่านั้น พวกเขาไม่ได้แทนที่ layoutAttributesForItem(at indexPath: IndexPath)นี่เป็นปัญหาเนื่องจากมุมมองคอลเลกชันเรียกฟังก์ชันหลังเป็นระยะเพื่อดึงแอตทริบิวต์โครงร่างสำหรับเส้นทางดัชนีเฉพาะ estimatedItemSizeหากคุณไม่ได้กลับคุณลักษณะที่เหมาะสมจากฟังก์ชั่นที่คุณอาจจะวิ่งเข้ามาในทุกประเภทของบักภาพเช่นในระหว่างการแทรกและลบภาพเคลื่อนไหวของเซลล์หรือเมื่อใช้เซลล์ตัวเองขนาดโดยการตั้งค่ามุมมองของคอลเลกชันของรูปแบบ แอปเปิ้ลเอกสารรัฐ:

    ทุกออบเจ็กต์โครงร่างแบบกำหนดเองคาดว่าจะใช้layoutAttributesForItemAtIndexPath:เมธอด

  • โซลูชันจำนวนมากยังตั้งสมมติฐานเกี่ยวกับrectพารามิเตอร์ที่ส่งผ่านไปยังlayoutAttributesForElements(in rect: CGRect)ฟังก์ชัน ตัวอย่างเช่นหลายคนตั้งอยู่บนสมมติฐานที่ว่าการrectเริ่มต้นบรรทัดใหม่เสมอซึ่งไม่จำเป็นต้องเป็นเช่นนั้นเสมอไป

กล่าวอีกนัยหนึ่ง:

วิธีแก้ปัญหาส่วนใหญ่ที่แนะนำในหน้านี้ใช้ได้กับแอปพลิเคชันบางอย่าง แต่ไม่ได้ผลตามที่คาดไว้ในทุกสถานการณ์


AlignedCollectionViewFlowLayout

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

⬅︎ ซ้าย :

เค้าโครงจัดชิดซ้าย

หรือ➡︎ ขวา :

เค้าโครงจัดชิดขวา

และยังมีตัวเลือกให้ แนวตั้งจัดแนวเซลล์ในในแถวที่เกี่ยวข้อง (ในกรณีที่มีความสูงต่างกัน)

คุณสามารถดาวน์โหลดได้ที่นี่:

https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout

การใช้งานตรงไปตรงมาและอธิบายไว้ในไฟล์ README โดยพื้นฐานแล้วคุณจะสร้างอินสแตนซ์AlignedCollectionViewFlowLayoutระบุการจัดแนวที่ต้องการและกำหนดให้กับcollectionViewLayoutคุณสมบัติของมุมมองคอลเลกชันของคุณ:

 let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, 
                                                         verticalAlignment: .top)

 yourCollectionView.collectionViewLayout = alignedFlowLayout

(มีให้บริการบนCocoapodsด้วย)


วิธีการทำงาน (สำหรับเซลล์ที่จัดชิดซ้าย):

แนวคิดที่นี่คือการพึ่งพาแต่เพียงผู้เดียวในlayoutAttributesForItem(at indexPath: IndexPath)ฟังก์ชั่น ในการที่layoutAttributesForElements(in rect: CGRect)เราได้รับเส้นทางดัชนีของเซลล์ทั้งหมดภายในrectแล้วเรียกใช้ฟังก์ชันแรกสำหรับทุกเส้นทางดัชนีเพื่อดึงเฟรมที่ถูกต้อง:

override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

    // We may not change the original layout attributes 
    // or UICollectionViewFlowLayout might complain.
    let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))

    layoutAttributesObjects?.forEach({ (layoutAttributes) in
        if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
            let indexPath = layoutAttributes.indexPath
            // Retrieve the correct frame from layoutAttributesForItem(at: indexPath):
            if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
                layoutAttributes.frame = newFrame
            }
        }
    })

    return layoutAttributesObjects
}

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

ตอนนี้สิ่งเดียวที่เราต้องทำคือใช้layoutAttributesForItem(at indexPath: IndexPath)ฟังก์ชันนี้อย่างถูกต้อง ซุปเปอร์คลาสUICollectionViewFlowLayoutได้ใส่จำนวนเซลล์ที่ถูกต้องไว้แล้วในแต่ละบรรทัดดังนั้นเราจึงต้องเลื่อนเซลล์เหล่านั้นไปทางซ้ายภายในแถวที่เกี่ยวข้อง ความยากอยู่ที่การคำนวณจำนวนพื้นที่ที่เราต้องเลื่อนแต่ละเซลล์ไปทางซ้าย

เนื่องจากเราต้องการให้มีระยะห่างคงที่ระหว่างเซลล์แนวคิดหลักก็คือสมมติว่าเซลล์ก่อนหน้า (เซลล์ด้านซ้ายของเซลล์ที่วางไว้ในปัจจุบัน) อยู่ในตำแหน่งที่เหมาะสมแล้ว จากนั้นเราจะต้องเพิ่มระยะห่างของเซลล์ให้กับmaxXค่าของเฟรมของเซลล์ก่อนหน้าและนั่นคือorigin.xค่าสำหรับเฟรมของเซลล์ปัจจุบัน

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

หากต้องการทราบว่าเซลล์ที่ดัชนีiอยู่ในบรรทัดเดียวกับเซลล์ที่มีดัชนีi-1 หรือไม่ ...

 +---------+----------------------------------------------------------------+---------+
 |         |                                                                |         |
 |         |     +------------+                                             |         |
 |         |     |            |                                             |         |
 | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
 |  inset  |     |intersection|        |                     |   line rect  |  inset  |
 |         |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -|         |
 | (left)  |     |            |             current item                    | (right) |
 |         |     +------------+                                             |         |
 |         |     previous item                                              |         |
 +---------+----------------------------------------------------------------+---------+

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

ดังนั้นฉันเพียงตรวจสอบว่าเซลล์ที่มีดัชนีi-1ตัดกับสี่เหลี่ยมเส้นนี้ที่สร้างจากเซลล์ที่มีดัชนีiหรือไม่

  • ถ้ามันตัดกันเซลล์ที่มีดัชนีiจะไม่ใช่เซลล์ที่อยู่ซ้ายสุดในบรรทัด
    →รับเฟรมของเซลล์ก่อนหน้า (ด้วยดัชนีi − 1 ) แล้วย้ายเซลล์ปัจจุบันไปข้างๆ

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

ฉันจะไม่โพสต์การใช้งานlayoutAttributesForItem(at indexPath: IndexPath)ฟังก์ชันจริงที่นี่เพราะฉันคิดว่าส่วนที่สำคัญที่สุดคือการเข้าใจแนวคิดและคุณสามารถตรวจสอบการใช้งานของฉันในซอร์สโค้ดได้ตลอดเวลา (มันซับซ้อนกว่าที่อธิบายไว้เล็กน้อยเพราะฉันยังอนุญาตให้มี.rightการจัดตำแหน่งและตัวเลือกการจัดแนวแนวตั้งต่างๆอย่างไรก็ตามมันก็เป็นไปตามแนวคิดเดียวกัน)


ว้าวฉันเดาว่านี่เป็นคำตอบที่ยาวที่สุดที่ฉันเคยเขียนบน Stackoverflow ฉันหวังว่านี่จะช่วยได้. 😉


ฉันใช้กับสถานการณ์นี้stackoverflow.com/questions/56349052/… แต่มันไม่ทำงาน คุณช่วยบอกฉันหน่อยว่าฉันจะบรรลุสิ่งนี้ได้อย่างไร
Nazmul Hasan

แม้จะมีสิ่งนี้ (ซึ่งเป็นโครงการที่ยอดเยี่ยมจริงๆ) ฉันพบว่ามีการกะพริบในมุมมองการเลื่อนแนวตั้งของแอป ฉันมี 20 เซลล์และในการเลื่อนครั้งแรกเซลล์แรกว่างเปล่าและเซลล์ทั้งหมดจะเลื่อนไปทางขวาหนึ่งตำแหน่ง หลังจากเลื่อนขึ้นและลงจนเต็มแล้วสิ่งต่างๆจะจัดเรียงอย่างเหมาะสม
Parth Tamane

โปรเจ็กต์สุดเจ๋ง! มันจะดีมากที่ได้เห็นมันเปิดใช้งานสำหรับคาร์เธจ
pjuzeliunas

30

ด้วย Swift 4.1 และ iOS 11 ตามความต้องการของคุณคุณสามารถเลือกหนึ่งใน2 การใช้งานที่สมบูรณ์ต่อไปนี้เพื่อแก้ไขปัญหาของคุณ


# 1. จัดชิดซ้าย autoresizing UICollectionViewCells

การดำเนินการดังต่อไปนี้แสดงให้เห็นถึงวิธีการใช้UICollectionViewLayout's layoutAttributesForElements(in:), UICollectionViewFlowLayouts' estimatedItemSizeและUILabel's preferredMaxLayoutWidthเพื่อเซลล์จัด autoresizing ซ้ายในUICollectionView:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let array = ["1", "1 2", "1 2 3 4 5 6 7 8", "1 2 3 4 5 6 7 8 9 10 11", "1 2 3", "1 2 3 4", "1 2 3 4 5 6", "1 2 3 4 5 6 7 8 9 10", "1 2 3 4", "1 2 3 4 5 6 7", "1 2 3 4 5 6 7 8 9", "1", "1 2 3 4 5", "1", "1 2 3 4 5 6"]

    let columnLayout = FlowLayout(
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.collectionViewLayout = columnLayout
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return array.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        cell.label.text = array[indexPath.row]
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

FlowLayout.swift

import UIKit

class FlowLayout: UICollectionViewFlowLayout {

    required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        super.init()

        estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        sectionInsetReference = .fromSafeArea
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
        guard scrollDirection == .vertical else { return layoutAttributes }

        // Filter attributes to compute only cell attributes
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })

        // Group cell attributes by row (cells with same vertical center) and loop on those groups
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            // Set the initial left inset
            var leftInset = sectionInset.left

            // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
            for attribute in attributes {
                attribute.frame.origin.x = leftInset
                leftInset = attribute.frame.maxX + minimumInteritemSpacing
            }
        }

        return layoutAttributes
    }

}

CollectionViewCell.swift

import UIKit

class CollectionViewCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.backgroundColor = .orange
        label.preferredMaxLayoutWidth = 120
        label.numberOfLines = 0

        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.layoutMarginsGuide.topAnchor.constraint(equalTo: label.topAnchor).isActive = true
        contentView.layoutMarginsGuide.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true
        contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true
        contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

ผลลัพธ์ที่คาดหวัง:

ป้อนคำอธิบายภาพที่นี่


# 2. จัดชิดซ้ายUICollectionViewCellด้วยขนาดคงที่

การใช้งานด้านล่างแสดงวิธีการใช้UICollectionViewLayout's layoutAttributesForElements(in:)และUICollectionViewFlowLayout' itemSizeเพื่อจัดแนวเซลล์ด้านซ้ายตามขนาดที่กำหนดไว้ล่วงหน้าในUICollectionView:

CollectionViewController.swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let columnLayout = FlowLayout(
        itemSize: CGSize(width: 140, height: 140),
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.collectionViewLayout = columnLayout
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 7
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

FlowLayout.swift

import UIKit

class FlowLayout: UICollectionViewFlowLayout {

    required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        super.init()

        self.itemSize = itemSize
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        sectionInsetReference = .fromSafeArea
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
        guard scrollDirection == .vertical else { return layoutAttributes }

        // Filter attributes to compute only cell attributes
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })

        // Group cell attributes by row (cells with same vertical center) and loop on those groups
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            // Set the initial left inset
            var leftInset = sectionInset.left

            // Loop on cells to adjust each cell's origin and prepare leftInset for the next cell
            for attribute in attributes {
                attribute.frame.origin.x = leftInset
                leftInset = attribute.frame.maxX + minimumInteritemSpacing
            }
        }

        return layoutAttributes
    }

}

CollectionViewCell.swift

import UIKit

class CollectionViewCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.backgroundColor = .cyan
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

ผลลัพธ์ที่คาดหวัง:

ป้อนคำอธิบายภาพที่นี่


เมื่อจัดกลุ่มแอตทริบิวต์ของเซลล์ตามแถวการใช้10หมายเลขตามอำเภอใจหรือไม่?
amariduran

27

วิธีง่ายๆในปี 2019

นี่เป็นหนึ่งในคำถามที่น่าหดหู่ที่สิ่งต่างๆเปลี่ยนแปลงไปมากในช่วงหลายปีที่ผ่านมา ตอนนี้เป็นเรื่องง่าย

โดยทั่วไปคุณเพียงแค่ทำสิ่งนี้:

    // as you move across one row ...
    a.frame.origin.x = x
    x += a.frame.width + minimumInteritemSpacing
    // and, obviously start fresh again each row

สิ่งที่คุณต้องการตอนนี้คือรหัสสำเร็จรูป:

override func layoutAttributesForElements(
                  in rect: CGRect)->[UICollectionViewLayoutAttributes]? {
    
    guard let att = super.layoutAttributesForElements(in: rect) else { return [] }
    var x: CGFloat = sectionInset.left
    var y: CGFloat = -1.0
    
    for a in att {
        if a.representedElementCategory != .cell { continue }
        
        if a.frame.origin.y >= y { x = sectionInset.left }
        
        a.frame.origin.x = x
        x += a.frame.width + minimumInteritemSpacing
        
        y = a.frame.maxY
    }
    return att
}

เพียงคัดลอกและวางลงในไฟล์ UICollectionViewFlowLayout - เสร็จแล้ว

โซลูชันการทำงานเต็มรูปแบบเพื่อคัดลอกและวาง:

นี่คือสิ่งทั้งหมด:

class TagsLayout: UICollectionViewFlowLayout {
    
    required override init() {super.init(); common()}
    required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder); common()}
    
    private func common() {
        estimatedItemSize = UICollectionViewFlowLayout.automaticSize
        minimumLineSpacing = 10
        minimumInteritemSpacing = 10
    }
    
    override func layoutAttributesForElements(
                    in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
        guard let att = super.layoutAttributesForElements(in:rect) else {return []}
        var x: CGFloat = sectionInset.left
        var y: CGFloat = -1.0
        
        for a in att {
            if a.representedElementCategory != .cell { continue }
            
            if a.frame.origin.y >= y { x = sectionInset.left }
            a.frame.origin.x = x
            x += a.frame.width + minimumInteritemSpacing
            y = a.frame.maxY
        }
        return att
    }
}

ป้อนคำอธิบายภาพที่นี่

และในที่สุดก็...

ขอบคุณ @AlexShubin ด้านบนที่ชี้แจงเรื่องนี้ก่อน!


เฮ้. ฉันสับสนเล็กน้อย การเพิ่มช่องว่างสำหรับทุกสตริงในตอนเริ่มต้นและสิ้นสุดยังคงแสดงคำของฉันในเซลล์รายการโดยไม่มีช่องว่างที่ฉันเพิ่ม มีความคิดที่จะทำอย่างไร?
Mohamed Lee

1
หากคุณมีส่วนหัวในมุมมองคอลเลกชันของคุณดูเหมือนว่าจะฆ่าพวกเขา
TylerJames

22

คำถามเกิดขึ้นมาระยะหนึ่งแล้ว แต่ไม่มีคำตอบและเป็นคำถามที่ดี คำตอบคือการแทนที่วิธีการหนึ่งในคลาสย่อย UICollectionViewFlowLayout:

@implementation MYFlowLayoutSubclass

//Note, the layout's minimumInteritemSpacing (default 10.0) should not be less than this. 
#define ITEM_SPACING 10.0f

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.

    //this loop assumes attributes are in IndexPath order
    for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
        if (attributes.frame.origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left; //will add outside loop
        } else {
            CGRect newLeftAlignedFrame = attributes.frame;
            newLeftAlignedFrame.origin.x = leftMargin;
            attributes.frame = newLeftAlignedFrame;
        }

        leftMargin += attributes.frame.size.width + ITEM_SPACING;
        [newAttributesForElementsInRect addObject:attributes];
    }   

    return newAttributesForElementsInRect;
}

@end

ตามที่แนะนำโดย Apple คุณจะได้รับแอตทริบิวต์เค้าโครงจากขั้นสูงและทำซ้ำ ถ้าเป็นอันแรกในแถว (กำหนดโดย origin.x อยู่ขอบซ้าย) ให้ปล่อยไว้เฉยๆและรีเซ็ต x เป็นศูนย์ จากนั้นสำหรับเซลล์แรกและทุกเซลล์ให้คุณเพิ่มความกว้างของเซลล์นั้นบวกระยะขอบ สิ่งนี้จะถูกส่งต่อไปยังรายการถัดไปในลูป หากไม่ใช่รายการแรกให้คุณตั้งค่า origin.x เป็นระยะขอบที่คำนวณแล้วและเพิ่มองค์ประกอบใหม่ในอาร์เรย์


ฉันคิดว่านี่เป็นวิธีที่ดีกว่าโซลูชันของ Chris Wagner ( stackoverflow.com/a/15554667/482557 ) สำหรับคำถามที่เชื่อมโยงการใช้งานของคุณไม่มีการเรียก UICollectionViewLayout ซ้ำ ๆ เป็นศูนย์
Evan R

1
ดูเหมือนจะไม่ได้ผลถ้าแถวนั้นมีรายการเดียวเนื่องจาก UICollectionViewFlowLayout จัดกึ่งกลาง คุณสามารถตรวจสอบศูนย์เพื่อแก้ไขปัญหา สิ่งที่ชอบattribute.center.x == self.collectionView?.bounds.midX
Ryan Poolos

ฉันใช้สิ่งนี้ แต่ด้วยเหตุผลบางอย่างมันแตกสำหรับส่วนแรก: เซลล์แรกจะปรากฏทางด้านซ้ายของแถวแรกและเซลล์ที่สอง (ซึ่งไม่พอดีกับแถวเดียวกัน) ไปที่แถวถัดไป แต่ไม่เหลือ ชิด แต่ตำแหน่ง x ของมันจะเริ่มต้นที่เซลล์แรกสิ้นสุด
Nicolas Miari

@NicolasMiari ลูปตัดสินใจว่าเป็นแถวใหม่และรีเซ็ตleftMarginด้วยการif (attributes.frame.origin.x == self.sectionInset.left) {ตรวจสอบ สิ่งนี้ถือว่าเค้าโครงเริ่มต้นได้จัดแนวเซลล์ของแถวใหม่ที่จะล้างด้วยsectionInset.left. หากไม่เป็นเช่นนั้นพฤติกรรมที่คุณอธิบายจะส่งผล ฉันจะแทรกเบรกพอยต์ภายในลูปและตรวจสอบทั้งสองด้านสำหรับเซลล์ของแถวที่สองก่อนการเปรียบเทียบ ขอให้โชคดี
Mike Sand

3
ขอบคุณ ... ฉันสิ้นสุดการใช้ UICollectionViewLeftAlignedLayout: github.com/mokagio/UICollectionViewLeftAlignedLayout มันจะลบล้างวิธีการอีกสองสามวิธี
Nicolas Miari

9

ฉันมีปัญหาเดียวกันลองใช้Cocoapod UICollectionViewLeftAlignedLayout เพียงรวมไว้ในโครงการของคุณและเริ่มต้นในลักษณะนี้:

UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];

ไม่มีวงเล็บเหลี่ยมเปิดในการเริ่มต้นเค้าโครงที่นั่น
RyanOfCourse

ฉันทดสอบสองคำตอบgithub.com/eroth/ERJustifiedFlowLayoutและ UICollectionViewLeftAlignedLayout รายการที่สองเท่านั้นที่ให้ผลลัพธ์ที่ถูกต้องเมื่อใช้ค่าประมาณรายการขนาด (ปรับขนาดเอง)
EckhardN

6

นี่คือคำตอบดั้งเดิมใน Swift มันยังคงใช้งานได้ดีเป็นส่วนใหญ่

class LeftAlignedFlowLayout: UICollectionViewFlowLayout {

    private override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElementsInRect(rect)

        var leftMargin = sectionInset.left

        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.origin.x == sectionInset.left {
                leftMargin = sectionInset.left
            }
            else {
                layoutAttribute.frame.origin.x = leftMargin
            }

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
        }

        return attributes
    }
}

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

มีข้อยกเว้นใหญ่อย่างหนึ่งที่น่าเศร้า หากคุณกำลังใช้'sUICollectionViewFlowLayout estimatedItemSizeภายในUICollectionViewFlowLayoutมีการเปลี่ยนแปลงเล็กน้อย ฉันไม่ได้ติดตามมันทั้งหมด แต่มันชัดเจนว่ามันเรียกวิธีอื่น ๆ หลังจากนั้นlayoutAttributesForElementsInRectในขณะที่ปรับขนาดเซลล์ด้วยตนเอง จากการลองผิดลองถูกพบว่าดูเหมือนว่าจะเรียกlayoutAttributesForItemAtIndexPathหาแต่ละเซลล์ทีละเซลล์ระหว่างการปรับขนาดอัตโนมัติบ่อยขึ้น การอัปเดตนี้ใช้LeftAlignedFlowLayoutงานได้ดีกับestimatedItemSize. มันใช้งานได้กับเซลล์ขนาดคงที่เช่นกันอย่างไรก็ตามการเรียกเค้าโครงเพิ่มเติมทำให้ฉันใช้คำตอบเดิมได้ทุกเมื่อที่ฉันไม่ต้องการปรับขนาดเซลล์อัตโนมัติ

class LeftAlignedFlowLayout: UICollectionViewFlowLayout {

    private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let layoutAttribute = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes

        // First in a row.
        if layoutAttribute?.frame.origin.x == sectionInset.left {
            return layoutAttribute
        }

        // We need to align it to the previous item.
        let previousIndexPath = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)
        guard let previousLayoutAttribute = self.layoutAttributesForItemAtIndexPath(previousIndexPath) else {
            return layoutAttribute
        }

        layoutAttribute?.frame.origin.x = previousLayoutAttribute.frame.maxX + self.minimumInteritemSpacing

        return layoutAttribute
    }
}

สิ่งนี้ใช้ได้กับส่วนแรก แต่ป้ายกำกับจะไม่ปรับขนาดในส่วนที่สองจนกว่าตารางจะเลื่อน มุมมองคอลเลกชันของฉันอยู่ในเซลล์ตาราง
EI Captain v2.0

5

จากคำตอบของ Michael Sandฉันจึงสร้างUICollectionViewFlowLayoutไลบรารีคลาสย่อยเพื่อทำซ้ายขวาหรือเต็ม (โดยทั่วไปคือค่าเริ่มต้น) แนวนอน - มันยังช่วยให้คุณกำหนดระยะห่างที่แน่นอนระหว่างแต่ละเซลล์ ฉันวางแผนที่จะเพิ่มเหตุผลกึ่งกลางแนวนอนและเหตุผลแนวตั้งเข้าไปด้วย

https://github.com/eroth/ERJustifiedFlowLayout


5

อย่างรวดเร็ว ตามคำตอบของมิคาเอล

override func layoutAttributesForElementsInRect(rect: CGRect) ->     [UICollectionViewLayoutAttributes]? {
    guard let oldAttributes = super.layoutAttributesForElementsInRect(rect) else {
        return super.layoutAttributesForElementsInRect(rect)
    }
    let spacing = CGFloat(50) // REPLACE WITH WHAT SPACING YOU NEED
    var newAttributes = [UICollectionViewLayoutAttributes]()
    var leftMargin = self.sectionInset.left
    for attributes in oldAttributes {
        if (attributes.frame.origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left
        } else {
            var newLeftAlignedFrame = attributes.frame
            newLeftAlignedFrame.origin.x = leftMargin
            attributes.frame = newLeftAlignedFrame
        }

        leftMargin += attributes.frame.width + spacing
        newAttributes.append(attributes)
    }
    return newAttributes
}

4

จากคำตอบทั้งหมดฉันเปลี่ยนไปเล็กน้อยและมันก็ใช้ได้ผลดีสำหรับฉัน

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let attributes = super.layoutAttributesForElements(in: rect)

    var leftMargin = sectionInset.left
    var maxY: CGFloat = -1.0


    attributes?.forEach { layoutAttribute in
        if layoutAttribute.frame.origin.y >= maxY
                   || layoutAttribute.frame.origin.x == sectionInset.left {
            leftMargin = sectionInset.left
        }

        if layoutAttribute.frame.origin.x == sectionInset.left {
            leftMargin = sectionInset.left
        }
        else {
            layoutAttribute.frame.origin.x = leftMargin
        }

        leftMargin += layoutAttribute.frame.width
        maxY = max(layoutAttribute.frame.maxY, maxY)
    }

    return attributes
}

4

ถ้าใครของคุณหันหน้าไปทางปัญหา - บางส่วนของเซลล์ที่อยู่ด้านขวาในมุมมองของคอลเลกชันเกินขอบเขตในมุมมองของคอลเลกชันที่ จากนั้นโปรดใช้สิ่งนี้ -

class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var leftMargin : CGFloat = sectionInset.left
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if Int(layoutAttribute.frame.origin.y) >= Int(maxY) {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }
        return attributes
    }
}

ใช้INTแทนการเปรียบเทียบค่าCGFloat


3

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

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)

        var leftMargin = sectionInset.left
        var prevMaxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in

            guard layoutAttribute.representedElementCategory == .cell else {
                return
            }

            if layoutAttribute.frame.origin.y >= prevMaxY {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            prevMaxY = layoutAttribute.frame.maxY
        }

        return attributes
    }
}

2

ขอบคุณสำหรับคำตอบของไมเคิลแซนด์ของ ฉันแก้ไขเป็นวิธีแก้ปัญหาของหลายแถว (การจัดตำแหน่งเดียวกัน Top y ของแต่ละแถว) ที่เป็น Left Alignment แม้กระทั่งการเว้นระยะห่างของแต่ละรายการ

static CGFloat const ITEM_SPACING = 10.0f;

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    CGRect contentRect = {CGPointZero, self.collectionViewContentSize};

    NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:contentRect];
    NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.
    NSMutableDictionary *leftMarginDictionary = [[NSMutableDictionary alloc] init];

    for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
        UICollectionViewLayoutAttributes *attr = attributes.copy;

        CGFloat lastLeftMargin = [[leftMarginDictionary valueForKey:[[NSNumber numberWithFloat:attributes.frame.origin.y] stringValue]] floatValue];
        if (lastLeftMargin == 0) lastLeftMargin = leftMargin;

        CGRect newLeftAlignedFrame = attr.frame;
        newLeftAlignedFrame.origin.x = lastLeftMargin;
        attr.frame = newLeftAlignedFrame;

        lastLeftMargin += attr.frame.size.width + ITEM_SPACING;
        [leftMarginDictionary setObject:@(lastLeftMargin) forKey:[[NSNumber numberWithFloat:attributes.frame.origin.y] stringValue]];
        [newAttributesForElementsInRect addObject:attr];
    }

    return newAttributesForElementsInRect;
}

1

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

สิ่งนี้อาจเกิดขึ้นเนื่องจากเค้าโครงโฟลว์ "xyz" กำลังแก้ไขแอตทริบิวต์ที่ส่งคืนโดย UICollectionViewFlowLayout โดยไม่ต้องคัดลอก

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

นี่คือวิธีที่ฉันใช้เค้าโครงสำหรับมุมมองคอลเลกชันของฉัน:

let layout = LeftAlignedCollectionViewFlowLayout()
layout.minimumInteritemSpacing = 5
layout.minimumLineSpacing = 7.5
layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
super.init(frame: frame, collectionViewLayout: layout)

นี่คือคลาสเค้าโครงโฟลว์

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElements(in: rect)?.map { $0.copy() as! UICollectionViewLayoutAttributes }

        var leftMargin = sectionInset.left
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            guard layoutAttribute.representedElementCategory == .cell else {
                return
            }

            if Int(layoutAttribute.frame.origin.y) >= Int(maxY) || layoutAttribute.frame.origin.x == sectionInset.left {
                leftMargin = sectionInset.left
            }

            if layoutAttribute.frame.origin.x == sectionInset.left {
                leftMargin = sectionInset.left
            }
            else {
                layoutAttribute.frame.origin.x = leftMargin
            }

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }

        return attributes
    }
}

คุณช่วยชีวิตฉันไว้
David 'mArm' Ansermot

0

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

1) เพื่อกำหนดส่วน (แถว) ของ UICollectionView ของฉัน:

(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

2) เพื่อกำหนดจำนวนรายการในส่วน คุณสามารถกำหนดจำนวนรายการที่แตกต่างกันสำหรับทุกส่วน คุณสามารถรับหมายเลขส่วนโดยใช้พารามิเตอร์ "ส่วน"

(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

3) เพื่อกำหนดขนาดเซลล์สำหรับแต่ละส่วนและแถวแยกกัน คุณสามารถรับหมายเลขส่วนและหมายเลขแถวโดยใช้พารามิเตอร์ 'indexPath' เช่น[indexPath section]สำหรับหมายเลขส่วนและ[indexPath row]สำหรับหมายเลขแถว

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

4) จากนั้นคุณสามารถแสดงเซลล์ของคุณในแถวและส่วนต่างๆโดยใช้:

(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

หมายเหตุ: ใน UICollectionView

Section == Row
IndexPath.Row == Column

0

คำตอบของ Mike Sand นั้นดี แต่ฉันประสบปัญหาบางอย่างเกี่ยวกับรหัสนี้ (เช่นเดียวกับเซลล์ที่มีความยาวถูกตัดออก) และรหัสใหม่:

#define ITEM_SPACE 7.0f

@implementation LeftAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
        if (nil == attributes.representedElementKind) {
            NSIndexPath* indexPath = attributes.indexPath;
            attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
        }
    }
    return attributesToReturn;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* currentItemAttributes =
    [super layoutAttributesForItemAtIndexPath:indexPath];

    UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];

    if (indexPath.item == 0) { // first item of section
        CGRect frame = currentItemAttributes.frame;
        frame.origin.x = sectionInset.left; // first item of the section should always be left aligned
        currentItemAttributes.frame = frame;

        return currentItemAttributes;
    }

    NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
    CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
    CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width + ITEM_SPACE;

    CGRect currentFrame = currentItemAttributes.frame;
    CGRect strecthedCurrentFrame = CGRectMake(0,
                                              currentFrame.origin.y,
                                              self.collectionView.frame.size.width,
                                              currentFrame.size.height);

    if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
        // the approach here is to take the current frame, left align it to the edge of the view
        // then stretch it the width of the collection view, if it intersects with the previous frame then that means it
        // is on the same line, otherwise it is on it's own new line
        CGRect frame = currentItemAttributes.frame;
        frame.origin.x = sectionInset.left; // first item on the line should always be left aligned
        currentItemAttributes.frame = frame;
        return currentItemAttributes;
    }

    CGRect frame = currentItemAttributes.frame;
    frame.origin.x = previousFrameRightPoint;
    currentItemAttributes.frame = frame;
    return currentItemAttributes;
}

0

แก้ไขคำตอบของ Angel García Olloqui เพื่อเคารพminimumInteritemSpacingจากผู้ได้รับมอบหมายcollectionView(_:layout:minimumInteritemSpacingForSectionAt:)หากดำเนินการตามนั้น

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let attributes = super.layoutAttributesForElements(in: rect)

    var leftMargin = sectionInset.left
    var maxY: CGFloat = -1.0
    attributes?.forEach { layoutAttribute in
        if layoutAttribute.frame.origin.y >= maxY {
            leftMargin = sectionInset.left
        }

        layoutAttribute.frame.origin.x = leftMargin

        let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout
        let spacing = delegate?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: 0) ?? minimumInteritemSpacing

        leftMargin += layoutAttribute.frame.width + spacing
        maxY = max(layoutAttribute.frame.maxY , maxY)
    }

    return attributes
}

0

รหัสด้านบนใช้ได้กับฉัน ฉันต้องการแบ่งปันรหัส Swift 3.0 ตามลำดับ

class SFFlowLayout: UICollectionViewFlowLayout {

    let itemSpacing: CGFloat = 3.0

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        let attriuteElementsInRect = super.layoutAttributesForElements(in: rect)
        var newAttributeForElement: Array<UICollectionViewLayoutAttributes> = []
        var leftMargin = self.sectionInset.left
        for tempAttribute in attriuteElementsInRect! {
            let attribute = tempAttribute 
            if attribute.frame.origin.x == self.sectionInset.left {
                leftMargin = self.sectionInset.left
            }
            else {
                var newLeftAlignedFrame = attribute.frame
                newLeftAlignedFrame.origin.x = leftMargin
                attribute.frame = newLeftAlignedFrame
            }
            leftMargin += attribute.frame.size.width + itemSpacing
            newAttributeForElement.append(attribute)
        }
        return newAttributeForElement
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.