另一个集合视图中的集合视图:节标题或单元格?

时间:2018-08-18 07:39:59

标签: ios swift uicollectionview uicollectionviewcell uicollectionreusableview

我已经有一个UICollectionView,它可以垂直滚动并显示一组自定义的UICollectionViewCell,它们的大小是固定的。

现在我被要求在所有其他单元格的顶部显示另一个UICollectionView,该单元格应该水平滚动,并且其单元格大小是动态的(我只会在异步网络调用完成后知道大小)。此外,可能不一定总是要显示此内部收集视图(取决于从网络调用接收到的数据),但如果显示,则应该仅显示一次(在一切)。

我的问题是:应对第二个内部集合视图的最佳方法应该是什么?我应该将其作为其他类型的单元格添加到外部视图控制器中,还是作为节头添加? 也许另一种布局方法会更好吗?

编辑:更多注意事项:

  • 当我要显示内部集合视图时,我需要对其进行动画处理
  • 整个内容应垂直滚动,此内部集合视图不应停留在屏幕顶部

1 个答案:

答案 0 :(得分:2)

  

“应对第二个内部集合视图的最佳方法应该是什么?”

     

“我应该将它添加为外部视图控制器中的一种不同类型的单元格,还是添加为节标题?”

     

“当我要显示内部集合视图时,需要对其进行动画处理”

     

“整个事物应该是垂直滚动的,这个内部集合视图不应停留在屏幕顶部。”

有时候退后一步,写下您的需求,独立考虑每个需求,

1)CollectionView的第一个单元格应水平滚动。

2)第一个单元格应垂直滚动到屏幕上方。

CollectionView的第一个单元格需要包含一个CollectionView本身。

3a)CollectionView的其他单元格具有静态大小。

3b)CollectionViews的第一个单元格具有动态大小。

需要两个单元格类,或者一个具有动态约束和子视图的单元格类。

4)应该对CollectionView的“头”单元进行动画处理。

第一个单元格的CollectionView必须是其动态单元格的委托。 (动画发生在cellForItemAt indexPath中)

请记住,UICollectionView是独立视图。 UICollectionViewController本质上是包含UIViewController的{​​{1}},UICollectionViewDelegateUICollectionViewDataSource。就像任何UIView一样,您可以继承UICollectionView并将其添加到另一个视图的子视图中,例如UICollectionView。这样,您可以将集合视图添加到单元格,并将单元格添加到该嵌套的集合视图。您还可以允许嵌套的集合视图处理UICollectionViewCellUICollectionViewDelegate中的所有委托方法,从本质上使其模块化和可重用。您可以在UICollectionViewDataSource方法内传递要显示在嵌套UICollectionView的每个单元格中的数据,并允许该类处理动画和设置。到目前为止,这是最好的方法,不仅可以重用,而且可以提高性能,尤其是在以编程方式创建视图时。

在下面的示例中,我有一个名为convenience init的ViewController,它将作为所有其他视图的视图控制器。

我也有两个CollectionView,UICollectionViewControllerParentCollectionView。 ParentCollectionView是UICollectionView的空实现。我可以使用HorizontalCollectionView中的collectionView,但是由于我希望将其完全模块化,因此稍后我将把ParentCollectionView分配给ViewController的collectionView。 ParentCollectionView将处理视图中的所有单元格静态单元格,包括一个包含Horizo​​ntalCollectionView的单元格。 Horizo​​ntalCollectionView将是在其便捷初始化程序中传递给它的所有“单元对象”(您的数据模型)的委托和数据源。也就是说,Horizo​​ntalCollectionView将管理自己的单元格,这样我们的UICollectionViewController就不会发胖。

除了两个CollectionViews和一个UICollectionViewController,我还有两个UICollectionViewController类,一个是静态大小,另一个是动态(随机生成的CGSize)。为了易于使用,我还有一个扩展名,该扩展名返回类名作为标识符,我不喜欢将硬编码的字符串用于可重复使用的单元格。这些单元格类别并没有什么不同,可以使用同一单元格并更改UICollectionViewCellsizeForItemAt indexPath中的单元格大小,但是为了演示起见,我要说它们是完全不同的单元格需要完全不同的数据模型。

现在,我们不希望让ParentCollectionView中的第一个单元出队,这是因为该单元将从内存中删除并重新放入队列以供重用,并且我们当然不希望我们的Horizo​​ntalCollectionView随机弹出。为避免这种情况,我们需要注册我们的StaticCollectionViewCell和仅将使用一次的通用单元,因为我添加了一个扩展,该扩展为我提供了该单元的类名,所以我将只使用cellForItemAt indexPath作为标识符。 / p>

我确定您可以很轻松地解决其余问题,这是我的完整实现:​​

ViewController.swift

UICollectionViewCell

ParentCollectionView.swift

