[SWIFT3] Tableview, searcher et multiselection

InsouInsou Membre
juin 2017 modifié dans API UIKit #1

Bonjour à  tous,


 


De retour pour un nouveau soucis :


 


J'ai une tableview avec la multiselection qui charge des utilisateurs et une barre de recherche, lorsque je sélectionne des utilisateurs normalement, j'arrive bien à  récupérer leur id, pas de soucis.


Mais lorsque je choisi un utilisateur via la recherche, lorsque je l'a ferme, il a coché le mauvais utilisateur (ce qui est normal, il coche l'utilisateur à  l'index X de ma recherche (X = la position dans la recherche), qui n'est plus le même quand je sors de la recherche).


 


Je sais pas si c'est bien clair donc voici mon code :



@IBOutlet weak var tableUtilisateurs: UITableView!
var ArrayUtilisateurs = [[String:AnyObject]]() //Array of dictionary
var ArrayUtilisateursFiltrés = [[String:AnyObject]]() //Array of dictionary

let searchController = UISearchController(searchResultsController : nil) // searchbar

override func viewDidLoad() {
super.viewDidLoad()

self.tableUtilisateurs.allowsMultipleSelection = true // par le code mais on peut aussi le faire dans l'editeur

chargeUtilisateurs() // fonction qui rempli le tableau d'utilisateur

// search bar - DEBUT`
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Recherche.."
definesPresentationContext = true
tableUtilisateurs.tableHeaderView = searchController.searchBar
// search bar - FIN
}

func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var LigneUtilisateur = [String:AnyObject]()

if (searchController.isActive && searchController.searchBar.text != ""){ // si on est dans la bar de recherche
LigneUtilisateur = ArrayUtilisateursFiltrés[indexPath.row]
}
else{ // comportement normal
LigneUtilisateur = ArrayUtilisateurs[indexPath.row]
}


var uneCellule: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "CELLULE")
if uneCellule == nil {
uneCellule = UITableViewCell(style: .subtitle, reuseIdentifier: "CELLULE")
}

if let NomPrenom = LigneUtilisateur["NomPrenom"]{
uneCellule.textLabel?.text = NomPrenom as? String
}

return uneCellule!
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (searchController.isActive && searchController.searchBar.text != ""){ // si on est dans la bar de recherche
return ArrayUtilisateursFiltrés.count
}
return ArrayUtilisateurs.count
}

// selection d'une cellule
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

// Pour la selection multiple
if selectedCell.isSelected{
selectedCell.isSelected = false
if selectedCell.accessoryType == UITableViewCellAccessoryType.none{
selectedCell.accessoryType = UITableViewCellAccessoryType.checkmark
}
else{
selectedCell.accessoryType = UITableViewCellAccessoryType.none
}
}
}

@IBAction func actionRecupLigne(_ sender: Any) {
if let TabLignesCochées = tableUtilisateurs.indexPathsForSelectedRows{

for(Ligne) in TabLignesCochées {
let LigneSelectionne:UITableViewCell = tableUtilisateurs.cellForRow(at: Ligne)!
if(LigneSelectionne.accessoryType.rawValue == 3){ // 3 = Ligne cochée
print(Ligne.row)
}
}
}
}

// search bar
func filtrerParTexte(searchText: String){
ArrayUtilisateursFiltrés = ArrayUtilisateurs.filter{ Utilisateur in
let NomPrenom = Utilisateur["NomPrenom"]! as! String
return NomPrenom.lowercased().contains(searchText.lowercased())
}
tableUtilisateurs.reloadData()
}

###########

// searchbar
extension IncidentDemanderPositionSelectionUtilisateursViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filtrerParTexte(searchText: searchController.searchBar.text!)
}
}

Exemple concret : 


 


Voici ma liste avant ma recherche :



+ + +
| Id | NomPrenom |
+ + +
| 0 | A |
| 1 | B |
| 2 | C |
| 3 | BB |
+ + +

Et ma liste après la recherche :



+ + +
| Id | NomPrenom |
+ + +
| 0 | B |
| 1 | BB |
+ + +

Les Id ont changé car il prends les numéro de ligne.. 


 


Comment faire pour qu'il sélectionne la bonne ligne lorsque je fais une recherche ? :/


 


Merci de votre aide :)


