UICollectionView Đặt số cột


100

Tôi mới bắt đầu tìm hiểu về UICollectionViews. Tôi tự hỏi nếu có ai biết cách chỉ định số cột trong một chế độ xem bộ sưu tập. Mặc định được đặt thành 3 (iPhone / dọc). Tôi đã xem tài liệu và dường như không thể tìm ra câu trả lời ngắn gọn.


1
Tôi có một ứng dụng UICollectionViewvề cơ bản mà tôi sử dụng dưới dạng lưới vì tôi biết tôi chỉ muốn 4 cột. Tôi làm điều này bằng cách tạo cho các ô của mình một kích thước nhất định và khoảng cách giữa chúng là một kích thước nhất định để chế độ xem bộ sưu tập chỉ hiển thị 4 cột, bất kể hướng nào. Điều này có giống với những gì bạn cần không?
jhilgert00

Hay là một câu hỏi tổng quát hơn, chẳng hạn như tôi có thể đặt động số cột hay không?
jhilgert00

Về cơ bản, tôi muốn có 7 cột ở chế độ dọc. Vì vậy, tôi đã tạo chiều rộng của các cột là 46px (320 px / 46px = 7). Tôi không biết họ là một tài sản không gian.
SNV7

Câu trả lời:


81

CollectionViews rất mạnh mẽ và có giá. Rất nhiều và rất nhiều tùy chọn. Như omz đã nói:

có nhiều cách bạn có thể thay đổi số lượng cột

Tôi khuyên bạn nên triển khai <UICollectionViewDelegateFlowLayout>Giao thức, cấp cho bạn quyền truy cập vào các phương pháp sau, trong đó bạn có thể có quyền kiểm soát tốt hơn đối với bố cục của bạn UICollectionViewmà không cần phân lớp nó:

  • collectionView:layout:insetForSectionAtIndex:
  • collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
  • collectionView:layout:minimumLineSpacingForSectionAtIndex:
  • collectionView:layout:referenceSizeForFooterInSection:
  • collectionView:layout:referenceSizeForHeaderInSection:
  • collectionView:layout:sizeForItemAtIndexPath:

Ngoài ra, việc triển khai phương pháp sau sẽ buộc UICollectionView của bạn cập nhật bố cục của nó theo hướng thay đổi: (giả sử bạn muốn kích thước lại các ô cho ngang và làm cho chúng kéo dài ra)

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
                               duration:(NSTimeInterval)duration{

    [self.myCollectionView.collectionViewLayout invalidateLayout];
}

Ngoài ra, đây là 2 hướng dẫn thực sự tốt về UICollectionViews:

http://www.raywenderlich.com/22324/beginning-uicollectionview-in-ios-6-part-12

http://skeuo.com/uicollectionview-custom-layout-tutorial


1
Điều này sẽ hoạt động cho tất cả các thiết bị iOS? Nên collectionView:layout:sizeForItemAtIndexPath:xử lý riêng cho tất cả các thiết bị?
Sharath

1
@Sharath, tất nhiên điều đó hoạt động cho mọi thứ. Bạn chỉ cần thực hiện phép toán của mình theo cách có thể thích ứng với mọi thiết bị;). Thiết kế thích ứng.
TheCodingArt

wow thật tuyệt, nhưng ví dụ thực tế? trong Android, chúng tôi có thể làm new GridLayoutManager(mContext, 4))(4 cột), rất dễ dàng hoặc chỉ cần sử dụng LinearLayoutManager(tất nhiên chỉ có 1 cột)
25

145

Với Swift 5 và iOS 12.3, bạn có thể sử dụng một trong 4 cách triển khai sau để đặt số lượng mục trên mỗi hàng trong UICollectionViewkhi quản lý nội dung và thay đổi kích thước (bao gồm cả xoay).


# 1. Subclassing UICollectionViewFlowLayoutvà sử dụng UICollectionViewFlowLayoutcủa itemSizetài sản

ColumnFlowLayout.swift:

