Cell Reusing in UITableView - ios

class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
var questions:[Question] = []
var sectionCountGlobal = 0
override func viewDidLoad() {
super.viewDidLoad()
questions = fillQuestions()
}
func fillQuestions()-> [Question]{
var temp : [Question] = []
var choices : [Choice] = []
let choice = Choice(id: 1, text: "choice ", status: 1, questionId: 1)
choices.append(choice)
choices.append(choice)
choices.append(choice)
choices.append(choice)
choices.append(choice)
choices.append(choice)
let q1 = Question(id: 1, text: "Ahmad 55 years old man with a history of hypertension and hypocholesteremia was in a wedding and during the party he starts to feel chest pain and dizzy, his wife brought him to the emergency department. The ER nurse checked his vital signs: BP 88/50, HR: 45, RR:10, SPaO2: 90% and O2 per nasal cannula was started at 4l/minute. Few seconds later Mr.Ahmad lost consciousness and the code blue team were activated.", choices: choices)
let q2 = Question(id: 1, text: "question 2", choices: choices)
let q3 = Question(id: 1, text: "question 3", choices: choices)
temp.append(q1)
temp.append(q2)
temp.append(q3)
return temp
}
func numberOfSections(in tableView: UITableView) -> Int {
return questions.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
sectionCountGlobal = section
return questions[section].choices.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let questionTextCell = tableView.dequeueReusableCell(withIdentifier: "QuestionTextCell") as! QuestionTextCell
questionTextCell.setQuestionText(text: questions[indexPath.section].text)
return questionTextCell
}else{
let choiceCell = tableView.dequeueReusableCell(withIdentifier: "ChoiceCell") as! ChoiceCell
choiceCell.choiceText.text = questions[indexPath.section].choices[indexPath.row].text
return choiceCell
}
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let questionNumber = "Q" + String(section+1)
return questionNumber
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 3
}
override func viewWillAppear(_ animated: Bool) {
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
}
I am working on a quiz app and there is multiple choices for each question so when checking the radio button in a cell and scroll to other cells i found that the other cells got checked without touching them what is the solution.
I tried different cell reusing methods also prepareForReuse() and nothing works how can i treat each cell independently without affect from other cells , i don't know the number of questions it is come from server.

In your cellForRowAt implementation you have to reset the cell's state according to whether it is selected or not. Due to cell reuse, you can get a cell which was previously selected, but now should not be selected - in that case you have to tell the cell to get unselected (and vice versa):
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let questionTextCell = tableView.dequeueReusableCell(withIdentifier: "QuestionTextCell") as! QuestionTextCell
questionTextCell.setQuestionText(text: questions[indexPath.section].text)
return questionTextCell
} else {
let choiceCell = tableView.dequeueReusableCell(withIdentifier: "ChoiceCell") as! ChoiceCell
// here detect if the cell should be selected and set it accordingly, so something like:
let isSelected = isSelectedChoice(questions[indexPath.section].choices[indexPath.row])
choiceCell.isSelected = isSelected
// of course this is just a mockup, since I don't know exactly how you manage selection,
// but it should get you on the right path
choiceCell.choiceText.text = questions[indexPath.section].choices[indexPath.row].text
return choiceCell
}
}

The issue in you code is you not changing the status of your radio button. When you select the option from didSelectRowAt method, you have to change the status of your choice. As per your choice model you can change the status of particular choice status. Following are both method that can manage your selection of choice(your status variable should be Bool type):
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let questionTextCell = tableView.dequeueReusableCell(withIdentifier: "QuestionTextCell") as! QuestionTextCell
questionTextCell.setQuestionText(text: questions[indexPath.section].text)
return questionTextCell
}else{
let choiceCell = tableView.dequeueReusableCell(withIdentifier: "ChoiceCell") as! ChoiceCell
// update your radio button UI
choiceCell.radioButton.isSelected = questions[indexPath.section].choices[indexPath.row].status
choiceCell.choiceText.text = questions[indexPath.section].choices[indexPath.row].text
return choiceCell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
questions[indexPath.section].choices[indexPath.row].status = !questions[indexPath.section].choices[indexPath.row].status
tableView.reloadData()
}

Related

Thread 1: Exc Bad Instruction in UITableViewCell

