Aller au contenu


Photo

[SWIFT3] Tableview, searcher et multiselection

swift tableview searchbar multiselection

  • Please log in to reply
19 réponses à ce sujet

#1 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 30 juin 2017 - 14:59

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 :)



#2 colas_

colas_

    Broyeur de fèves

  • Membre
  • PipPipPipPipPipPip
  • 1 431 messages

Posté 01 juillet 2017 - 13:15

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)

small-logo.png

Mathématiques pour classes prépa et enseignement supérieur sur iPad et iPhone

www.improov.fr > < Improov sur facebook >


#3 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 03 juillet 2017 - 08:47

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 ?



#4 Larme

Larme

    Broyeur de fèves

  • Artisan chocolatier
  • PipPipPipPipPipPip
  • 1 949 messages
  • LocationParis

Posté 03 juillet 2017 - 09:04

Sorti de son contexte comme ça, j'aurais dit comme là : http://forum.cocoaca...iew-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.


  • Joanna Carter aime ceci
Tant que vous avez des dents, mangez des pommes. Tant que vous avez de l'argent, croquez la Pomme.

#5 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 03 juillet 2017 - 13:06

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) ?



#6 Larme

Larme

    Broyeur de fèves

  • Artisan chocolatier
  • PipPipPipPipPipPip
  • 1 949 messages
  • LocationParis

Posté 03 juillet 2017 - 13:12

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.


Tant que vous avez des dents, mangez des pommes. Tant que vous avez de l'argent, croquez la Pomme.

#7 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 03 juillet 2017 - 14:27

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 ?



#8 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 03 juillet 2017 - 18:01

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]]()

???  ???



#9 colas_

colas_

    Broyeur de fèves

  • Membre
  • PipPipPipPipPipPip
  • 1 431 messages

Posté 03 juillet 2017 - 19:18

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  (liste de méthode delegate) ?


small-logo.png

Mathématiques pour classes prépa et enseignement supérieur sur iPad et iPhone

www.improov.fr > < Improov sur facebook >


#10 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 03 juillet 2017 - 19:18

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()
  }
}

  • colas_ aime ceci

#11 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 04 juillet 2017 - 10:34

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à ^^



#12 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 04 juillet 2017 - 12:01

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.


  • Insou aime ceci

#13 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 04 juillet 2017 - 15:43

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



#14 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 04 juillet 2017 - 15:46

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 ^^



#15 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 04 juillet 2017 - 16:03

Bon.

 

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



#16 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 04 juillet 2017 - 16:06

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 :)



#17 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 04 juillet 2017 - 16:10

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.



#18 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 04 juillet 2017 - 16:18

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 ^^



#19 Joanna Carter

Joanna Carter

    Broyeur de fèves

  • Contrôleur d'arômes
  • 1 887 messages
  • LocationPlestin-les-Grèves (22)

Posté 04 juillet 2017 - 16:25

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.



#20 Insou

Insou

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 226 messages

Posté 05 juillet 2017 - 08:55

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 ^^







Also tagged with one or more of these keywords: swift, tableview, searchbar, multiselection

0 utilisateur(s) li(sen)t ce sujet

0 membre(s), 0 invité(s), 0 utilisateur(s) anonyme(s)