import UIKit

class ColumnFlowLayout: UICollectionViewFlowLayout {

    let cellsPerRow: Int

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

        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
    }

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

    override func prepare() {
        super.prepare()

        guard let collectionView = collectionView else { return }
        let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        itemSize = CGSize(width: itemWidth, height: itemWidth)
    }

    override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
        let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
        context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
        return context
    }

}

CollectionViewController.swift:

import UIKit

class CollectionViewController: UICollectionViewController {

    let columnLayout = ColumnFlowLayout(
        cellsPerRow: 5,
        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(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

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

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

}

nhập mô tả hình ảnh ở đây


# 2. Sử dụng UICollectionViewFlowLayout's itemSizephương pháp

import UIKit

class CollectionViewController: UICollectionViewController {

    let margin: CGFloat = 10
    let cellsPerRow = 5

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let collectionView = collectionView, let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else { return }

        flowLayout.minimumInteritemSpacing = margin
        flowLayout.minimumLineSpacing = margin
        flowLayout.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)

        collectionView.contentInsetAdjustmentBehavior = .always
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func viewWillLayoutSubviews() {
        guard let collectionView = collectionView, let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
        let marginsAndInsets = flowLayout.sectionInset.left + flowLayout.sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + flowLayout.minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        flowLayout.itemSize =  CGSize(width: itemWidth, height: itemWidth)
    }

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

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

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

}

# 3. Sử dụng UICollectionViewDelegateFlowLayout's collectionView(_:layout:sizeForItemAt:)phương pháp

import UIKit

class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    let inset: CGFloat = 10
    let minimumLineSpacing: CGFloat = 10
    let minimumInteritemSpacing: CGFloat = 10
    let cellsPerRow = 5

    override func viewDidLoad() {
        super.viewDidLoad()

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

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return minimumLineSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return minimumInteritemSpacing
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let marginsAndInsets = inset * 2 + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
        return CGSize(width: itemWidth, height: itemWidth)
    }

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

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

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

}

#4. Subclassing UICollectionViewFlowLayoutvà sử dụng UICollectionViewFlowLayoutcủa estimatedItemSizetài sản

CollectionViewController.swift:

import UIKit

class CollectionViewController: UICollectionViewController {

    let items = [
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "Lorem ipsum dolor sit amet, consectetur.",
        "Lorem ipsum dolor sit amet.",
        "Lorem ipsum dolor sit amet, consectetur.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing.",
        "Lorem ipsum.",
        "Lorem ipsum dolor sit amet.",
        "Lorem ipsum dolor sit.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.",
        "Lorem ipsum dolor sit amet, consectetur."
    ]

    let columnLayout = FlowLayout(
        cellsPerRow: 3,
        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(Cell.self, forCellWithReuseIdentifier: "Cell")
    }

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

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
        cell.label.text = items[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 {

    let cellsPerRow: Int

    required init(cellsPerRow: Int = 1, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        self.cellsPerRow = cellsPerRow

        super.init()

        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }

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

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath) else { return nil }
        guard let collectionView = collectionView else { return layoutAttributes }

        let marginsAndInsets = collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + sectionInset.left + sectionInset.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
        layoutAttributes.bounds.size.width = ((collectionView.bounds.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)

        return layoutAttributes
    }

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

        let layoutAttributes = superLayoutAttributes.compactMap { layoutAttribute in
            return layoutAttribute.representedElementCategory == .cell ? layoutAttributesForItem(at: layoutAttribute.indexPath) : layoutAttribute
        }

        // (optional) Uncomment to top align cells that are on the same line
        /*
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            guard let max = attributes.max(by: { $0.size.height < $1.size.height }) else { continue }
            for attribute in attributes where attribute.size.height != max.size.height {
                attribute.frame.origin.y = max.frame.origin.y
            }
        }
         */

        // (optional) Uncomment to bottom align cells that are on the same line
        /*
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            guard let max = attributes.max(by: { $0.size.height < $1.size.height }) else { continue }
            for attribute in attributes where attribute.size.height != max.size.height {
                attribute.frame.origin.y += max.frame.maxY - attribute.frame.maxY
            }
        }
         */

        return layoutAttributes
    }

}

