UITableView button color issue - swift

I have some questions to answer with Yes or Not in a UITableView, each option (yes/no) is it's own button in the cell.
First, I have to handle the click event, because I have to change button's backgroundColor on click and I have to add the clicked row to create another struct with the answers.
My actual code is like this:
extension MyViewController: UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//click row
}
}
extension MyViewController: UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellAnswers", for: indexPath) as! MyTableViewCell
//iterate here? and how can i do that?
cell.actionYes = {
cell.buttonYes.backgroundColor = .blue
cell.buttonNo.backgroundColor = .darkGray
}
cell.actionNot = {
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .blue
}
let question = questions[indexPath.row]
cell.question = question
return cell
}
}
Like you see, I have clousures to handle click event and change backgroundColor, but if i click one button, the rest change too.
and my TableViewCell:
import UIKit
class MyTableViewCell: UITableViewCell{
#IBOutlet weak var question: UILabel!
#IBOutlet weak var buttonYes: UIButton!
#IBOutlet weak var buttonNo: UIButton!
var actionYes: (() -> Void)? = nil
var actionNo: (() -> Void)? = nil
#IBAction func answerYes(_ sender: Any) {
actionYes?()
}
#IBAction func answerNo(_ sender: Any) {
actionNo?()
}
var question: QuestionResponse!{
didSet{
updateUI()
}
}
func updateUI(){
question.text = question.value
}
}
I don't have any idea how to iterate the the cells to fill the correct color on buttons.

What you are facing is the result of cell reusing that UTableView does.
Whenever a cell is out of the screen, (after scrolling) gets added to a queue to be reused. That's why you need to call dequeueReusableCell(withIdentifier:for:) function to get a cell that you need to prepare for displaying your data. That means that a UITableViewCell instance may already have some changes left over from the last time that has been used.
The correct way to deal with a UTableView is to keep whatever you change in you datasource. For example you can add an answer property to your QuestionResponse model, like this:
enum Answer {
case yes, no
}
struct QuestionResponse {
var answer: Answer?
}
And store there the user's answer. Also you have to make sure that you always set the background color of your buttons to avoid unwanted leftover colors from previous usage of the same cell instance:
extension MyViewController: UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellAnswers", for: indexPath) as! MyTableViewCell
//iterate here? and how can i do that?
cell.actionYes = {
cell.buttonYes.backgroundColor = .blue
cell.buttonNo.backgroundColor = .darkGray
}
cell.actionNot = {
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .blue
}
let question = questions[indexPath.row]
switch question.answer {
case .yes:
cell.buttonYes.backgroundColor = .blue
cell.buttonNo.backgroundColor = .darkGray
case .no:
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .blue
default:
cell.buttonYes.backgroundColor = .darkGray
cell.buttonNo.backgroundColor = .darkGray
}
cell.question = question
return cell
}
}

Related

Show hidden label by button click inside tableView Cell (expand/collapse cell)

