UICollectionView is a space that appears very frequently in the project, and it can flexibly show various layouts. Usually, the commonly used horizontal, vertical and grid effects can be basically completed by using the Layout provided to us by the system. Recently, a custom Layout requirement has been made, and the process is briefly recorded here, and some simple use of DragAndDrop is also mentioned later.

  • Prepare knowledge
  • Basic Layout
  • Custom Layout
  • Drag And Drop

Prepare knowledge

The core concepts of UICollectionView are as follows:

  • Layout (Layout)
  • Data Source
  • Delegate (agent)

UICollectionViewLayout

An abstract base class that is used to generate UICollectionView layout information, each cell layout information managed by UICollectionViewLayoutAttributes.

System provides us with a fluid layout class – UICollectionViewFlowLayout, we can use this definition we common layout. It defines layout orientation, cell size, spacing, and more.

UICollectionViewDataSource

A protocol-compliant delegate provides the UICollectionView with various information about the data: grouping, number of cells, contents of each Cell, and so on.

Common proxy methods:

// Grouping information optional func numberOfSections(inCollectionView: UICollectionView) -> Int // The number of cells in each group UICollectionView, numberOfItemsInSection Section: Int) -> Int UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell // Returns UICollectionView Header or Footer optional func collectionView(_) collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableViewCopy the code

UICollectionViewDelegate

UICollectionViewDelegate provides us with events for cell clicks and display events for views.

Common proxy methods:

// Cell click the event optional func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) // the cell view willDisplay optional func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell,forItemAt indexPath: IndexPath)
Copy the code

Basic Layout

  • In preparation, in the following examples, the data source proxy method is also implemented as follows:
extension LayoutViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 14
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell".for: indexPath) as! ImageCell
        
        let random = dataSource[indexPath.row]
        
        cell.showImage.image = UIImage(named: "\(random)")

        return cell
    }
}
Copy the code
  • Set the Basic Layout Layout
letLayout = UICollectionViewFlowLayout () / / vertical scroll layout scrollDirection =. Vertical/layout/cell size. The itemSize = CGSize(width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.width) self.collectionView.collectionViewLayout = layoutCopy the code

This is the most basic use, not to repeat, basic use if you do not understand can go to see the official documentation.

Custom Layout

The Layout provided by the system is just a simple streaming Layout. When we need some special Layout, we can only inherit from UICollectionViewLayout to define the Layout.

A custom Layout

class CustomCollectionViewLayout: UICollectionViewLayout {
    private var itemWidth: CGFloat = 0   // cell 宽度
    private var itemHeight: CGFloat = 0  // cell 高度

    private var currentX: CGFloat = 0    // 当前 x 坐标
    private var currentY: CGFloat = 0    // 当前 y 坐标
	
	 // 存储每个 cell 的布局信息
    private var attrubutesArray = [UICollectionViewLayoutAttributes]()
}
Copy the code

Layout related preparation

/ / layout related preparation / / / / the cache for each invalidateLayout calls UICollectionViewLayoutAttributes / / computing collectionViewContentSize override funcprepare() {
    super.prepare()

    guard letcount = self.collectionView? .numberOfItems(inSection: 0) else { return} // Get the attributes of each item and store themfor i in0.. <count {let indexPath = IndexPath(row: i, section: 0)

        guard let attributes = self.layoutAttributesForItem(at: indexPath) else { break }
        attrubutesArray.append(attributes)
    }
}
Copy the code

Provides layout property objects

/ / layout objects override func layoutAttributesForElements (in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    returnattrubutesArray } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {// get the widthletcontentWidth = self.collectionView! .frame.size. Width // Create an item attribute with indexpathlet temp = UICollectionViewLayoutAttributes(forCellWith: indexPath) // Calculates the width and height of the itemlet typeOne: (Int) -> () = { index in
        self.itemWidth = contentWidth / 2
        self.itemHeight = self.itemWidth
        
        temp.frame = CGRect(x: self.currentX, y: self.currentY, width: self.itemWidth, height: self.itemHeight)
        
        if index == 0 {
            self.currentX += self.itemWidth
        } else {
            self.currentX += self.itemWidth
            self.currentY += self.itemHeight
        }
    }
    
    let typeTwo: (Int) -> () = { index in
        ifIndex == 2 {self.itemWidth = (contentWidth * 2) / 3.0 self.itemHeight = (self.itemWidth * 2) / 3.0 temp.frame = CGRect(x: self.currentX, y: self.currentY, width: self.itemWidth, height: self.itemHeight) self.currentX += self.itemWidth }else{self.itemWidth = contentWidth / 3.0 self.itemHeight = (self.itemWidth * 2) / 3.0 temp.frame = CGRect(x: self.currentX, y: self.currentY, width: self.itemWidth, height: self.itemHeight) self.currentY += self.itemHeightif index == 4 {
                self.currentX = 0
            }
        }
    }
    
    let typeThree: (Int) -> () = { index in
        ifIndex == 7 {self.itemWidth = (contentWidth * 2) / 3.0 self.itemHeight = (self.itemWidth * 2) / 3.0 temp.frame = CGRect(x: self.currentX, y: self.currentY, width: self.itemWidth, height: self.itemHeight) self.currentX += self.itemWidth self.currentY += self.itemHeight }else{self.itemWidth = contentWidth / 3.0 self.itemHeight = (self.itemWidth * 2) / 3.0 temp.frame = CGRect(x: self.currentX, y: self.currentY, width: self.itemWidth, height: self.itemHeight)if index == 5 {
                self.currentY += self.itemHeight
            } else{self.currentX += self.itemWidth self.currenty -= self.itemheight}} Display type of. // In a real project, the data is evaluated using a Block or Delegate in the controller based on the value of indexPath and the display type is determined based on the result. // Custom Layout calculates the display position based on the display type.let judgeNum = indexPath.row % 8
    
    switch judgeNum {
    case 0, 1:
        typeOne(judgeNum)
    case 2, 3, 4:
        typeTwo(judgeNum)
    case 5, 6, 7:
        typeThree(judgeNum)
    default:
        break} // When currentX reaches the far right of the screen, the currentX line breaks to the next line.if currentX >= contentWidth {
        currentX = 0
    }

    return temp
}
Copy the code

