cell variable is nil where value was set collectionview index path - ios

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

Related

Taking the value on didSelectItemAt for indexPath and add it to a delegate / protocol to populate a header cell

Using protocol / delegate & retrieve the data from didSelectItemAt (collectionViews).
// This class have the data
// WhereDataIs: UICollectionViewController
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//Pass some data from the didSelect to a delegate
let test = things[indexPath.item]
guard let bingo = test.aThing else { return }
print("bingo: ", bingo)
That bingo is printing the value that I need. So pretty good right there.
Now, I can't use the method of the protocol inside of that function, that's bad execution so the compiler or Xcode says hey, you will declare the method as a regular method, not the nested way.
//Bridge
protocol xDelegate {
func x(for headerCell: HeaderCell)
}
//This is the class that need the data
//class HeaderCell: UICollectionViewCell
var xDelegate: xDelegate?
//it's init()
override init(frame: CGRect) {
super.init(frame: frame)
let sally = WhereDataIs()
self.xDelegate = sally
xDelegate?.x(for: self)
}
// This extension is added at the end of the class WhereDataIs()
// Inside of this class is it's delegate.
var xDelegate: xDelegate? = nil
extension WhereDataIs: xDelegate {
func x(for headerCell: HeaderCell) {
//Smith value will work because it's dummy
headerCell.lbl.text = "Smith"
// What I really need is instead of "Smith" is the value of didselectItemAt called bingo.
headerCell.lbl.text = bingo
}
}
Hat's off for anyone who would like to guide me in this.
Not using delegates, but it will work
Solved by:
1) go to the controller of the collectionView. make a new variable to store the items.
// Pass the item object into the xController so that it can be accessed later.
//(didSelectItemAt)
xController.newVar = item
//xController class: UICollectionView...
// MARK: - Properties
var newVar: Thing?
Finally in that class, you should have the the method viewForSupplementaryElementOfKind in which you register the HeaderCell and then just add...
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath) as! HeaderCell
header.lbl.text = newVar.whatEverYourDataIs
Little generic but it works. :)
I think you can just add this to the end of your current code in didSelectItemAt indexPath:
guard let header = collectionView.supplementaryView(forElementKind: "yourElementKind", at: indexPath) as? HeaderCell else { return }
header.lbl.text = bingo
collectionView.reloadData()
Edit: Keep everything else for now to ensure you get a good result first.
Let me know if this works, happy to check back.

UIcollectionview Custom Cell class to didselectitem at

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)

How to pass a collection view cell's indexPath to another view controller

I'm following a tutorial for making an Instagram-esque app, and the tutorial goes through how to display all data (image, author, likes, etc) all in one collection view. I'm trying to do it a little bit differently so only the images are displayed in the collection view, then if an image is tapped, the user is taken to a different view controller where the image plus all the other data is displayed.
So in my view controller with the collection view (FeedViewController), I have my array of posts declared outside the class (Post is the object with all the aforementioned data):
var posts = [Post]()
Then inside the FeedViewController class, my cellForItemAt indexPath looks like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCell
// creating the cell
cell.postImage.downloadImage(from: posts[indexPath.row].pathToImage)
photoDetailController.authorNameLabel.text = posts[indexPath.row].author
photoDetailController.likeLabel.text = "\(posts[indexPath.row].likes!) Likes"
photoDetailController.photoDetailImageView.downloadImage(from: posts[indexPath.row].pathToImage)
return cell
}
And then I also obviously have a function to fetch the data which I can post if necessary, but I think the problem is because PhotoDetailController doesn't know the indexPath (although I may be wrong).
When I run the app and try to view the FeedViewController I get a crash fatal error: unexpectedly found nil while unwrapping an Optional value, highlighting photoDetailController.authorNameLabel line.
Am I correct in assuming the problem is because the indexPath is available only in the collection view data sources within FeedViewController? If so, how can I pass that indexPath to PhotoDetailController so my code works correctly?
Thanks for any advice!
EDIT: Edited my didSelectItem method:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let photoDetailController = self.storyboard?.instantiateViewController(withIdentifier: "photoDetail") as! PhotoDetailController
photoDetailController.selectedPost = posts[indexPath.row]
self.navigationController?.pushViewController(photoDetailController, animated: true)
}
and cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCell
cell.postImage.downloadImage(from: posts[indexPath.row].pathToImage)
return cell
}
And in PhotoDetailController:
var selectedPost: Post! inside the class, then:
override func viewDidLoad() {
super.viewDidLoad()
self.authorNameLabel.text = selectedPost[indexPath.row].author
self.likeLabel.text = "\(selectedPost[indexPath.row].likes!) Likes"
self.photoDetailImageView.downloadImage(from: selectedPost[indexPath.row].pathToImage)
}
But still getting error use of unresolved identifier "indexPath
EDIT 2: Storyboard
You are getting this nil crash because this photoDetailController is not yet loaded so all its outlet are nil also the way currently you are doing is also wrong.
You need to add the controller code in didSelectItemAt method and perform the navigation.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let photoDetailController = self.storyboard?.instantiateViewController(withIdentifier: "YourIdentifier") as! PhotoDetailController
photoDetailController.selectedPost = posts[indexPath.row]
self.navigationController?.pushViewController(photoDetailController, animated: true)
}
Now simply create one instance property of type Post with name selectedPost in PhotoDetailController and set all the detail from this selectedPost object in viewDidLoad of PhotoDetailController.
Edit: Set your viewDidLoad like this in PhotoDetailController
override func viewDidLoad() {
super.viewDidLoad()
self.authorNameLabel.text = selectedPost.author
self.likeLabel.text = "\(selectedPost.likes!) Likes"
self.photoDetailImageView.downloadImage(from: selectedPost.pathToImage)
}

Access nested collectionView in viewController that does not have reference to it

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.

Showing Cells in UICollectionViewController

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
}

Resources