How toaccess properties / values from a UITableViewCell? - ios

I want to access my view objects from UITableViewCell, but I cannot. I can't cast my cell object because I only have the string UITableViewCell. For example "ClientTableViewCell".
How can I access the views and objects in the cell without casting?
I tried to do as follows:
let cell = (val as AnyObject).dequeueReusableCell(withIdentifier: "ClientTableViewCell") as! UITableViewCell
I can get a cell like this but I can't reach the contents.Itried mirror reflection like this:
let mirror = Mirror(reflecting: cell)
but child label and value comes some optional

guard let cell: ClientTableViewCell = tableView.dequeueReusableCell(withIdentifier: String(describing: ClientTableViewCell.self), for: indexPath) as? ClientTableViewCell else { return UITableViewCell() }
try this

Related

Loading table view with data with 2 different prototype cells

I am trying to populate a tableView with different prototype cells at different times. The problem is any code after let cell = tableView.dequeueReusableCell(withIdentifier: "MeArticlesCell") as? MeArticlesCell else {return UITableViewCell()} isn't being called and I can't figure out why. Im returning the correct amount of rows.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(postsSelected){
guard let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as? feedMessagesCell else {return UITableViewCell()}
//the code in this part loads posts fine
} else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as? feedMessagesCell else {return UITableViewCell()}
//this part never gets called
}
}
Basically your guard statement fails and goes into the else part.
guard let cell = tableView.dequeueReusableCell(withIdentifier: "messageCell") as? feedMessagesCell else { // this fails
// enters here
return UITableViewCell()
// exist the function - nothing will be run after
}
My guess is it fails to cast your it as a feedMessagesCell, so tableView.dequeueReusableCell(withIdentifier: "messageCell") as? feedMessagesCell makes cell to be nil ( and then enter the else part )
You're using dequeueReusableCell(withIdentifier:) method to init cell which return nil if it doesn't find any reusable cell. More here
A UITableViewCell object with the associated identifier or nil if no
such object exists in the reusable-cell queue.
You should use dequeueReusableCell(withIdentifier:for:) which init a new cell if it doesn't find any cell for reuse. More here
A UITableViewCell object with the associated reuse identifier. This
method always returns a valid cell.

How to change data in cell in dynamic UITableView xcode project?