I have a stackView(vertical) which contains labels and bottom description label is hidden by default. And I implemented an arrow button at the right side of the cell. By clicking the button, I just want to show the hidden description label and stackView should expand automatically and make cell bigger. This was my basic idea to implement expandable cell.
So this is the code I used to get desired results:
#objc func downArrowButtonClicked(_ sender: UIButton){
let indexPath = IndexPath(row: sender.tag, section: 0)
selectedIndex = indexPath
selectedCellIndex = sender.tag
isDescHidden = !isDescHidden
tableView.reloadRows(at: [indexPath], with: .automatic)
}
Above is the code for the button inside clicked cell. I went with the idea to reload that particular index. I created a variable named selectedCellIndex of in which I use in cellForRowAt method to make some changes.
I also had the implement some code in viewDidLayoutSubviews() as when I first clicked the cell wasn't getting expanded fully. here's that just in case:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let indexPath = selectedIndex
tableView.reloadRows(at: [indexPath ?? IndexPath(row: 0, section: 0)], with: .automatic)
}
And calling it in willDisplay method which finally fixed the cell expansion issue:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
viewDidLayoutSubviews()
}
And here is my cellForRowAt function:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
if indexPath.row == 0 {
cell.lblTitle.text = "Title 1"
cell.lblDesc.text = "Desc 1"
}
else if indexPath.row == 1 {
cell.lblTitle.text = "Title 2"
cell.lblDesc.text = "Desc 2"
}
else {
cell.lblTitle.text = "Title 3"
cell.lblDesc.text = "Desc 3"
}
if selectedCellIndex != nil {
if isDescHidden == false {
if cell.isDescHidden == true {
cell.lblDesc.isHidden = false
cell.btnArrow.setImage(UIImage(systemName: "chevron.up"), for: .normal)
}
else {
cell.lblDesc.isHidden = true
cell.btnArrow.setImage(UIImage(systemName: "chevron.down"), for: .normal)
}
}
else {
if cell.isDescHidden == true {
cell.lblDesc.isHidden = true
cell.btnArrow.setImage(UIImage(systemName: "chevron.down"), for: .normal)
}
else {
cell.lblDesc.isHidden = false
cell.btnArrow.setImage(UIImage(systemName: "chevron.up"), for: .normal)
}
}
cell.isDescHidden = !cell.isDescHidden
}
cell.btnArrow.tag = indexPath.row
cell.btnArrow.addTarget(self, action: #selector(downArrowButtonClicked(_:)), for: .touchUpInside)
return cell
}
This approach gets too confusing as you can see from the above code. The isDescHidden variable is defined in both Main view controller as well as table view cell class and I was trying to use both to expand or collapse a particular cell. However first time it works but if I have 3 cells expanded, collapsing button click doesn't work for 1-2 clicks then works.
Is there a better approach for this kind of problem? Or is there any way I can directly set cell.isDescHidden value from #objc func downArrowButtonClicked(_ sender: UIButton) function? So I can use that in cellForRowAt function?
I would be glad if I could directly make changes to cell variables from that.
Use the following function for automatic height for rows and provided top and bottom constraints to your stackView
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Here is my CustomCell
class CustomCell: UITableViewCell {
#IBOutlet weak var titleCell: UILabel!
#IBOutlet weak var detail: UILabel!
#IBOutlet weak var arrowButton: UIButton!
let upArrow = UIImage(systemName: "chevron.up.circle.fill")
let downArrow = UIImage(systemName: "chevron.down.circle.fill")
var onArrowClick: ((UIButton)->())!
#IBAction func handleArrowButton(sender: UIButton){
onArrowClick(sender)
}
func updateArrowImage(expandStatus: Bool){
arrowButton.setImage(expandStatus ? downArrow : upArrow, for: .normal)
}
}
For sample Data
let data = [
["Nothing", "description is very long description is very long description is very long description is very "],
["Nothing", "description is very long "],
["Nothing", "description is very long "],
["Nothing", "description is very long "],
["Nothing", "description is very long "]
]
var eachCellStatus: [Bool] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
for _ in data {
eachCellStatus.append(true)
}
}
TableView methods are like this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
cell.titleCell.text = data[indexPath.row][0]
cell.detail.text = data[indexPath.row][1]
cell.detail.isHidden = eachCellStatus[indexPath.row]
cell.updateArrowImage(expandStatus: self.eachCellStatus[indexPath.row])
cell.onArrowClick = { button in
self.eachCellStatus[indexPath.row].toggle()
cell.updateArrowImage(expandStatus: self.eachCellStatus[indexPath.row])
tableView.reloadRows(at: [indexPath], with: .automatic)
}
return cell
}
First create a model to show data into cell. You have to preserve the state of cell.
struct CellData {
var title: String
var details: String
var isExpanded: Bool
}
In CustomTableViewCell add a property for cellData and assign Outlets data from it. Also create a protocol to reload row from UIViewController
protocol CustomTableViewCellDelegate {
func reloadRow(sender: CustomTableViewCell, flag: Bool)
}
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var detailsLabel: UILabel!
#IBOutlet weak var showButton: UIButton!
var indexPath: IndexPath?
var delegate: CustomTableViewCellDelegate?
var data: CellData? {
didSet {
if let data = data {
if data.isExpanded == false {
detailsLabel.isHidden = true
showButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
}else {
detailsLabel.isHidden = true
showButton.setImage(UIImage(systemName: "chevron.up"), for: .normal)
}
titleLabel.text = data.title
detailsLabel.text = data.details
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
titleLabel.text = nil
detailsLabel.text = nil
}
#IBAction func showButtonAction(_ sender: UIButton) {
if var data = data {
data.isExpanded.toggle()
delegate?.reloadRow(cell: self, flag: data.isExpanded)
}
}
}
In UIViewController add an array of CellData type. You may assign it's data in viewDidLoad() method.
var tableData: [CellData]
Modify numberOfRowsInSection() and cellForRow() method like bleow.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
cell.data = tableData[indexPath.row]
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
Then confirm CustomTableViewCellDelegate protocol to UIViewController
extension ViewController: CustomTableViewCellDelegate {
func reloadRow(sender: CustomTableViewCell, isExpanded: Bool) {
guard let tappedIndexPath = tableView.indexPath(for: sender) else { return }
tableData[tappedIndexPath.row].isExpanded = isExpanded
tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
}
}

