0

Im trying to put checkmarks after doing a search. I've seen some answers here but it doesn't work for me. Ive tried some of the answers but it will only make an error.

Here's the code that I wrote.

struct finalCheckednames { var name: String; var checkMark: Bool}

class ShowBroadcastNamesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {

var nameslist = ["Mark","Chris", "James", "Sandra"]
var myIndex = 0
var nameArray = String()
var filteredData = [String]()
var isSearching = false
var selectedNamesToBroadcast = [String]()
var selectedNamesIndex:Int? = nil
var checkmarks = [Int : Bool]()
var hasChecked = Bool()

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? broadcastNamesTableViewCell {

        let text: String!

        if isSearching {
            text = filteredData[indexPath.row]

        } else {
            text = nameslist[indexPath.row]
        }

        cell.configureCell(text: text)

        if checkmarks[indexPath.row] != nil {
            cell.accessoryType = checkmarks[indexPath.row]! ? .checkmark : .none
        } else {
            checkmarks[indexPath.row] = false
            cell.accessoryType = .none
        }

        return cell

    } else {

        return UITableViewCell()
    }
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  if isSearching {
            selectedNamesIndex = filteredData[indexPath.row] // im also getting an error here

        } else {

    selectedNamesIndex = indexPath.row
    let cell = tableView.cellForRow(at: indexPath)

    if let index = selectedNamesIndex {
        if (cell?.accessoryType == .checkmark) {
            cell!.accessoryType = .none
            checkmarks[indexPath.row] = false
            hasChecked = false

            let indexPathTapped = tableView.indexPath(for: cell!)
            let name = nameslist[(indexPathTapped!.row)]
            print(name, hasChecked)
            if let index = selectedNamesToBroadcast.index(of: name) {
                selectedNamesToBroadcast.remove(at: index)
            }

        } else {
            cell!.accessoryType = .checkmark
            checkmarks[indexPath.row] = true
            hasChecked = true

            let indexPathTapped = tableView.indexPath(for: cell!)
            let name = nameslist[(indexPathTapped!.row)]
            print(name, hasChecked)
            selectedNamesToBroadcast.append(name)

            let selectedNamesToBroadcast = name

            finalCheckednames(name: name, checkMark: hasChecked)
            print(finalCheckednames.self, "XXXX")
        }
    }

}

