ios - 另一个集合视图中的集合视图:部分标题或单元格?
问题描述
我已经有一个垂直滚动并显示具有固定大小UICollectionView
的自定义 s 的集合。UICollectionViewCell
现在我被要求UICollectionView
在所有其他单元格顶部显示另一个,它应该水平滚动并且其单元格大小是动态的(我只会在异步网络调用完成后知道大小)。此外,这个内部集合视图可能并不总是需要显示(它取决于从网络调用接收到的数据),但如果是,它应该只显示一次(在所有内容之上)。
我的问题是:处理第二个和内部集合视图的最佳方法应该是什么?我应该将它作为不同类型的单元格添加到外部视图控制器中,还是作为部分标题添加?也许另一种布局方法会更好?
编辑:更多注意事项:
- 当我要显示它时,我需要为内部集合视图设置动画
- 整个东西应该是垂直可滚动的,这个内部集合视图不应该粘在屏幕顶部
解决方案
“处理第二个和内部集合视图的最佳方法应该是什么?”
“我应该将它作为不同类型的单元格添加到外部视图控制器中,还是作为部分标题添加?”
“当我要展示它时,我需要为内部集合视图设置动画”
“整个东西应该是垂直可滚动的,这个内部集合视图不应该粘在屏幕顶部。”
有时退后一步写下你的要求,独立思考每一个要求是有帮助的:
1) CollectionView 的第一个单元格应该水平滚动。
2) 第一个单元格应垂直滚动过屏幕。
CollectionView 的第一个单元格需要包含 CollectionView 本身。
3a) CollectionView 的其他单元格是静态大小的。
3b) CollectionViews 的第一个单元格是动态大小的。
需要两个单元类,或者一个具有动态约束和子视图的单元类。
4) CollectionView 的第一个单元格应该是动画的。
第一个单元格的 CollectionView 需要是其动态单元格的代表。(动画发生在cellForItemAt indexPath
)
请记住,UICollectionView
这是独立的观点。AUICollectionViewController
本质上是 a UIViewController
,UICollectionViewDelegate
并且UICollectionViewDataSource
包含 a UICollectionView
。就像任何 UIView 一样,您可以将其子类UICollectionView
化并将其添加到另一个视图的子视图中,例如UICollectionViewCell
. 通过这种方式,您可以将集合视图添加到单元格并将单元格添加到嵌套的集合视图中。您还可以允许嵌套集合视图处理来自的所有委托方法,UICollectionViewDelegate
并从UICollectionViewDataSource
本质上使其模块化和可重用。您可以传递要在方法内嵌套的每个单元格中显示的数据,UICollectionView
并convenience init
允许该类处理动画和设置。这是迄今为止最好的方法,不仅可以重用,还可以提高性能,尤其是当您以编程方式创建视图时。
在下面的示例中,我有一个UICollectionViewController
名为 ViewController 的视图控制器,它将成为所有其他视图的视图控制器。
我也有两个 CollectionViewsParentCollectionView
和HorizontalCollectionView
. ParentCollectionView 是 UICollectionView 的空实现。我可以使用collectionView
我的,UICollectionViewController
但因为我希望它完全模块化,我稍后会将我的 ParentCollectionView 分配给 ViewController 的 collectionView。ParentCollectionView 将处理视图中的所有单元格静态单元格,包括包含我们的 HorizontalCollectionView 的单元格。HorizontalCollectionView 将成为在其便利初始化程序中传递给它的所有“单元格对象”(您的数据模型)的委托和数据源。也就是说,HorizontalCollectionView 会管理它自己的单元格,这样我们UICollectionViewController
就不会发胖。
除了两个 CollectionView 和一个 UICollectionViewController 之外,我还有两个UICollectionViewCell
类,一个是静态大小,另一个是动态(随机生成的 CGSize)。为了便于使用,我还有一个将类名作为标识符返回的扩展,我不喜欢对可重用单元格使用硬编码字符串。这些单元格类别并没有太大的不同,可以使用相同的单元格并更改单元格大小,sizeForItemAt indexPath
但cellForItemAt indexPath
为了演示,我要说它们是完全不同的单元格,需要完全不同的数据模型。
现在,我们不希望 ParentCollectionView 中的第一个单元格出列,这是因为单元格将从内存中删除并放回队列中以供重用,我们当然不希望 HorizontalCollectionView 随机弹出。为了避免这种情况,我们需要同时注册我们的 StaticCollectionViewCell 和一个只会使用一次的通用单元,因为我添加了一个扩展,它为我提供了单元的类名,我将使用UICollectionViewCell
它作为标识符。
我相信你不会很难弄清楚其余的,这是我的完整实现:
ViewController.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()
}
}
ParentCollectionView.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")
}
}
HorizontalCollectionView.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()
}
}
}
DynamicCollectionViewCell.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
}
}
StaticCollectionViewCell.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
}
}
CollectionViewCellExtentions.swift
import UIKit
extension UICollectionViewCell {
/// Get the string identifier for this class.
///
/// - Returns: String
class var identifier: String {
return NSStringFromClass(self).components(separatedBy: ".").last!
}
}
推荐阅读
- angular - 无法读取未定义的属性“emailVerified”
- python - 给定可变数量的条件,如何在数据帧上设置值?
- ruby-on-rails - 如何使用 Ruby 2.7.0 修复 Rails 的警告消息
- android - React Native:如何在React Native中获取手机中安装的应用列表
- flutter - 自定义订单添加主题的订单颤振
- python - python中的嵌套if条件
- programming-languages - 类型化编程语言
- javascript - 单击图像并被重定向到新页面,单击的图像动态添加到新页面?
- singularity-container - 使用 Singularity 版本 3 构建与 v2 兼容的 Singularity 映像
- python - 如何在 matplotlib 中用日期字符串交换 x 刻度