UISwitches in UITableViewCell change states together

I have a very strange problem in my Swift project. I use UIswitch in my dynamic cells in my one-section uitable. Whenever I click on the 6th switch, the first one changes its state with it and vice versa. The .valueChanged function only works for the one that is clicked (correct behaviour).
I cannot figure out why the switches change state together.
This is the code for table cells:
import UIKit
class RoutineTableViewCell: UITableViewCell {
#IBOutlet weak var selectionSwitch: UISwitch!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var previewImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
and here is the code in my table view controller:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: RoutineTableReuseIdentifier, for: indexPath) as? RoutineTableViewCell
else{
return RoutineTableViewCell()
}
let exercise = section!.exercises[indexPath.row]
cell.title.text = exercise.title
cell.previewImage.image = UIImage(named: (exercise.gifName + ".gif"))
cell.selectionSwitch.addTarget(self, action: #selector(self.addRemoveExercise(_:)), for: .valueChanged)
return cell
}
#IBAction func addRemoveExercise(_ sender: UISwitch!) {
let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
if(sender.isOn){
customizedSection?.exercises[indexPath!.row] = section!.exercises[indexPath!.row]
}
else{
customizedSection?.exercises[indexPath!.row] = ExerciseModel()
}
}
Your tableView:
import UIKit
class TableViewController: UITableViewController,switchValues {
func changed(_ cell: UITableViewCell, _ mySwitch: UISwitch) {
let index = tableView.indexPath(for: cell)
switchStates[(index?.row)!] = mySwitch.isOn
tableView.reloadData()
}
var switchStates : [Bool] = [false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return switchStates.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
cell.mySwitch.isOn = switchStates[indexPath.row]
cell.delegate = self
return cell
}
Your cell:
import UIKit
protocol switchValues {
func changed(_ cell:UITableViewCell,_ mySwitch:UISwitch)
}
class MyTableViewCell: UITableViewCell {
#IBOutlet weak var mySwitch: UISwitch!
var delegate:switchValues!
#IBAction func valueChanged(_ sender: UISwitch) {
delegate.changed(self, mySwitch)
}
}

TableViewController issue: won't save user input from textView

Update:
so goal of this to save the text that user enters in a UITextView in cell, so that the text is saved for that particular cell number and does not duplicate, move or remove the text.
as suggested I am trying to handle the textViewdidChange func inside of the custom cell, by doing the following:
var onTextEntered: ((String?) -> ())!
func setup() {
notesTextView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
onTextEntered(notesTextView.text)
}
making a string that holds the text and then adding the text to the String every time textViewDidChange is called (trying to explain this to myself as we go along, so please correct me if my explanation needs it).
next in CellForRowAt
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "TableViewNotesCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! TableViewNotesCell
cell.setup()
cell.onTextEntered = {input in
if let text = input {
self.cellText[indexPath.row] = text // here is the error (index out of range)
}
if indexPath.row < self.cellText.count {
cell.notesTextView.text = self.cellText[indexPath.row] ?? "placeholder"
}
}
return cell
}
when I do the above code, as soon as textViewDidChange is called (when i type a single letter or digit in textView) the I get the error : "Fatal error: Index out of range" on the line which I use the array of cellText[indexPath.row] = text. please help or let me know if my understanding of the process is wrong would love to learn!
You can try to save for every single edit
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// inside cellForRowAt
cell.textView.delegate = self
cell.textView.tag = indexPath.row
return cell
}
#objc func textViewDidChange(_ tex: UITextView) {
cellEndEdit[tex.tag] = tex.text!
}
class VC:UIViewController,UITextViewDelegate {
give default values for the array
var cellEndEdit = [String](repeating: "", count: numberOfRows)
Assuming, that you tableView has a variable number of cells, and all of them have a UITextView, whose content should be recorded and indexed, I would recommend to create a custom UITableViewCell, that handles the textView itself.
class CustomTableViewCell: UITableViewCell, UITextViewDelegate {
#IBOutlet weak var textView: UITextView()
var onTextEntered: ((String?) -> ()) //that's a callback
func setup() {
textView.delegate = self
}
override func textViewDidChange(textView: UITextView) {
onTextEntered(textView.text)
}
}
Since you're working with a sorted list of user inputs, you should have your array at hands, in which you can store and from which you can retrieve data. So if some data already exists, grep through your array and fill the cells that deserve it. Also define the onTextEntered callback here to tell the cell what to do, if it gets called (in your case, store the text of the UITextView in your array).
//your carrier, if you store the already existing user inputs some where, map them in here
//depending on how you calculate the number of cells, this array has to have the same size so use the same logic for this
var yourStringCarrierArray: [String?] = []
override func tableView(_ tableview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCell", for: indexPath) as! CustomTableViewCell
cell.setup()
cell.onTextEntered = { input in
if let text = input {
self.yourStringCarrierArray[indexPath.row] = text
}
if indexPath.row < yourStringCarrierArray.count {
cell.textView.text = yourStringCarrierArray[indexPath.row] ?? "placeholder string, because there's no input here so far"
}
}
I hope this will help or at least give you a new perspective, it has been a while, i coded in Swift. Feel free to ask me, if something is unclear.
Use an object to save the string value, since String in swift is value type. Here is an example:
class TestViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataArray: [CellData] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.delegate = self
tableView.dataSource = self
for _ in 0..<20 {
dataArray.append(CellData())
}
}
}
extension TestViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
cell.setData(data: dataArray[indexPath.row])
return cell
}
}
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var textView: UITextView!
var data: CellData!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
textView.text = ""
textView.delegate = self
}
func setData(data: CellData) -> Void {
self.data = data
self.textView.text = data.stringValue
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension TestTableViewCell: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
self.data.stringValue = textView.text
}
}
class CellData {
var stringValue: String = ""
}