 func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    if searchBar.text == nil || searchBar.text == "" {
        isSearching = false
        view.endEditing(true)
        tableView.reloadData()
    } else {
        isSearching = true
        filteredData = namelist.filter{$0.range(of: searchText, options: .caseInsensitive) != nil}
        tableView.reloadData()

I was hoping for the checkmarks to stay after searching. So atleast i can save all the checkmarked items somewhere.

Nick
  • 104
  • 11
  • If I understand you correctly you should create a struct so that the string and checkmark are tied together, `struct Name { var name: String; var checkMark: Bool}`, and then use that struct in your arrays – Joakim Danielson Sep 12 '19 at 13:05
  • does that mean i should put an `Int` in the struct too? because im also using the `Int` in `var checkmarks = [Int : Bool]()` – Nick Sep 12 '19 at 13:09
  • I don't think so if the Int is supposed to represent an index or similar. – Joakim Danielson Sep 12 '19 at 13:13
  • I updated the code(posted on top) and tried to print `finalCheckednames`, it just printed finalCheckednames XXXX. apologies, im new to this. – Nick Sep 12 '19 at 13:41
  • You are supposed to use the struct in your arrays instead of String, nameslist and filteredData – Joakim Danielson Sep 12 '19 at 13:46
  • Does that mean i need to replace `filteredData = namelist.filter{$0.range(of: searchText, options: .caseInsensitive) != nil}` in `func searchBar`? – Nick Sep 12 '19 at 14:02

1 Answers1

0

Your code is very, very, very complicated. The most efficient way to accomplish your request is to use the isChecked information in the struct, nothing else, no extra arrays, dictionaries, properties.

Give the struct and the Bool a more meaningful name.

struct Person {
    let name: String
    var isChecked: Bool
}

In the controller name the data source array people and the filter array filteredPeople. Further you need only isSearching, delete all other properties

class ShowBroadcastNamesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {

    var people = [Person(name: "Mark", isChecked: false),
                  Person(name: "Chris", isChecked: false),
                  Person(name: "James", isChecked: false),
                  Person(name: "Sandra", isChecked: false)]

    var filteredPeople = [Person]()
    var isSearching = false

var myIndex = 0
var nameArray = String()
var filteredData = [String]()
var selectedNamesToBroadcast = [String]()
var selectedNamesIndex:Int? = nil
var checkmarks = [Int : Bool]()
var hasChecked = Bool()

In cellForRow force unwrap the custom cell and name it uppercased (good practice). Pass the name to configureCell and set the accessory view depending on the value of isChecked

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BroadcastNamesTableViewCell

    let person = isSearching ? filteredPeople[indexPath.row] : people[indexPath.row]
    cell.configureCell(text: person.name)
    cell.accessoryType = person.isChecked ? .checkmark : .none
    return cell
}

In didSelectRow toggle isChecked (and update also the people array if necessary) and reload the row, that's all

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if isSearching {
        filteredPeople[indexPath.row].isChecked.toggle()
        let index = people.firstIndex{$0.name == filteredPeople[indexPath.row].name}!
        people[index].isChecked.toggle() 
    } else {
        people[indexPath.row].isChecked.toggle()
    }
    tableView.reloadRows(at: [indexPath], with: .automatic)
}

In textDidChange search for the name

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    if searchText.isEmpty {
        isSearching = false
        filteredPeople.removeAll()
        view.endEditing(true)
    } else {
        isSearching = true
        filteredPeople = people.filter{$0.name.range(of: searchText, options: .caseInsensitive) != nil}
    }
    tableView.reloadData()
 }

In this answer I already suggested a more efficient version of textDidChange, please consider to follow the advices.

To get the checked people just write

let checkedPeople = people.filter{$0.isChecked}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • thank you for all of your help @vadian. its working. But when i search for a name and i checkmark it, it works, but when I stop searching and goes back to the original list the check mark of the searched name is gone. – Nick Sep 12 '19 at 15:43
  • Make the `struct` a `class` to get reference semantics. I updated the answer. – vadian Sep 12 '19 at 15:54
  • sad to say, its still the same. the checkmark disappears after search – Nick Sep 12 '19 at 16:12
  • OK, revert the changes and keep `Person` a struct. I changed `didSelectAt` to update the main array. Replace the method. Note: If there are two or more people with the same name you need another property to distinguish the items. – vadian Sep 12 '19 at 16:21
  • Thank you so much. its working now. Just have a question how come when i print `checkedPeople` it prints `Nameofproject.Person(name: "Mark", isChecked: true)` Database doesnt accept this format. I just need the String `Mark` in the database – Nick Sep 12 '19 at 16:32
  • Please read the code and try to understand it. You get the name with the `.name` property like in `cellForRow`, `didSelect` or `textDidChange`. – vadian Sep 12 '19 at 16:35
  • Thanks @vidian for your help – Nick Sep 13 '19 at 08:52
  • i need the `.name` which is also filtered by `isChecked` – Nick Sep 13 '19 at 14:34
  • 1
    If you want an array of names rather than an array of `Person` `map` the array: `let checkedNames = people.filter{$0.isChecked}.map{$0.name}` – vadian Sep 13 '19 at 14:36
  • have you encounter this before https://stackoverflow.com/questions/57935114/placing-map-annotation-of-specific-database-value – Nick Sep 16 '19 at 12:59
  • Sorry, I'm not familiar with Firebase – vadian Sep 16 '19 at 20:17