I can't seem to figure out what I'm doing wrong.
I have a custom cell which has its own UITableViewCell and nib (3 labels).
If I select the cell I want to show/hide the Resolved Label. I can't seem to change the ResolvedLabel to Hidden or Show. The function gets called as I can see the Print Statement.
Code in didSelectRowAt
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ResolvedIssueTableViewCell
if cell.resolvedLabel.isHidden == true {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED RESOLVED")
cell.resolvedLabel.isHidden = false
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else if cell.resolvedLabel.isHidden == false {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED NOT RESOLVED")
cell.resolvedLabel.isHidden = true
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
}
You should call tableView.reloadData() in the end of didSelectRowAt function.
I've done some refactor your code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let cell = tableView.cellForRow(at: indexPath) as? ResolvedIssueTableViewCell else { return }
if cell.resolvedLabel.isHidden {
print("THIS HAS BEEN SELECTED RESOLVED")
cell.resolvedLabel.isHidden = false
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else {
print("THIS HAS BEEN SELECTED NOT RESOLVED")
cell.resolvedLabel.isHidden = true
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
tableView.reloadData() // Here is the magic :)
}
You can learn more from Nicholas Swift's Medium Article
Honestly, it doesn't matter why this doesn't work because the whole approach should be different.
Instead of directly trying to manipulate the cell, something that can be problematic for a lot of reasons (the main one being cell reusability), change the model (as you already do) and refresh the tableView - or better yet refresh just the affected cell.
So your code would be something like this (please note that I have no way to test this, so it's from the top of my head, but the genera idea is valid):
1. Make the change in the model
2. Reflect that change in your view (the cell)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let cell = tableView.cellForRow(at: indexPath) as? ResolvedIssueTableViewCell else {
print("Wrong type of cell. Bailing out...")
return
}
let isResolved = ...// Probably by looking into something like `self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId)` judging from your code
if isResolved {
print("THIS HAS BEEN SELECTED RESOLVED")
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else {
print("THIS HAS BEEN SELECTED NOT RESOLVED")
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
then make the visual state of your cells reflect your model:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
cell.resolvedLabel.isHidden = <whatever_makes_sense>
...
}
After select row. call tableview.reloaddata() function.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ResolvedIssueTableViewCell
if cell.resolvedLabel.isHidden == true {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED RESOLVED")
cell.resolvedLabel.isHidden = false
self.resolvedIssueType.append(self.issues[indexPath.row].partType)
self.resolvedIssues.append(self.issues[indexPath.row].documentId)
print(self.resolvedIssues)
} else if cell.resolvedLabel.isHidden == false {
tableView.deselectRow(at: indexPath, animated: true)
print("THIS HAS BEEN SELECTED NOT RESOLVED")
cell.resolvedLabel.isHidden = true
if let index = self.resolvedIssues.firstIndex(of:self.issues[indexPath.row].documentId) {
self.resolvedIssues.remove(at: index)
self.resolvedIssueType.remove(at: index)
}
print(self.resolvedIssues)
print(self.resolvedIssueType)
}
tableView.reloadData()
}
As always, keep all information about one row in the data model rather than using separate arrays. In didSelectRowAt modify the model and reload the row. This is the most reliable and efficient way.
In the data model add a property
var isResolved = false
in cellForRow set the hidden property of the text field depending on isResolved.
let issue = self.issues[indexPath.row]
cell.resolvedLabel.isHidden = issue.isResolved
Replace didSelectRowAt with (yes, the 3 lines are sufficient)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
self.issues[indexPath.row].isResolved.toggle()
tableView.reloadRows(at: [indexPath], with: .none)
}
and delete the two extra arrays.
To get all resolved items just filter the array
let resolvedIssues = self.issues.filter{ $0.isResolved }
Related
I am using two images for cell, one i am showing when tableview cell is expand and the other one when cell is collapse
when cell expand i am using arrow-bottom
when cell is collapse arrow-right
for cell expand and collapse i am using below code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.cellForRow(at: indexPath) as! FAQTableVIewCell
if selectedIndex == indexPath.row{
if self.isCollapse == false{
cell.arrowImage.image = UIImage(named: "arrow-rightt")
self.isCollapse = true
}else{
cell.arrowImage.image = UIImage(named: "arrow-bottom")
self.isCollapse = false
}
}else{
self.isCollapse = true
}
self.selectedIndex = indexPath.row
tableView.reloadRows(at: [indexPath], with: .automatic)
}
but the above code not working properly, first time when i expand cell its not showing bottom arrow, and if i expand two cells then also its not showing properly, pls do help with code
i need when cell expand then i need to show bottom-arrow, when cell in normal(back to its orginal position or collapsed position) then i need to show right-arrow
When you reload rows, they get in cellForRowAt method.
You must apply changes in this method. Store collapsed indexPath and check it in method.
With array:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//use your cell identifier
let cell = tableView.dequeueReusableCell(withIdentifier: "FAQTableVIewCell", for: indexPath) as! FAQTableVIewCell
let image = collapsedCells.contains(indexPath) ? UIImage(named: "arrow-rightt") : UIImage(named: "arrow-bottom")
cell.image = image
//other settings
return cell
}
var collapsedCells = [IndexPath]()
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//use your height values instead 44 and 120
return collapsedCells.contains(indexPath) ? 44 : 120
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let index = collapsedCells.firstIndex(of: indexPath) {
collapsedCells.remove(at: index)
} else {
collapsedCells.append(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
It's easier to explain by example. I have original array which is searched and filtered array with searched items. If i found one item after searching and tap on it, i mark it as done (I have todo list), but when i cancel my search, I find that the first element in the original array is marked, not the third item.
I googled some threads and found almost similar problems, but solutions doesn't suit to my problem. For example:
didSelectRowAtIndexPath indexpath after filter UISearchController - Swift
And here some code. Especially at didSelectRowAt I mark the items to done. Does anyone have any ideas?
private var searchBarIsEmpty: Bool {
guard let text = searchController.searchBar.text else { return false }
return text.isEmpty
}
private var isFiltering: Bool {
return searchController.isActive && !searchBarIsEmpty
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering {
return filteredTasks?.count ?? 0
}
return manager.tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Keys.cell.rawValue, for: indexPath) as! ToDoCell
var currentItem: Task
if isFiltering {
currentItem = filteredTasks?[indexPath.row] ?? manager.tasks[indexPath.row]
} else {
currentItem = manager.tasks[indexPath.row]
}
cell.titleLabel.text = currentItem.taskName
cell.descriptionLabel.text = currentItem.description
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let accessoryType: UITableViewCell.AccessoryType = manager.changeState(at: indexPath.row) ? .checkmark : .none
tableView.cellForRow(at: indexPath)?.accessoryType = accessoryType
}
When you use tableView.dequeueReusableCell, you may get the old cell, so you should update it. You should read doc.
#PGDev already said in comments that you should save checked/unchecked status in your model.
I hope my example will help you.
You can contain state of cells in cell models:
class YourCellModel {
var task: Task
var checked: Bool
init(task: Task, checked: Bool) {
self.task = task
self.checked = checked
}
}
And add it in ToDoCell:
//...
var model: YourCellModel {
didSet {
updateViews()
}
}
func updateViews() {
titleLabel.text = task.taskName
descriptionLabel.text = task.description
if model.checked {
//...
} else {
//....
}
}
And update model here:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Keys.cell.rawValue, for: indexPath) as! ToDoCell
// You should contain cell models to remember their states
let model = cellModels[indexPath.row]
cell.model = model
return cell
}
When the user checks a cell, you should save it in your cell model. You can do it in ToDoCell:
func checked() {
model.checked = true
}
Note: If isFiltering is true, it is a different array of cell models.
UPD. I noticed your Task is similar to a cell model. You can save checked status there. But your cell should have access to it.
Each of my cells in tableView has its own progressBar which is being filled in cellForRowAt method and it works fine.
My problem is whenever I select a cell, it's progress bar refreshes filling.
Does anyone have an idea why and how to prevent it? I think didSelectRowAt method is not a reason because that method does nothing with the progressBar.
Method for filling progressBar:
func updateProgressBar(_ cell: TestTableViewCell, level: String) {
let cellCards = Float64(self.getNumberOfCardsForLevel(level: level))!
let totalCards = Float64(self.totalCards())!
delay(0.3) {
cell.cellProgress.setProgress(Float(cellCards / totalCards), animated: true)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
cell.accessoryType = .disclosureIndicator;
cell.cellProgress.progress = 0
cell.cellTitle.text = "Test"
cell.numOfCardsInCell.text = getNumberOfCardsForLevel(level: "1")
updateProgressBar(cell, level: "1")
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.indexPathsForSelectedRows?.forEach { index in
if(self.cellSelectedImage[indexPath.row] == UIImage(named: "imageTwo") ) {
tableView.deselectRow(at: indexPath, animated: false)
self.cellSelectedImage[indexPath.row] = UIImage(named: "imageOne")
}
else {
print("Selected row -> \(index.row)")
if self.cellSelectedImage[indexPath.row] == UIImage(named: "imageTwo") {
self.cellSelectedImage[indexPath.row] = UIImage(named: "imageOne")
}
}
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
I am trying to make a search bar in iOS and have made it to where it filters results and then when you click on it, it shows a checkmark. When I delete the search text, the check mark goes away and the full page of cells appears but the cell that I selected in the search is not selected with a check mark. This is implemented in my cell by setSelected method:
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
accessoryType = .checkmark
} else {
accessoryType = .none
}
}
I haven't any logic with checkmark in cellForRowAt and didSelectRowAt methods.
Here is a GIF of my problem
Can someone help me?
I assume all you're currently doing is setting the checkmark on your cell. The cell will be reused and you'll lose the checkmark.
What you need to do is to keep track of the items, e.g. in a Set, that the user has checked so far. Upon rendering items in the table view, you'll need to consult this set whether the item is checked. If it is, you'll want to add the checkmark to the cell.
Something along these lines where Item is your cell model. This should get you started. You should be able to extend this to a data set with groups for table headers.
/// The items that have been checked.
var checkedItems = Set<Item>()
/// All items that are shown when no search has been performed.
var allItems: [Item]
/// The currently displayed items, which is the same as `allItems` in case no
/// search has been performed or a subset of `allItems` in case a search is
/// currently active.
var displayedItems: [Item]
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = displayedItems[indexPath.row]
let cell = // Construct your cell as usual.
if checkedItems.contains(item) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = displayedItems[indexPath.row]
if checkedItems.contains(item) {
checkedItems.remove(item)
cell.accessoryType = .none
} else {
checkedItems.insert(item)
cell.accessoryType = .checkmark
}
}
func updateSearchResults(for searchController: UISearchController) {
displayedItems = // Set the displayed items based on the search text.
tableView.scrollRectToVisible(.zero, animated: false)
tableView.reloadData()
}
I solved my problem in this way:
var selectedCellIndex: String?
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let key = sectionTitles[indexPath.section]
if let values = dictionary[key] {
selectedCellIndex = values[indexPath.row]
}
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
let generator = UIImpactFeedbackGenerator(style: .light)
generator.impactOccurred()
}
public func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.tintColor = Color.main()
cell.selectionStyle = .none
let key = sectionTitles[indexPath.section]
if let values = dictionary[key] {
cell.textLabel?.text = values[indexPath.row]
if values[indexPath.row] == selectedCellIndex {
cell.accessoryType = .checkmark
}
}
return cell
}
SWIFT 3 and Firebase - I'm trying to use the UITable inEditing mode to insert cells into firebase and have a firebase listener that updates my arraty. My insertions to fire base are fine, but I keep getting an index out of range when I try to do the insertion with the new add button i created. Has anyone used Firebase with UITable inEditing mode to insert into a table and update firebase.
This is what I looking for:
enter image description here
The problem maybe be how I wrote the cellForAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("zzz indexPath index path row: \(indexPath.row)")
print("zzz indexPath name \(myPins[indexPath.row].pinName)")
print("ZZZ myPins Count \(myPins.count)")
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
} else {
//let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
}
return cell
}
i get this error message: fatal error: Index out of range
Here is the rest of the table set up code:
//When the use press the editing button it will make the table enditable
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
//if the viw is in editing mode then we will set the table to be in enditing mode.
if isEditing {
//This is adding the addtional cell in for add place
tableView.beginUpdates()
let indexPath = IndexPath(row: 0, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
// self.tableView.insertRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
tableView.setEditing(true, animated: true)
} else {
//when the 'Done' is pressed this will take away the add button
tableView.beginUpdates()
let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
self.tableView.deleteRows(at: [indexPathOfFirstRow], with: UITableViewRowAnimation.automatic)
tableView.endUpdates()
//otherwise the table will not be in editing mode.
tableView.setEditing(false, animated: false)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//STEP 1 for adding a row that will fuction as add place
//If we are in editing mode a 1 will be return and the extra cell will be added
let adjustment = isEditing ? 1 : 0
//add the extra cell if we are in editing mode
return myPins.count + adjustment
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
if indexPath.row == 0 && isEditing {
//this means we are in editing mode an we to add the cell that will be addPlace
//let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
cell.textLabel?.text = "Add Place"
print("add place was called index.row is : \(indexPath.row)")
//addCell.addButton.tag = indexPath.row
//addCell.addButton.addTarget(self, action: "addPlace", for: .touchUpInside)
} else {
// let cell = tableView.dequeueReusableCell(withIdentifier: "PinCell", for: indexPath)
//this is the info I want to dispaly in the cell
let pin = myPins[indexPath.row]
cell.textLabel?.text = pin.pinName
return cell
}
//add the green plus button icon for the extra row added
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
// let indexRow = IndexPath(item: 0, section: 0)
if indexPath.row == 0 {
return .insert
}
return .delete
}
//delete with a with a swipe
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
print("pins count before deleate\(myPins.count)")
if editingStyle == .delete {
let pin = myPins[indexPath.row]
guard let currentUser = FIRAuth.auth()?.currentUser?.uid else {return}
rootRef.child(child.users.rawValue).child(currentUser).child(users.myPins.rawValue).child(plotToEdit!.plotID).child(pin.placeID).removeValue()
}
//When you press on the green plus button as appose to the row.
else if editingStyle == .insert {
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
present(autocompleteController, animated: true, completion: nil)
}
}
//When in ending more I can slect the first row that is add places
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// let indexPathOfFirstRow = IndexPath(item: 0, section: 0)
if isEditing && (indexPath.row > 0) {
return nil
}
return indexPath
}