Swift - select single radioButton in tableView

I have a tableView, which is expandable. The headerCell has a checkBox, a label and a radioButton. The collapsableCell has a checkBox and a label.
I have used M13Checkbox Library to implement the checkBox and the radioButton.
The problem is when I select a radioButton or a checkBox of HeaderViewCell at index 0, then the radioButton/checkBox at index 8,16,24 also get selected. I know it is because of the numberOfSections in tableView property, But then how do I select just single radioButton at a time.
My requirement is I have to select single radioButton in the tableView and CheckBoxes can have multiple selections for the HeaderCell.
I am badly stuck on this issue. I have googled a lot but nothing worked. Any help or suggestion much appreciated.
func numberOfSections(in tableView: UITableView) -> Int {
return states.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return states[section].cities.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50.0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if (states[indexPath.section].expanded) {
return 44
}else{
return 0.0
}
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "headerviewcell") as! HeaderView
var list = states[section]
headerCell.customInit(titleLabel: list.stateName, section: section, delegate: self)
return headerCell
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "subcells") as! CollapsibleCell
cell.selectionStyle = .none
cell.textLabel?.text = states[indexPath.section].cities[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// works for headers or cell??
}
func toggleHeader(header : HeaderView, section : Int){
states[section].expanded = !states[section].expanded
tableView.beginUpdates()
for i in 0 ..< states[section].cites.count {
tableView.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
}
tableView.endUpdates()
}
UPDATE:
Here is my code for HeaderView. Whenever I select any radioButton, then any random radioButton for other sections also get selected. Upon scrolling the state and section of radioButtons changes. Please help me solve this issue. I am aware it's related to cell reuse property, but I can't solve it.
import UIKit
import M13Checkbox
protocol HeaderViewDelegate {
func toggleHeader(header : HeaderView, section : Int)
}
protocol CustomHeaderDelegate: class {
func didTapButton(in section: Int, headerView : HeaderView, button : M13Checkbox)
}
class HeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var stateCheckBox: M13Checkbox!
#IBOutlet weak var stateNameLabel: UILabel!
#IBOutlet weak var favouriteState: M13Checkbox!
var delegate : HeaderViewDelegate?
weak var delegateHeader: CustomHeaderDelegate?
var sectionNumber : Int!
var section : Int!
override func awakeFromNib() {
stateCheckBox.boxType = .square
stateCheckBox = .bounce(.fill)
favouriteState.boxType = .circle
favouriteState.setMarkType(markType: .radio, animated: true)
favouriteState.stateChangeAnimation = .bounce(.stroke)
}
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
self.addGestureRecognizer(UITapGestureRecognizer(target : self, action: #selector(selectHeaderView)))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder : aDecoder)
self.addGestureRecognizer(UITapGestureRecognizer(target : self, action: #selector(selectHeaderView)))
}
func selectHeaderView(gesture : UITapGestureRecognizer) {
let cell = gesture.view as! HeaderView
delegate?.toggleHeader(header: self, section: cell.section)
}
func customInit(titleLabel : String, section : Int, delegate : HeaderViewDelegate) {
self.stateNameLabel.text = titleLabel
self.section = section
self.delegate = delegate
}
#IBAction func selectPrimaryCondition(_ sender: M13Checkbox) {
// get section when favourite state radioButton is selected
delegateHeader?.didTapButton(in: sectionNumber, headerView : self, button : sender)
}
override func prepareForReuse() {
// What do do hereā€¦??
}
}
TableViews are tricky when saving data without knowing some kind of saved state that can be held in some sort of object class is it can do some weird things such as reuse cells; selecting incorrect cells in your case. I'm guessing that you've created some kind of object for your states and cities. I'd make sure that they're classes and not structs and make sure they have some sort of isTapped Bool so you're able to actually save the state for that individual object like so:
class CellObject: NSObject {
var isTapped: Bool
init(isTapped: Bool) {
self.isTapped = isTapped
}
}
Instead of just setting your isSelected to true or false inside your didSelect method, set THAT cells object isTapped to true or false AS WELL as changing the state to whatever you want, i.e change it's background colour to black to show it's selected. When loading your cell inside of your cellForRowAtIndexPath method, check if the cells object isTapped is either true of false and set it's state, colours etc. accordingly. This is the barebones of the idea but I hope this points you in the right direction.

Swift UiTableView not reloading search results

I am having a weird issue where for some reason my UITableView is not being reloading after performing a search. The console prints out the correctly filtered data, but the table simply doesn't change. I have never encountered this issue, so I first attempted the solutions which naturally came to mind:
Tried tableView.reloadData() in the Main Queue
Quit Xcode, clean build, reinstall
Cleared out the derived data dir
I have found several similar issue in SO, but all of the solutions I've seen are things I've tried, mainly reloading tableview in main queue.
Hoping maybe I just simply have an issue in my code or something I'm missing.
I am running Xcode 8.3.3
import UIKit
class CategoriesViewController: UIViewController {
var isFiltering = false
var location = Location()
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
var categoriesSearchResults = [Category]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.allowsSelection = true
tableView.keyboardDismissMode = .onDrag
let nib = UINib(nibName: "CategoryTableViewCell", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier:"CategoryTableViewCell");
searchBar.returnKeyType = UIReturnKeyType.done
searchBar.autocapitalizationType = .none
searchBar.delegate = self
}
extension CategoriesViewController : UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("HI")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return self.categoriesSearchResults.count
}
return self.location.categories.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if let cell = self.tableView.dequeueReusableCell(withIdentifier: "CategoryTableViewCell", for: indexPath) as? CategoryTableViewCell {
var category: Category
if isFiltering {
category = self.categoriesSearchResults[indexPath.row]
} else {
category = self.location.categories[indexPath.row]
}
cell.name.text = category.name
cell.status.textColor = UIColor.lightGray
cell.status.text = "Not Verified"
}
return cell
}
}
extension CategoriesViewController : UISearchBarDelegate {
func searchBarIsEmpty() -> Bool{
return self.searchBar.text?.isEmpty ?? true
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.isFiltering = true
self.categoriesSearchResults.removeAll()
tableView.reloadData()
self.view.endEditing(true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBarIsEmpty() {
self.view.endEditing(true)
self.isFiltering = false
} else {
self.isFiltering = true
self.categoriesSearchResults = self.location.categories.filter({ (category: Category) -> Bool in
return category.name.lowercased().contains(searchText.lowercased())
})
}
tableView.reloadData()
}
}
and my custom table view cell:
import UIKit
class CategoryTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var status: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func prepareForReuse() {
super.prepareForReuse()
self.name.text = ""
self.status.text = ""
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Thank you in advance.
EDIT: Might also be worth mentioning, when I am actively searching, the function tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) is not called??
The scope of if let nests in its scope. In your code you are always returning let cell = UITableViewCell(). Try returning it inside the if let :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if let cell = self.tableView.dequeueReusableCell(withIdentifier: "CategoryTableViewCell", for: indexPath) as? CategoryTableViewCell {
var category: Category
if isFiltering {
category = self.categoriesSearchResults[indexPath.row]
} else {
category = self.location.categories[indexPath.row]
}
cell.name.text = category.name
cell.status.textColor = UIColor.lightGray
cell.status.text = "Not Verified"
/// RETURN CELL HERE
return cell
}
return cell
}