0

I want to search in my tableView, set checkmarks and save the objects with the checkmarks in Realm. But if I set a checkmark after a search and cancel the search, the checkmark is at the indexPath that I clicked on, and not at the object. I can't explain it better, so here's an example: After I search an exercise.

After I clicked the cancel button

Here's my code:

class ShowExcercisesTableViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

//Properties
let realm = try! Realm()
var request2: Results<Excercise>?{
    didSet{
        tableView.reloadData()
    }
}
var searchOrNot: Excercise?
var searchResults = try! Realm().objects(Excercise.self)
var resultSearchController: UISearchController!
var shouldShowSearchResults = false
var muscleGroupForSearch: String?

//Searchbar Funktionen
func filterResultsWithSearchString(searchString: String){
    let predicate = NSPredicate(format: "name CONTAINS [c]%@ AND muscleGroup =%@ AND copied = false", searchString, muscleGroupForSearch!)
    searchResults = realm.objects(Excercise.self).filter(predicate).sorted(byProperty: "name", ascending: true)
}

func updateSearchResults(for searchController: UISearchController) {
    let searchString = searchController.searchBar.text
    filterResultsWithSearchString(searchString: searchString!)
    tableView.reloadData()
}

func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
    shouldShowSearchResults = true
    tableView.reloadData()
}

func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
    shouldShowSearchResults = false
    tableView.reloadData()
}

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    if !shouldShowSearchResults {
        shouldShowSearchResults = true
        tableView.reloadData()
    }
    resultSearchController.searchBar.resignFirstResponder()
}

//Lifecycle
override func viewDidLoad() {
    super.viewDidLoad()

    self.resultSearchController = ({
        let controller = UISearchController(searchResultsController: nil)
        controller.searchResultsUpdater = self
        controller.searchBar.delegate = self
        controller.dimsBackgroundDuringPresentation = false
        controller.searchBar.sizeToFit()
        controller.searchBar.placeholder = "Suche Übungen..."

        self.tableView.tableHeaderView = controller.searchBar

        return controller
    })()

    self.tableView.reloadData()
}

//TableView Funktionen
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if shouldShowSearchResults {
        return searchResults.count
    }
    else{
        return request2!.count
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: ShowExcercisesTableViewCell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.showExcercises, for: indexPath) as! ShowExcercisesTableViewCell
    if shouldShowSearchResults{
        let excercise = searchResults[indexPath.row]
        cell.nameLabel.text = excercise.name
        if fromTrainingPlan{
        if excercise.selected == true{
            cell.accessoryType = .checkmark
        }
        else{
            cell.accessoryType = .none
        }
        }
        return cell
    }
    else{
        let excercise = request2![indexPath.row]
        cell.nameLabel.text = excercise.name
        if fromTrainingPlan{
        if excercise.selected == true{
            cell.accessoryType = .checkmark
            }
        else{
            cell.accessoryType = .none
        }
        }
        return cell
    }
}

//Checkmarks
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if fromTrainingPlan == true && request2 != nil{
        if shouldShowSearchResults{
            searchOrNot = searchResults[indexPath.row]
        }
        else{
            searchOrNot = request2![indexPath.row]
        }
        tableView.deselectRow(at: indexPath, animated: true)

        let cell: ShowExcercisesTableViewCell = tableView.cellForRow(at: indexPath) as! ShowExcercisesTableViewCell
        do {
            try realm.write {
                searchOrNot!.selected = !searchOrNot!.selected
            }
        }
        catch{
            print(error)
        }

        if searchOrNot!.selected {
            cell.accessoryType = .checkmark
        }
        else {
            cell.accessoryType = .none
        }
    }
}

Sorry for so much code, I'm not sure what is relevant and what not. Is there any way to set the checkmarks at the right places after the search? Thanks in advance!

It's working now.

2 Answers2

0

the checkmark is at the indexPath that I clicked on

You are not telling the code on what indexPath you want the checkmark. When your cell gets reused :

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

You need to keep a reference to what indexPath is supposed to be selected and show the checkMark so the cells shows the checkMark on the correct indexPath when the cells are reused internally.

EDIT:

Seems some people downvote and modify my answers as their own without reading how the framework works and reading Apples Documentation.

Reusable Cells

prepareForReuse

