如何删除 UICollectionView Flow 布局大小中的单元格空间?

Posted

技术标签:

【中文标题】如何删除 UICollectionView Flow 布局大小中的单元格空间?【英文标题】:How to remove cell space in UICollectionView Flow layout size? 【发布时间】:2020-05-05 04:43:07 【问题描述】:

我正在将集合视图布局自定义为 3 列 [左侧 2 个单元格和右侧 1 个纵向单元格]。我无法移除 let side 2 单元格的顶部空间。

代码:

import UIKit

extension UICollectionView

    func getSize(noOfCellsInRow: Int, isPotrait: Bool = true)->CGSize


        let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout

        let totalSpace = flowLayout.sectionInset.left
                 + flowLayout.sectionInset.right
                 + (flowLayout.minimumInteritemSpacing * CGFloat(noOfCellsInRow - 1))

        let size = Int((self.bounds.width - totalSpace) / CGFloat(noOfCellsInRow))

        return CGSize(width: size, height: isPotrait ? size+size : size)
    


class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
    return 3
  

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
    cell.contentView.backgroundColor = .red
    return cell
  

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

      if indexPath.row == 1
         return collectionView.getSize(noOfCellsInRow: 2,isPotrait: true)
      else
        return collectionView.getSize(noOfCellsInRow: 2,isPotrait: false)
     

    

对应代码的输出

预期输出

我怎样才能让它工作?任何想法都会对我有很大帮助。提前谢谢...

【问题讨论】:

【参考方案1】:

我已经定制了 Apple 的 MosaicLayout 示例以适合您的预期输出。

这是自定义 UICollectionViewLayout,添加了 twoFiftyFifty 作为此段样式。

enum MosaicSegmentStyle 
    case twoFiftyFifty
    case fullWidth
    case fiftyFifty
    case twoThirdsOneThird
    case oneThirdTwoThirds


class MosaicLayout: UICollectionViewLayout 

    var contentBounds = CGRect.zero
    var cachedAttributes = [UICollectionViewLayoutAttributes]()

    /// - Tag: PrepareMosaicLayout
    override func prepare() 
        super.prepare()

        guard let collectionView = collectionView else  return 

        // Reset cached information.
        cachedAttributes.removeAll()
        contentBounds = CGRect(origin: .zero, size: collectionView.bounds.size)

        // For every item in the collection view:
        //  - Prepare the attributes.
        //  - Store attributes in the cachedAttributes array.
        //  - Combine contentBounds with attributes.frame.
        let count = collectionView.numberOfItems(inSection: 0)

        var currentIndex = 0
        var segment: MosaicSegmentStyle = .fiftyFifty
        var lastFrame: CGRect = .zero

        let cvWidth = collectionView.bounds.size.width

        while currentIndex < count 
            let segmentFrame = CGRect(x: 0, y: lastFrame.maxY + 1.0, width: cvWidth, height: (self.collectionView?.frame.width)!)

            var segmentRects = [CGRect]()
            switch segment 
            case .twoFiftyFifty:
                let horizontalSlices = segmentFrame.dividedIntegral(fraction:0.5, from: .minXEdge)
                let verticalSlices = horizontalSlices.first.dividedIntegral(fraction: 0.5, from: .minYEdge)
                segmentRects = [verticalSlices.first, verticalSlices.second, horizontalSlices.second]

            case .fullWidth:
                segmentRects = [segmentFrame]

            case .fiftyFifty:
                let horizontalSlices = segmentFrame.dividedIntegral(fraction: 0.5, from: .minXEdge)
                segmentRects = [horizontalSlices.first, horizontalSlices.second]

            case .twoThirdsOneThird:
                let horizontalSlices = segmentFrame.dividedIntegral(fraction: (2.0 / 3.0), from: .minXEdge)
                let verticalSlices = horizontalSlices.second.dividedIntegral(fraction: 0.5, from: .minYEdge)
                segmentRects = [horizontalSlices.first, verticalSlices.first, verticalSlices.second]

            case .oneThirdTwoThirds:
                let horizontalSlices = segmentFrame.dividedIntegral(fraction: (1.0 / 3.0), from: .minXEdge)
                let verticalSlices = horizontalSlices.first.dividedIntegral(fraction: 0.5, from: .minYEdge)
                segmentRects = [verticalSlices.first, verticalSlices.second, horizontalSlices.second]
            

            // Create and cache layout attributes for calculated frames.
            for rect in segmentRects 
                let attributes = UICollectionViewLayoutAttributes(forCellWith: IndexPath(item: currentIndex, section: 0))
                attributes.frame = rect

                cachedAttributes.append(attributes)

// contentBounds = contentBounds.union(lastFrame) contentBounds = contentBounds.union(rect)

                currentIndex += 1
                lastFrame = rect
            