Réponses

  • Comme ça je dirais : n'y a-t-il pas un delegate pour tu puisses sélectionner à  la main la bonne ligne ?

    Je suis surpris par ce comportement (que la bonne ligne ne reste pas sélectionnée)
  • InsouInsou Membre
    juillet 2017 modifié #3

    Comme ça je dirais : n'y a-t-il pas un delegate pour tu puisses sélectionner à  la main la bonne ligne ?



     


    Comment ça ? 


     



    Je suis surpris par ce comportement (que la bonne ligne ne reste pas sélectionnée)



     


    Point de vue du code, c'est plutôt logique, il coche la ligne X, sauf que X change si je suis dans une recherche ou pas..


    Du coup, comment cocher la bonne ligne, sans se baser sur X ?


  • Sorti de son contexte comme ça, j'aurais dit comme là  : http://forum.cocoacafe.fr/topic/15296-collectionview-lock-image/


    Mets cette valeur sélectionnée dans le modèle.


     


    Bon, maintenant, cela ne s'adapte peut-être pas à  ton cas (il n'y a pas de vraie raison à  ce qu'un item soit sélectionné au niveau "BDD/Structure" en soit).


     


    Du coup, il faut gérer une sorte de contexte.


     


    Ce qui me gêne, c'est que tu considère "indexPath" (dans ton cas en "row") comme un "id", ce qui n'est pas vraiment le cas.


    Ces objets n'ont pas un moyen d'être identifié de manière unique ?


    Si c'est le cas, garde-celui ci en mémoire et lis si l'identifiant unique est dans la liste auquel cas marque le en tant que selected, autrement non.


  • InsouInsou Membre
    juillet 2017 modifié #5


    Ce qui me gêne, c'est que tu considère "indexPath" (dans ton cas en "row") comme un "id", ce qui n'est pas vraiment le cas.


    Ces objets n'ont pas un moyen d'être identifié de manière unique ?


    Si c'est le cas, garde-celui ci en mémoire et lis si l'identifiant unique est dans la liste auquel cas marque le en tant que selected, autrement non.




     


    C'est ce qui me gène aussi.. (le row)


    Si, mon objet à  un identifiant unique par ligne.. 


     


    Exemple : 



    ["IdCommun": 3, "NomPrenom": Steve Jobs]



     


    Mais comment sélectionner la ligne avec son identifiant unique (IdCommun) ?


  • LarmeLarme Membre
    juillet 2017 modifié #6

    ArrayUtilisateursFiltrés = ArrayUtilisateurs.filter{ Utilisateur in
                let NomPrenom = Utilisateur["NomPrenom"]! as! String
                return NomPrenom.lowercased().contains(searchText.lowercased())
            }
            tableUtilisateurs.reloadData()

    Ce code ne touche en aucun cas à  Utilisateur["idCommun"].


    Donc normalement, tu devrais avoir par exemple dans ArrayUtilisateursFiltrés :


    ["IdCommun": 3, "NomPrenom": Steve Jobs]


    ["IdCommun": 8, "NomPrenom": Steve Wozniak]


    En ayant potentiellement des "trous".

    Donc garde un arrayOfSelectedUsers: [UInt8] qui contiendrait les idCommun sélectionnés.


     


    Après concernant ton code, évite les majuscules au début des noms de variables (ArrayUtilisateursFiltrés -> arrayUtilisateursFiltrés, elles doivent être réservés à  des nom de classes. 

    J'passerais le fait de mettre des noms français et d'utiliser des accents.


  • InsouInsou Membre
    juillet 2017 modifié #7

    Donc normalement, tu devrais avoir par exemple dans ArrayUtilisateursFiltrés :


    ["IdCommun": 3, "NomPrenom": Steve Jobs]


    ["IdCommun": 8, "NomPrenom": Steve Wozniak]


    En ayant potentiellement des "trous".



     


    Jusque là , c'est bon, j'ai bien ça..


     



    Donc garde un arrayOfSelectedUsers: [UInt8] qui contiendrait les idCommun sélectionnés.



     


    Dans mon code, j'ai rajouté : 



    var ArrayUtilisateursSelectionnés = [String]()

    et j'ai revu le code de didSelectRowAtIndexPath, sauf que c'est là  que j'ai un soucis..



    func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {


    var LigneUtilisateur = [String:AnyObject]()

    if (searchController.isActive && searchController.searchBar.text != ""){ // si on est dans la bar de recherche
    LigneUtilisateur = ArrayUtilisateursFiltrés[indexPath.row]
    }
    else{ // comportement normal
    LigneUtilisateur = ArrayUtilisateurs[indexPath.row]
    }

    if let IdCommun = LigneUtilisateur["IdCommun"]{
    if(ArrayUtilisateursSelectionnés.contains(IdCommun as! String)){ // existe déjà  dans le tableau
    ArrayUtilisateursSelectionnés.remove(at: ArrayUtilisateursSelectionnés.index(of: IdCommun as! String)!)
    }
    else{ // n'existe pas, on l'ajoute ua tableau
    ArrayUtilisateursSelectionnés.append(IdCommun as! String)
    }

    // PROBLEME ICI
    let selectedCell:UITableViewCell = tableView.cellForRow(at: indexpath as IndexPath)!
    selectedCell.contentView.backgroundColor = UIColor.orange
    // Pour la selection multiple
    if selectedCell.isSelected{
    selectedCell.isSelected = false
    if selectedCell.accessoryType == UITableViewCellAccessoryType.none{
    selectedCell.accessoryType = UITableViewCellAccessoryType.checkmark
    }
    else{
    selectedCell.accessoryType = UITableViewCellAccessoryType.none
    }

    }

                


     


     


    En gros, j'aimerai cocher la ligne sélectionnée..


    Mais j'ai un soucis avec l'indexPath..


    Comment retrouver le bon indexPath en fonction du fait que je suis soit dans une recherche ou pas ?


  • Joanna CarterJoanna Carter Membre, Modérateur
    juillet 2017 modifié #8

    Pourquoi tu as cette ligne :



    @IBOutlet weak var tableUtilisateurs: UITableView!

    L'UITableViewController contient déjà  une var tableView


     


    Et pourquoi tu as ces lignes :



    var ArrayUtilisateurs = [[String:AnyObject]]()

    var ArrayUtilisateursFiltrés = [[String:AnyObject]]()

    Tu as dit que tes utilisateurs comportent d'un Int pour l'Id et un String pour le NomPrenom.


     


    Du coup, pourquoi pas avoir :



    var ArrayUtilisateurs = [[Int : String]]()

    var ArrayUtilisateursFiltrés = [[Int : String]]()

    ???  ???


  • Oui pardon, j'avais en mémoire sur MacOS, la gestion automatique de ce genre de problème si ta TableView est liée à  un ArrayController !


     


    Est-ce que tu trouves ce dont tu a besoin là  (liste de méthode delegate) ?


  • Joanna CarterJoanna Carter Membre, Modérateur
    juillet 2017 modifié #10

    Mieux que tous ça, utilises une classe pour tes données :



    class Utilisateur
    {
    let id: Int

    let nomPrenom: String

    var isSelected = false

    init(id: Int, nomPrenom: String)
    {
    self.id = id

    self.nomPrenom = nomPrenom
    }
    }


    class ViewController: UITableViewController, UISearchResultsUpdating
    {
    let utilisateurs = [Utilisateur(id: 1, nomPrenom: "Une"), Utilisateur(id: 2, nomPrenom: "Deux"), Utilisateur(id: 3, nomPrenom: "Trois"), Utilisateur(id: 4, nomPrenom: "Quatre"), Utilisateur(id: 5, nomPrenom: "Cinq"), Utilisateur(id: 6, nomPrenom: "Six")]

    var utilisateursFiltrés = [Utilisateur]()

    var isFiltered = false

    lazy var searchController: UISearchController =
    {
    let searchController = UISearchController(searchResultsController: nil)

    searchController.searchResultsUpdater = self

    searchController.dimsBackgroundDuringPresentation = false

    searchController.hidesNavigationBarDuringPresentation = false

    self.definesPresentationContext = true

    searchController.searchBar.autocapitalizationType = .none

    searchController.searchBar.tintColor = self.tableView.tintColor

    searchController.searchBar.barTintColor = self.tableView.backgroundColor

    return searchController
    }()

    override func viewDidLoad()
    {
    super.viewDidLoad()

    tableView.tableHeaderView = searchController.searchBar
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
    return isFiltered ? utilisateursFiltrés.count : utilisateurs.count
    }

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

    let utilisateur = isFiltered ? utilisateursFiltrés[indexPath.row] : utilisateurs[indexPath.row]

    cell.textLabel?.text = utilisateur.nomPrenom

    cell.accessoryType = utilisateur.isSelected ? .checkmark : .none

    return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
    let utilisateur = isFiltered ? utilisateursFiltrés[indexPath.row] : utilisateurs[indexPath.row]

    utilisateur.isSelected = !utilisateur.isSelected

    tableView.reloadRows(at: [indexPath], with: .automatic)
    }

    func updateSearchResults(for searchController: UISearchController)
    {
    let searchText = searchController.searchBar.text ?? ""

    utilisateursFiltrés = utilisateurs.filter
    {
    utilisateur in

    return utilisateur.nomPrenom.localizedStandardContains(searchText)
    }

    isFiltered = utilisateursFiltrés.count > 0

    tableView.reloadData()
    }
    }

  • J'viens de tester et de réadapter mon code et ça fonctionne nickel.


    Merci Joanna, ton code est vachement plus simple que le mien et c'est très clair à  comprendre.. j'étais parti pour me compliquer la vie :s Heureusement que t'es là  ^^


  • Joanna CarterJoanna Carter Membre, Modérateur

    La simplicité - ce qui arrive avec l'expérience  8--)


     


    ... et de s'occuper comme consultante pour plus de 25 ans  ::)


     


    Le clé est de bien séparer les données des UIs. Oui, on a la sélection dans une UITableView mais il faut, quand-même, maintenir la sélection dans ton modèle. L'UI ne devrait que réfléchir l'état du modèle.


     


    Avec une sélection, on peut faire comme je l'ai fait, ou on peut maintenir une liste d'articles sélectionnées à  part ; les deux sont également valides.


     


    Oh, et utiliser les classes/structs à  la place des dictionnaires ; c'est beaucoup plus facile.

  • Joanna CarterJoanna Carter Membre, Modérateur
    juillet 2017 modifié #13

    P.S. Avec ce code, tu n'as plus besoin de l'id dans la classe, à  moins que tu ne l'utilises pas ailleurs.


  • Si si, l'id j'en ai besoin..


    Je récupère un tableau d'id (sélectionnés) que j'envoi ensuite à  mon api ^^


  • Joanna CarterJoanna Carter Membre, Modérateur

    Bon.


     


    Autre question - comment stockes-tu les données ?


  • Comment ça ?


     


    Là  pour le moment, je récupère via une requête ajax, la liste de mes utilisateurs.. j'en choisi certains et en validant je renvoi les Id de ceux qui m'intéressent vers la suite de mon api.


     


    J'ai pas spécialement besoin de stocker quoique ce soit ici :)


  • Joanna CarterJoanna Carter Membre, Modérateur

    T'as pensé de ce qui puisse arriver si ton appli était fermé par le système ?


     


    C'est conseillé d'avoir une cache locale pour que l'utilisateur ne doive pas attendre pendant que ton appli fasse la récupération du serveur encore une fois.


  • Pour le coup, je suis obligé de refaire une récupération de la liste via le serveur à  chaque fois que j'ouvre cette page.. la liste peut changer à  tout moment et elle doit toujours être à  jour.


     


    Par contre, j'serai intéressé de voir comment mettre ça en place, ça me servira surement à  un autre moment ^^


  • Joanna CarterJoanna Carter Membre, Modérateur

    Dans mon code, j'ai utilisé une classe Utilisateur. Tu pourrais transférer cette idée en utilisant presque la même classe avec CoreData pour que tu puisses créer les objets à  chaque récupération et les stocker sur l'iBidule de ton utilisateur.


     


    Du coup, si l'utilisateur se trouvait "hors réseau", il pourrait continuer d'utiliser ton appli avec les dernières données récupérées.


  • InsouInsou Membre
    juillet 2017 modifié #20

    Du coup, si l'utilisateur se trouvait "hors réseau", il pourrait continuer d'utiliser ton appli avec les dernières données récupérées.



     


    Pour le coup, c'est pas possible.. quand bien même il aurait la liste des utilisateurs gardée cache, il faut obligatoirement une connexion pour valider l'action après vu que je renvoi les utilisateurs sélectionnés vers l'api ^^


     


    Je garde quand même l'idée du cache en tête, c'est sûr que ça me servira à  un moment ^^


Connectez-vous ou Inscrivez-vous pour répondre.