我已经有一个UICollectionView
,它可以垂直滚动并显示一组自定义的UICollectionViewCell
,它们的大小是固定的。
现在我被要求在所有其他单元格的顶部显示另一个UICollectionView
,该单元格应该水平滚动,并且其单元格大小是动态的(我只会在异步网络调用完成后知道大小)。此外,可能不一定总是要显示此内部收集视图(取决于从网络调用接收到的数据),但如果显示,则应该仅显示一次(在一切)。
我的问题是:应对第二个内部集合视图的最佳方法应该是什么?我应该将其作为其他类型的单元格添加到外部视图控制器中,还是作为节头添加? 也许另一种布局方法会更好吗?
编辑:更多注意事项:
答案 0 :(得分:2)
“应对第二个内部集合视图的最佳方法应该是什么?”
“我应该将它添加为外部视图控制器中的一种不同类型的单元格,还是添加为节标题?”
“当我要显示内部集合视图时,需要对其进行动画处理”
“整个事物应该是垂直滚动的,这个内部集合视图不应停留在屏幕顶部。”
有时候退后一步,写下您的需求,独立考虑每个需求,
1)CollectionView的第一个单元格应水平滚动。
2)第一个单元格应垂直滚动到屏幕上方。
CollectionView的第一个单元格需要包含一个CollectionView本身。
3a)CollectionView的其他单元格具有静态大小。
3b)CollectionViews的第一个单元格具有动态大小。
需要两个单元格类,或者一个具有动态约束和子视图的单元格类。
4)应该对CollectionView的“头”单元进行动画处理。
第一个单元格的CollectionView必须是其动态单元格的委托。 (动画发生在cellForItemAt indexPath
中)
请记住,UICollectionView
是独立视图。 UICollectionViewController
本质上是包含UIViewController
的{{1}},UICollectionViewDelegate
和UICollectionViewDataSource
。就像任何UIView一样,您可以继承UICollectionView
并将其添加到另一个视图的子视图中,例如UICollectionView
。这样,您可以将集合视图添加到单元格,并将单元格添加到该嵌套的集合视图。您还可以允许嵌套的集合视图处理UICollectionViewCell
和UICollectionViewDelegate
中的所有委托方法,从本质上使其模块化和可重用。您可以在UICollectionViewDataSource
方法内传递要显示在嵌套UICollectionView
的每个单元格中的数据,并允许该类处理动画和设置。到目前为止,这是最好的方法,不仅可以重用,而且可以提高性能,尤其是在以编程方式创建视图时。
在下面的示例中,我有一个名为convenience init
的ViewController,它将作为所有其他视图的视图控制器。
我也有两个CollectionView,UICollectionViewController
和ParentCollectionView
。 ParentCollectionView是UICollectionView的空实现。我可以使用HorizontalCollectionView
中的collectionView
,但是由于我希望将其完全模块化,因此稍后我将把ParentCollectionView分配给ViewController的collectionView。 ParentCollectionView将处理视图中的所有单元格静态单元格,包括一个包含HorizontalCollectionView的单元格。 HorizontalCollectionView将是在其便捷初始化程序中传递给它的所有“单元对象”(您的数据模型)的委托和数据源。也就是说,HorizontalCollectionView将管理自己的单元格,这样我们的UICollectionViewController
就不会发胖。
除了两个CollectionViews和一个UICollectionViewController,我还有两个UICollectionViewController
类,一个是静态大小,另一个是动态(随机生成的CGSize)。为了易于使用,我还有一个扩展名,该扩展名返回类名作为标识符,我不喜欢将硬编码的字符串用于可重复使用的单元格。这些单元格类别并没有什么不同,可以使用同一单元格并更改UICollectionViewCell
或sizeForItemAt indexPath
中的单元格大小,但是为了演示起见,我要说它们是完全不同的单元格需要完全不同的数据模型。
现在,我们不希望让ParentCollectionView中的第一个单元出队,这是因为该单元将从内存中删除并重新放入队列以供重用,并且我们当然不希望我们的HorizontalCollectionView随机弹出。为避免这种情况,我们需要注册我们的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()
}
}
HorizontalCollectionView.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
}
}