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
Related
I have a table view that is initialized with three rows when loaded. Later, I want to extract cell data (from labels) and use it when that specific cell is selected by the user.
Even though initialization works well and I can see all the data being displayed, the dequeueReusableCell in the didSelectRowAt methods returns empty cells, with no data.
What is the problem?
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? ArticleTableViewCell else {
fatalError("Whoopse, dequeueing the cell failed")
}
let title = cell.articleLabel.text
// Do other stuff
}
The title variable above will be empty, even though it's data is shown on the display.
Replace
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? ArticleTableViewCell else {
fatalError("Whoopse, dequeueing the cell failed")
}
with ( not recommended )
guard let cell = tableView.cellForRow(at:indexPath) as? ArticleTableViewCell else {
fatalError("Whoopse, dequeueing the cell failed")
}
but ##better## is to access the data source array with the clicked index
let item = arr[indexPath.row]
I never get this print . it is like is always nil, doesnt matter how much i scroll it up or down :/
if let update = tableView.cellForRow(at: indexPath ) as? RestCell {
print("VISIBLE CELL")
}
Complete code..
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell : RestCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! RestCell
// Pass data to Cell :) clean the mess at the View Controller ;)
cell.restData = items[indexPath.row]
// Send cell so it can check update the image to the right cell ;)
// cell.cell = tableView.cellForRow(at: indexPath ) as? RestCell
//print("LA CELDA ES \(tableView.cellForRow(at: indexPath ))")
if let update = tableView.cellForRow(at: indexPath ) as? RestCell {
print("VISIBLE CELL")
}
return cell
}
You have not returned your cell from cellForRow method so this is the reason why your if statement is returning false. I'm not sure why you are trying to do this but there have to be a better way. If you want to look for visible cells you can use UITableView visibleCells variable.
I've got problems when I scroll down in my UITableview. The table shows me cells with old content when the cell is reused.
The Probleme is the following:
Swift wants to reuse an old cell, but doesn't properly clear the old content from the old cell. This leads to cells with old content, although I'm providing new data to the cells.
Architecture of the UITableView if the following:
Each custom cell has their own identifier
Each custom cell is separated in an own class
Screenshots of the problem:
Beginning of the Questionnaire Screen Shot:
The scrolled down table:
The problem here is the "Handedness"-Cell which is showing the cell number 3 (because of the reuse of the cell), which is not right
The numberOfSection-Method
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
The numberOfRowsInSection-Method
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(section == 0){
return questionnaireStructure.count
} else {
return 1
}
}
The cellForRowAt-Method
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// first section is the normal Questionnaire
if(indexPath.section == 0){
// current questionnaireStructure
let questStruct:QuestionnaireStructure? = questionnaireStructure[indexPath.row]
// current cell is a "Headline"
if(questStruct?.elementtype == "elements/headlines"){
let cell = tableView.dequeueReusableCell(withIdentifier: "HeadlineStructureCellID", for: indexPath) as! Headline
cell.headline.text = questStruct?.headline
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else if(questStruct?.elementtype == "elements/texts"){
// current cell is a "texts"
let cell = tableView.dequeueReusableCell(withIdentifier: "TextsStructureCellID", for: indexPath) as! Texts
cell.textsLabel.text = questStruct?.text
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else if(questStruct?.questiontype == "Slider"){
// currrent cell is a "slider-Question"
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSliderStructureCellID", for: indexPath) as! Slider
cell.sliderQuestion.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
let values = (questStruct?.values)!
let valueArray = values.array as! [Values]
cell.slider.minimumValue = Float(valueArray[0].min)
cell.slider.maximumValue = Float(valueArray[0].max)
let answers = (questStruct?.answers)!
let answerArray = answers.array as! [Answers]
cell.minLabel.text = answerArray[0].label
cell.maxLabel.text = answerArray[1].label
return cell
} else if(questStruct?.questiontype == "SingleChoice"){
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSingleChoiceStructureCellID", for: indexPath) as! SingleChoiceCell
let radioButtonController = SSRadioButtonsController()
radioButtonController.delegate = self
radioButtonController.shouldLetDeSelect = true
cell.radioButtonController = radioButtonController
cell.updateCellData(questStruct: questStruct!, indexInTable: indexPath.row)
return cell
} else if(questStruct?.questiontype == "MultipleChoice"){
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionMultipleChoiceStructureCellID", for: indexPath) as! MultipleChoiceCell
cell.multQuestionLabel.text = questStruct?.question
cell.questStruct = questStruct
return cell
} else if(questStruct?.questiontype == "YesNoSwitch"){
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionYesNoSwitchStructureCellID", for: indexPath) as! YesNoSwitch
cell.yesNoQuestion.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else if(questStruct?.questiontype == "TextDate"){
let cell = tableView.dequeueReusableCell(withIdentifier: "Datepicker", for: indexPath) as! DatePicker
cell.question.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSingleChoiceStructureCellID", for: indexPath) as! SingleChoiceCell
//cell.singleChoiceLabel.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
} else {
//last section is the save button
// show the save button when the Questionnaire is loaded
if(questionnaireStructure.count != 0){
let cell = tableView.dequeueReusableCell(withIdentifier: "SaveStructureCellID", for: indexPath) as! SaveQuestionnaire
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "TextsStructureCellID", for: indexPath) as! Texts
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
}
}
What I checked:
the data of "questStruct" is providing the latest data
overriding the "prepareForReuse"-Methode without success
Here:
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionSingleChoiceStructureCellID", for: indexPath) as! SingleChoiceCell
//cell.singleChoiceLabel.text = questStruct?.question
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
You need to "reset" the cell in case it's being reused. Options are:
write a reset() function in the cell, to clear any assigned data and display "default" content, or
create an empty questStruct and call cell.updateCellData(questStruct: questStruct!, indexInTable: indexPath.row)
Option 1. is probably the easiest and most straight-forward.
Are you sure the data isn't actually duplicated in the questStruct array? If that's not the case then all I can think is that it looks like you have two places where a single choice cell is used. In one of them you set a bunch of data, while in the other one you don't seem to set any data. I'm talking about that last else statement where you have the part where you set singleChoiceLabel.text except it's commented out. If that condition gets hit and it's reusing a cell that was configured for the other singleChoiceStructure branch of the if condition then the information will still be filled out from the previous configuration. It's possible the questionType property of one of your QuestionnaireStructure objects is either spelled incorrectly or just a value you haven't accounted for, which is causing the if statement to hit the else which returns an unconfigured QuestionSingleChoice cell that might still have information from the last time it was used.
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
I am new to swift . i am doing my project programatically and I load data from api to the tableView and tableView like ios setting page ..
now i need all rows information when click "Add to cart" button. How can i do it?
here is my code sample :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: cartHeaderCell, for: indexPath) as! CartHeaderCell
cell.configureCell(indexPath.item)
return cell
case 1:
let obj = data?[indexPath.row]
var cell = UITableViewCell()
switch obj {
case is Addon:
cell = tableView.dequeueReusableCell(withIdentifier: addonCell, for: indexPath) as! AddonCell
let switchView = UISwitch(frame: .zero)
switchView.setOn(false, animated: true)
cell.accessoryView = switchView
guard let addon = obj as? Addon else {
return cell
}
cell.textLabel?.text = "\(addon.name) + €\(addon.price)"
case is AddonGroup:
cell = tableView.dequeueReusableCell(withIdentifier: addonGroupCell, for: indexPath) as! AddonGroupCell
cell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
guard let addonGroup = obj as? AddonGroup else {
return cell
}
if let addons = addonGroup.addonList {
cell.detailTextLabel?.text = ""
var selectedAddons = ""
for _addon in addons
{
if _addon.isSelect == true {
selectedAddons = selectedAddons + "\(_addon.name)"
}
}
cell.detailTextLabel?.text = selectedAddons
}
cell.textLabel?.text = addonGroup.name
...........................................
As Fahim was mentioning, you need to set up a data model that records that status of each cell before / during / after the user interaction with each cell. So when the cell goes off screen and then comes back on, it will be presented with the correct state of the model.
Secondly, for the UISwitchViews, you should be instantiating and adding those to the contentView within each cell in order to keep the cellForRow function clean and problem free. The reason leads me into my next point: how to record the status of each UISwitchView after the user has interacted with a UISwitchView. You are going to want to create a protocol and add a delegate within the UICollectionViewCell(that inherits class and the delegate should be a weak var), in order to update the model whenever the UISwitch is tapped.
If you have any more questions i can do my best to help!