Scroll range

/ / scroll range override var collectionViewContentSize: CGSize {return CGSize(width: UIScreen.main.bounds.size.width, height: currentY + 20)
}
Copy the code

Boundary changes

// Handles boundary modifications in custom layouts // returnstrueOverride func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    return true
}
Copy the code

use

let layout = CustomCollectionViewLayout()
self.collectionView.collectionViewLayout = layout
Copy the code

Drag And Drop

Before, in order to drag the cell in the UICollectionView, you had to define your own gesture for dragging, In iOS11 increased UICollectionViewDragDelegate and UICollectionViewDropDelegate two protocols convenient we do this.

Turn on the drag and drop gesture to set up the proxy

// Enable the drag and drop gesture to set the proxy. self.collectionView.dragInteractionEnabled =true
self.collectionView.dragDelegate = self
self.collectionView.dropDelegate = self
Copy the code

To implement proxy

UICollectionViewDragDelegate

Only one UICollectionViewDragDelegate method must be implemented.

  1. Create one or more NSitemProviders and use nSitemProviders to pass the contents of the collection view item.
  2. Encapsulate each NSItemProvider in a corresponding UIDragItem object.
  3. Returns the dragItem.
extension LayoutViewController: UICollectionViewDragDelegate {
	func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
	
	    let imageName = self.dataSource[indexPath.row]
	
	    let image = UIImage(named: imageName)!
	
	    let provider = NSItemProvider(object: image)
	
	    let dragItem = UIDragItem(itemProvider: provider)
	
	    return [dragItem]
	}
}

Copy the code

If you want to support multiple drags at once, you also need to implement the following method, which follows roughly the same steps as above.

func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem]
Copy the code

If you want to customize the drag view style after dragging, you can implement:

/* Customize the appearance of the cell during dragging. If you return nil, it's rendered in cell style. */ func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt indexPath: IndexPath) -> UIDragPreviewParameters? {return nil
}
Copy the code

UICollectionViewDropDelegate

Implementation at the top of the agreement, let’s drag has been achieved in the process, but now when open, the cell will not as we expected to reach the corresponding position, at this point, the need to implement UICollectionViewDropDelegate agreement, to deal with the receiving drag content.

extension LayoutViewController: UICollectionViewDropDelegate {/ / return a UICollectionViewDropProposal object, told how the cell into a new location. func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {ifsession.localDragSession ! = nil {// Drag gesture from the same app.return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
        } else{// Drag gestures from other apps.returnUICollectionViewDropProposal(operation: .copy, intent: InsertAtDestinationIndexPath)}} / * when finger leave the screen UICollectionView will call. This method must be implemented to receive dragged data. */ func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {let destinationIndexPath = coordinator.destinationIndexPath ?? IndexPath(item: 0, section: 0)

        switch coordinator.proposal.operation {
        case .move:
            let items = coordinator.items

            if items.contains(where: { $0.sourceIndexPath ! = nil }) {if items.count == 1, letItem = items.first {// Find the data for the operationletTemp = dataSource[item.sourceindexpath!.row] // the dataSource deletes the operation data from its original location and inserts it into a new location. dataSource.remove(at: item.sourceIndexPath! .row) dataSource. Insert (temp, at: destinationIndexpath.row) // Merge collectionView operations into one animation. collectionView.performBatchUpdates({ collectionView.deleteItems(at: [item.sourceIndexPath!] ) collectionView.insertItems(at: [destinationIndexPath]) }) coordinator.drop(item.dragItem, toItemAt: destinationIndexPath) } } default:return}}}Copy the code

There are many protocols for Drag and Drap that can be used. We have not used them. This is just a basic effect.

The Demo in this

Drag and Drop with Collection and Table View

A Tour Of UICollectionView