I have a very similar problem to this question and this question and I think they might be related. The difference is that the delegate is not called when a generic view model is used.
In the first example below everything works as expected. Initialize a ViewController, which is a subclass of SimpleListViewController. When the cell is selected it prints "Here" because the superclass (SimpleListViewController) conforms to UICollectionViewDelegate.
class SimpleListVCModel {
var dataSource: UICollectionViewDiffableDataSource<Section, String>! = nil
var items = Array(0..<10).map{"This is item \($0)"}
enum Section: String {
case main
}
func configureDataSource(using collectionView: UICollectionView) {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { (cell, indexPath, item) in
var content = cell.defaultContentConfiguration()
content.text = "\(item)"
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource<Section, String>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: String) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
}
func applySnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
class ViewController: SimpleListViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
view.backgroundColor = .red
configureHierarchy()
model.configureDataSource(using: collectionView)
model.applySnapshot()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Here")
}
}
class SimpleListViewController: UIViewController, UICollectionViewDelegate {
var collectionView: UICollectionView! = nil
let model = SimpleListVCModel()
func configureHierarchy() {
let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
view.addSubview(collectionView)
}
}
Now If I introduce generics, the collectionView delegate function is never called.
First create a subclass of SimpleListVCModel specifically for the ViewController.
class ViewControllerViewModel: SimpleListVCModel {
func foo(){
print("Foo")
}
}
Then slightly modify SimpleListViewController to use a generic SimpleListVCModel.
class SimpleListViewController<M: SimpleListVCModel>: UIViewController, UICollectionViewDelegate {
let model: M
var collectionView: UICollectionView! = nil
//MARK: - Initializer
init(model: M) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Views
func configureHierarchy() {
let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
view.addSubview(collectionView)
}
}
Finally modify ViewController to use a generic version of the SimpleListVCModel.
class ViewController: SimpleListViewController<ViewControllerViewModel> {
override init(model: ViewControllerViewModel = ViewControllerViewModel()) {
super.init(model: model)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
view.backgroundColor = .red
configureHierarchy()
model.configureDataSource(using: collectionView)
model.applySnapshot()
model.foo()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Here")
}
}
This does appear to be similar to the case in the answer I linked to in comments, but since it's a little different I'll post this answer here.
First, in your SimpleListViewController, implement didSelectItemAt:
class SimpleListViewController<M: SimpleListVCModel>: UIViewController, UICollectionViewDelegate {
let model: M
var collectionView: UICollectionView! = nil
//MARK: - Initializer
init(model: M) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: - Views
func configureHierarchy() {
let layout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .insetGrouped))
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.delegate = self
view.addSubview(collectionView)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in SimpleListViewController")
}
}
When you do that, you'll see an error in ViewController:
Overriding declaration requires an 'override' keyword
For the moment, comment out your didSelectItemAt func in ViewController.
Now, when you select an item, didSelectItemAt in SimpleListViewController will be called and it will print to the debug console.
Next, un-comment didSelectItemAt in ViewController and add the override keyword:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in ViewController")
}
Running the app now and selecting an item will call that func and print to the debug console.
Note that you can also call super ... so, if you have some default code you want executed in SimpleListViewController:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in SimpleListViewController")
// do something common
}
and in ViewController:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
super.collectionView(collectionView, didSelectItemAt: indexPath)
print("Selected item: \(indexPath) in ViewController")
}
and you'll get both print statements in the debug console.
If you'd rather not do it that way, you can remove the implementation from SimpleListViewController and instead declare the #objc method signature in ViewController:
class ViewController: SimpleListViewController<ViewControllerViewModel> {
override init(model: ViewControllerViewModel = ViewControllerViewModel()) {
super.init(model: model)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List"
view.backgroundColor = .red
configureHierarchy()
model.configureDataSource(using: collectionView)
model.applySnapshot()
model.foo()
}
#objc (collectionView:didSelectItemAtIndexPath:)
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Selected item: \(indexPath) in ViewController")
}
}
Note that you can no longer call super with that approach.
Related
I'm working on a 3rd party framework for swift so i cannot use the delegate methods of UICollectionViewDelegate myself but I do need them for some custom logic.
Tried multiple approaches to make it work, including method swizzling but in the end I felt like it was too hacky for what i'm doing.
Now i'm subclassing UICollectionView and setting the delegate to an internal (my) delegate.
This works well except for when the UIViewController hasn't implemented the method.
right now my code looks like this:
fileprivate class UICollectionViewDelegateInternal: NSObject, UICollectionViewDelegate {
var userDelegate: UICollectionViewDelegate?
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || userDelegate?.responds(to: aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if userDelegate?.responds(to: aSelector) == true {
return userDelegate
}
return super.forwardingTarget(for: aSelector)
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let collection = collectionView as! CustomCollectionView
collection.didEnd(item: indexPath.item)
userDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath)
}
}
class CustomCollectionView: UICollectionView {
private let internalDelegate: UICollectionViewDelegateInternal = UICollectionViewDelegateInternal()
required init?(coder: NSCoder) {
super.init(coder: coder)
super.delegate = internalDelegate
}
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
super.delegate = internalDelegate
}
func didEnd(item: Int) {
print("internal - didEndDisplaying: \(item)")
}
override var delegate: UICollectionViewDelegate? {
get {
return internalDelegate.userDelegate
}
set {
self.internalDelegate.userDelegate = newValue
super.delegate = nil
super.delegate = self.internalDelegate
}
}
}
In the ViewController I just have a simple set up with the delegate method for didEndDisplaying not implemented
Is it possible to listen to didEndDisplaying without the ViewController having it implemented?
Edit 1:
Here's the code of the ViewController to make it a little more clear what i'm doing
class ViewController: UIViewController {
#IBOutlet weak var collectionView: CustomCollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
1000
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .blue
return cell
}
// func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// print("view controller - did end displaying: \(indexPath.item)")
// }
}
the didEndDisplaying of CustomCollectionView is only triggered when i uncomment the didEndDisplaying method in the ViewController.
what i'm looking for is to have the didEndDisplaying of CustomCollectionView also triggered if the didEndDisplaying method in the ViewController is NOT implemented.
hope it's a little more clear now
Edit 2:
Figured out that the code above had some mistakes which made the reproduction not work as I intended. updated the code above.
also made a github page to make it easier to reproduce here:
https://github.com/mees-vdb/InternalCollectionView-Delegate
I did a little reading-up on this approach, and it seems like it should work - but, obviously, it doesn't.
Played around a little bit, and this might be a solution for you.
I made very few changes to your existing code (grabbed it from GitHub - if you want to add me as a Collaborator I can push a new branch [same DonMag ID on GitHub]).
First, I implemented didSelectItemAt to make it a little easier to debug (only one event at a time).
ViewController class
class ViewController: UIViewController {
#IBOutlet weak var collectionView: CustomCollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
1000
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .blue
return cell
}
// DonMag - comment / un-comment these methods
// to see the difference
//func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// print("view controller - did end displaying: \(indexPath.item)")
//}
//func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// print("view controller - didSelectItemAt", indexPath)
//}
}
UICollectionViewDelegateInternal class
fileprivate class UICollectionViewDelegateInternal: NSObject, UICollectionViewDelegate {
var userDelegate: UICollectionViewDelegate?
override func responds(to aSelector: Selector!) -> Bool {
return super.responds(to: aSelector) || userDelegate?.responds(to: aSelector) == true
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
if userDelegate?.responds(to: aSelector) == true {
return userDelegate
}
return super.forwardingTarget(for: aSelector)
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let collection = collectionView as! CustomCollectionView
collection.didEnd(item: indexPath.item)
userDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let collection = collectionView as! CustomCollectionView
collection.didSel(p: indexPath)
userDelegate?.collectionView?(collectionView, didSelectItemAt: indexPath)
}
}
CustomCollectionView class
// DonMag - conform to UICollectionViewDelegate
class CustomCollectionView: UICollectionView, UICollectionViewDelegate {
private let internalDelegate: UICollectionViewDelegateInternal = UICollectionViewDelegateInternal()
required init?(coder: NSCoder) {
super.init(coder: coder)
super.delegate = internalDelegate
}
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
super.delegate = internalDelegate
}
func didEnd(item: Int) {
print("internal - didEndDisplaying: \(item)")
}
func didSel(p: IndexPath) {
print("internal - didSelectItemAt", p)
}
// DonMag - these will NEVER be called,
// whether or not they're implemented in
// UICollectionViewDelegateInternal and/or ViewController
// but, when implemented here,
// it allows (enables?) them to be called in UICollectionViewDelegateInternal
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("CustomCollectionView - didEndDisplaying", indexPath)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("CustomCollectionView - didSelectItemAt", indexPath)
}
override var delegate: UICollectionViewDelegate? {
get {
// DonMag - return self instead of internalDelegate.userDelegate
return self
//return internalDelegate.userDelegate
}
set {
self.internalDelegate.userDelegate = newValue
super.delegate = nil
super.delegate = self.internalDelegate
}
}
}
I have a UICollectionView inside a UICollectionViewCell, and a separate NSObject that is the dataSource. I am able to set the dataSource for the external UICollectionView, but not the internal one.
Here's the cell containing the internal UICollectionView:
class FeaturedCell: UICollectionViewCell, UICollectionViewDelegate {
#IBOutlet var collectionView: UICollectionView!
let data = FeaturedData()
override init(frame: CGRect) {
super.init(frame: frame)
//setUp()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setUp() {
collectionView.dataSource = data
collectionView.delegate = self
collectionView.reloadData()
}
}
extension FeaturedCell {
func setCollectionViewDataSourceDelegate <D: FeaturedData> (_ dataSourceDelegate: D, forRow row: Int) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.reloadData()
print("Reload Data")
}
}
And the UIView containing the external UICollectionView:
class MainView: UIView, UICollectionViewDelegate {
#IBOutlet var collectionView: UICollectionView!
let data = MainData()
override func awakeFromNib() {
setUp()
}
func setUp() {
collectionView.dataSource = data
collectionView.delegate = self
collectionView.backgroundColor = UIColor.orange
collectionView.collectionViewLayout = createLayout()
collectionView.isPagingEnabled = true
collectionView.bounces = false
collectionView.allowsSelection = true
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("WillDisplay")
guard let cell: FeaturedCell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeaturedCell", for: indexPath) as? FeaturedCell else {
fatalError("Unable to dequeue FeaturedCell.")
}
cell.setCollectionViewDataSourceDelegate(featuredData, forRow: indexPath.item)
}
}
Both of these methods are being called, but the dataSource and delegates are never being set. I also followed this tutorial exactly (with a UITableView, even) and it still would not set the dataSource or delegate. What am I doing wrong?
I think this will help
class FeaturedCell: UICollectionViewCell {
#IBOutlet var collectionView: UICollectionView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
extension FeaturedCell {
func setCollectionViewDataSourceDelegate <T: UICollectionViewDelegate , D: FeaturedData> (delegate: T dataSource: D, forRow row: Int) {
collectionView.delegate = delegate
collectionView.dataSource = dataSource
collectionView.reloadData()
print("Reload Data")
}
}
and in the MainView
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("WillDisplay")
guard let cell: FeaturedCell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeaturedCell", for: indexPath) as? FeaturedCell else {
fatalError("Unable to dequeue FeaturedCell.")
}
cell.setCollectionViewDataSourceDelegate(self, featuredData, forRow: indexPath.item)
}
And for the tutorial you followed this a Github link for the tutorial, compare your code with that and see where did you missed out.
Hope this will helps.
Solved it. The problem was that this:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("WillDisplay")
guard let cell: FeaturedCell = collectionView.dequeueReusableCell(withReuseIdentifier: "FeaturedCell", for: indexPath) as? FeaturedCell else {
fatalError("Unable to dequeue FeaturedCell.")
}
cell.setCollectionViewDataSourceDelegate(featuredData, forRow: indexPath.item)
}
Needed to be changed to this:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
print("WillDisplay")
guard let featuredCell = cell as? FeaturedCell else { return }
featuredCell.setCollectionViewDataSourceDelegate(featuredData, forRow: indexPath.item)
}
I have a ViewController with a CollectionView and would like to put the UICollectionViewDataSource in an extra-class, so that it can be reused. (I'm not using the interface builder).
Therefore, I created an extra-class:
class MyDataSource: NSObject, UICollectionViewDelegate, UICollectionViewDataSource {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MYIDENTIFIER", for: indexPath)
cell.backgroundColor = .blue
return cell
}
}
In the ViewController with the CollectionView, I set it:
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MYIDENTIFIER")
let myDataSource = MyDataSource()
collectionView.delegate = myDataSource
collectionView.dataSource = myDataSource
}
However, the collectionView will stay empty - there are no cells on it.
However, if I put all the DataSource Functions into the ViewController class itself and set the delegate and DataSource to self, then everything works.
Is it not possible to outsource the UICollectionViewDataSource in other files, or is there anything more you have to do?
Just in case. Here is the reason I was saying about calling the init method. #Andrey Chernukha, sorry if it caused any confusion. This is why I said that it works with the init but I guess it is the same reason as Pascal
import UIKit
class ViewController: UIViewController {
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
return cv
}()
var dataSource : CustomDataSource!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.addSubview(collectionView)
collectionView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
dataSource = CustomDataSource(collectionView: collectionView)
}
}
class CustomDataSource : NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate {
let collectionView : UICollectionView
init(collectionView: UICollectionView) {
self.collectionView = collectionView
super.init()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
}
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.backgroundColor = .red
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
class Cell : UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I tested it and the reason is that dataSource is weak in a collectionView so it is deallocated immediately after it's instantiated if declared in viewDidLoad()
thanks for the help! I tried what Galo suggested, but for me this didn't work. However, I found the solution:
You have to initialize the delegate as property of the ViewController, and not in its viewDidLoad function! Then everything is working correctly!
let myDataSource = MyDataSource()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MYIDENTIFIER")
collectionView.delegate = myDataSource
collectionView.dataSource = myDataSource
}
It's another scope issue often been overlooked.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MYIDENTIFIER")
let myDataSource = MyDataSource(). //See the problem here?
collectionView.delegate = myDataSource
collectionView.dataSource = myDataSource
} //myDataSource will be released!
Newbie trying to learn programming concepts with swift and Cocoa Touch
So, I have once custom CategoryCell: UICollectionViewCell
class CategoryCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
print("set background Color")
backgroundColor = UIColor.black
}
}
Paired with one programatically created UICollectionView
class FeaturedAppsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
private let cellID = "cellID"
private let featuredApps = "Featured Apps"
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = featuredApps
collectionView?.backgroundColor = UIColor.yellow
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellID)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID , for: indexPath) as! CategoryCell
print("Cell created")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 150)
}
}
Code under App
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let layout = UICollectionViewLayout()
let featuredAppsController = FeaturedAppsController(collectionViewLayout: layout)
window?.rootViewController = UINavigationController(rootViewController: featuredAppsController)
return true
}
I've registered the custom cell in viewDidLoad();
Also, I'm making it's width to be equal to the view's width.
But all it shows me is UICollectionView with yellow color, that's it 🤷🏻♂️
try this. you missing some codes. you should have
#IBOutlet weak var collectionView: UICollectionView!
and also u missing this datasourse and delegates of collectionView
collectionView.dataSource = self
collectionView.delegate = self
this is the full code.
import UIKit
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
print("Cell created")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 150)
}
}
Try this and let me know.
Create your xib files and then register your cell with collection view
//Register cell
let cellNIB = UINib(nibName: "YourCollectionViewXibName", bundle: nil)
collectionView.register(cellNIB, forCellWithReuseIdentifier: cellID)
If it's all programmatically then init it with layout like this
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let featuredAppsController = FeaturedAppsController(collectionViewLayout: layout)
window?.rootViewController = UINavigationController(rootViewController: featuredAppsController)
You are using UICollectionViewLayout()
You should use:
let layout = UICollectionViewFlowLayout()
I am interested in having a collectionview as a part of a collection view cell but for some reason cannot figure out how this would be done. Where would I implement the necessary methods for the cells collectionview?
There's an article that Ash Furrow wrote that explains how to put an UICollectionView inside an UITableViewCell. It's basically the same idea when using it inside an UICollectionViewCell.
Everything is done programatically. No storyboards.
I added a UICollectionView inside my UICollectionViewCell. I also show how to add again a UICollectionViewCell inside the created UICollectionView to have this result
import UIKit
class CategoryCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
private let cellId = "cell"
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
return collectionView
}()
func setupViews() {
backgroundColor = .blue
addSubview(appsCollectionView)
appsCollectionView.delegate = self
appsCollectionView.dataSource = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)
addConstrainstWithFormat("H:|-8-[v0]-8-|", views: appsCollectionView)
addConstrainstWithFormat("V:|[v0]|", views: appsCollectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
return cell
}
}
class AppCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews(){
backgroundColor = .red
}
}
My UICollectionViewController
import UIKit
class FeaturedAppsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId = "cell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
collectionView?.backgroundColor = .white
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellId)
}
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: cellId, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(view.frame.width, 150)
}
}
The whole explanation can be found and was developed by "Let´s build that app": https://www.youtube.com/watch?v=Ko9oNhlTwH0&list=PL0dzCUj1L5JEXct3-OV6itP7Kz3tRDmma
This is too late for this answer but it might help others. This is an example of UICollectionView inside a UICollectionViewCell.
Lets start by having a mainCollectionView. Then on each cell of this collection create and initialize a new UICollectionView and right place to do that is in this following delegate of UICollectionView
func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath)
e.g I initialize the MainCollectionViewCell here and then MainCollectionViewCell handles the logic to create a new UICollectionView
guard let collectionViewCell = cell as? MainCollectionViewCell else { return }
collectionViewCell.delegate = self
let dataProvider = ChildCollectionViewDataSource()
dataProvider.data = data[indexPath.row] as NSArray
let delegate = ChildCollectionViewDelegate()
collectionViewCell.initializeCollectionViewWithDataSource(dataProvider, delegate: delegate, forRow: indexPath.row)
collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
Here is the initializer on MainCollectionViewCell that creates a new UICollectionView
func initializeCollectionViewWithDataSource<D: protocol<UICollectionViewDataSource>,E: protocol<UICollectionViewDelegate>>(dataSource: D, delegate :E, forRow row: Int) {
self.collectionViewDataSource = dataSource
self.collectionViewDelegate = delegate
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .Horizontal
let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: flowLayout)
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseChildCollectionViewCellIdentifier)
collectionView.backgroundColor = UIColor.whiteColor()
collectionView.dataSource = self.collectionViewDataSource
collectionView.delegate = self.collectionViewDelegate
collectionView.tag = row
self.addSubview(collectionView)
self.collectionView = collectionView
collectionView.reloadData()
}
Hope that helps !!
I did an example for this and put in on github. It demonstrates the use UICollectionView inside a UICollectionViewCell.
https://github.com/irfanlone/Collection-View-in-a-collection-view-cell
Easiest solution for collectionview inside collectionview using storyboard and Swift 5
Please refer this link for nested collectionview example
import UIKit
class ParentViewController:UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Declare variable
//MARK: Decalare outlet
#IBOutlet weak var outerCollectionView: UICollectionView!
#IBOutlet weak var pageControl: UIPageControl!
let outerCount = 4
//MARK: Decalare life cycle methods
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = outerCount
}
//MARK: Collection View delegate methods
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return outerCount
}
//MARK: Collection View datasource methods
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "OuterCell", for: indexPath) as! OuterCollectionViewCell
cell.contentView.backgroundColor = .none
return cell
}
//MARK:- For Display the page number in page controll of collection view Cell
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleRect = CGRect(origin: self.outerCollectionView.contentOffset, size: self.outerCollectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
if let visibleIndexPath = self.outerCollectionView.indexPathForItem(at: visiblePoint) {
self.pageControl.currentPage = visibleIndexPath.row
}
}
}
class OuterCollectionViewCell: UICollectionViewCell ,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var InnerCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
InnerCollectionView.delegate = self
InnerCollectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InnerCell", for: indexPath) as! InnerCollectionViewCell
cell.contentView.backgroundColor = .green
return cell
}
}
class InnerCollectionViewCell: UICollectionViewCell{
}