With the code below, I want to print the name and the price in each table cell. The build is done without any problem, but when I run the app, it says Bad Instruction error in the var item1 = arrData[i]["name"]
Here's the full code:
class ViewController3: UIViewController, UITableViewDelegate,
UITableViewDataSource {
let arrData: [[String:Any]] = [
["name": "spiderman", "price": 5000],
["name": "superman", "price": 15000],
["name": "batman", "price": 3000],
["name": "wonder woman", "price": 25000],
["name": "gundala", "price": 15000],
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrData.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell = tableView.dequeueReusableCell(withIdentifier: identifier)
var i = 0
while i <= arrData.count {
var item1 = arrData[i]["name"]
var item2 = arrData[i]["price"]
cell?.textLabel?.text = "\(item1) \(item2)"
i = i + 1
}
return cell!
}
}
Instead of while loop use indexPath.row to use show proper data at each row in your UITableView. And use reusable cell like below:
let identifier = "Cell"
override func viewDidLoad() {
super.viewDidLoad()
tableview.register(UITableViewCell.self, forCellReuseIdentifier: identifier)
tableview.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
let item1 = arrData[indexPath.row]["name"]
let item2 = arrData[indexPath.row]["price"]
cell.textLabel?.text = "\(item1!) \(item2!)"
return cell
}
Fix this piece while i < arrData.count. Index is out of bounds.
When you use instruction i <= arrData.count for 5th index you will get crash. You should change to, or better to use for in instruction
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "Cell"
var cell = tableView.dequeueReusableCell(withIdentifier: identifier)
var i = 0
while i < arrData.count {
var item1 = arrData[i]["name"]
var item2 = arrData[i]["price"]
cell?.textLabel?.text = "\(item1) \(item2)"
i = i + 1
}
return cell!
}
Subscript of array is start from zero. It means the first element is not arrData[1] but arrData[0]. so while i <= arrData.count will cause out of bounds of array.
Try while i < arrData.count
PS, codes in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) is wired, why you add a while loop? it will cause all cell of table view looks same.

Restrict table view unless select all cell in swift

i have a table view in which i'm populating data getting from my service. the data is totally dynamic and table view contain sections and cell under it all the things are dynamic. I have a button action outside the table view which is used to add the selected cell data. Now i want to restrict the button that it does not add the data till all the cell under the sections are selected. I want user to first check the cells and than add through add button. My code for the table view is this,
func numberOfSections(in tableView: UITableView) -> Int {
return AddonCategoryModel!.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return AddonCategoryModel![section].name
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 34
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return AddonCategoryModel![section].addonItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = addonTableView.dequeueReusableCell(withIdentifier: "addonCell", for: indexPath) as! RestaurantMenuDetailAddonTVC
cell.addonTitleLbl.text = AddonCategoryModel![indexPath.section].addonItems[indexPath.row].name
cell.priceLbl.text = String(AddonCategoryModel![indexPath.section].addonItems[indexPath.row].price)
if selection[indexPath.section].isSelected[indexPath.row] {
cell.radioBtn.setImage(UIImage (named: "radio"), for: UIControlState.normal)
addonItemName = cell.addonTitleLbl.text!
addonItemprice = AddonCategoryModel![indexPath.section].addonItems[indexPath.row].price
addonItemId = AddonCategoryModel![indexPath.section].addonItems[indexPath.row].addonPKcode
addonItemNameArray.append(addonItemName)
addonItemPriceArray.append(addonItemprice)
addonItemIdArray.append(addonItemId)
let defaults = UserDefaults.standard
defaults.set(addonItemName, forKey: "addonItemName")
defaults.set(addonItemprice, forKey: "addonItemPrice")
defaults.set(addonItemId, forKey: "addonItemId")
defaults.synchronize()
}
else {
cell.radioBtn.setImage(UIImage (named: "uncheckRadio"), for: UIControlState.normal)
}
cell.radioBtn.tag = indexPath.row
// cell.radioBtn.addTarget(self, action: #selector(checkBoxSelection(_:)), for: .touchUpInside)
cell.selectionStyle = .none
cell.backgroundColor = UIColor.clear
return cell
}
My screen looks like this,
Basically, you have to set selected true and false based on user have selected the row or deselected the row, then just check in your data set is anything selected if yes then make the button highlighted/enable else disable/unhighlighted
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selection[indexPath.section].isSelected = true
tableView.reloadData()
CheckIfAnyOneIsSelected()
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
selection[indexPath.section].isSelected = false
tableView.reloadData()
CheckIfAnyOneIsSelected()
}
func CheckIfAnyOneIsSelected() {
//loop through your array and check if anyone is selected if yes break the loop and set the button to enable
//else make the button disable
var anyOneSelecte = false
for singleModel in AddonCategoryModel {
for item in addonItems {
if item.isSelected == true
anyOneSelecte = true
break;
}
}
if anyOneSelecte {
//enable your button
} else {
//disable your button
}
}
I have created demo, Let's say you have two Model classes,
class AddOnCategoryModel {
var name: String = ""
var arrValues = [Category]()
init(name: String) {
self.name = name
}
}
class Category {
var name: String = ""
var price : String = ""
var isSelected: Bool = false
}
and following is the mainArray,
for i in 0...2 {
let model = AddOnCategoryModel(name: "Section \(i)")
for j in 0...3 {
let cate = Category()
cate.name = "Category \(j)"
model.arrValues.append(cate)
}
mainArray.append(model)
}
Now considering you have following ListTableCell
There are two IBOutlets
#IBOutlet weak var lblTemp: UILabel!
#IBOutlet weak var btnRadio: UIButton!
FYI. Please set btnRadio default and selected image properly.
Your UITableViewDataSource methods,
func numberOfSections(in tableView: UITableView) -> Int {
return mainArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mainArray[section].arrValues.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListTableCell") as! ListTableCell
let category = mainArray[indexPath.section]
cell.lblTemp.text = category.arrValues[indexPath.row].name
cell.btnRadio.tag = indexPath.row
cell.tag = indexPath.section
cell.btnRadio.addTarget(self, action: #selector(btnRadioTapped), for: .touchUpInside)
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return mainArray[section].name
}
Please find btnRadioTapped method,
#objc func btnRadioTapped(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
let cell = sender.superview?.superview as! ListTableCell
let addOnModel = mainArray[cell.tag]
let category = addOnModel.arrValues[sender.tag]
category.isSelected = sender.isSelected
}
Not let's check all checkbox's are selected or not in button tap event like this,
#IBAction func btnTapped(_ sender: UIButton) {
var isCheckedAll = true
for (_ , item) in mainArray.enumerated() {
let value = item.arrValues.filter({$0.isSelected==false})
if value.count > 0 {
isCheckedAll = false
break;
}
}
print("Done ", isCheckedAll)
}
It will return true if all radioButtons are selected, and return false if any one radioButton is not selected.
Let me know in case of any queries. This is just demo, you have to do minor changes as per your final requirements.
UPDATE
Please find didSelectRowAt indexPath method below,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let model = mainArray[indexPath.section]
let category = model.arrValues[indexPath.row]
category.isSelected = !category.isSelected
let cell = tableView.cellForRow(at: indexPath) as! ListTableCell
cell.btnRadio.isSelected = category.isSelected
}

