How to correctly cast to subclass in Swift? - ios

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!

Related

Swift: Set custom UITableViewCell class inside if statement

I'm a Swift newbie and struggling to do something pretty simple.
I want to change the class of a tableViewCell when its tapped. After a lot of Googling I'm now trying to do this by setting a boolean flag (in a dict) and checking the value to determine which class to use.
I've come unstuck with Swift basics of trying to set a variable inside an if statement:
// I think I need to instantiate the cell variable here to be used inside
// and after the if statement but don't know what class type to use.
// I've tried lots of "var cell: xxx = yyy" variations but no luck
if selectedRows[indexPath.row] == true {
let cell = tableView.dequeueReusableCellWithIdentifier("Tier3CellExpanded", forIndexPath: indexPath) as! Tier3CellExpanded
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("Tier3Cell", forIndexPath: indexPath) as! Tier3TableViewCell
}
let image = UIImage(named: entry.thumbnail)
cell.thumbImageView.image = image
cell.busNameLabel.text = entry.busName
cell.busAddressLabel.text = entry.address
return cell
If anyone could point me in the right direction that would be great.
I'm just expanding on Charles A. 's answer to show you how to declare cell outside the if statement but still use 2 different cell types.
//All shared properities would belong to this class
var cell: MySuperclassCellsInheritFrom
if selectedRows[indexPath.row] {
cell = tableView.dequeueReusableCellWithIdentifier("Tier3CellExpanded", forIndexPath: indexPath) as! Tier3CellExpanded
if let expandedCell = cell as? Tier3CellExpanded {
//Set properties specific to Tier3CellExpanded
}
}
else {
cell = tableView.dequeueReusableCellWithIdentifier("Tier3Cell", forIndexPath: indexPath) as! Tier3TableViewCell
if let regularCell = cell as? Tier3TableViewCell {
//Set properties specific to Tier3TableViewCell
}
}
// Configure cell
// Properties that both subclasses share can be set here
return cell
This is possible since we declared cell as UITableViewCell and then cast it after dequeing with identifiers. The cast is possible because the cells you are dequeuing are subclasses of UITableViewCell. So after casting you can now set all of that subclasses individual properties.
This method is useful in case there is other code that you want to apply to both cells that you won't need to duplicate in each if statement such as backgroundColor changes or other base UITableViewCell properties.
You can try like this
if selectedRows[indexPath.row] == true {
let cell = tableView.dequeueReusableCellWithIdentifier("Tier3CellExpanded", forIndexPath: indexPath) as! Tier3CellExpanded
let image = UIImage(named: entry.thumbnail)
cell.thumbImageView.image = image
cell.busNameLabel.text = entry.busName
cell.busAddressLabel.text = entry.address
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("Tier3Cell", forIndexPath: indexPath) as! Tier3TableViewCell
let image = UIImage(named: entry.thumbnail)
cell.thumbImageView.image = image
cell.busNameLabel.text = entry.busName
cell.busAddressLabel.text = entry.address
return cell
}
In your code you are declaring a constant in the if block and another in the else block (that's what the let keyword does), so those will go out of scope immediately after you've set them. Do you have another variable outside your if statement called cell?
I would expect the code to look something like:
let cell: SomeCellType
if selectedRows[indexPath.row] {
cell = ...
}
else {
cell = ...
}
// Configure cell
return cell

swift variable type won't change after downcast if the variable has been declared?

I intended to downcast a UITableViewCell to different subclasses based on which section it is in.
Suppose a subclass of UITableViewCell is CellOne and it has a var nameLabel. In one case, I downcast (as!) the UITableViewCell returned by dequeueReusableCellWithIdentifier(_:_:) to CellOne and assigned it to cell, a variable declared as var cell = UITableViewCell() outside the switch block.
But after that, cell can't access nameLabel that is held by CellOne instance. I got the error message: "Value of type 'UITableViewCell' has no member nameLabel".
So it seems cell has not been downcasted to UITableViewCell's subclass.
Is there any way that cell can access nameLabel (after declared outside the block)? Can a variable be downcasted to another type by assigning a subclass instance to it after it has been declared?
Thank you very much for your time.
Here is my code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell()
switch indexPath.section {
case 0:
cell = tableView.dequeueReusableCellWithIdentifier("cellOne", forIndexPath: indexPath) as! CellOne
cell.nameLabel.text = "one" // Error: Value of type 'UITableViewCell' has no member 'nameLabel'
case 1:
cell = tableView.dequeueReusableCellWithIdentifier("cellTwo", forIndexPath: indexPath) as! CellTwo
...... // Other cases
}
return cell
}
And code for CellOne:
class CellOne: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
......
}
The problem is where you declared the variable cell you specifically said that its a UITableViewCell,so you will have issue to downcast it as yourCustomCell.
change this :
var cell = UITableViewCell()
Based on your comment you want to use multiple custom cells
I think you should declare those customs cells within the case code block
switch indexPath.section
{
case 0:
let cell = tableView.dequeueReusableCellWithIdentifier("cellOne", forIndexPath: indexPath) as! CellOne
cell.nameLabel.text = "one"
case 1:
let secondCell = tableView.dequeueReusableCellWithIdentifier("cellTwo", forIndexPath: indexPath) as! CellTwo
// do whatever you want
}
I find it's better to make your cells more generic to they can work in more than one 'section'. ie. instead of 'CellOne' create your cell as 'NameCell' which can be used across all sections of your table.
Cells should be reusable.
let nameCell = tableView.dequeueReusableCellWithIdentifier("nameCell") as! NameCell
switch indexPath.section{
case 0:
nameCell.nameLabel.text = "name1"
break
case 1:
nameCell.nameLabel.text = "name2"
break
...
default:
...
}

Switching between custom cells in Swift

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.

Overriding UITableView in Parse with Swift

All,
In swift while using Parse as my backend, I have created a class which inherits from PFQueryTableViewController. I see my data going into a tableview - thats fine.
I am trying to customise my cells a bit, and I overriding CellForRowAtIndexPath - like below :
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell!
{
var cellIdentifier = "eventCell"
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? PFTableViewCell
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: cellIdentifier)
}
// configure cell
var CellTitle = object.objectForKey("Title") as String
cell?.textLabel = CellTitle
}
}
As the object using comes back as [AnyObject] in swift, I have created a variable and I have casted it to a string. And then I am trying to show that string in Cell.textlabel.
I am getting the error : Cannot assign to the result of this expression.
Can anyone please show me the right direction on this.
I think the problem is that you're attempting to assign a String directly to cell?.textLabel UILabel. Instead try changing this line:
cell?.textLabel = CellTitle
to this
cell.textLabel?.text = CellTitle
so you're setting the text property of the UILabel instead.

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