I have a UITableView and a custom TableViewCell class, I am setting a non-null value in viewDidLoad method, but while setting the value to UITableViewCell in cellForRow method , the value magically becomes nil. I am unable to access the set variable from my custom UITableViewCell
Here is the snippet of code. Print statement in ViewDidLoad prints the value while the one in cellForRowAtIndexPath returns nil
override func viewDidLoad() {
super.viewDidLoad()
Docco360Util.shared().getDoctorsWithResultBlock { (doctorObjects, error) in
if let err = error{
print(err)
}else{
self.doctors=doctorObjects
print(doctorObjects![1].professionalHeader)//output is printed here
self.doctorTableView.reloadData()
}
}
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifierForUsers = "DoctorTableViewCell"
var cell = tableView.dequeueReusableCell(withIdentifier: identifierForUsers, for: indexPath) as! DoctorTableViewCell
if cell == nil{
cell=DoctorTableViewCell(style: .default, reuseIdentifier: identifierForUsers)
}
print(self.doctors[indexPath.row].professionalHeader)//output in nil here
cell.doctor=self.doctors[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DoctorTableViewCell", for: indexPath) as! DoctorTableViewCell
print(self.doctors[indexPath.row].professionalHeader)
cell.doctor = self.doctors[indexPath.row]
return cell
}
First, you should ensure that the closure of Docco360Util.shared().getDoctorsWithResultBlock is executed in the main (UI) thread. If not, you should put it into a DispatchQueue.main.async block.
Then, you should verify that the result block is executed (e.g. the print statement prints something). Btw, why do you call print(doctorObjects![1].professionalHeader) with index 1 and not 0? In tableView(tableView:cellForRowAt:) you element with index 0 - maybe this doctor is nil?
You could start by setting a breakpoint to your property declaration, a watchpoint, or implement a property observer (willSet) and add a breakpoint her, to check if somebody is messing up with your variables.
If all this does not help, you might want to post more code, expecially all lines of code where you write your self.doctors array.
I could finally fix it. The problem was in declaration of variables. I was using Objective-C header file for declarations. While declaring I declared them as "weak" instead of "retain" and that was causing the problem.
Related
in my View:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TransactionTableCell", for: indexPath) as! TransactionTableCell
let newItem = getTransactionsInSection(section: sectionHeader[indexPath.section])[indexPath.row]
cell.configure(item: newItem)
}
in my TransactionTableCell
func configure(item: TransactionModel) {
guard let withdrawalBonuses = item.withdrawalBonuses,
withdrawalBonuses < 0,
let accruedBonuses = item.accruedBonuses,
accruedBonuses > 0 else {
configureWithOneOperation(item)//shows one line of operation
return
}
//show 2 lines of operations
firstOperationAmountLabel.text = "+\(Int(accruedBonuses))"
secondOperationAmountLabel.text = "\(Int(withdrawalBonuses))"
}
When I scroll the cell , second operation line is appears in wrong cells where its shouldn't be, even If I reload my table , that also has this problem.
You should use prepareForReuse() method
Simply just clear data of your labels:
override func prepareForReuse() {
super.prepareForReuse()
firstOperationAmountLabel.text = nil
secondOperationAmountLabel.text = nil
}
There are few things to check here.
Make sure you reset all fields before configure a new cell.
If you have created a cell using xib or storyboard, make sure you haven't filled labels with static text.
Is your guard statements passing for every item?
Else block for guard configures cell with a single operation, Is it handling all ui elements in cell?
I've already looked at the post UITableView.reloadData() is not working. I'm not sure that it applies to my situation, but let me know if I'm wrong.
My app has a tableView. From the main viewController I am opening another viewController, creating a new object, and then passing that object back to the original viewController, where it is added to an array called timers. All of that is working fine. However, when I call tableView.reloadData() in didUnwindFromNewTimerVC() to display the updated contents of the timers array, nothing happens.
NOTE: I have verified that the timers array is updated with the new object. Its count increments, and I can access its members. Everything else in didUnwindFromNewTimerVC() executes normally. The tableView just isn't updating to reflect it.
Here is my code:
import UIKit
class TimerListScreen: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tabelView: UITableView!
var timers = [Timer]()
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tabelView.delegate = self
tabelView.dataSource = self
let tempTimer = Timer(timerLabel: "temp timer")
timers.append(tempTimer)
}
#IBAction func didUnwindFromNewTimerVC(_sender:UIStoryboardSegue){
guard let newTimerVC = _sender.source as? newTimerVC else{return}
newTimerVC.timer.setTimerLabel(timerLabel: newTimerVC.timerLabel.text!)
timers.append(newTimerVC.timer)
tableView.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as? TimerCell{
let timer = timers[indexPath.row]
cell.updateUI(Timer: timer)
return cell
}else{
return UITableViewCell()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 78
}
}
Thank you
Please note the spelling. There are two table view instances: the outlet tabelView and a (pointless) instance tableView.
Reload the data of the outlet
tabelView.reloadData()
and delete the declaration line of the second instance let tableView ....
However I'd recommend to rename the outlet to correctly spelled tableView (you might need to reconnect the outlet in Interface Builder).
And force unwrap the cell
let cell = tabelView.dequeueReusableCell(withIdentifier: "TimerCell", for: indexPath) as! TimerCell
and remove the if - else part. The code must not crash if everything is hooked up correctly in IB.
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
I have UITableView and the contect is Dynamic Prototype.
so I have 5 cells and each cell has it own identifier.
so when I try to return the value in (cellForRowAt)
it would let me.
would please help with that ?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (indexPath.section) == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as UITableViewCell!
// cell?.textLabel!.text = objectsArray[indexPath.section].sectionObjects[indexPath.row]
return cell!
}
else if (indexPath.section) == 2 {
let cell3 = tableView.dequeueReusableCell(withIdentifier: "cellThree") as UITableViewCell!
return cell3!
}
else if (indexPath.section) == 3 {
let cell4 = tableView.dequeueReusableCell(withIdentifier: "cellFour") as UITableViewCell!
return cell4!
}
else if (indexPath.section) == 4 {
let cell5 = tableView.dequeueReusableCell(withIdentifier: "cellFive") as UITableViewCell!
return cell5!
}
return cell!
}
Thanks !
New Update :-
so the error that is showing to me is (Use of unresolved identifier 'cell')
so when I return at the end (return cell!) it shows this error.However, if I deleted that line it shows me another error asking me to return a value
so bottomline I'm confised what value should I return at the end of (cellForRowAt).
Return values
This method can only return one cell each time it's called. The method will be called each time a new cell is about to appear on the screen. Take a look at the method signature:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
That UITableViewCell at the end is the expected return value and in this case it's a single cell.
The error
Let's take a look at your error:
Use of unresolved identifier 'cell'
"Unresolved identifier" means that it has no idea what "cell" is as it has not been declared in the current scope. But don't you declare cell in this method? Yes you do, but they are in a separate scope. Variable scope defines how long a variable lives and where it can be seen. You can tell when a new variable scope is declared when you see a new set of curly brackets {}, which each of your if statements declare. Variables declared within each set of curly brackets can only be seen by code contained within those brackets. Once execution leaves those brackets, those variables are gone. Let's take a look at what variables are viewable in the scope of your final return statement by removing the scopes created by your if statements:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cell!
}
Now it is clear that cell doesn't exist at the point of the return. You'll need to declare cell in the method's scope and give it some value. Please note that force unwrapping your cell optionals will likely lead to a crash at runtime as the method must not return nil. You'll want to create cells when tableView.dequeueReusableCell() returns nil.
EDIT: I just realized you aren't using the for indexPath parameter in dequeueReusableCellWithIdentifier. Use that, otherwise it's the wrong one. See:
https://developer.apple.com/documentation/uikit/uitableview/1614878-dequeuereusablecellwithidentifie
https://www.natashatherobot.com/ios-using-the-wrong-dequeuereusablecellwithidentifier/
#Nawaf you can't return multiple values from any function, hence you can't return multiple cells from one call of cellForRow. You may be misunderstanding how cellForRowAtIndexPath works. Right now your code is not populating anything in the cells.
Use of unresolved identifier 'cell'
Furthermore, your error can be occurring for multiple reasons. For one, check that your view controller is correctly assigned to the one in storyboard and that you have assigned a reuseIdentifier in storyboard.
See the links for more info:
https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614861-tableview?language=objc
How does cellForRowAtIndexPath work?
Also a side note for code quality: don't force cast to the cell because that can cause unintended errors. Use conditional binding instead:
guard let cell = tableView.dequeueReusableCellWithIdentifier(...) as? UITableViewCell else {
// What to do when the cast failed
}
Good luck :)
I am looking to captured image tap event on the first record of UITableView, when user taps I cell.imageAvtar I just want to capture that event.
This is the code I am using
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("details", forIndexPath: indexPath) as! AccountCell
if (indexPath.row == 0) {
(cell.contentView.viewWithTag(101) as! UIImageView).image = UIImage(named: "no_image_available.jpg")
}
return cell
}
But (cell.contentView.viewWithTag(101) is returning as nil.I have tried (cell.contentView.viewWithTag(100) tried (cell. imageAvtar.viewWithTag(101) as well.
check your imageView's tag in interface builder or storyboard if it is 0 make it 101 and retry..
you can also check
for subView in cell.contentView.subView {
print("subView tag --> \(subView.tag)!")
}
try this in your cellForRowAtIndexPath
Try,
cell.viewWithTag(101) or self.view.viewWithTag(101) if tag is unique(i.e. if you are not using this tag at other place).
Second thing you have to add gesture recognizer to capture event on it. How you come to know that it's returning nil ? It may not return nil. You are making another mistake. Make sure that no_image_available.jpg is properly available in project!
Another thing is make sure that you have set tag properly.
I have used IBOutlets as vadian and jrturton advised.
This is the working code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("details", forIndexPath: indexPath) as! AccountCell
if (indexPath.row == 0) { let tapGestureRecognizer = UITapGestureRecognizer(target:self, action:Selector("imageTapped:"))
cell.imageAvtar.userInteractionEnabled = true
cell.imageAvtar.addGestureRecognizer(tapGestureRecognizer)
}
}
func imageTapped(img: AnyObject)
{
print("event captured")
//your logic comes here
}