I need to get reference of a collectionView that is nested inside another collectionView so that i can perform UI updates.
In my ViewController class i have an outlet for the "outer" collectionView:
#IBOutlet var outerCollectionView: UICollectionView!
I set the DataSource and Delegate for the "inner" collectionView in willDisplayCell:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
guard let outerViewCell = cell as? OuterCollectionCell else { return }
outerViewCell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forItem: indexPath.item)
outerViewCell.innerCollectionView.reloadData()
outerViewCell.innerCollectionView.layoutIfNeeded()
}
Then i can populate the cells for both collectionViews in cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.outerCollectionView {
let outerCell: OuterCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierOuter, for: indexPath) as! OuterCollectionCell
outerCell.labelCell.text = self.packArray[indexPath.item].title
// other stuff....
return outerCell
} else {// innerCollectionView
let innerCell: InnerCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifierInner, for: indexPath) as! InnerCollectionCell
innerCell.imageCell.file = self.multiPartArray[collectionView.tag][indexPath.item].image
// other stuff....
return innerCell
}
}
This produces the following result:
Where collectionView.tag is the instance of the "inner" collectionView (0 being blue ect).
Now i want to perform UI updates on cells and for various reasons can not use cellForItemAt. So what i have done previously is something like:
if let editCell = collectionView.cellForItem(at: indexPath) as? InnerCollectionCell {
editCell.imageCell.image = UIImage(named: "test")
}
However to do this i need reference to the "inner" collectionView which i don't have because it may have been reused by the time these UI updates are performed. What i do have however is the collectionView.tag which is just an Int reference and the indexPath of the cell i want to perform the update on.
Can I the UI of the specified cell inside a collectionView that i don't have a direct reference to or is this just wishful thinking?
I should with some sort of delegate function i just cant think how. Also for reference below is my OuterCollection class where i define the "inner" collectionView and set the datasource and delegate.
class OuterCollectionCell: UICollectionViewCell {
#IBOutlet var labelCell: UILabel!
// define the smaller collection view that is in the cell
#IBOutlet var innerCollectionView: UICollectionView!
// set delegate and datasource for new collectionView
// this is download collection manager
func setCollectionViewDataSourceDelegate
<D: UICollectionViewDataSource & UICollectionViewDelegate>
(dataSourceDelegate: D, forItem item: Int) {
innerCollectionView.delegate = dataSourceDelegate
innerCollectionView.dataSource = dataSourceDelegate
innerCollectionView.tag = item
innerCollectionView.reloadData()
innerCollectionView.layoutIfNeeded()
}
}
you can have the delegate and datasource methods of innercollectionview defined in the OuterCollectionCell class. Try this:-
// Set the delegate and datasource in your custom cell class as below:-
class OuterCollectionCell: UITableViewCell {
// set the delegate & datasource of innercollectionView in the init or
// awakefromnib or storyboard.
// I have set it in storyboard and it works for me.
// Implement the datasource and delegate methods of innercollectionView in this class.
}
This way the delegate and datasource of innerCollectionView will be separate from the outercollectionview and you can handle all the logic of innecrCollectionView in the OuterCollection cell class.
Related
my collectionview contains multiple custom cell class that created programmatically. Fun part is I will have a cell that contains another collectionview inside of it. So how to click into this collectionview to present a new view controller? Below are my sample codes on how to create the cell.
class MainView: UIViewController {
** main view to display UIcollectionview **
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: userCellIdent, for: indexPath) as! UserContainerViewCell
cell.Users = users * to display users array*
return cell
}
class UserContainerViewCell: UICollectionViewCell{
* the uicollectionview that inside a cell , also contains extension to UIcollectioview datasource and delegate to display the cells within this cell*
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ident, for: indexPath) as! userCell
let user = featureUsers![indexPath.row]
cell.config(withUser: user)
return cell
}
}
class userCell: UICollectionViewCell {
func config(withUser: UserProfile) { }
** cell class to display the info from the user array **
}
So with this setup, how to click into the cell and present a new controller with the User info in the array?
You have to pass a delegate or hit notification to viewController on didSelect of internal collectionViewCell and in that method you can present new view controller .
check out the link
Swift: How to use "didSelectItemAtIndexPath" in UITableViewController(contains UICollectionView)?
You can try
class UserContainerViewCell: UICollectionViewCell {
var myInstance: MainView!
}
class MainView: UIViewController {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: userCellIdent, for: indexPath) as! UserContainerViewCell
cell.myInstance = self
}
}
then inside didSelectItemAtRow
let vc = myInstance.storyboard?.instantiateViewController(withIdentifier: "infoView") as! InfoController
vc.providesPresentationContextTransitionStyle = true;
vc.definesPresentationContext = true;
vc.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myInstance.present(vc, animated: false, completion: nil)
Im attempting to set up a colletionView inside a collectionView with separation between the data sources so its easier for me to manipulate UI elements.
I have my main ViewController which sets the datasource and delegate of the "outer" collection view.
class DownloadCollectionController: UIViewController {
#IBOutlet weak var outerCollectionView: UICollectionView!
var outerDataProvider : OuterDatasourceDelegate!
var outerDelegate: OuterDatasourceDelegate!
override func viewDidLoad() {
super.viewDidLoad()
// set the datasource fot the outer collection
outerDataProvider = OuterDatasourceDelegate()
// crashes on this line when selecting a cell
outerCollectionView.dataSource = outerDataProvider
outerDelegate = OuterDatasourceDelegate()
outerCollectionView.delegate = outerDelegate
}
}
The OuterDatasourceDelegate class which has some functions to populate the cell and then the willDisplayAt function the sets the datasource and delegate for the 'inner" collection view:
class OuterDatasourceDelegate: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return GlobalOperationStore.sharedInstance.globalPackArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// cell stuff...
return outerCell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// set the datasource and delegate for the inner collection
guard let outerViewCell = cell as? OuterCollectionCell else { return }
let dataProvider = InnerDatasourceDelegate()
let delegate = InnerDatasourceDelegate()
outerViewCell.initializeCollectionViewWithDataSource(dataProvider, delegate: delegate, forItem: indexPath.item)
outerViewCell.innerCollectionView.reloadData()
outerViewCell.innerCollectionView.layoutIfNeeded()
}
}
This InnerDataSourceDelegate class just has the same functions as above that are required to populate the cells.
And my custom cell class that has reference to the "inner" collectionView and contains the initializeCollectionViewWithDataSource function called above:
class OuterCollectionCell: UICollectionViewCell {
var collectionViewDataSource : UICollectionViewDataSource!
var collectionViewDelegate : UICollectionViewDelegate!
// define the smaller collection view that is in the cell
#IBOutlet var innerCollectionView: UICollectionView!
// set delegate and datasource for new collectionView
func initializeCollectionViewWithDataSource<D: UICollectionViewDataSource, E: UICollectionViewDelegate>(_ dataSource: D, delegate :E, forItem item: Int) {
self.collectionViewDataSource = dataSource
self.collectionViewDelegate = delegate
innerCollectionView.delegate = self.collectionViewDelegate
innerCollectionView.dataSource = self.collectionViewDataSource
innerCollectionView.tag = item
innerCollectionView.reloadData()
innerCollectionView.layoutIfNeeded()
}
It displays fine when it is first loaded:
But when i select a cell it prints as i have asked:
cell no: 0 of collection view: 2
And then i get a fatal error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
In my ViewController class on this line:
outerCollectionView.dataSource = outerDataProvider
because outerCollectionView is nil. But i can not figure out why it is nil. Im not sure why its going through viewDidLoad again as this loaded when the ViewController now won't load again until i leave the VC and come back?
----- EDIT ------
i spend lot of time .. but i can't figure out why it is not working .. cell for index path called and set value properly but cell class i found nil value .. if your need any information then let me know .
in my collectionview index path
in collectionview cell
here is my code :
for collectionview :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: menuBarItemDetailId, for: indexPath) as! MenuBarItemDetail
cell.shopCategory = "600"
print(cell.shopCategory)
return cell
}
for cell :
class MenuBarItemDetail: UICollectionViewCell , UITableViewDataSource , UITableViewDelegate {
var shopCategory : String?
override init(frame: CGRect) {
super.init(frame: frame)
print("shopCategory :-> ...i am calling from cell :\(shopCategory)")
}
Because your awakeFromNib method called first and then cellForRow .You are assigning the value of variable in cellForRow so when first project executes its value is nil.
Solution
your variable in your custom cellclass
var myVar : String?
Create a method in your custom cellclass
func myMethod(str : String){
myVar = str
print("var : \(myVar)")
}
in cellForItem, call your function like this
cell.myMethod(str: "343")
Output
When you are writing
let cell = collectionView.dequeueReusableCell(withReuseId`entifier: "MenuBarItemDetail", for: indexPath) as! MenuBarItemDetail`
At that time "override init(frame: CGRect)" is called.
and no value is assigned to shopCategory at the time of init that's the reason you are getting nil.
Add a function "getShopCategory" into MenuBarItemDetail and when every you want to access the value of shopCategory you can get that using getShopCategory function.
import Foundation
import UIKit
class MenuBarItemDetail: UICollectionViewCell {
var shopCategory : String?
func getShopCategory() {
print("shopCategory :-> ...i am calling from cell :\(shopCategory)")
}
}
Controller Class
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuBarItemDetail", for: indexPath) as! MenuBarItemDetail
cell.shopCategory = "600"
print(cell.shopCategory)
cell.getShopCategory()
return cell
}
cell is the current instance of MenuBarItemDetail so it will return assigned value of shopCategory
Please let me know if it's working for you.
Thank you
I use a main class call newsFeedCointroller as UICollectionViewController.
1. In cell inside I have a newsfeed with a like button (to populate the cell I use a class called "FeedCell")
2. Out from cells (in mainview) I have a label (labelX) used for "splash message" with a function called "messageAnimated"
How can I call the "messageAnimated" function from the button inside the cells.
I want to to change the label text to for example: "you just liked it"...
Thanks for helping me
In your FeedCell you should declare a delegate (Read about delegate pattern here)
protocol FeedCellDelegate {
func didClickButtonLikeInFeedCell(cell: FeedCell)
}
In your cell implementation (suppose that you add target manually)
var delegate: FeedCellDelegate?
override func awakeFromNib() {
self.likeButton.addTarget(self, action: #selector(FeedCell.onClickButtonLike(_:)), forControlEvents: .TouchUpInside)
}
func onClickButtonLike(sender: UIButton) {
self.delegate?.didClickButtonLikeInFeedCell(self)
}
In your View controller
extension FeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("feedCell", forIndexPath: indexPath) as! FeedCell
// Do your setup.
// ...
// Then here, set the delegate
cell.delegate = self
return cell
}
// I don't care about other delegate functions, it's up to you.
}
extension FeedViewController: FeedCellDelegate {
func didClickButtonLikeInFeedCell(cell: FeedCell) {
// Do whatever you want to do when click the like button.
let indexPath = collectionView.indexPathForCell(cell)
print("Button like clicked from cell with indexPath \(indexPath)")
messageAnimated()
}
}
I have tried to make a UICollectionViewController where I can show a image for each cell. When I want to open this ViewController it shows me an error
import UIKit
private let reuseIdentifier = "Cell"
class RodelCollectionViewController: UICollectionViewController {
var personService: PersonService!
override func viewDidLoad() {
super.viewDidLoad()
assert(personService != nil, "Person Service has to be set, otherwise this class can't do anything useful.")
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Register cell classes
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personService.allPersons().count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCollectionCell", forIndexPath: indexPath)
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.item]
}
return cell
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let PersonDetailViewController = segue.destinationViewController as? PersonDetailViewController,
let person = (sender as? RodelCollectionViewCell)?.personView?.person {
PersonDetailViewController.person = person
}
}
This is the error
I have tried a lot to fix it but it allways shows me the same error. I don't know where I have to solve this
Did you assign the cell identifier ("PersonCollectionCell") to the cell in the xib file or in the storyboard?
I noticed you declared private let reuseIdentifier = "Cell" that you use to register the cell. But you are using a different reuseIdentifier "PersonCollectionCell" when dequeuing the cell.
Also,
I wouldn't recommend using a function personService.allPersons() inside:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
This method gets called every time a cell will be reuse/dequeued and could bring performance issues in the future. Instead I would save the result inside an array and update it every time something change and can affect what personService.allPersons() returns.
I would declared a lazy variable like this:
private lazy var allPersons: [WhateverTheTypeIs] = {
let allPersons = self.personService.allPersons()
return allPersons
}
and in the collectionView datasource methods use allPersons instead of the method itself.
Hope this helps.
Another problem which is found with your code is in the
self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
Here you are trying to register a default UICollectionViewCell and in the cellForItemAtIndexPath you are trying to check for the
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.item]
}
Here in this code you are checking for your custom cell how this cell become custom cell
if you want to register and create your custom cell your should be like this:
At viewDidLoad()
self.collectionView!.registerClass(RodelCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
At cellForItemAtIndexPath
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! RodelCollectionViewCell
Default cell
If you want to keep the default cell your code will remain same as it's currently but it will not go inside the condition of custom cell the cell may be show empty if you don't do anything else in the cellforrow
Update
Put both of the code in the cellForItemAtIndexPath
To change cell background color
cell.contentView.backgroundColor = UIColor.redColor()
As person view is coming nil for now as testing purpose we can add a sample view
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCollectionCell", forIndexPath: indexPath)
if let rodelCollectionViewCell = cell as? RodelCollectionViewCell {
rodelCollectionViewCell.personView?.person = personService.allPersons()[indexPath.row]
}
cell.contentView.backgroundColor = UIColor.redColor()
let lbl = UILabel(frame:CGRectMake(0,0,100,21))
lbl.text = "\(indexPath.row)" //replace this value with your original value if it displays for the first time
cell.contentView.addSubview(lbl)
return cell
}