Best way to perform mutually exclusive selection in a tableview in different sections in swift 3

I have a tableview with different sections, I need to be able to multiselect from different sections, but the rows in each section should be able to select mutually exclusive wise. For eg: in below screenshot I should be able to select either Margarita or BBQ Chicken from Pizza and same for Deep dish pizza but I should be able to multiselect between Pizza section and Deep dish pizza
Below is my code so far, I was wondering what would be the best way to approach this.
let section = ["Pizza", "Deep dish pizza"]
let items = [["Margarita", "BBQ Chicken"], ["Sausage", "meat lovers"]]
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.section[section]
}
override func numberOfSections(in tableView: UITableView) -> Int {
return section.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
// Configure the cell...
cell.textLabel?.text = items[indexPath.section][indexPath.row]
return cell
}
You should create some data element to track the selection for each row.
I would suggest a dictionary of [Int:Int] where the key is the section and the value is the row.
When a row is selected, you can then easily check to see if another row is already selected in that section, and deselect it if required.
var rowSelections = [Int:Int]()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let section = indexPath.section
if let row = self.rowSelections[section] {
tableView.deselectRow(at: IndexPath(row:row, section:section), animated: true)
}
self.rowSelections[section]=indexPath.row
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let section = indexPath.section
self.rowSelections[section]=nil
}
Ok, so I figured it out, I created a method that would loop and check through all the rows and called it in both tableview didselect and deselect
func updateTableViewSelections(selectedIndex:IndexPath)
{
for i in 0 ..< tableView.numberOfSections
{
for k in 0 ..< tableView.numberOfRows(inSection: i)
{
if let cell = tableView.cellForRow(at: IndexPath(row: k, section: i))
{
if sections.getType(index: i) == selectedIndex.section
{
if (selectedIndex.row == k && cell.isSelected)
{
cell.setSelected(cell.isSelected, animated: false)
}
else
{
cell.setSelected(false, animated: false)
}
}
}
}
}
}
First allow multiple selection:
yourTableView.allowsMultipleSelection = true
To get the select rows:
let selectedRows = tableView.indexPathsForSelectedRows
Then within the didselectrow function you can iterate through the selected rows and ensure that only 1 row within the section can be selected.

UITableView indexPath.row not increasing