If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.

  • Downvote because this is incorrect. He is checking whether something is selected and determining checkmark based on that. He does not need to save an index path in any way. – ColdLogic Feb 05 '17 at 19:33
  • Sure, your answer was a perfect copy of mine. And what happens if he wants to put multiple options in cellForRowAtIndexPath, or rather, multiple cells? Then your "If / Else" statement in your copied "answer" will fail totally and check/uncheck all the cells. –  Feb 05 '17 at 20:35
  • Your answer of `you need to keep a reference to what indexPath is supposed to be selected` is wrong. Sorry. – ColdLogic Feb 06 '17 at 03:54
  • @ColdLogic Maybe my English is not that good, but lets try again: **"And what happens if he wants to put multiple options in cellForRowAtIndexPath, or rather, multiple cells?"** or the Apple Documentations are wrong but you are right instead. –  Feb 06 '17 at 09:29
  • If he were to use multiple cells, then he would use his solution where he checks the data and determines the checkmark based on that. If he were to save a reference to the indexPath, he would have no link between the data and the indexPath and thus, have the same problem. No where do I say "the apple documentation is wrong". I say your solution to the problem is wrong. If you want to jump in a chat, I will be more than happy to help you fix your answer. – ColdLogic Feb 06 '17 at 16:34
  • **"where he checks the data and determines the checkmark based on that."** , and "check the data" in this matter is the **indexPath** , which he has no checks on at the moment based on your answer, thank you for clearing my point. We fixed it now no need. Let me know if you need any further help understanding, I would gladly help you you can email me or go into chat. –  Feb 06 '17 at 18:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134990/discussion-between-coldlogic-and-sneak). – ColdLogic Feb 06 '17 at 18:24
0

In your cellForRowAtIndexPath, you need to disable the checkmark for cells that do not need it. You are only enabling for cells that do.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: ShowExcercisesTableViewCell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier.showExcercises, for: indexPath) as! ShowExcercisesTableViewCell
    if shouldShowSearchResults{
        let excercise = searchResults[indexPath.row]
        cell.nameLabel.text = excercise.name
        if excercise.selected == true{
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none // Add this code here
        }
        return cell
    }
    else{
        let excercise = request2![indexPath.row]
        cell.nameLabel.text = excercise.name
        if excercise.selected == true {
            cell.accessoryType = .checkmark
        } else {
            cell.accessoryType = .none // Add this code here
        }
        return cell
    }
}

UITableViews reuse the cells internally. So when you are searching, you are saying cell one has a checkmark, then when you cancel, it goes back to the table and looks at the cells and your cellForRow code never tells it that cell one is no longer checked, thus it maintains the checkmark there. The cell is not being recreated, its already exists, so you cannot make an assumption about what state it is in (not checked or checked).

ColdLogic
  • 7,206
  • 1
  • 28
  • 46
  • You are downvoting my answer and telling me i'm wrong that the code cellForRowAtIndexPath does not know what cell should be marked , and you basically repeat the exact same thing. I clearly just said that cellForRowAtIndexPath does not know what cell should be marked and you repeated my answer. Congratulation for copying my answer. nd what happens if he wants to put multiple options in cellForRowAtIndexPath, or rather, multiple cells? Then your "If / Else" statement in your copied "answer" will fail totally and check/uncheck all the cells. –  Feb 05 '17 at 20:35
  • @Sneak Ummmm, my answers was in no way a copy of yours. You edited yours after mine was posted. You post is 3 text lines of "you need to save a reference to the index path". I apologize if you think I was attacking you or something, but your answer is wrong. If you correct it, and I will remove my down vote. – ColdLogic Feb 06 '17 at 04:00
  • " You edited yours after mine was posted." , uummmmmmm yeah I edited it and **added the Apple Documentation for you to read** , maybe you can't see the EDIT mark ? –  Feb 06 '17 at 09:34
  • I tried your solution, but unfortunately it doesn't work.. The behavior changed a bit. Now if I select the objects in the tableView before I begin with the search, and then use the search, the right objects stay selected. But if I select objects from the searchResults array after I used the searchbar and then use the cancel button of the searchbar, so that the request2 array is shown again, the wrong objects are selected.. Maybe any other ideas? Btw: Sorry for my bad english – Marcel Spindler Feb 06 '17 at 14:00
  • It's working now! I only changed a bit in the didSelectRow function. I edited my question. – Marcel Spindler Feb 06 '17 at 14:51
  • @Sneak Yes, I do see your edit time. If you changed your post after I posted mine, mine cannot be a copy of yours... Copy&Pasting lines from Apple documentation does not make your proposed solution any more correct. – ColdLogic Feb 06 '17 at 16:37
  • @MarcelSpindler Since you have so few results at the moment in your tableView Try force scrolling your cells out of the screen and scroll down again when you have multiple results, and see what happens to your checkmarks. :) –  Feb 06 '17 at 18:32
  • If I scroll the cells out of the screen, the checkmarks stay also in the right cells, thanks for help everyone! – Marcel Spindler Feb 06 '17 at 22:20
  • @MarcelSpindler Woo! – ColdLogic Feb 06 '17 at 22:21