Switching between custom cells in Swift - ios

I'm trying to switch between two custom cell-classes in swift, but I can't seem to figure out how to return the cell.
My code looks like this, and the error is in the last line:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
if istrue{
var cell: CustomTableCell = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell
let data = myList[indexPath.row] as Model
cell.customLabel.text = data.username
cell.dateLabel.text = printDate(data.date)
return cell
}else{
var cell: CustomTableCell2 = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell2
let data = myList[indexPath.row] as Model
cell.titleLabel.text = data.username
cell.dateLabel2.text = printDate(data.date)
return cell
}
}return nil
I've also tried to "return cell" in the last line and to delete the other two lines of "return cell" in the if- and else-statements but that didn't work, it just gives me the error saying "cell" is an unresolved identifier.
I've never done this before so I'm not sure if this is the right way of tackling the problem either.
Any suggestions on how to proceed would be appreciated.

Define a variable of UITableViewCell type and initialize it in both the if and the else branches, then use it as the return value:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var retCell: UITableViewCell
if istrue{
var cell: CustomTableCell = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell
let data = myList[indexPath.row] as Model
cell.customLabel.text = data.username
cell.dateLabel.text = printDate(data.date)
retCell = cell
}else{
var cell: CustomTableCell2 = self.tv.dequeueReusableCellWithIdentifier("cell") as CustomTableCell2
let data = myList[indexPath.row] as Model
cell.titleLabel.text = data.username
cell.dateLabel2.text = printDate(data.date)
retCell = cell
}
return retCell
}
Note that you cannot return nil because the return type of this method is a non-optional UITableViewCell, so it must always be an instance of (a class derived from) UITableViewCell.
Alternatively, you can just return the cell as you do on each of the if and else branches, but remove the ending return out of the if/else scope - it's not needed. Moreover, in your code it is also misplaced because out of the method scope.
Personal note: in functions I usually avoid return statements in the middle of the body, preferring a single exit path at the end - that's just a personal preference, so feel free to choose the one that you like more.

Related

Swift 2.0 two table views in one view controller cellForRowIndexPath

Ive seen a few questions here about this but no defitnive answer Im using an up to date version of Xcode and swift...
Im trying to work with two table views in one view controller here is my cellForRowIndexPath function
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if(tableView == self.allTableView){
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! BMRadioAllTableViewCell
cell.mixImage.image = mixPhotoArray[indexPath.item]
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
cell.mixDateLabel.text = dateFormatter.stringFromDate(mixDateArray[indexPath.item])
}
if(tableView == self.featuredTableView){
// SET UP THE NEXT TABLE VIEW
}
return cell
}
Im getting the error "Value of type UITableViewCell has no member "xxxx" so its obviously not registering the change to cell I'm making in the if statement.
I have tried various other ways, like declaring the variable within the if statement and returning it there. But you get the error "Missing return in a function expected to return UITableViewCell" since you need to get it outside the if statement.
If you have two different cell types for two different tables, you should make your code look something like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(tableView == self.allTableView){
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! BMRadioAllTableViewCell
cell.mixImage.image = mixPhotoArray[indexPath.item]
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
cell.mixDateLabel.text = dateFormatter.stringFromDate(mixDateArray[indexPath.item])
return cell;
} else {
var cell = ...
// SET UP THE NEXT TABLE VIEW
return cell
}
}
There's no need to have a single generic cell variable that handles both tables.
The error doesn't really have to do with you trying to configure two table views.
Even though you're casting the result of dequeueReusableCellWithIdentifier:forIndexPath: to BMRadioAllTableViewCell, you are then assigning it to a variable of type UITableViewCell!. As such, it will be a compiler error for you to access fields of BMRadioAllTableViewCell.
You would either need to change the cell type to BMRadioAllTableViewCell, or have a locally scoped variable of the correct type that you configure and then assign to cell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell!
if(tableView == self.allTableView){
let bmRadioAllCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! BMRadioAllTableViewCell
bmRadioAllCell.mixImage.image = mixPhotoArray[indexPath.item]
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
bmRadioAllCell.mixDateLabel.text = dateFormatter.stringFromDate(mixDateArray[indexPath.item])
cell = bmRadioAllCell
}
if(tableView == self.featuredTableView){
// SET UP THE NEXT TABLE VIEW
}
return cell
}

reloadRowsAtIndexPaths doesn't update my cell data