Cell.swift:

import UIKit

class Cell: UICollectionViewCell {

    let label = UILabel()

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

        label.numberOfLines = 0
        backgroundColor = .orange
        contentView.addSubview(label)

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

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

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        layoutIfNeeded()
        label.preferredMaxLayoutWidth = label.bounds.size.width
        layoutAttributes.bounds.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        return layoutAttributes
    }

    // Alternative implementation
    /*
    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
        label.preferredMaxLayoutWidth = layoutAttributes.size.width - contentView.layoutMargins.left - contentView.layoutMargins.right
        layoutAttributes.bounds.size.height = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
        return layoutAttributes
    }
    */

}

nhập mô tả hình ảnh ở đây


3
Các giải pháp sử dụng "viewWillTransition (to size ..." không xử lý xoay thiết bị một cách chính xác. Vì nó được gọi trước khi đặt khung, nên kích thước cũ của collectionView được sử dụng để tính toán kích thước ô. (Chưa thử các giải pháp khác)
vale

2
Tôi đã sử dụng giải pháp số 1, bạn là người hùng mới của tôi.
Tom Wolters

1
Tôi đã sử dụng giải pháp # 1 của bạn. Tuyệt vời của nó.
shahil

1
câu trả lời tuyệt vời và đầy đủ, được triển khai # 2
Được dẫn dắt vào

1
Giải pháp số 1 là hoàn hảo! Cảm ơn bạn đã đóng góp!
Brian,

61

Tôi đã triển khai UICollectionViewDelegateFlowLayouttrên của mình UICollectionViewControllervà ghi đè phương thức chịu trách nhiệm xác định kích thước của Ô. Sau đó, tôi lấy chiều rộng màn hình và chia nó với yêu cầu cột của tôi. Ví dụ: tôi muốn có 3 cột trên mỗi kích thước màn hình. Vì vậy, đây là mã của tôi trông như thế nào -

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    CGFloat screenWidth = screenRect.size.width;
    float cellWidth = screenWidth / 3.0; //Replace the divisor with the column count requirement. Make sure to have it in float.
    CGSize size = CGSizeMake(cellWidth, cellWidth);

    return size;
}

2
Đơn

2
Bạn nên đảm bảo đặt khoảng cách mục thành 0 để tránh có n-1 mục có khoảng cách cao trên mỗi hàng.
Joe

41

Mở rộng câu trả lời của noob:

func collectionView(collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

        let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
        let totalSpace = flowLayout.sectionInset.left
            + flowLayout.sectionInset.right
            + (flowLayout.minimumInteritemSpacing * CGFloat(numberOfItemsPerRow - 1))
        let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(numberOfItemsPerRow))
        return CGSize(width: size, height: size)
}

Điều này cho phép bất kỳ khoảng cách nào giữa các ô. Nó giả định rằng một Intbiến thành viên được gọi numberOfItemsPerRowvà tất cả các ô đều là hình vuông và có cùng kích thước. Như đã lưu ý trong câu trả lời của jhilgert00, chúng ta cũng phải phản ứng với những thay đổi về định hướng, nhưng bây giờ bằng cách sử dụng viewWillTransitionToSizenhư willRotateToInterfaceOrientationđược khấu hao.


hãy size = Int ((collectionView.bounds.width - totalSpace) / CGFloat (numberOfItemsPerRow)) - bạn nên loại bỏ đúc để Int, bởi vì trong một số trường hợp nó làm cho không gian nhỏ giữa các tế bào
Makalele

14

Đây là mã làm việc cho Swift 3, để có bố cục hai cột:

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

    let nbCol = 2

    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let totalSpace = flowLayout.sectionInset.left
        + flowLayout.sectionInset.right
        + (flowLayout.minimumInteritemSpacing * CGFloat(nbCol - 1))
    let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(nbCol))
    return CGSize(width: size, height: size)
}