import UIKit

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    // Programmically add our empty / custom ParentCollectionView
    let parentCollectionView: ParentCollectionView = {
        let layout = UICollectionViewFlowLayout()
        let cv = ParentCollectionView(frame: .zero, collectionViewLayout: layout)
        cv.translatesAutoresizingMaskIntoConstraints = false
        return cv
    }()


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        setup()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func setup() {
        // Assign this viewcontroller's collection view to our own custom one.
        self.collectionView = parentCollectionView

        // Set delegate and register Static and empty cells for later use.
        parentCollectionView.delegate = self
        parentCollectionView.register(StaticCollectionViewCell.self, forCellWithReuseIdentifier: StaticCollectionViewCell.identifier)
        parentCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: UICollectionViewCell.identifier)

        // Add simple Contraints
        let guide = self.view.safeAreaLayoutGuide

        parentCollectionView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
        parentCollectionView.leftAnchor.constraint(equalTo: guide.leftAnchor).isActive = true
        parentCollectionView.rightAnchor.constraint(equalTo: guide.rightAnchor).isActive = true
        parentCollectionView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
    }

    // MARK: - CollectionView

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        // Erroneous Data from your network call, data should be a class property.
        let data = Array.init(repeating: "0", count: 12)

        // Skip if we dont have any data to show for the first row.
        if (indexPath.row == 0 && data.count > 0) {

            // Create a new empty cell for reuse, this cell will only be used for the frist cell.
            let cell = parentCollectionView.dequeueReusableCell(withReuseIdentifier: UICollectionViewCell.identifier, for: IndexPath(row: 0, section: 0))

            // Programmically Create a Horizontal Collection View add to the Cell
            let horizontalView:HorizontalCollectionView = {
                // Only Flow Layout has scroll direction
                let layout = UICollectionViewFlowLayout()
                layout.scrollDirection = .horizontal
                // Init with Data.
                let hr = HorizontalCollectionView(frame: cell.frame, collectionViewLayout: layout, data: data)
                return hr
            }()
            // Adjust cell's frame and add it as a subview.
            cell.addSubview(horizontalView)
            return cell
        }

        // In all other cases, just create a regular cell.
        let cell = parentCollectionView.dequeueReusableCell(withReuseIdentifier: StaticCollectionViewCell.identifier, for: indexPath)
        // Update Cell.

        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // 30 sounds like enough.
        return 30
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        //If you need your first row to be bigger return a larger size.
        if (indexPath.row == 0) {
            return StaticCollectionViewCell.size()
        }


        return StaticCollectionViewCell.size()
    }

}

Horizo​​ntalCollectionView.swift

import UIKit

class ParentCollectionView: UICollectionView {

    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
    }

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

DynamicCollectionViewCell.swift

import Foundation
import UIKit

class HorizontalCollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    // Your Data Model Objects
    var data:[Any]?

    // Required
    override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
        super.init(frame: frame, collectionViewLayout: layout)
    }

    convenience init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout, data:[Any]) {
        self.init(frame: frame, collectionViewLayout: layout)

        // Set These
        self.delegate = self
        self.dataSource = self
        self.data = data
        // Setup Subviews.
        setup()
    }

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

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // return zero if we have no data to show.
        guard let count = self.data?.count else {
            return 0
        }
        return count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = self.dequeueReusableCell(withReuseIdentifier: DynamicCollectionViewCell.identifier, for: indexPath)
        // Do Some fancy Animation when scrolling.
        let endingFrame = cell.frame
        let transitionalTranslation = self.panGestureRecognizer.translation(in: self.superview)
        if (transitionalTranslation.x > 0)  {
            cell.frame = CGRect(x: endingFrame.origin.x - 200, y: endingFrame.origin.y - 100, width: 0, height: 0)
        } else {
            cell.frame = CGRect(x: endingFrame.origin.x + 200, y: endingFrame.origin.y - 100, width: 0, height: 0)
        }

        UIView.animate(withDuration: 1.2) {
            cell.frame = endingFrame
        }

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        // See DynamicCollectionViewCell size method, generate a random size.
        return DynamicCollectionViewCell.size()
    }

    func setup(){
        self.backgroundColor = UIColor.white
        self.register(DynamicCollectionViewCell.self, forCellWithReuseIdentifier: DynamicCollectionViewCell.identifier)
        // Must call reload, Data is not loaded unless explicitly told to.
        // Must run on Main thread this class is still initalizing.
        DispatchQueue.main.async {
            self.reloadData()
        }
    }

}

StaticCollectionViewCell.swift

import Foundation
import UIKit

class DynamicCollectionViewCell: UICollectionViewCell {

    /// Get the Size of the Cell
    /// Will generate a random width element no less than 100 and no greater than 350
    /// - Returns: CGFloat
    class func size() -> CGSize {
        let width = 100 + Double(arc4random_uniform(250))
        return CGSize(width: width, height: 100.0)
    }

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

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

    func setup() {
        self.backgroundColor = UIColor.green
    }
}

CollectionViewCellExtentions.swift

import Foundation
import UIKit

class StaticCollectionViewCell: UICollectionViewCell {

    /// Get the Size of the Cell
    /// - Returns: CGFloat
    class func size() -> CGSize {
        return CGSize(width: UIScreen.main.bounds.width, height: 150.0)
    }

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

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

    func setup() {
        self.backgroundColor = UIColor.red
    }


}

Nested Collection Views