[SWIFT] TableView, SearchBar, 2 recherches différentes
Holà ^^
J'ai un problème un peu tordus et je vais essayé de vous expliquer ça simplement..
J'ai une interface de recherche :
Lorsque j'arrive dessus, je peux commencer à écrire une adresse, l'autocompletion m'aide si j'en ai besoin.. pas de soucis, ça fonctionne.
J'aimerai rajouter un autre mode de recherche dans une liste qui existe déjà..
Cette liste s'afficherai lorsque je clic sur l'icon "favoris" (après la barre de recherche)
En gros, quand je clic sur l'icon "favoris", ma liste actuelle se vide et se remplis par mes favoris (une liste en JSON) et j'aimerai garder le fonctionnement de la barre de recherche tant qu'a faire
Puis si je veux repasser en mode "recherche d'adresse", je reclic sur le bouton "favoris" qui me vide ma liste des favoris et remet le fonctionnement normal.
J'ai aucune idée de comment faire ça..
Est ce que je peux utiliser la même tableView pour faire ça ou est ce que je dois avoir 2 tablesView, en cachant celle qui ne me sert pas ?
Si je met 2 tablesView, est ce qu'une des deux (celle des favoris) ne va pas se mélanger les pinceaux et interagir avec les extensions alors qu'elle ne devrait pas ?
Voici mon code pour l'instant :
Je récupère une adresse (String) via la vue précédente pour remplir ma SearchBar..
Et stock les coordonnées et l'adresse à l'appuie sur le bouton Valider pour m'en re-servir sur l'autre vue.
class ChoixAdresseController: UIViewController {
@IBOutlet weak var searchbarAdresse: UISearchBar!
@IBOutlet weak var tableViewResultatAdresse: UITableView!
@IBOutlet weak var boutonValider: UIBarButtonItem!
@IBOutlet weak var boutonFavoris: UIButton!
var adresse : String = ""
var coordonnees : String = ""
var searchCompleter = MKLocalSearchCompleter()
var searchResults = [MKLocalSearchCompletion]()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.barTintColor = couleurBandeau
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: couleurTexteBandeau]
searchbarAdresse.barTintColor = UIColor(hex: "#484D54")
if(adresse != ""){
searchbarAdresse.text = adresse
}
searchCompleter.delegate = self
}
@IBAction func ationBoutonRetour(_ sender: Any) {
dismiss(animated: true, completion: nil) // On ferme la fenetre
}
@IBAction func actionBoutonValider(_ sender: Any) {
if let nouvelleAdresse = searchbarAdresse.text {
if(nouvelleAdresse != ""){
// stock les données
DB.store["nouvelleAdresse"] = nouvelleAdresse
DB.store["nouvelleAdresse_coordonnees"] = coordonnees
dismiss(animated: true, completion: nil) // On ferme la fenetre
}
}
}
@IBAction func actionBoutonFavoris(_ sender: Any) {
let imageFavoris = UIImage(named: "icon_favoris")
// Changement de bouton quand on clic dessus
if let imageBouton = boutonFavoris.imageView?.image {
if(imageBouton == imageFavoris){
boutonFavoris.setImage(UIImage(named: "icon_pin_favoris"), for: .normal)
}
else{
boutonFavoris.setImage(UIImage(named: "icon_favoris"), for: .normal)
}
}
}
}
extension ChoixAdresseController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchCompleter.queryFragment = searchText
}
}
extension ChoixAdresseController: MKLocalSearchCompleterDelegate {
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
searchResults = completer.results
tableViewResultatAdresse.reloadData()
}
func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
// handle error
}
}
extension ChoixAdresseController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}
// pour chaque cellule
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let searchResult = searchResults[indexPath.row]
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = searchResult.title
cell.detailTextLabel?.text = searchResult.subtitle
cell.imageView?.image = UIImage(named: "icon_building")
return cell
}
}
extension ChoixAdresseController: UITableViewDelegate {
// Quand on clic sur une ligne
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let completion = searchResults[indexPath.row]
let searchRequest = MKLocalSearch.Request(completion: completion)
let search = MKLocalSearch(request: searchRequest)
search.start { (response, error) in
if let coordinate = response?.mapItems[0].placemark.coordinate {
self.searchbarAdresse.text = response?.mapItems[0].placemark.title
self.coordonnees = String(stringInterpolationSegment: coordinate.latitude)+","+String(stringInterpolationSegment: coordinate.longitude)
self.actionBoutonValider("")
}
}
}
}
Comment faire pour gérer 2 formats différents (un avec le code actuelle et l'autre avec les favoris en JSON) dans une seule tableView et garder le fonctionnement de recherche ?
Réponses
• Une seule
UITableView
.• - Soit un seul type d'objet pour le modèle de la TableView, qui s'initie soit avec un MKLocalSearchCompleter.resultClass, soit avec un dictionnaire de tes favoris.
- Soit, si tu as déjà un vrai objet dans tes favoris, une vraie classe custom, tu peux utiliser un protocol, mais je te conseille d'utiliser un objet model qui sera un wrapper, car tu sembles encore débutant, et c'est plus simple à comprendre.
• Enfin, tu as un array d'un seul type d'objets du coup, et quand tu changes ce que tu souhaites afficher, tu changes juste l'array qui remplit ta tableView.
Je crois comprendre la théorie mais je ne vois pas du tout comment mettre ça en pratique..
J'aime pas demander ça mais aurais-tu un bout de code pour me débloquer ?
Grosso Modo:
Je suppose que là, tu changes montre tes favoris ou non :
Il y a sûrement des problèmes de unwrap et autres typos, et je n'ai aucune idée de à quoi ressemble ton array de favoris, mais voilà l'idée.
Moi, j'ai un petit enum :
Et, avec ça, je peut déterminer quelles données à récupérer do mon DataProvider
Du coup, lorsque je veux changer le contenu de la UICollectionView, il ne faut que changer l'enum et appeler reloadData :
@Larme : Woah.. merci.. j'ai adapté tout ça à mon code et tout se goupille plutôt bien ^^
En fait à chaque fois que ça appelle source, je test si je suis dans mes favoris ou dans la recherche d'adresse et j'adapte mes valeurs selon mon cas..
C'est le cas dans "didSelectRowAt" par exemple..
J'ai un dernier truc où je galère..
Comment faire pour qu'il recherche dans ma nouvelle source (favoris) ?
Edit : Est ce qu'il n'y aurait pas une histoire de filtre sur la valeur de ma source ?
Bon bah en effet, il y avait bien une histoire de filtre sur ma source ^^
J'ai fais comme ça :
Quand je clic sur mon bouton "Favoris", je sauvegarde ma source (mon JSON de favoris)
et ma fonction de recherche est devenue :
..et hop, ça fonctionne bien
pfiouf.. merci, j'pensais pas que ça allait se passer aussi bien
@Joanna Olalala ton code est beaucoup trop compliqué pour moi
@Larme Je reviens sur cette fonction et ton commentaire..
Pourquoi je ne devrais pas avoir nil ici ?
L'identifiant de la cellule, c'est pas juste quand je veux faire des cellules personnalisées ?
Si je veux juste utiliser le type de cellule par défaut et remplir avec mes données, je suis obligé de mettre un identifiant à la cellule ?
Il faut, au moins, ajouter un cellule par défaut comme prototype dans le UITableView, remplir l'identifiant a avec n'importe quoi (e.g. "mon identifiant") et utiliser le code suivant :
https://developer.apple.com/documentation/uikit/uitableview/1614891-dequeuereusablecell
Bah c'est ça que je ne comprends pas...
Pourquoi il faut ajouter une cellule par défaut ?
Pour moi, il le faut uniquement si tu fais des cellules personnalisés nan ?
Si je veux changer l'affichage par défaut des cellules.. mais sinon, aucun intérêt, j'ai juste à remplir les cellules par défauts avec mes données et il s'occupe du reste.
Je me dis qu'Apple met à disposition une tableView avec des cellules de base, qu'on peut utiliser juste en remplissant avec nos données et que si on veut changer l'affichage, là on utilise l'identifiant et une cellule personnalisable.
Quel intérêt d'ajouter une cellule par défaut pour refaire la même que celle qui m'est proposée de base ?
J'essaie juste de comprendre en quoi c'est mieux
Il s'agit la réutilisation des cellules pour ne pas avaler la mémoire.
Et, en plus, il y a au moins trois ou quatre variantes de la cellule "par défaut". Donc, on peut choisir l'agencement en IB, la donner un identifiant et, hop ! pas de code nécessaire.
Je viens de tilter ce qui m'avait échappé.
Au départ, je pensais que tu utilisais
dequeueReusableCell(withIdentifier:)
, car je m'attendais à ce que tu le fasses. Or le paramètrereuseIdentifier
n'est pas optionnel. Cela n'aurait pas dû compiler, et si jamais cela fonctionnait, cela aurait dû crasher. Donc cela m'étonnait grandement tout ça, d'où mon commentaire.La subtilité qui m'avait échappée, c'est que tu appelles en réalité
UITableViewCell.init(style:, reuseIdentifier:)
et non pasdequeueReusableCell(withIdentifier:)
.Donc en réalité, tu TE DOIS d'utiliser
dequeueReusableCell(withIdentifier:)
à moins que tu sois un « grand expert », que tu bidouilles et encore.Cela n'a rien avoir à "ajouter une cellule par défaut", c'est tout le concept du REUSE qui te manque et qui est très important pour les
UITableView
/UICollectionView
. Tu bypasses la totalité de l'optimization mémoire mise en place par UIKit (note que ce concept existe sur Android avec le RecycleView).Or pour cela, tu te dois d'utiliser un identifier, que la cellule soit basique ou non.
Tu dois faire normalement au début:
Et dans
tableView(_:cellForRowAt:)
:let cell = dequeueReusableCell(withIdentifier: ""myIdentifier", for: indexPath)
Première réaction : Cool, j'suis un grand expert
Deuxième réaction : Hé merde, j'vais devoir revenir sur ce code
Mais ceci dit, c'est plus clair maintenant ^^
Du coup, je n'utilise pas le storyboard pour mettre un identifiant à ma cellule, je peux le faire via le code si j'ai bien compris ?
Je vais faire quelques tests et remettre tout ça à jour parce que c'est pas le seul endroit où j'utilise des cellules..
Après quelques tests :
Cas 1 :
Dans viewDidLoad :
Dans cellForRowAt :
Résultat : J'ai perdu le style : .subtitle
Cas 2 :
Dans viewDidLoad :
J'ai enlevé ma ligne précédente :
Dans cellForRowAt :
J'ai gardé la ligne rajoutée :
Dans le storyboard :
J'ai rajouté un prototype de cellule avec le style "subtitle" (Ok Joanna, je viens de comprendre où se situait les plusieurs style par défaut ^^) et je lui ai donné l'identifiant que je réutilise dans mon code (reuseIdentifierCellule)
Résultat :
Je retrouve bien mon style "subtitle", tout fonctionne bien.
Le fait de passer faire ça par le storyboard, ça remplace donc la ligne de code que j'ai enlevé si j'ai bien suivi ?
Donc là normalement, j'utilise bien le concept de REUSE ? ^^
Voilà ! tu l'as bien pigé
pfiouf, une bonne chose de réglée
Merci à vous 2 ^^
Je vais finir par vous créditer dans mon app à ce rythme là lol
(et à bientôt pour la suite de mes soucis avec swift )