[SWIFT3] Tableview, searcher et multiselection
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
Je suis surpris par ce comportement (que la bonne ligne ne reste pas sélectionnée)
Comment ça ?
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.
C'est ce qui me gène aussi.. (le row)
Si, mon objet à un identifiant unique par ligne..
Exemple :
Mais comment sélectionner la ligne avec son identifiant unique (IdCommun) ?
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.
Jusque là , c'est bon, j'ai bien ça..
Dans mon code, j'ai rajouté :
et j'ai revu le code de didSelectRowAtIndexPath, sauf que c'est là que j'ai un soucis..
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 ?
Pourquoi tu as cette ligne :
L'UITableViewController contient déjà une var tableView
Et pourquoi tu as ces lignes :
Tu as dit que tes utilisateurs comportent d'un Int pour l'Id et un String pour le NomPrenom.
Du coup, pourquoi pas avoir :
??? ???
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) ?
Mieux que tous ça, utilises une classe pour tes données :
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 Heureusement que t'es là ^^
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.
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 ^^
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
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 ^^
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.
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 ^^