UICollectionView Establecer el número de columnas

100

Acabo de comenzar a aprender sobre UICollectionViews. Me pregunto si alguien sabe cómo especificar el número de columnas en una vista de colección. El valor predeterminado es 3 (iPhone / retrato). He mirado la documentación y parece que no puedo encontrar una respuesta concisa.

SNV7
fuente
1
Tengo una aplicación que uso UICollectionViewbásicamente como una cuadrícula porque sé que solo quiero 4 columnas. Hago esto haciendo que mis celdas tengan un tamaño determinado y el espacio entre ellas de un tamaño determinado para que la vista de colección solo muestre 4 columnas, sin importar la orientación. ¿Es esto similar a lo que necesitas?
jhilgert00
¿O es más una pregunta general, como si puedo establecer dinámicamente el número de columnas o no?
jhilgert00
Básicamente busco tener 7 columnas en modo retrato. Así que hice el ancho de las columnas de 46 px (320 px / 46 px = 7). No sabía que era una propiedad de espaciado.
SNV7

Respuestas:

81

CollectionViews son muy poderosos y tienen un precio. Muchas y muchas opciones. Como dijo omz:

hay varias formas de cambiar el número de columnas

Sugeriría implementar el <UICollectionViewDelegateFlowLayout>Protocolo, dándole acceso a los siguientes métodos en los que puede tener un mayor control sobre el diseño de su UICollectionView, sin la necesidad de subclasificarlo:

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

Además, la implementación del siguiente método obligará a su UICollectionView a actualizar su diseño en un cambio de orientación: (digamos que desea cambiar el tamaño de las celdas para el paisaje y estirarlas)

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

    [self.myCollectionView.collectionViewLayout invalidateLayout];
}

Además, aquí hay 2 tutoriales realmente buenos sobre UICollectionViews:

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

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

jhilgert00
fuente
1
¿Funcionará esto para todos los dispositivos iOS? ¿Debe collectionView:layout:sizeForItemAtIndexPath:manejarse para todos los dispositivos por separado?
Sharath
1
@Sharath, por supuesto que funciona para todo. Solo necesita hacer sus cálculos de una manera que se adapte a todos los dispositivos;). Diseño adaptativo.
TheCodingArt
wow tan genial, pero ejemplo real? en Android podemos hacer new GridLayoutManager(mContext, 4))(4 columnas), muy fácil, o simplemente usar LinearLayoutManager(por supuesto, solo tiene 1 columna)
user25
145

Con Swift 5 y iOS 12.3, puede usar una de las 4 siguientes implementaciones para establecer la cantidad de elementos por fila en su UICollectionViewmientras administra inserciones y cambios de tamaño (incluida la rotación).


# 1. Subclasificación UICollectionViewFlowLayouty uso UICollectionViewFlowLayoutde la itemSizepropiedad

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
    }

}

ingrese la descripción de la imagen aquí


# 2. Usando UICollectionViewFlowLayoutel itemSizemétodo de

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. Usando UICollectionViewDelegateFlowLayoutel collectionView(_:layout:sizeForItemAt:)método de

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. Subclasificación UICollectionViewFlowLayouty uso UICollectionViewFlowLayoutde la estimatedItemSizepropiedad

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
    }
    */

}

ingrese la descripción de la imagen aquí

Imanou Petit
fuente
3
Las soluciones que usan "viewWillTransition (para dimensionar ..." no manejan la rotación del dispositivo correctamente. Dado que se llama antes de que se establezca el marco, la dimensión anterior de collectionView se usa para calcular el tamaño de celda. (No he probado el otras soluciones todavía)
vale
2
Usé la solución # 1, eres mi nuevo héroe.
Tom Wolters
1
Usé tu solución # 1. Es magnífico.
shahil
1
gran y completa respuesta, implementada # 2
Liderado el
1
¡La solución # 1 es perfecta! ¡Gracias por la contribucion!
Brian
61

Implementé UICollectionViewDelegateFlowLayouten my UICollectionViewControlleranule el método responsable de determinar el tamaño de la celda. Luego tomé el ancho de la pantalla y lo dividí con el requisito de mi columna. Por ejemplo, quería tener 3 columnas en cada tamaño de pantalla. Así que así es como se ve mi código:

- (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;
}
novato
fuente
2
Mucho más simple, gracias por la cordura y menos código
jjxtra
2
Debe asegurarse de establecer el espaciado entre elementos en 0 para evitar tener n-1 elementos muy espaciados por fila.
Joe
41

Ampliando la respuesta del novato:

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)
}

Esto permite cualquier espacio entre las celdas. Asume una Intvariable miembro llamada numberOfItemsPerRowy también que todas las celdas son cuadradas y del mismo tamaño. Como se señaló en la respuesta de jhilgert00, también debemos reaccionar a los cambios de orientación, pero ahora al usar viewWillTransitionToSizeas willRotateToInterfaceOrientationse deprecia.

sgib
fuente
let size = Int ((collectionView.bounds.width - totalSpace) / CGFloat (numberOfItemsPerRow)) - debe eliminar la conversión a Int, porque en algunos casos
crea
14

Aquí está el código de trabajo para Swift 3, para tener un diseño de dos columnas:

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)
}

No dude en cambiar "nbCol" al número de columnas que desee.

fraxool
fuente
¿Qué hay de malo en todas las soluciones cuando las inserciones son (1.0,1.0,1.0,1.0), el diseño se convierte en 1 columna en este caso?
rd_
@fraxool Gran respuesta, pero si volteo el dispositivo, el diseño se restablece ... ¿Alguna solución para eso?
Maksim Kniazev
8

Si eres vago usando delegate.

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)
        }
    }
}