I have a UITableView in my ViewController.
One of the cell could be tap into another TableViewController to allow select a value.
I want to update my cell after back from the callee ViewController.
right now, i could pass back the selected value by delegate.
However, i tried following way, none of them works.
self.mainTable.reloadData()
dispatch_async(dispatch_get_main_queue()) {
self.mainTable.reloadData()
}
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
self.mainTable.endUpdates()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
was called and executed without error.
but the UI just doesn't change
here is the way I update value in cellForRowAtIndexPath
if let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell! {
currentCell.textLabel?.text = address
return currentCell
}
Here is my cellForRowAtIndexPath -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let id = "Cell"
println(indexPath)
if indexPath.row == 1 {
var cell = tableView.dequeueReusableCellWithIdentifier(id) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: id)
cell?.contentMode = UIViewContentMode.Center
cell?.selectionStyle = UITableViewCellSelectionStyle.None
cell?.contentView.addSubview(mapView!)
}
return cell!
}else{
let cell = UITableViewCell()
cell.textLabel?.text = self.address
return cell
}
}
Here is the delegate method -
func passBackSelectedAddress(address: String) {
self.address = address
var indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.mainTable.endUpdates()
}
My fix:
After more debug, i find the cause,
the self.address value is updated in delegate, however it roll back to previous value in cellForRowAtIndexPath.
I change the property to a static property, then resolve the problem.
I'm not sure what's wrong with instance property, and why it reverses back.
static var _address:String = ""
It seems like you're trying to grab a cell from the UITableView and then update the textLabel value that way. However, UITableView and UITableViewCell are not meant to be updated in this way. Instead, store the value of address in your class and update this value when the delegate calls back into your class. If cellForRowAtIndexPath constructs the UITableViewCell with the value of self.address, calling mainTable.reloadData() after should update the cell to the new value.
For example:
var address: String
func delegateCompleted(address: String) {
self.address = address
self.mainTable.reloadData()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(<your identifier>)
if (indexPath == <your address cell indexPath>) {
let textLabel = <get your textLabel from the cell>
textLabel?.text = self.address
}
return cell
}
Your cellForRowAtIndexPath has some problems -
You are using the same re-use identifier for different types of cell (one with a map, one without)
When you allocate the table view cell for the other row, you don't include the re-use identifier.
You have no way of referring to the map view that you are adding after the method exits because you don't keep a reference.
If you are using a storyboard then you should create the appropriate prototype cells and subclass(es) and assign the relevant cell reuse ids. If you aren't then I suggest you create a cell subclass and register the classes against the reuse identifiers. Your cellForRowAtIndexPath will then look something like -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var returnCell:UITableViewCell
if indexPath.row == 1 {
var myMapCell = tableView.dequeueReusableCellWithIdentifier("mapCell", forIndexPath:indexPath) as MYMapCell
myMapCell.contentMode = UIViewContentMode.Center
myMapCell.selectionStyle = UITableViewCellSelectionStyle.None
// Set the properties for a map view in the cell rather than assigning adding an existing map view
returnCell=myMapCell
}else{
returnCell = tableView.dequeueReusableCellWithIdentifier("addressCell", forIndexPath:indexPath)
returnCell.textLabel?.text = self.address
}
return returnCell;
}

How can I specify both section and row in the indexPath call to my array?

Previously, I was populating my TableView by using this code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cellIdentifier = "Cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath:
indexPath) as CustomTableViewCell
let entry = entries[indexPath.row]
cell.entryLabel!.text = entry.labelOne
cell.entryDayLabel!.text = entry.day
cell.entryDateLabel!.text = entry.date
return cell
}
I've since added sections to my Table, and I'm having trouble figuring out how to specify both the section and the row in this call to my array.
I've tried
let entry = entries[indexPath.section]
and I've tried
let entry = entries[indexPath.row + indexPath.section]
But neither work correctly.
Is there a proper way to do this that I'm missing? Any help is greatly appreciated. Thanks!
indexPath.section
will return the current section.
What you usually want to do then, is add some conditional logic like:
if(indexPath.section == 0)
{
entry = entries[indexPath.row]
}
else if(indexPath.section == 1)
{
entry = whateverDataSource[indexPath.row]
}
Are you using two different tables and/or cell types for your sections?

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!

EXC_BAD_INSTRUCTION breakpoint when starting app xCode

I have a tableview in my app and when I start my app it crashes on the following function.
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
}
It crashes on the line of var cell
It gives the following error:
I can't figure out what's wrong with my code.
The whole function:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
let data: NSManagedObject = mylist[ip.row] as NSManagedObject
cell.textLabel.text = data.valueForKeyPath("voornaam") as String
cell.detailTextLabel.text = data.valueForKeyPath("achternaam") as String
return cell
}
EDIT:
What I got now:(Still gives the same error)
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
// Configure the cell...
let cellId: NSString = "Cell"
var cell: UITableViewCell? = tableView?.dequeueReusableCellWithIdentifier(cellId) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
}
let data: NSManagedObject = mylist[indexPath.row] as NSManagedObject
cell!.textLabel.text = data.valueForKey("voornaam") as String
cell!.detailTextLabel.text = data.valueForKey("achternaam") as String
//cell!.textLabel.text = "Hoi"
return cell
}
This is happening because the as operator is defined to cast an object to a given type and crash if the conversion fails. In this case, the call to dequeue returns nil the first time you call it. You need to use the as? operator, which will attempt to cast the given object to a type, and return an optional that has a value only if the conversion succeeded:
var cell: UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellId) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellId)
}
...
Because cell is now an optional value, use cell! when you want to call methods on it to force-unwrap the UITableViewCell inside it.
Additionally, your code had a second problem: it never created a fresh cell. dequeue will return nil the first time it's called on your table view. You need to instantiate a new UITableViewCell as in my code sample and then return it from the cellFor... method. The table view will then save the cell and return it on future calls to dequeue.
First off, why are you doing an optional binding on line if let ip = indexPath? This argument is not optional and you don't need to do optional binding or unwrap it. But this shouldn't cause your code to crash.
Remove your let data line and assign literal strings to your cells and see if it still crashes.
May I suggest that you check to see if you set the tableview's delegates? I made that mistake once in the flurry of setting everything else up.
Perhaps it is too late but I like to share my experience. I had similar error as I copied the entire code from another project. So I think the variables and functions won't be recognised so I had to drag them (cntr+drag) then it is solved.
Sorry if I couldn't explain better. I am new this.

Resources