Passing data from cellforRowatIndexPath to UITableViewCell - ios

I am trying to pass in the String "random text" to equal the property imageURL that is located inside of the custom Cell UserCell from CellForRowAtIndexPath. In didSet of the cell class, I can print "random text" just fine, however, for reasons unrelated to this question, I need to print "random text" inside of the lazy var. When I try to print it inside the lazy var, or even awakeFromNib, it gives me nil. I have an idea of why this is happening. I'm assuming the compiler runs certain code for a custom cell class before initializing any self properties. I'm wondering if there is a way to get around that.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath) as! UserCell
cell.imageURL = "random text"
return cell
}
Inside of the custom cell (UserCell)
class UserCell: UITableViewCell {
var imageURL: String? {
didSet{
print(imageURL) //prints the imageURL
}
}
lazy var profileImageView: UIImageView = {
let imageView = UIImageView()
print(imageURL) /////prints nil
return imageView
}()

Related

What am i doing in the code below that is causing me to get errors?

I am getting following errors:
1) Non-optional expression of type 'UITableViewCell' used in a check for optionals
2) Value of type 'UITableViewCell' has no member 'congigureCell'
Please
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell:UITableViewCell = countryList.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell // Error 1 happens here {
let text: String!
if inSearchMode {
text = filteredCountriesList[indexPath.row]
} else {
text = countriesList[indexPath.row]
}
cell.congigureCell(text: text) // Error 2 happens here
return cell
} else {
return UITableViewCell()
}
}
1) The ! mark at the end of
countryList.dequeueReusableCell(withIdentifier: "cell")!
uses force unwrap to make it non-optional, so you shouldn't check it inside if let, or even better way is to just remove ! mark
2) congigureCell probably the method of different class, not UITableViewCell. You should substitude UITableViewCell by this class to cast it
Make sure you have done following steps.
Add cell identifier in storyboard to your custom cell. i.e "cell"
Assign delegate and datasource of your YourTableview to YOURViewController.swift via storyboard or in code.
In YOURViewController.swift access cell using datasource of table
view as.
Add a custom class of sub class UITableViewCell and assign it to
tour cell in storyboard.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = countryList.dequeueReusableCell(withIdentifier: "cell") as! YOURTableViewCellClass {
let text: String!
if inSearchMode {
text = filteredCountriesList[indexPath.row]
} else {
text = countriesList[indexPath.row]
}
cell.congigureCell(text: text) // Error 2 happens here
return cell }
The ! mark is uses to force unwrap the optional value that can be nil. But "if let" and "guard let" has been check for optionals, so you don't need ! mark.
Just use
if let cell:UITableViewCell = countryList.dequeueReusableCell(withIdentifier: "cell") as UITableViewCell
cell in this line is 'UITableViewCell', but congigureCell is not a member of UITableViewCell.
If you want to use your own cell(like MyCell), you should convert it to MyCell.
let myCell = cell as! MyCell
1 .Instead of dequeueReusableCellWithIdentifier: use dequeueReusableCellWithIdentifier:forIndexPath: the later one never provides a nil value so you dont need to worry about the 'nil error'.
2.UItableViewCell dont have configure cell or congigureCell as in your case instead you have to create a custom tableViewCell and add function as configureCell() and then in this line
if let cell:UITableViewCell = countryList.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
replace as UITableViewCell as as yourCustomTableViewCellClass

Overlapping Buttons due to Cell Reuse

I am having some trouble with the dequeue reusable cell function in my UITableView. The tableview has a couple of cells, each of which contains a button.
When I scroll, the cell is recreated, and new buttons begin to overlap old buttons (until I have a bunch of the same buttons in the same cell). I've heard that your supposed to use the removeFromSuperview function to fix this, but i'm not really sure how to do it.
Here is a picture of my app:
And here is the cellForRowAtIndexPath (where the problem is occurring)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath)
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Sample Item"
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
let actionButton = YSSegmentedControl(
frame: CGRect.zero,
titles: [
"No",
"Yes"
])
The reason you are seeing multiple buttons appear is because the cellForRowAtIndexPath: method is called every time a new table cell is needed. Since you are likely creating the button in that method body, it is getting recreated every time the cell is reused and you'll see them stack on top like that. The correct way to use dequeueReusableCell: with custom elements is to create a subclass of a UITableViewCell and set that as the class of your table cell in your storyboard. Then when you call dequeueReusableCell: you'll get a copy of your subclass that will contain all of your custom code. You will need to do a type conversion to get access to any of that custom code like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
if let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as? MyCustomCellClass {
cell.nameLabel.text = "Sample item"
}
// This return path should never get hit and is here only as a typecast failure fallback
return tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath);
}
Your custom cell subclass would then look something like this:
class MyCustomCellClass: UITableViewCell {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var actionButton: UIButton!
#IBAction func actionButtonPressed(_ sender: UIButton) {
//Do something when this action button is pressed
}
}
You can add new label/button in cellForRowAtIndexPath, but you need to make sure there is no existing label/button before you create and add new ones. One way to do it is to set a tag to the label/button, and before you generate new label/button, check if the view with the tag is already in the cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if let label = cell.viewWithTag(111) as? UILabel
{
label.text = "Second Labels"
}
else{
let label = UILabel()
label.tag = 111
label.text = "First Labels"
cell.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.frame = CGRect(x:0, y:10, width: 100, height:30)
}
return cell
}