PD: También debería llamarse después de la rotación

HotJard
fuente
2
También funciona en Swift 4.0 ... después de los cambios en las dos últimas líneas: let width = floor ((frame.width - insetSpace - innerSpace) / itemsInRow) layout.itemSize = CGSize (width: width, height: width)
g212gs
8

Actualizado a Swift 3:

En lugar del diseño de flujo, prefiero usar un diseño personalizado para un número de columna y un número de fila específicos. Porque:

  1. Se puede arrastrar horizontalmente si el número de columna es muy grande.
  2. Es más aceptable lógicamente debido al uso de columnas y filas.

Celda normal y celda de encabezado: (agregue UILabel como IBOutlet a su xib):

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

Diseño personalizado:

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

CollectionView:

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)")
}
}

Utilice CollectionView desde 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()
}
}

¡Finalmente puedes tener CollectionView elegante!

ingrese la descripción de la imagen aquí

Brian Como Apple
fuente
el problema de tomar esta solución es su altura y ancho estáticos
rd_
2
El diseño @rd_Custom significa que puedes hacer lo que quieras. Mi código solo muestra el caso más simple ......
brianLikeApple
7

Debido a que UICollectionViewes tan flexible, hay varias formas de cambiar el número de columnas, según el tipo de diseño que utilice.

El UICollectionViewFlowLayout(que es probablemente con lo que está trabajando) no especifica un número de columnas directamente (porque depende del tamaño / orientación de la vista). La forma más sencilla de cambiarlo sería establecer la itemSizepropiedad y / o minimumInteritemSpacing/ minimumLineSpacing.

omz
fuente
4

Swift 3.0. Funciona para direcciones de desplazamiento horizontal y vertical y espaciado variable

Especifique el número de columnas

let numberOfColumns: CGFloat = 3

Configurar flowLayoutpara renderizar especificadonumberOfColumns

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)
}
Jože Ws
fuente
Todas esas respuestas largas en todas partes y esto funciona con 5 líneas de código :)
J. Doe
1
Buen intento. Pero esto no funcionará si usa un sectionInset en collectionViewLayout. Para arreglar esto, (1) calcule el ancho disponible tomando el ancho de la celda y restando flowLayout.sectionInset.left y flowLayout.sectionInset.right de él. (2) Donde calcule el cellWidth, reemplace 'view.frame.width' con el valor calculado en (1). Además, recuerde que el marco de la vista solo se finalizará después de la carga y puede cambiar, por lo que tendrá que reconstruir el diseño según sea necesario.
Womble
4

Actualizado a Swift 5+ iOS 13

Collectionview El tamaño estimado no debe ser ninguno

ingrese la descripción de la imagen aquí

Declarar margen para celda

let margin: CGFloat = 10

En viewDidLoad configurar 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)

Método 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)
}
Sandeep Maurya
fuente
2

Se trata de un diseño que quieres dibujar. Puede crear una clase personalizada heredando de UICollectionViewFlowLayout. Actualmente no existe ningún método directo para configurar columnas. Si desea lograr este tipo de funcionalidad, debe hacerlo manualmente. Debe manejarlo en su clase de diseño de flujo personalizado.

Ahora surgirá la pregunta de cómo lo harás. Si no desea alterar el marco de la celda, puede ajustar

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

Otra forma es proporcionarle sus propias posiciones de celdas. Anulando los siguientes dos métodos, que se llamarán durante la formación de su diseño.

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

UICollectionViewLayoutAttributes es una clase que se ocupará de la posición de la celda, el marco, Zindex, etc.

Swapnil
fuente
2

Esta respuesta es para swift 3.0 ->

primero se ajusta al protocolo:

 UICollectionViewDelegateFlowLayout

luego agregue estos métodos:

    //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
    }
MhmdRizk
fuente
1

Solo quería agregar a la respuesta n. ° 2 de Imanou Petit. Para garantizar que los márgenes sean exactos independientemente del ancho de la pantalla, utilizo un solucionador iterativo con un margen deseado y el número de columnas como entradas. También agregué una bandera direccional sobre dónde se compararán sus márgenes finales con su objetivo.

El solucionador iterativo se muestra a continuación y devuelve cellWidth y margin.

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)
}

Yo lo llamo así:

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)
    ...
}

Luego aplico los valores de cellWidth y margin al diseño de flujo como tal:

extension MyCollectionController : UICollectionViewDelegateFlowLayout

{func collectionView (_ collectionView: UICollectionView, diseño 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
}

}

Espero que esto ayude. Probablemente haya una forma más fácil de garantizar que los márgenes sean exactos, pero este es un método. Además, este código no se ha probado para dispositivos que permiten la rotación de vistas de colección.

Gracias,

Jan-Michael Tressler
fuente
0

la solución perfecta es usar UICollectionViewDelegateFlowLayout, pero puede calcular fácilmente el ancho de la celda y dividirlo en el número deseado de columnas que desee

lo complicado es hacer el ancho sin fracción

(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;}
Amr enojado
fuente
0

Hice un diseño de colección.

Para hacer visible el separador, establezca el color de fondo de la vista de colección en gris. Una fila por sección.

Uso:

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

Diseño:

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]
}
}
Chen Jiling
fuente
0

Prueba esto, funciona perfecto para mí

Siga los pasos a continuación:

1. Defina valores en el archivo .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

O

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

2.Agregue el código en los métodos de fuente de datos de diseño de vista de colección como se muestra a continuación,

 #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);
}

Espero que esto sea de ayuda para alguien.

Jaywant Khedkar
fuente
0

Primero agregue UICollectionViewDelegateFlowLayout como protocolo.

Luego:

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)
}
Mahdi Moqadasi
fuente