Problème de mémoire avec la carte
Bonjour à tous !
J'ai constaté, avec mon iPhone, que la carte prenait beaucoup de mémoire lorsqu'elle était affichée un bon nombre de fois. J'ai même réussi à saturer la mémoire de mon iPhone (1.6 gigas).
Donc, j'ai recherché une solution. D'après les forums, la solution serait de vider les variables qui contiennent la carte. J'ai utilisé le code suivant :
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
applyMapViewMemoryFix()
}
func applyMapViewMemoryFix() {
mapView.showsUserLocation = false
mapView.delegate = nil
mapView.removeFromSuperview()
mapView = nil
}
Le problème de mémoire est moins présent, mais il persiste.
Il y a cependant une autre solution : créer une instance de mapKit. C'est une bonne idée : la carte sera enregistrer dans la mémoire une fois pour toute.
Cependant, je vous avoue que je ne vois pas du tout comment faire... Je ne vois pas comment je pourrai créer la carte de manière à ce qu'elle soit enregistrer une et une seule fois dans la mémoire.
Je vous remercie pour votre aide !
Réponses
Voici le code qui créé la MKMapView :
Le code pour le var ??
Que fais-tu en updateNavigationControllers() ?
Désolé, c'est important ! Je me suis rendu compte que lorsque l'utilisateur allait sur la carte, et qu'il regardait les détails d'une annotation, et qu'il retournait sur la carte (et ainsi de suite), il y avait des clones des pages de la carte et de la page qui montre les détails des annotations. Donc, j'ai pris la liberté de supprimer les doublons en modifiant la liste des views controllers dans le navigation controller.
Voilà :
Je vais éditer le code.
!!!!!!!!!!!!!!!!!!!!!!!
Voilà ! Cette sorte de bricolage ne devrait jamais être nécessaire. Tu as une une grosse faute de logique dans ton appli.
Je sais bien que c'est du bricolage, et je suppose qu'il faut que je change ça tout de suite !
Je suppose qu'il faut que j'utilise un delegate qui va modifier les informations afficher dans la page des détails d'une annotation et que je cache la page de la carte.
Qu'est-ce que tu as comme storyboard ?
J'ai fait une capture d'écran.
J'ai un navigation controller, une view Annotations qui contient une tableView montrant toutes les annotations, une view Map qui contient la carte, une view Annotation qui montre les détails d'une annotation et qui fait le lien entre la tableView et la carte, et une autre view, similaire à la précédente qui permet d'ajouter une annotation.
Donc, tu as un segue de ton MapViewController vers ton AnnotationViewController que a, à son tour, un segue vers ton MapViewController ??????
Boucle infinie !!!!!
Et tu as deux segues du TableViewController, l'un vers l'AnnotationViewController qui a, à son tour, un segue vers le MapViewController ; et l'autre directement vers le MapViewController !!!
Le UINavigationController gère une pile de UIViewControllers. Si tu crées une boucle, tu n'auras que des problèmes.
Il faut repenser 8--)
Effectivement, j'ai créé une boucle infini. Je voulais que l'application soit facile à utiliser, mais je n'ai pas vérifier la pertinence du code !
Et j'aime bien faire très compliqué aussi !
Ce que je peux faire, c'est supprimer le segue qui va de l'AnnotationViewController vers le MapViewController. Mais l'utilisateur ne pourra plus afficher la carte après avoir regardé les détails d'une annotation...
Bref, il faut vraiment que je repense tout ça.
Je te remercie pour ton aide, Joanna Carter !
Edit : J'ai compris ! Il faut sois que j'affiche la carte directement après le UIAnnotationsViewController (la liste des annotations), suit que j'affiche le détails. Mais je ne peux pas proposer les deux. Je pense que je vais choisir la carte pour mon application.
Et donc, l'annotationViewController sera le dernier View Controller dans le Navigation Controller.
Tu as pensé de ceci ?
Je te remercie beaucoup Joanna !!
Je vais modifier tout ça !
Joanna Carter, tu as trouvé LA solution !
En effet, si la carte est directement affichée avec un tab bar controller, elle n'est chargée qu'une seule fois, et donc, il n'y a pas de souci de mémoire.
Or, j'aimerai tout de même faire quelque chose qui se rapproche de ce que j'avais fait au départ.
J'ai pensé à une solution : au lieu de supprimer les UIViewController du Navigation Controller, je pourrai tout simplement simuler un click sur l'item de la carte ou sur un autre item pour revenir. Le problème est que je ne parviens pas, avec le code, à récupérer la tab bar que j'ai créé dans mon storyboard. Comment faire ?
Draken, j'ai essayé ce que tu dis, à savoir charger la carte en mémoire au lancement de l'application, et franchement, cette solution serait top pour moi, mais je ne sais pas comment faire...
Oui, j'ai suivi une formation, mais il y a plein de choses que l'on a pas vu...
Cependant, j'ai essayé de faire comme ce que tu as dit, mais je n'y suis pas parvenu...
Pourtant, j'ai réussi à enregistrer le UIViewController de la Map dans AppDelegate (c'est une solution que j'ai trouvé sur Internet), mais je ne sais pas du tout si c'est correct...
Voici ce que j'ai fait :
Ah non, ce n'est pas correct du tout. A chaque appel de showMap() l'application recrée une nouvelle instance de " MapViewController".
Ce qu'il faut c'est créer l'objet UNE SEULE FOIS et conserver son adresse dans une variable de l'AppDelegate.
Draken aurais-tu un exemple concret s'il te plait ?
J'ai déjà essayé de créer une variable dans l'AppDelegate, mais à chaque fois, j'ai eu des crash !
J'avais fait : let map = MapViewController()
Et comment l'utiliser correctement avec les segues ?
EDIT : J'ai compris pourquoi ça crash, mais je ne sais pas comment corriger. J'ai besoin d'accéder au context de Core Data, et donc, je l'appel sur différente page. Le souci est que je l'ai enregistré dans l'AppDelegate, et que ça crash lorsque c'est l'AppDelegate lui-même qui instancie le MapViewController.
Petit exemple avec une UIImage chargée dans l'AppDelegate.
Dans un ViewController quelconque :
* attend stoà¯quement un commentaire de Ceroce *
Céroce, j'ai aussi essayé ta solution, qui correspond à celle de Joanna Carter.
J'ai effectivement mis la carte dans un UItabbar, et comme ça, elle n'est chargée qu'une seule fois. C'est très bien. Maintenant, j'aimerai pouvoir simuler le "click" de l'item de la tabbar qui correspond à la map.
Mais je ne sais pas comment faire pour récupérer les items de la tabbar que j'ai créé dans mon storyboard.
Il me faut aussi savoir comment transmettre des données à la map (par exemple pour centrer la map sur une annotation) et pouvoir revenir facilement, c'est-à -dire en simulant un navigation controller pour que l'utilisateur ne soit pas perdu.
Draken, je te remercie pour ton exemple !
Une règle de la POO: "Tell, don't ask". Si l'AppDelegate instancie le ManagedObjectContext(MOC), alors c'est lui qui doit le passer au premier VC. La manière de le faire est tirée par les cheveux, mais Apple l'a prévu ainsi (de mémoire):
* se planque au fond de la classe, dans l'armoire à coté du radiateur *
* murmure " C'est pas moi monsieur, j'ai copié un exemple d'Apple sur l'utilisation du PersistentContainer de CoreData, par l'intermédiaire de l'AppDelegate *
Les joies du storyboard... " c'est plus facile que les xib, qu'y disaient ".
Le rootViewController de la window (voir mon code plus haut) est le UITabBarController.
On peut donc écrire un truc du style:
Dans cette solution, il y a un objet plus haut dans la hiérarchie qui peut accéder aux deux VC, et qui peut se mettre en délégué des deux pour leur retransmettre les informations.
On encore, qui fait qu'un VC est le délégué de l'autre.
Il me semble que la solution est plutôt de ne pas utiliser un UITabBarController, mais un UINavigationController, et dépiler au besoin. On encore présenter le mapVC en modal.
(le code d'exemple d'Apple est souvent une occasion de démythifier l'ingénieur(e) d'Apple qui serait une sorte de surhumain.)
Je crois que je suis en train d'essayer de faire quelque chose d'impossible à faire.
Ce que je voulais faire, c'est créer un UIViewController avec une carte, le garder en mémoire et l'afficher lorsque je voudrais. Or,il semble que c'est simplement impossible, et surtout, contraire à toutes les règles de la POO.
Ce que je pourrai faire, c'est mettre la carte en premier.
Céroce, j'avais comme ce que tu as dit, avec un UINavigationController (et pas avec un UITabbarController), et comme Joanna Carter l'a dit, j'ai fait du bricolage en supprimant avec le code les UIViewController qui étaient en double dans le UINavigationController.
Le problème reste la mémoire, car à chaque fois que la carte est affcihée, l'application utilise de plus en plus la RAM. Et je ne sais pas du tout mais j'espère apprendre très vite à gérer la mémoire utilisée par mes applications.
Céroce, je te remercie pour toutes tes explications !
Je pense que je vais suivre ma dernière idée, et mettre la carte en premier dans le UINavigationController, comme ça, la carte ne sera chargée qu'une seule fois et je pourrai proposer à l'utilisateur la navigation que j'ai prévu initialement.
Je vous remercie pour votre aide !
Si c'est possible, d'ailleurs c'est ce qui se passe quand tu as un UITabBarController: il retient les VC qu'il affiche. Pareil quand un tu as un UINavigationController: les VC empilés dessus sont retenus.
Ton problème est justement que tu empiles à chaque fois une nouvelle instance, donc au bout d'un moment, forcément, ça prend beaucoup de mémoire. Il faut dépiler, pour réutiliser l'instance existante.
Je te remercie Céroce, mais je ne sais pas comment faire.
J'ai bien compris comment fonctionne le Segue, même si je n'ai pas encore tout utiliser, comme modal par exemple.
Oui, le problème est que je créé une nouvelle instance à chaque fois.
Lorsque j'utilise dismiss, ou que je tape sur le bouton retour d'un UINavigationController, je suppose que les UIViewController sont supprimé de la mémoire. La question est de savoir comment cacher et afficher un UIViewController dans une UINavigationController sans instancier ledit UIViewController.
Je crois que je sais ce que je pourrai faire : instancier la mapViewController dans l'AppDelegate, et créer des segue avec le code, sans passer par le storyboard.
Voici ce que j'ai fait dans AppDelegate :
Et dans un UIViewController :
J'aurai du utiliser PerformSegue de manière à caster plus facilement la view.
Est-ce correct Céroce ??
Pour ton problème de changer le tab vers la carte, il faut créer une notification et une classe pour le Tab Bar Controller :
Puis, dans la classe pour la liste des annotations, il faut lier un bouton à une action avec le code suivant :
Mais, c'est pourquoi que l'utilisateur ne puisse pas changer le tab soi-même ?
Grâce à vous, j'ai réussi !!
Dans l'AppDelegate :
Pour afficher la carte :
Pour remettre les variables à 0, afin de centrer la carte correctement :
Et voilà ! Il n'y a plus de problème de mémoire.
Je vous remercie pour votre aide !