Vui lòng thay đổi "nbCol" thành số cột mong muốn của bạn.


Có gì sai với mọi giải pháp khi nội bộ là (1.0,1.0,1.0,1.0), bố cục trở thành 1 cột trong trường hợp này!
rd_

@fraxool Câu trả lời tuyệt vời, nhưng nếu tôi lật thiết bị, bố cục sẽ đặt lại ... Bất kỳ cách khắc phục nào cho điều đó?
Maksim Kniazev

8

Nếu bạn lười sử dụng ủy quyền.

extension UICollectionView {
    func setItemsInRow(items: Int) {
        if let layout = self.collectionViewLayout as? UICollectionViewFlowLayout {
            let contentInset = self.contentInset
            let itemsInRow: CGFloat = CGFloat(items);
            let innerSpace = layout.minimumInteritemSpacing * (itemsInRow - 1.0)
            let insetSpace = contentInset.left + contentInset.right + layout.sectionInset.left + layout.sectionInset.right
            let width = floor((CGRectGetWidth(frame) - insetSpace - innerSpace) / itemsInRow);
            layout.itemSize = CGSizeMake(width, width)
        }
    }
}

PS: Cũng nên gọi sau khi quay


2
Nó cũng hoạt động trong Swift 4.0 ... sau khi thay đổi hai dòng cuối cùng: let width = floor ((frame.width - insetSpace - innerSpace) / itemsInRow) layout.itemSize = CGSize (width: width, height: width)
g212gs

8

Đã cập nhật lên Swift 3:

Thay vì bố cục dòng, tôi thích sử dụng bố cục tùy chỉnh cho số cột và số hàng cụ thể. Bởi vì:

  1. Nó có thể được kéo theo chiều ngang nếu số cột rất lớn.
  2. Nó được chấp nhận một cách hợp lý hơn vì sử dụng cột và hàng.

Ô bình thường và ô Tiêu đề: (Thêm UILabel làm IBOutlet vào xib của bạn):

class CollectionViewCell: UICollectionViewCell {

@IBOutlet weak var label: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
    self.backgroundColor = UIColor.black
    label.textColor = UIColor.white
}
}

class CollectionViewHeadCell: UICollectionViewCell {

@IBOutlet weak var label: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
    self.backgroundColor = UIColor.darkGray
    label.textColor = UIColor.white
}
}

Bố cục tùy chỉnh:

let cellHeight: CGFloat = 100
let cellWidth: CGFloat = 100

class CustomCollectionViewLayout: UICollectionViewLayout {
    private var numberOfColumns: Int!
    private var numberOfRows: Int!

    // It is two dimension array of itemAttributes
    private var itemAttributes = [[UICollectionViewLayoutAttributes]]()
    // It is one dimension of itemAttributes
    private var cache = [UICollectionViewLayoutAttributes]()

override func prepare() {
    if self.cache.isEmpty {

        self.numberOfColumns = self.collectionView?.numberOfItems(inSection: 0)
        self.numberOfRows = self.collectionView?.numberOfSections

        // Dynamically change cellWidth if total cell width is smaller than whole bounds
        /* if (self.collectionView?.bounds.size.width)!/CGFloat(self.numberOfColumns) > cellWidth {
         self.cellWidth = (self.collectionView?.bounds.size.width)!/CGFloat(self.numberOfColumns)
         }
         */
        for row in 0..<self.numberOfRows {
            var row_temp = [UICollectionViewLayoutAttributes]()
            for column in 0..<self.numberOfColumns {

                let indexPath = NSIndexPath(item: column, section: row)

                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
                attributes.frame = CGRect(x: cellWidth*CGFloat(column), y: cellHeight*CGFloat(row), width: cellWidth, height: cellHeight)

                row_temp.append(attributes)

                self.cache.append(attributes)
            }
            self.itemAttributes.append(row_temp)
        }
    }
}
override var collectionViewContentSize: CGSize {
    return CGSize(width: CGFloat(self.numberOfColumns)*cellWidth, height: CGFloat(self.numberOfRows)*cellHeight)
}

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