Is a UITableViewCell and its outlets initialized after `dequeueReusableCellWithIdentifier`?

Is it save to access an outlet of a custom UITableViewCell right after instantiating it with dequeueReusableCellWithIdentifier?
E.g.
class MyCell: UITableViewCell {
#IBOutlet weak var myImageView: UIImageView!
var image: UIImage?
override func awakeFromNib() {
update()
}
func update() {
myImageView.image = image
}
}
class MyViewController: UIView() {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as! MyCell
cell.image = UIImage(...)
cell.update()
}
}
I have used this implementation a lot but very rarely (<0.001%) I get a crash report pointing to line myImageView.image = image.
UPDATE:
So far the crashes have been observed only for one specific implementation where 1 outlet is linked to many UIImageView() in custom cells because they share the same class.
The simple method dequeueReusableCellWithIdentifier: returns an optional which is not safe.
Use this method instead which is safe because it returns an non-optional cell
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier",
forIndexPath: indexPath) as! MyCell
Since the image property of an UIImageView object can be nil it's recommended to declare related UIImage properties as optional (?) rather than implicit unwrapped optional (!) without the default initializer (())

How to set UITableViewCell's imageView?

In the tableView(tableView:, cellForRowAtIndexPath:) -> UITableViewCell method, how to set the optional imageView through the cell?
cell!.imageView?.image = someLoadedImage in this case, if the imageView: UIImageView? property of the cell is nil when constructed, then the assignment will be failed, right?
According to The "Swift Programming Guide", john.residence?.address = someAddress, "In this example, the attempt to set the address property of john.residence will fail, because john.residence is currently nil" (Optional Chaining Chapter).
class Residence {
...
var address: Address?
}
class Person {
var residence: Residence?
}
let john = Person()
let someAddress = Address()
john.residence?.address = someAddress // will fail
here is the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier(simpleTableIdentifier)
as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Default, reuseIdentifier: simpleTableIdentifier)
}
let image = UIImage(named: "star")
let highlightedImage = UIImage(named: "star2")
cell!.imageView?.image = image // can compile and run
cell!.imageView?.highlightedImage = highlightedImage
cell?.textLabel!.text = dwarves[indexPath.row]
return cell!
}
We have reason to check if the cell is nil because if there is no available reusable cell for us in the queue to use at this time we have to create a new one.

How to correctly cast to subclass in Swift?

I have a UITableView with a lot of different cells, based on whats in the content array of the datasource they should show custom content.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = nil
let objectAtIndexPath: AnyObject = contentArray![indexPath.row]
if let questionText = objectAtIndexPath as? String {
cell = tableView.dequeueReusableCellWithIdentifier("questionCell", forIndexPath: indexPath) as QuestionTableViewCell
cell.customLabel.text = "test"
}
return cell!
}
Here I get the error that
UITableViewCell does not have the attribute customLabel
which QuestionTableViewCell does have. Whats wrong with my cast to QuestionTableViewCell?
The problem is not your cast but your declaration of cell. You declared it as an optional UITableViewCell and that declaration remains forever - and is all that the compiler knows.
Thus you must cast at the point of the call to customLabel. Instead of this:
cell.customLabel.text = "test"
You need this:
(cell as QuestionTableViewCell).customLabel.text = "test"
You could make this easier on yourself by declaring a different variable (since you know that in this particular case your cell will be a QuestionTableViewCell), but as long as you are going to have just one variable, cell, you will have to constantly cast it to whatever class you believe it really will be. Personally, I would have written something more like this, exactly to avoid that repeated casting:
if let questionText = objectAtIndexPath as? String {
let qtv = tableView.dequeueReusableCellWithIdentifier("questionCell", forIndexPath: indexPath) as QuestionTableViewCell
qtv.customLabel.text = "test"
cell = qtv
}
The problem is this var cell : UITableViewCell? = nil. You declare it as UITableViewCell? and it has that type forever.
You can declare another variable
let questionCell = cell as! QuestionTableViewCell
questionCell.customLabel.text = "test"
you can do any one of the following:
replace : cell.customLabel.text = "test"
with
cell?.customLabel.text = "text1"
change var cell : UITableView? = nil to var cell : UITableView!

Resources