I have a Dynamic Prototypes Table view that has different cells, I'm adding the cells to the table view and I want to change their content. All the tutorials I find is for a tableview with only one type of cell, but I have 8 different types. How would I change their content (ie, textfields etc) and how would I get actions from them back to the main tableview controller to do some business logic? (ie button pressed etc)
What I did is:
I created a costume class for each cell type and connected them under customClass, class field.
I attached the textfields etc, actions and references to these classes.
this is my cellAtRow function I assume I would change it in this function somehow?
or reference the classes from here?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print ("indexPath: ", indexPath)
print ("indexPath: ", indexPath[0])
print ("-------")
if (sectionsData[indexPath[0]] == "header") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "description") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerInfoCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "diagnoses") {
let cell = tableView.dequeueReusableCell(withIdentifier: "diagnosisCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "perscription") {
let cell = tableView.dequeueReusableCell(withIdentifier: "perscriptionCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "notes") {
let cell = tableView.dequeueReusableCell(withIdentifier: "notesCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addFaxHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "addFaxCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addFax") {
let cell = tableView.dequeueReusableCell(withIdentifier: "emailNameCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addEmailHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "addEmailCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "addEmails") {
let cell = tableView.dequeueReusableCell(withIdentifier: "emailNameCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "givePermissionHeadline") {
let cell = tableView.dequeueReusableCell(withIdentifier: "permissionCell", for: indexPath)
return cell
} else if (sectionsData[indexPath[0]] == "select answer") {
let cell = tableView.dequeueReusableCell(withIdentifier: "selectAnswerCell", for: indexPath)
return cell
}
You need to use
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as! HeaderTableViewCell
to call cell.yourTextField.text for example
You have to cast your cells to the class they belong. On the second line of the code block you can see an example of this.
if (sectionsData[indexPath[0]] == "header") {
let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as! HeaderTableViewCell
cell.titleLbl.text = "Title"
cell.delegate = self // To receive actions back
return cell
}
. . . // More of the same
// default return
To send calls back to your main view controller you can add protocols to your cells like so:
protocol HeadTableViewCellProcol{
func bttnPressed()
}
class HeadTableViewCell: UITableViewCell{
var delegate: HeadTableViewCellProcol?
#IBAction func bttnPressedInCell(){
delegate?.bttnPressed()
}
}
The this of this protocols like the protocols you had to implement for your UITableView. You will also have to implement these protocols in your main VC.
You need to cast UITableViewCell into your dynamic cell class. You can try the following:
guard let cell = tableView. dequeueReusableCell(withIdentifier: "perscription", for: indexPath) as? PerscriptionTableViewCell else { return UITableViewCell() }
cell.setupCell() //You have access to cell's public funcs and vars now
return cell
Using optional unwrapping, you can be sure that your app is likely to be safe from type casting crashes.
As the Apple documentation says, the return type of the dequeueReusableCell is a UITableViewCell.
Apple Documentation
Return Value: A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.
Your custom cells classes should inherit from UITableViewCell and to be able to use an instance of your custom cell you need to cast the returning UITableViewCell of the dequeReusableCell into your desire custom cell type.
let cell = tableView.dequeueReusableCell(withIdentifier: "customCellIdentifierCell", for: indexPath) as! YourCutsomTableViewCell
For customizing, every cell is responsible for his own configuration. You should have a function (you can use protocols or inherit from a superclass) and inside the cellForRowAtIndexPath, after casting it, call the setup function.
customCell.setup() //you can add some parameters if its needed

convert String content to UITableViewCell type in swift

Can you convert the content of a Swift 3 String into a type through a specific function? I'll include an example:
I've declared multiple UITableViewCell classes as follows:
class ScrollFeedCell : UITableViewCell {...}
class AdCell : UITableViewCell {...}
class MovieCell : UITableViewCell {...}
I want to declare the conversion function, in my view controller, as follows:
func convert(String) -> Any {}
Then I want to use the following:
class TableView : UITableViewController {
let typeArray = [String]
override func viewDidLoad() {
//add a huge ton of strings into the typeArray
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
let c = typeArray[indexPath.section]
if c == "ScrollFeddCell" || c == "AdCell" || c == "MovieCell" {
cell = tableView.dequeueReusableCell(withIdentifier: content[indexPath.section], for: indexPath) as! convert(c)
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "CategoryScrollFeed_Cell", for: indexPath)
}
return cell
}
}
I do not think this is possible. Even if it is somehow possible, I think it is going to involve lots of dirty tricks which is not really worth it in this situation.
In fact, the only place you used your imaginary convert method is here:
cell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! convert(c)
^^^^^^^^^^
Why do you want to cast it to the right type? Since this is very dynamic, the compiler can't know what members will the type returned by convert have. Basically, too dynamic. It is not useful to cast it to the right type here.
The enclosing method returns a UITableViewCell anyway, so you can just return the return value of dequeueResuableCell without the compiler complaining.
"But I want to configure the cell after dequeuing it though..." you might say.
Well, you are going to configure a ScrollFeedCell in a different way from a MovieCell, right? So you can't just write all the configuration code after this line:
cell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! convert(c)
You still have to write an if statement and check whether the cell is a MovieCell, ScrollFeedCell or AdCell. So why not delete the above line and do this instead:
if c == "ScrollFeedCell" {
let scrollFeedCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! ScrollFeedCell
// configure cell here
cell = scrollFeedCell
} else if c == "AdCell" {
let adCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! AdCell
// configure cell here
cell = adCell
} else if c == "MovieCell" {
let movieCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! MovieCell
// configure cell here
cell = movieCell
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "CategoryScrollFeed_Cell", for: indexPath)
}
Edit:
Try this:
if c == "ScrollFeedCell" {
let scrollFeedCell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath) as! ScrollFeedCell
scrollFeedCell.delegate = self
cell = scrollFeedCell
} else if c == "AdCell" || c == "MovieCell" { // add your other cell types here.
cell = tableView.dequeueReusableCell(withIdentifier:
content[indexPath.section], for: indexPath)
} else {
cell = tableView.dequeueReusableCell(withIdentifier: "CategoryScrollFeed_Cell", for: indexPath)
}
Please consider what you want to do is necessary or not. Why you want to convert them to specific cell type? It will work just keep the cell as UITableViewCell and return it. If you have specific actions for different cells, you should separate the if cases:
if c == "ScrollFeddCell" {
cell = tableView.dequeueReusableCell(withIdentifier: c, for: indexPath) as! ScrollFeddCell
//cell.doSomethingForScorll()
}
else {
cell = tableView.dequeueReusableCell(withIdentifier: c, for: indexPath)
//do nothing for common cells.
}
//....
A little late, but for those looking for an answer:
I know what you want, and I agree with your need.
In my case, I need to do this because in my app, I not only receive the data from the server, but ALSO the layout of such data inside the cell. So far, I haven't been able to find a solution. In your case, it seems a little easier:
// 1. Create a protocol with the configuring method
protocol configureCellProtocol
{
func configure(cell:MyData)
}
// 2. Add this protocol to the 8 classes derived from UITableViewCell that define your cells
// 3. Cast the reusable cell to this protocol: (note that you have to do a double cast,
// both to configureCellProtocol and UITableViewCell, (that's what the & is for) otherwise,
// you won't be able to return the configured cell
let thisCell=tableView.dequeReusableCell(
withReuseIdentifier: cellClass, for: indexPath) as! UITableViewCell & configureCellProtocol
// 4. then you can call the method like this:
thisCell.configure(cell:configurationData)
// which in turn will call the specific method in each class. Then you have to define the configure
// method in each class, otherwise you'll get a crash at runtime. Do what you need to configure
// your cells in each class in this method
//
in step 3, cellClass is a String, which in turn is the class name that you register. In your case, you would have to select it from an array according to the criteria that makes every cell different

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 - Type Casting

I am building a custom UITableView with custom cells.
Each of the custom cells are a subclass of FormItemTableViewCell
I am attempting to populate the cell data in cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = FormItemTableViewCell();
if(indexPath.row == 1){
cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
} else {
cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
}
cell.questionLabel.text = "What is the meaning of life?";
return cell
}
How do I access the elements in the subclass?
For example: TwoOptionTableViewCell has a segControl
while the OneTextFieldTableViewCell has a answerTextField
There are some decent answers in this question but most of them have one bad thing in common, they force unwrapped optionals, which you should avoid as much as you can (pretty much the only acceptable place to use them is in IBOutlets)
This is what I think is the best way to handle this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("Identifier", forIndexPath: indexPath) as? FormItemTableViewCell else {
fatalError("Cell is not of kind FormItemTableViewCell")
}
switch cell {
case let cell as TwoOptionTableViewCell where indexPath.row == 1:
// Configure cell, which is an object of class TwoOptionTableViewCell, but only when we are in row 1
break
case let cell as TwoOptionTableViewCell:
// Configure cell, which is an object of class TwoOptionTableViewCell, when the row is anything but 1
break
case let cell as OneTextFieldTableViewCell:
// Configure cell, which is an object of class OneTextFieldTableViewCell
break
case _: print("The cell \(cell) didn't match any patterns: \(indexPath)")
}
cell.questionLabel.text = "What is the meaning of life?";
return cell
}
Now let me walk you through the reasons I think it's the best way.
First of all, it doesn't force unwraps any optionals, everything is unwrapped nicely in the switch case.
It dequeues your cell from the table (something you should always do) and makes sure it's a subclass of FormItemTableViewCell, otherwise it throws a fatal error.
By using a switch case, it casts cell into the class you need, and at the same time it checks if it's the index path you want. So if you want to share some logic in different rows that share a class, you can compare indexPath.row to multiple values. If you don't use the where clause, it will use the same logic in all places where it finds a cell with that class.
Do note that you will need to add some logic to get the desired identifier depending on the row.
You can use one of the two approaches:
1) The best way:
if(indexPath.row == 1) {
let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
// the type of cell is TwoOptionTableViewCell. Configure it here.
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
// the type of cell is TwoOptionTableViewCell. Configure it here.
return cell
}
2) If you declare cell just once, as a superclass, then you have to downcast it like this.
var cell: FormItemTableViewCell
cell = ... // dequeue and assign the cell like you do in your code.
if let twoOptionCell = cell as? TwoOptionTableViewCell
{
// configure twoOptionCell
}
else if let oneTextFieldCell = cell as? OneTextFieldTableViewCell
{
// configure oneTextFieldCell
}
return cell
This is more verbose, once you add the code to dequeue the cell. So I personally prefer and recommend the first approach.
If I understand correctly, you want to keep main declaration of cell as FormItemTableViewCell to access common properties.
You can create a new variable and assign it the casted version.
Do your stuff with this instance as this is a class object it will point to same reference.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = FormItemTableViewCell();
// this can be replaced with below line as I don't see the purpose of creating an instance here while you use dequeue below.
// var cell: FormItemTableViewCell!
if(indexPath.row == 1){
cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath);
let tempCell = cell as! TwoOptionTableViewCell;
// access members of TwoOptionTableViewCell on tempCell
tempCell.segControl.someProperty = 0;
} else {
cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath);
let tempCell = cell as! OneTextFieldTableViewCell;
// access members of OneTextFieldTableViewCell on tempCell
tempCell.answerTextField.text = "42";
}
cell.questionLabel.text = "What is the meaning of life?";
return cell
}
You're going to have to conditionally cast them in that case. I like using Enums for Rows/Sections instead of == 1 (depending on how your TableView is setup), but basically you'll want to do the following:
if indexPath.row == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
// Note that we cast the cell to TwoOptionTableViewCell
// access `segControl` here
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
// This cell we cast to OneTextFieldTableViewCell.
// access `answerTextField` here
return cell
}
What you were doing was defining the cell as FormItemTableViewCell, so subsequent accesses would only know it in that form even though you explicitly cast it to a subclass during assignment.
As a side-note, you don't have to assign to the var as you did there, what you could do is let cell: FormItemTableViewCell. Then in the if-statements you could define new cells of the subclasses, operate on them, and then assign back to your original cell and then return that. This is useful if you're going to be performing the same operations on both cell types after the if statements (such as setting a background colour or something, regardless of which subclass you have).
Here is my favourite way of handling this situation:
enum CellTypes {
case TwoOption, OneTextField
init(row: Int) {
if row == 1 {
self = .TwoOption
} else {
self = .OneTextField
}
}
var reuseIdentifier: String {
switch self {
case .TwoOption: return "twoOptionReuseIdentifier"
case .OneTextField: return "oneTextFieldReuseIdentifier"
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: FormItemTableViewCell
let cellType = CellTypes(row: indexPath.row)
switch cellType {
case .TwoOption:
let twoOptionCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
// do stuff with the `segControl`
cell = twoOptionCell
case .OneTextField:
let textFieldCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
// do stuff with the `answerTextField`
cell = textFieldCell
}
// Here do something regardless of which CellType it is:
cell.questionLabel.text = "What is the meaning of life?"
return cell
}

Resources