    var layoutAttributes = [UICollectionViewLayoutAttributes]()

    for attributes in cache {
        if attributes.frame.intersects(rect) {
            layoutAttributes.append(attributes)
        }
    }
    return layoutAttributes
}
}

Bộ sưu tậpXem:

let CellIdentifier = "CellIdentifier"
let HeadCellIdentifier = "HeadCellIdentifier"

class CollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {

init() {
    let layout = CustomCollectionViewLayout()

    super.init(frame: CGRect.zero, collectionViewLayout: layout)

    self.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: CellIdentifier)
    self.register(UINib(nibName: "CollectionViewHeadCell", bundle: nil), forCellWithReuseIdentifier: HeadCellIdentifier)

    self.isDirectionalLockEnabled = true
    self.dataSource = self
    self.delegate = self
}

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

func updateCollectionView() {
    DispatchQueue.main.async {
        self.reloadData()
    }
}

// MARK: CollectionView datasource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 20
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 20
}
override func numberOfItems(inSection section: Int) -> Int {
    return 20
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let column = (indexPath as NSIndexPath).row
    let row = (indexPath as NSIndexPath).section

    if column == 0 {
        let cell : CollectionViewHeadCell = collectionView.dequeueReusableCell(withReuseIdentifier: HeadCellIdentifier, for: indexPath) as! CollectionViewHeadCell

        cell.label.text = "\(row)"

        return cell
    }
    else if row == 0 {
        let cell : CollectionViewHeadCell = collectionView.dequeueReusableCell(withReuseIdentifier: HeadCellIdentifier, for: indexPath) as! CollectionViewHeadCell

        cell.label.text = "\(column)"

        return cell
    }
    else {
        let cell : CollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifier, for: indexPath) as! CollectionViewCell

        cell.label.text = String(format: "%d", arguments: [indexPath.section*indexPath.row])

        return cell
    }
}

// MARK: CollectionView delegate
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

    let column = (indexPath as NSIndexPath).row
    let row = (indexPath as NSIndexPath).section

    print("\(column)  \(row)")
}
}

Sử dụng CollectionView từ ViewController:

class ViewController: UIViewController {
let collectionView = CollectionView()

override func viewDidLoad() {
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    self.view.addSubview(collectionView)
    self.view.backgroundColor = UIColor.red

    self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[collectionView]|", options: [], metrics: nil, views: ["collectionView": collectionView]))
    self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[collectionView]|", options: [], metrics: nil, views: ["collectionView": collectionView]))
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    collectionView.updateCollectionView()
}
}

Cuối cùng, bạn có thể có CollectionView ưa thích!

nhập mô tả hình ảnh ở đây


vấn đề khi thực hiện giải pháp này là chiều cao và chiều rộng tĩnh của bạn
rd_

2
Bố cục @rd_Custom có ​​nghĩa là bạn có thể làm những gì bạn muốn! Mã của tôi chỉ hiển thị trường hợp đơn giản nhất ......
brianLikeApple

7

Bởi vì UICollectionView rất linh hoạt, có nhiều cách bạn có thể thay đổi số lượng cột, tùy thuộc vào loại bố cục bạn sử dụng.

Các UICollectionViewFlowLayout(mà có lẽ những gì bạn đang làm việc với) không xác định một số cột trực tiếp (vì nó phụ thuộc vào quan điểm kích thước / định hướng). Cách dễ nhất để thay đổi nó là đặt thuộc itemSizetính và / hoặc minimumInteritemSpacing/ minimumLineSpacing.


4

Swift 3.0. Hoạt động cho cả hướng cuộn ngang và dọc và khoảng cách thay đổi

Chỉ định số cột