I am loading data into a UITableView. The first load happens properly for the first 10 cells in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
the indexPath.row increments properly and loads the data into the proper cells from the data source. I then implemented a load more when the bottom of the table is reached. Now func tableView is called but it is stuck at indexPath.row = 9. I have implemented a checker in
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
And it appears that the proper number of rows has been added.
Edit: I having issue with the my second uitableview (there are two in this scene) The checker is a print statement that is called and returns the proper uitableView and this happens before the tableView gets stuck at the same value.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.table {
return users2.count
}
else {
print("married barry", tableFeedCount)
return tableFeedCount
}
}
Try following:
Declare boolean
let boolNotMoreData : Bool = true
Append new data to your data source
let arrResponse: [Any]? = (responseObject["news"] as? [Any])
if arrResponse?.count == 0{
boolNotMoreData = false;
}
for dictResponse in arrResponse as! [[String: Any]] {
self.arrDataSource.append(NewsClass(responseDict: dictResponse))
}
self.tblViewNews?.reloadData()
Now fetch new data
private func tableView(_ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == arrNews.count - 1 {
if boolNotMoreData {
currentPage += 1
getYourData()
}
}
}
This worked Successfully
#IBOutlet weak var Submitted: UITableView!
#IBOutlet weak var ViewAssigenment: UITableView!
var Arrayone:[String] = []
var ArrayTwo:[String] = []
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
var count:Int?
if tableView == self.ViewAssigenment
{
count = Arrayone.count
}
else if tableView == self.Submitted
{
count = ArrayTwo.count
}
return count!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
if tableView == self.ViewAssigenment
{
let cell = tableView.dequeueReusableCell(withIdentifier: "ViewCell") as!
ViewAssigenmentTableViewCell
let obj =Arrayone[indexPath.row]
cell.lblTitle.text = obj.AssTitle
return cell
}
else
{
let cell1 = tableView.dequeueReusableCell(withIdentifier: "Submittcell") as! SubmittedAssigenmentTableViewCell
let obj2 = ArrayTwo[indexPath.row]
cell1.lbltitle.text = obj2.AssTitle
return cell1
}
}

Adding two Custom cells in tableView

I have a tableView on mainStoryboard with two custom cells.
I would like to set two more cells at different row.
However When I implemented the code the added cells replaces original cells. (Custom cell of "Basic grammar3" and "Basic grammar5" are disappearing.)
I was trying to find the answer but could not find out.
I have image and code added below.
import UIKit
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tblStoryList: UITableView!
var array = PLIST.shared.mainArray
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.array.count + 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 || indexPath.row == 3 || indexPath.row == 5 {
let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath) as! HeaderCell
cell.headerTitle.text = indexPath.row == 0 ? "First Stage" : indexPath.row == 3 ? "Second Stage" : "Third Stage"
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "StoryTableviewCell", for: indexPath) as! StoryTableviewCell
//making plist file
let dict = self.array[indexPath.row - 1]
let title = dict["title"] as! String
let imageName = dict["image"] as! String
let temp = dict["phrases"] as! [String:Any]
let arr = temp["array"] as! [[String:Any]]
let detail = "progress \(arr.count)/\(arr.count)"
//property to plist file を぀ăȘぐ
cell.imgIcon.image = UIImage.init(named: imageName)
cell.lblTitle.text = title
cell.lblSubtitle.text = detail
cell.selectionStyle = UITableViewCellSelectionStyle.none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
return
}
tableView.deselectRow(at: indexPath as IndexPath, animated:true)
if indexPath.row == 3 {
return
}
tableView.deselectRow(at: indexPath as IndexPath, animated:true)
if indexPath.row == 5 {
return
}
tableView.deselectRow(at: indexPath as IndexPath, animated:true)
let messagesVc = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
messagesVc.object = self.array[indexPath.row - 1]
self.navigationController?.show(messagesVc, sender: self)
}
You could use sections for your table view. Now, you are returning 1 in your numberOfSections function. And it is creating only one section. If you want to use headers, you can use sections for your need. And also you can fill your table view cells with multidimendional arrays. For example:
For adjusting your section headers:
let lessonTitles = ["First Stage", "Second Stage"]
Titles for sections:
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < lessonTitles.count {
return lessonTitles [section]
}
return nil
}
For adjusting your sections and rows:
let lessons = [["Basic Grammar 1", "Basic Grammar 2"], ["Basic Grammar 3", "Basic Grammar 4"]]
Number of sections function should be:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return lessons.count
}
Number of rows in section should be:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lessons[section].count
}
And creating your cells is like this:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellText = data[indexPath.section][indexPath.row]
...
}
Try like this...
func numberOfSections(in tableView: UITableView) -> Int
{
return numberOfStages
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return numberOfRowsInCurrentStage
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
return customizedCell
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
return requiredHeight
}
func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
{
return stageCountView
}
You can use viewForHeaderInSection if you want to show stage count on top.
edit: The comment by raki is the much better solution (use headers). I leave this here in case you want something closer to your existing implementation.
You have to change your numbering scheme in order to insert these additional rows (and not replace existing rows). So you might want to adjust the row for the "normal" elements like this:
func adjustRow(_ row: Int) -> Int {
if row < 3 {
return row
} else if row < 5 {
return row+1
} else {
return row+2
}
}

Resources