//            // Determine the next segment style.
//            switch count - currentIndex 
//            case 1:
//                segment = .fullWidth
//            case 2:
//                segment = .fiftyFifty
//            default:
//                switch segment 
//                case .fullWidth:
//                    segment = .fiftyFifty
//                case .fiftyFifty:
//                    segment = .twoThirdsOneThird
//                case .twoThirdsOneThird:
//                    segment = .oneThirdTwoThirds
//                case .oneThirdTwoThirds:
//                    segment = .fiftyFifty
//                
//            
        
    

    /// - Tag: CollectionViewContentSize
    override var collectionViewContentSize: CGSize 
        return contentBounds.size
    

    /// - Tag: ShouldInvalidateLayout
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool 
        guard let collectionView = collectionView else  return false 
        return !newBounds.size.equalTo(collectionView.bounds.size)
    

    /// - Tag: LayoutAttributesForItem
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
        return cachedAttributes[indexPath.item]
    

    /// - Tag: LayoutAttributesForElements
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        var attributesArray = [UICollectionViewLayoutAttributes]()

        // Find any cell that sits within the query rect.
        guard let lastIndex = cachedAttributes.indices.last,
              let firstMatchIndex = binSearch(rect, start: 0, end: lastIndex) else  return attributesArray 

        // Starting from the match, loop up and down through the array until all the attributes
        // have been added within the query rect.
        for attributes in cachedAttributes[..<firstMatchIndex].reversed() 
            guard attributes.frame.maxY >= rect.minY else  break 
            attributesArray.append(attributes)
        

        for attributes in cachedAttributes[firstMatchIndex...] 
            guard attributes.frame.minY <= rect.maxY else  break 
            attributesArray.append(attributes)
        

        return attributesArray
    

    // Perform a binary search on the cached attributes array.
    func binSearch(_ rect: CGRect, start: Int, end: Int) -> Int? 
        if end < start  return nil 

        let mid = (start + end) / 2
        let attr = cachedAttributes[mid]

        if attr.frame.intersects(rect) 
            return mid
         else 
            if attr.frame.maxY < rect.minY 
                return binSearch(rect, start: (mid + 1), end: end)
             else 
                return binSearch(rect, start: start, end: (mid - 1))
            
        
    

divideIntegral 扩展:

extension CGRect 
    func dividedIntegral(fraction: CGFloat, from fromEdge: CGRectEdge) -> (first: CGRect, second: CGRect) 
        let dimension: CGFloat

        switch fromEdge 
        case .minXEdge, .maxXEdge:
            dimension = self.size.width
        case .minYEdge, .maxYEdge:
            dimension = self.size.height
        

        let distance = (dimension * fraction).rounded(.up)
        var slices = self.divided(atDistance: distance, from: fromEdge)

        switch fromEdge 
        case .minXEdge, .maxXEdge:
            slices.remainder.origin.x += 1
            slices.remainder.size.width -= 1
        case .minYEdge, .maxYEdge:
            slices.remainder.origin.y += 1
            slices.remainder.size.height -= 1
        

        return (first: slices.slice, second: slices.remainder)
    

MosaicCell 类:

class MosaicCell: UICollectionViewCell 
    static let identifer = "kMosaicCollectionViewCell"

    var imageView = UIImageView()
    var assetIdentifier: String?

    override init(frame: CGRect) 
        super.init(frame: frame)
        self.clipsToBounds = true
        self.autoresizesSubviews = true

        imageView.frame = self.bounds
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.addSubview(imageView)

        // Use a random background color.
        let redColor = CGFloat(arc4random_uniform(255)) / 255.0
        let greenColor = CGFloat(arc4random_uniform(255)) / 255.0
        let blueColor = CGFloat(arc4random_uniform(255)) / 255.0
        self.backgroundColor = UIColor(red: redColor, green: greenColor, blue: blueColor, alpha: 1.0)
    

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

    override func prepareForReuse() 
        super.prepareForReuse()
        imageView.image = nil
        assetIdentifier = nil
    

你的 ViewController 类:

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout 

    override func viewDidLoad() 
        let mosaicLayout = MosaicLayout()
        collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: mosaicLayout)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.alwaysBounceVertical = true
        collectionView.indicatorStyle = .white
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(MosaicCell.self, forCellWithReuseIdentifier: MosaicCell.identifer)
    

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 3
    

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MosaicCell.identifer, for: indexPath)
        cell.contentView.backgroundColor = .red
        return cell
    

这是 Apple 示例的链接:https://developer.apple.com/documentation/uikit/uicollectionview/customizing_collection_view_layouts

您需要根据需要配置以后的细分。

【讨论】:

很高兴为您提供帮助,@MountAin! 一个帮助,我不能做 3 行。我最多只能制作 2 行。我尝试了部分但不起作用。你能帮我制作 3 行 fullWidth 吗?这是视频streamable.com/b2yac5 这是我在代码segmentRects = [ CGRect(x: 0, y: lastFrame.maxY + 1.0, width: cvWidth, height: cvWidth/1.5)]中修改的内容 第三行不在范围内 @MountAin,您在 Apple 示例代码中发现了一个错误。做得好!我已经更新了我的答案来解决这个问题。 contentBounds = contentBounds.union(lastFrame) 没有拾取最后一个单元格,所以我将其更改为 contentBounds = contentBounds.union(rect)。我已经确认它对我有用。请让我知道它是否也为您解决了问题。【参考方案2】:

您需要自定义自己的自定义UICollectionViewLayout,如PinterestLayout,系统内置UICollectionViewFlowLayout仅适用于网格布局。

【讨论】:

好的,谢谢。我会试试的

以上是关于如何删除 UICollectionView Flow 布局大小中的单元格空间?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 UICollectionView Image 一张一张删除

UICollectionView如何删除单元格(相当于commitEditingStyle)?

如何从 UICollectionView 中正确删除单元格?

如何删除 UICollectionView 单元格中的单个对象?

如何删除所有项目并将新项目添加到 UICollectionView?

如何快速从 UICollectionView 中的 UIView 中删除焦点阴影?