let numberOfColumns: CGFloat = 3

Định cấu hình flowLayoutđể hiển thị được chỉ địnhnumberOfColumns

if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
    let horizontalSpacing = flowLayout.scrollDirection == .vertical ? flowLayout.minimumInteritemSpacing : flowLayout.minimumLineSpacing
    let cellWidth = (collectionView.frame.width - max(0, numberOfColumns - 1)*horizontalSpacing)/numberOfColumns
    flowLayout.itemSize = CGSize(width: cellWidth, height: cellWidth)
}

Tất cả những câu trả lời dài ở khắp mọi nơi và các công trình này với 5 dòng mã :)
J. Doe

1
Nỗ lực tốt. Nhưng điều này sẽ không hoạt động nếu bạn sử dụng sectionInset trên collectionViewLayout. Để khắc phục điều này, (1) tính toán chiều rộng có sẵn bằng cách lấy chiều rộng ô và trừ đi flowLayout.sectionInset.left và flowLayout.sectionInset.right từ nó. (2) Nơi bạn tính toán Độ rộng ô, hãy thay thế 'view.frame.width' bằng giá trị được tính tại (1). Ngoài ra, hãy nhớ rằng khung của chế độ xem sẽ chỉ được hoàn thiện sau khi tải và có thể thay đổi, vì vậy bạn sẽ phải xây dựng lại bố cục nếu cần.
Womble

4

Đã cập nhật lên Swift 5+ iOS 13

Kích thước ước tính của Collectionview không được

nhập mô tả hình ảnh ở đây

Khai báo lề cho ô

let margin: CGFloat = 10

Trong viewDidLoad configure minimumInteritemSpacing, minimumLineSpacing,sectionInset

 guard let collectionView = docsColl, let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }

    flowLayout.minimumInteritemSpacing = margin
    flowLayout.minimumLineSpacing = margin
    flowLayout.sectionInset = UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)

Phương thức UICollectionViewDataSourcesizeForItemAt

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

    let noOfCellsInRow = 2   //number of column you want
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let totalSpace = flowLayout.sectionInset.left
        + flowLayout.sectionInset.right
        + (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1))

    let size = Int((collectionView.bounds.width - totalSpace) / CGFloat(noOfCellsInRow))
    return CGSize(width: size, height: size)
}

2

Tất cả về bố cục mà bạn muốn vẽ. Bạn có thể tạo lớp tùy chỉnh kế thừa từ UICollectionViewFlowLayout. Hiện tại không có bất kỳ phương pháp trực tiếp nào để đặt cột. Nếu bạn muốn đạt được loại chức năng này, bạn cần thực hiện theo cách thủ công. Bạn cần xử lý nó trong lớp bố cục luồng tùy chỉnh của mình.

Bây giờ câu hỏi sẽ nảy sinh bạn sẽ làm như thế nào? Nếu bạn không muốn làm phiền khung ô, bạn có thể điều chỉnh

 collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
 collectionView:layout:minimumLineSpacingForSectionAtIndex:

Một cách khác là cung cấp cho bạn vị trí riêng của các ô. Bằng cách ghi đè hai phương thức bên dưới, phương thức này sẽ được gọi trong quá trình hình thành bố cục của bạn.

  - (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
  - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path

UICollectionViewLayoutAttributes là lớp sẽ xử lý vị trí ô, khung, Zindex, v.v.


2

Câu trả lời này dành cho swift 3.0 ->

đầu tiên tuân theo giao thức:

 UICollectionViewDelegateFlowLayout

sau đó thêm các phương thức sau:

    //this method is for the size of items      
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = collectionView.frame.width/3
        let height : CGFloat = 160.0
        return CGSize(width: width, height: height)
    }
    //these methods are to configure the spacing between items

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsetsMake(0,0,0,0)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

1

Tôi chỉ muốn thêm vào câu trả lời số 2 của Imanou Petit. Để đảm bảo lề chính xác bất kể chiều rộng màn hình, tôi sử dụng trình giải lặp lại với lề mong muốn và số cột làm đầu vào. Tôi cũng đã thêm một lá cờ định hướng về nơi lợi nhuận cuối cùng của họ sẽ được so sánh với mục tiêu của họ.

Bộ giải lặp được hiển thị bên dưới và trả về chiều rộng và lề của ô.

private func iterativeCellSpacing(targetMargins : CGFloat,
                                  cellsPerRow : Int,
                                  isMinTarget : Bool) -> (CGFloat, CGFloat)
{
    var w : CGFloat = 0
    var m : CGFloat = targetMargins
    let cols : CGFloat = CGFloat(cellsPerRow)

    let numMargins : CGFloat = cols + 1.0
    let screenWidth : CGFloat = collectionView!.bounds.size.width


    var delta = CGFloat.greatestFiniteMagnitude
    while abs(delta) > 0.001
    {
        let totalMarginSpacing = numMargins * m
        let totalCellSpacing = screenWidth - totalMarginSpacing

        if (isMinTarget)
        {
            w = floor(totalCellSpacing / cols)
            m = ceil((screenWidth - cols * w) / numMargins)
        }
        else
        {
            w = ceil(totalCellSpacing / cols)
            m = floor((screenWidth - cols * w) / numMargins)
        }

        delta = screenWidth - w * CGFloat(cellsPerRow) - m * numMargins
    }

    return (w, m)
}

Tôi gọi nó như vậy:

fileprivate var margin: CGFloat = 20
fileprivate var cellWidth : CGFloat = 80
fileprivate let cellsPerRow = 4

override func viewDidLoad()
{
    super.viewDidLoad()

    (cellWidth, margin) = iterativeCellSpacing(targetMargins: margin, cellsPerRow: 4, isMinTarget: true)
    ...
}

Sau đó, tôi áp dụng các giá trị cellWidth và margin cho bố cục luồng như sau:

extension MyCollectionController : UICollectionViewDelegateFlowLayout

{func collectionView (_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {return CGSize (width: cellWidth, height: cellWidth)}

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

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
{
    return UIEdgeInsetsMake(margin, margin, margin, margin)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
{
    return margin
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
{
    return margin
}

}

Hi vọng điêu nay co ich. Có lẽ có một cách dễ dàng hơn để đảm bảo lợi nhuận là chính xác, nhưng đây là một phương pháp. Ngoài ra, mã này chưa được thử nghiệm cho các thiết bị cho phép các lần xem bộ sưu tập xoay vòng.

Cảm ơn,


0

giải pháp hoàn hảo là Sử dụng UICollectionViewDelegateFlowLayout nhưng bạn có thể dễ dàng tính chiều rộng ô và chia cho số cột mong muốn mà bạn muốn

khó là làm cho chiều rộng không có phân số

(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath

{
 CGFloat screenWidth = self.view.frame.size.width;
   CGFloat marginWidth = (screenWidth - collectionView.frame.size.width);


   CGFloat cellWith = (collectionView.frame.size.width - marginWidth )/3;
   cellWith= floorf(cellWith);




  CGSize retval = CGSizeMake(cellWith,cellWith);


  return retval;}

0

Tôi đã thực hiện một bố cục bộ sưu tập.

Để hiển thị dải phân cách, Đặt màu nền của chế độ xem bộ sưu tập thành màu xám. Một hàng cho mỗi phần.

Sử dụng:

let layout = GridCollectionViewLayout()
layout.cellHeight = 50 // if not set, cellHeight = Collection.height/numberOfSections
layout.cellWidth = 50  // if not set, cellWidth = Collection.width/numberOfItems(inSection)
collectionView.collectionViewLayout = layout

Bố trí:

import UIKit

class GridCollectionViewLayout: UICollectionViewLayout {


var cellWidth : CGFloat = 0
var cellHeight : CGFloat = 0
var seperator: CGFloat = 1

private var cache = [UICollectionViewLayoutAttributes]()



override func prepare() {

    guard let collectionView = self.collectionView else {
        return
    }

    self.cache.removeAll()



        let numberOfSections = collectionView.numberOfSections

        if cellHeight <= 0
        {
            cellHeight = (collectionView.bounds.height - seperator*CGFloat(numberOfSections-1))/CGFloat(numberOfSections)
        }

        for section in 0..<collectionView.numberOfSections {

            let numberOfItems = collectionView.numberOfItems(inSection: section)

            let cellWidth2 : CGFloat
            if cellWidth <= 0
            {
                cellWidth2 = (collectionView.bounds.width - seperator*CGFloat(numberOfItems-1))/CGFloat(numberOfItems)
            }
            else
            {
                cellWidth2 = cellWidth
            }


            for row in 0..<numberOfItems {


                let indexPath = NSIndexPath(row: row, section: section)

                let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath as IndexPath)
                attributes.frame = CGRect(x: (cellWidth2+seperator)*CGFloat(row),
                                          y: (cellHeight+seperator)*CGFloat(section),
                                          width: cellWidth2,
                                          height: cellHeight)

                //row_temp.append(attributes)

                self.cache.append(attributes)
            }
            //self.itemAttributes.append(row_temp)
        }

}

override var collectionViewContentSize: CGSize {

    guard let collectionView = collectionView else
    {
        return CGSize.zero
    }

    if (collectionView.numberOfSections <= 0)
    {
        return collectionView.bounds.size
    }

    let width:CGFloat
    if cellWidth <= 0
    {
        width = collectionView.bounds.width
    }
    else
    {
        width = cellWidth*CGFloat(collectionView.numberOfItems(inSection: 0))
    }

    let numberOfSections = CGFloat(collectionView.numberOfSections)
    var height:CGFloat = 0
    height += numberOfSections * cellHeight
    height += (numberOfSections - 1) * seperator



    return CGSize(width: width, height: height)
}

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

    var layoutAttributes = [UICollectionViewLayoutAttributes]()

    for attributes in cache {
        if attributes.frame.intersects(rect) {
            layoutAttributes.append(attributes)
        }
    }
    return layoutAttributes
}

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return cache[indexPath.item]
}
}

0

Hãy thử cái này, nó hoạt động hoàn hảo cho tôi,

Làm theo các bước sau:

1. Xác định các giá trị trong tệp .h.

     #define kNoOfColumsForCollection 3
     #define kNoOfRowsForCollection 4
     #define kcellSpace 5
     #define kCollectionViewCellWidth (self.view.frame.size.width - kcellSpace*kNoOfColumsForCollection)/kNoOfColumsForCollection
     #define kCollectionViewCellHieght (self.view.frame.size.height-40- kcellSpace*kNoOfRowsForCollection)/kNoOfRowsForCollection

HOẶC LÀ

  #define kNoOfColumsForCollection 3
  #define kCollectionViewCellWidthHieght (self.view.frame.size.width - 6*kNoOfColumsForCollection)/kNoOfColumsForCollection

2.Thêm mã vào bộ sưu tập Xem các phương pháp nguồn dữ liệu Bố cục như bên dưới,

 #pragma mark Collection View Layout data source methods
// collection view with autolayout

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 4;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
 {
 return 1;
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(4, 4, 4, 4);
}
- (CGSize)collectionView:(UICollectionView *)collectionView
              layout:(UICollectionViewLayout *)collectionViewLayout
   sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
 return 
 CGSizeMake(kCollectionViewCellWidth,kCollectionViewCellHieght);
 // CGSizeMake (kCollectionViewCellWidthHieght,kCollectionViewCellWidthHieght);
}

Hy vọng điều này sẽ giúp ích cho một số người.


0

Đầu tiên hãy thêm UICollectionViewDelegateFlowLayout làm giao thức.

Sau đó:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
        var columnCount = 3
        let width  = (view.frame.width - 20) / columnCount
        return CGSize(width: width, height: width)
}
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.