Avis d'expert en SwiftUI mapview

Bonjour les codeurs,
Je me suis lancée dans un projet sur les bornes électriques pour les voitures, j’aimerais une analyse de pointure pour savoir si je fais fausse route.

L’idée je vais lire un fichier de donnée (csv) que l’on trouve sur data.gouv, une fois les données lues, je crée un tableau, qui va être affiché sur une carte.

Mon problème :
une fois que la carte s’affiche, la console se remplit de messages d’erreur, et cela bloque la carte, je ne peux pas non plus cliquer sur les points pour afficher le détail (certains seulement).

Je cherche l’origine du problème, mais je ne trouve pas mon erreur, je cherche depuis un bon moment déjà.

Je ne sais pas si on peut créer une carte avec des données venant d’un tableau, c’est peut-être la taille du tableau qui pose problème ou les données du csv ou je fais fausse route complètement plus de pistes.

Source des données Fichier consolidé des Bornes de Recharge pour Véhicules Électriques (IRVE) - data.gouv.fr même si j’ai recrée un csv avec comme séparateur le « ; »

Autre point pour le moment le fichier de données csv est stockée sur le serveur web wordpress mutualisé chez ovh je ne sais pas si celui-ci est dimensionné pour un grand nombre de connexions simultanées.

Pour les personnes qui téléchargeront le projet, le bouton en haut de la carte désactive le suivi de le l’utilisateur pour pouvoir déplacer le carte sinon la carte se recentre sur la position GPS.

Les bornes apparaissent en rouge normalement on peut cliquer dessus pour afficher le détail cela ne fonctionne pas sur toutes je pense que cela est dû à l’erreur de maj dans la console.

D’animation de chargement est assez longue, car le fichier csv est volumineux
Toute la partie chargement du fichier se trouve dans le fichier accesDonnees la construction de la carte le contentView

Merci d’avance aux personnes qui voudront bien regarder le projet pour m’aider, car je sèche et je ne souhaite pas continuer dans l’erreur si je fais fausse route.

Je ne suis pas un développeur pro, mais juste un amateur qui apprend tout seul avec son lot d’erreurs dans la construction et du code. Le projet est en construction donc j’ai aussi plein d’erreurs sur la compatibilité entre les versions de swiftUI mais je me concentre pour le moment sur l’erreur d’affichage des points sur la carte.

code complet sous git pour exécution Xcode

Chargement des données

import SwiftUI
import UIKit

@MainActor // Gestion des files attentes processeur, pour les tâches asynchrones 
class AccesDonnees:ObservableObject {
      // tableau de donnée pour affichage points sur la carte 
    @Published var listeBornes:[BorneModele] = []
    @Published var afficherCarte:Bool = false
    @Published var chargementBorbes:Bool = false
    @Published var ChargementExplication = "Chargement en cours"
    // lecture du fichier csv
    
    func lectureDonnees() async throws {
        //Task.init() {
        //let statusTache = Task { () -> String in
        let statusTache = Task { () in
            if let sourceFichier = URL(string: "https://titastus.com/wp-content/uploads/titastusdev/bornesrecharges/bornesrecharges.csv") {
                for try await line in sourceFichier.lines {
                    let  colonnes = line.split(separator: ";")
                    //let nom_amenageur = colonnes[0]
                    //let siren_amenageur = colonnes[1]
                    //let contact_amenageur = colonnes[2]
                    //let nom_operateur = colonnes[3]
                    //let contact_operateur = colonnes[4]
                    //let telephone_operateur = colonnes[5]
                    //let nom_enseigne = colonnes[6]
                    //let id_station_itinerance = colonnes[7]
                    //let id_station_local = colonnes[8]
                    let nom_station = nettoyageChaine(chaine: String(colonnes[9]))
                    //let implantation_station = colonnes[10]
                    let adresse_station = nettoyageChaine(chaine: String(colonnes[11]))
                    //let code_insee_commune = colonnes[12]
                    //let coordonneesXY = colonnes[13]
                    let nbre_pdc = nettoyageChaine(chaine: String(colonnes[14]))
                    //let id_pdc_itinerance = colonnes[15]
                    // let id_pdc_local = colonnes[16]
                    let puissance_nominale = nettoyageChaine(chaine: String(colonnes[17]))
                    let prise_type_ef = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[18])))
                    let prise_type_2 = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[19])))
                    let prise_type_combo_ccs = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[20])))
                    let prise_type_chademo = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[21])))
                    let prise_type_autre = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[22])))
                    let gratuit = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[23])))
                    let paiement_acte = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[24])))
                    let paiement_cb = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[25])))
                    let paiement_autre = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[26])))
                    //let horaires = colonnes[27]
                    let accessibilite_pmr = nettoyageChaine(chaine: String(colonnes[31]))
                    let condition_acces = nettoyageChaine(chaine: String(colonnes[28]))
                    let station_deux_roues = convertirStrVSBouleen(chaine: nettoyageChaine(chaine: String(colonnes[33])))
                    let raccordement = nettoyageChaine(chaine: String(colonnes[34]))
                    let num_pdl = nettoyageChaine(chaine: String(colonnes[37]))
                    //let date_mise_en_service = colonnes[32]
                    //let observations = colonnes[33]
                    //let date_maj = colonnes[34]
                    //let last_modified = colonnes[35]
                    //let datagouv_dataset_id = colonnes[36]
                    //let datagouv_resource_id = colonnes[37]
                    //let datagouv_organization_or_owner = colonnes[38]
                    let consolidated_longitude = convertirStrVSDouble(chaine: nettoyageChaine(chaine: String(colonnes[43])))
                    let consolidated_latitude = convertirStrVSDouble(chaine: nettoyageChaine(chaine: String(colonnes[44])))
                    //let consolidated_code_postal = colonnes[41]
                    //let consolidated_commune = colonnes[42]
                    //let consolidated_is_lon_lat_correct = colonnes[43]
                    //let consolidated_is_code_insee_verified = colonnes[44]
                    
                    if nom_station != "\"nom_station(9)\"" {
                        let mesBornes = BorneModele(nom_station: nom_station, adresse_station: adresse_station,puissance_nominale: puissance_nominale, consolidated_longitude: consolidated_longitude, consolidated_latitude: consolidated_latitude, prise_type_ef: prise_type_ef, prise_type_2: prise_type_2, prise_type_combo_ccs: prise_type_combo_ccs, prise_type_chademo: prise_type_chademo,prise_type_autre:prise_type_autre,num_pdl:num_pdl,nbre_pdc:nbre_pdc, paiement_acte:paiement_acte, gratuit: gratuit, paiement_cb: paiement_cb,paiement_autre: paiement_autre, condition_acces: condition_acces, accessibilite_pmr:accessibilite_pmr,station_deux_roues:station_deux_roues, raccordement:raccordement)
                        
                        
                        //DispatchQueue.main.async {
                            self.listeBornes.append(mesBornes)
                           //self.chargementBorbes = true
                        //}
                    }
                } // fin for
            } // fin du fin
            //return " \(ChargementExplication)"
        }
        let resultatTache = await statusTache.result
        
            switch resultatTache {
            case .success( _):
                    self.chargementBorbes = true
                    self.ChargementExplication = "Chargement terminé"
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.7) {
                    self.afficherCarte = true
                 }
                
                   
                
            case .failure(let error):
                self.ChargementExplication = "Error: \(error.localizedDescription)"
                self.chargementBorbes = false
            }
        
        //}
    }
    
    // suppression des guillemets
    func nettoyageChaine(chaine:String) -> String {
        var newchaine:String?
        let caractereRecherche = "\""
        if chaine.contains(caractereRecherche) {
            newchaine = chaine.replacingOccurrences(of: caractereRecherche, with: "")
            newchaine = newchaine!.trimmingCharacters(in: .whitespaces)
        }
        return newchaine!
    }
    
    //convertir chaine vers double
    func convertirStrVSDouble(chaine:String) -> Double {
        var resultatDouble:Double?
        if let convertirDouble = Double(chaine) {
            resultatDouble = convertirDouble
        } else {
            resultatDouble = 0.000
        }
        return resultatDouble!
    }
    
    //convertir chaine vers booleen
    func convertirStrVSBouleen(chaine:String) -> Bool {
        var resultatBooleen:Bool?
        if let convertirBooleen = Bool(chaine) {
            resultatBooleen = convertirBooleen
        } else {
            resultatBooleen = false
        }
        return resultatBooleen!
    }
}

struct ContentView: View {
    @StateObject var lireDonnees:AccesDonnees = AccesDonnees()
    @StateObject var valeurAleatoire:Aleatoire = Aleatoire()
    @StateObject var suivreUtilisateur:SuiviUtilisateurViewModel = SuiviUtilisateurViewModel(CLLocation(latitude: 0, longitude: 0))
    @State private var montrerPopup:Bool = false
    @State private var BorneSelectionnee:String = ""
    @State private var montrerFenetre:Bool = false // pour suppression 
    @State private var borneSelectionAffichee:Bool = false
    @State private var valeurPuissance:String = ""
    
    //parametre écran
    let milieu = UIScreen.main.bounds.height / 2
    let largeurEcran = UIScreen.main.bounds.width
    let hauteurEcran = UIScreen.main.bounds.height
    
    // partie animation
    // paramétre pour animation capsule
    @State private var capsuleLargeur:CGFloat = 15
    @State private var capsuleHauteur0:CGFloat = 100
    @State private var capsuleHauteur1:CGFloat = 100
    @State private var capsuleHauteur2:CGFloat = 100
    @State private var couleurCapsule:[Color] = [Color("MonRouge"),.gray, Color("MonVert")]
    @State private var timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        NavigationView {
            if (self.lireDonnees.afficherCarte) == true {
                GeometryReader { geo in
                    VStack {
                        //ZStack {
                        // Map avec annotation
                        Map(coordinateRegion: $suivreUtilisateur.coordoneGeo, interactionModes: .all, showsUserLocation: true, userTrackingMode: .none, annotationItems: lireDonnees.listeBornes, annotationContent: { mesBornes in

                            MapAnnotation(coordinate: mesBornes.coordonneGeo(), anchorPoint: CGPoint(x: 0.5, y: 0.5)) {
                                // exemple avec onTapeGesture
                                Image(systemName: Ressources.image.borneRecharge.rawValue)
                                    .foregroundColor(.red)
                                    .opacity(montrerPopup ? 0 : 1)
                                    .animation(Animation.linear(duration: 0.2))
                                    .onTapGesture {
                                        withAnimation {
                                            BorneSelectionnee = mesBornes.nom_station
                                            transfertInfoBorne(infoBorne: mesBornes)
                                            self.montrerPopup = true
                                        }
                                    }
                                //////////////
                                if montrerPopup {
                                    if mesBornes.nom_station == BorneSelectionnee {
                                        ZStack(alignment: .top) {
                                            DetailsBornesVuePopup(libelle: .constant(mesBornes.nom_station), adresse: .constant(mesBornes.adresse_station), latitudeSTR: .constant(String(mesBornes.consolidated_latitude)), longitudeSTR: .constant(String(mesBornes.consolidated_longitude)), montrerFenetreDetail:$montrerFenetre)
                                            
                                        }
                                    }
                                }
                            }
                            
                        })
                       // .onReceive(lireDonnees.listeBornes) {listeBornes in
                         //   self.tests = listeBornes
                        //}
                        //.ignoresSafeArea(.container, edges: [.top, .vertical])
                        .ignoresSafeArea(.all)
                        // ferme la vue popup de la carte si on
                        .onTapGesture {
                            withAnimation {
                                montrerPopup = false
                                borneSelectionAffichee = false
                            }
                        }// bouton localisation
                        .overlay(alignment:.topTrailing,content: {
                            Button {
                                suivreUtilisateur.suivreUtilisateur.toggle()
                            } label: {
                                Image(systemName: (suivreUtilisateur.suivreUtilisateur) ? Ressources.image.localiser.rawValue : Ressources.image.nonLocaliser.rawValue)
                                    .padding()
                                    .background(.black.opacity(0.60))
                                    .font(.title2)
                                    .clipShape(Circle())
                                    .foregroundColor(suivreUtilisateur.suivreUtilisateur ? .green : .red)
                            }
                        })
                        .sheet(isPresented: $montrerPopup) {
                            ZStack() {
                                CaracteristiquesBornesVue()
                                    .frame(height:UIScreen.main.bounds.width - 10)
                                    .presentationDetents([.fraction(0.49)])
                                    .overlay(alignment:.topTrailing,content:  {
                                        Button {
                                            self.montrerPopup = false
                                        } label: {
                                            Image(systemName: Ressources.image.fermer.rawValue)
                                                .padding(5)
                                                .font(.title2)
                                                .foregroundColor(.primary)
                                                .clipShape(Circle())
                                                .padding(5)
                                        }
                                    })
                            }
                            
                        }
                       
                        
                    } // fin Vstack
                    
                }// fin du géo
                
            } else {
                // le if est déprecier
                HStack(spacing:0){
                    // annimation de chargement
                    VueCapsule(largeur: $capsuleLargeur, hauteur: $capsuleHauteur0, color: $couleurCapsule[0])
                    VueCapsule(largeur: $capsuleLargeur, hauteur: $capsuleHauteur1, color: $couleurCapsule[1])
                    VueCapsule(largeur: $capsuleLargeur, hauteur: $capsuleHauteur2, color: $couleurCapsule[2])
                    HStack(spacing:0){
                        Text("\(lireDonnees.ChargementExplication)")
                            .font(.caption2)
                            .multilineTextAlignment(.leading)
                            .frame(width: 200,height: 100)
                    }
                    
                }
            }
        } // fin du if  lire borne
        
        
        //} // VStack
        // execute ne tâche chargement des donnée
        .task {
            try? await lireDonnees.lectureDonnees()
        }
        
        
        
        // timer pour animation
        .animation((Animation.linear))
        .onReceive(timer) { time in
            capsuleHauteur0 = valeurAleatoire.hauteurAleatoire()
            capsuleHauteur1 = valeurAleatoire.hauteurAleatoire()
            capsuleHauteur2 = valeurAleatoire.hauteurAleatoire()
        }
    } // fin du navigationView
}

message erreur

dans la console à l’affichage de la carte :

screen application

Merci à tous

Je ne suis pas certain de quel est le soucis que tu rencontres mais ceci m’inquiète :

Sachant que listeBornes est Published et donc surveillé par SwiftUI.
Donc chaque ajout d’une bonne dans la liste provoque un redessin de ta View et comme tu as beaucoup de points dans ton CSV (combien d’ailleurs ?) ça peut entrainer des soucis.
Tu devrais essayer de créer une liste locale, puis à la fin de ton chargement mettre la liste complète dans ta variable listeBornes en un seul coup

Merci maxime pour ta réponse, et merci à tous, je vais explorer cette solution.

Je reviens vers vous dès que possible.

Excel indique plus de 10000 lignes :sweat_smile:, je fais avec les données qui sont dispo sur le sujet ! et le format aussi ! j’ai pas trouvé grand chose d’autre :grinning:

1 « J'aime »

Effectivement ça commence à faire pas mal, donc si tu redessines à chaque fois, ça peut vraiment créer un lag visible.

Bonjour, Maxime et bonjour les codeurs, je viens de faire l’essai du chargement unique en une fois, le problème est exactement le même.

@Published var listeBornes:[BorneModele] = []
    var testBornes:[BorneModele] = []
testBornes.append(mesBornes)
switch resultatTache {
            case .success( _):
                    self.chargementBorbes = true
                    self.ChargementExplication = "Chargement terminé"
                    self.listeBornes = testBornes
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.7) {
                    self.afficherCarte = true
                 }

Je vais faire l’essai avec un jeu de donnée tout petit, construire un csv tout petit pour voir.

Pour les très grandes listes avec j’ai déjà utilisé avec foreEach une commande LazyVStack(spacing: 20) pour ne pas tout afficher et saturer si je ne dis pas de bêtise, il existe la même chose pour les cartes ?

j’ai d’autres essais à faire à très vite.

Je pense que la Map est Lazy par défaut, en tous cas c’était le cas sur UIKit donc je suppose que c’est toujours le cas sur SwiftUI mais peut être que je me trompe.

Peux-tu essayer de mettre toutes les écritures dans tes variables @Published dans un DispatchQueue.main.async() ?
Ça inclut ces 3 là :

self.chargementBorbes = true
self.ChargementExplication = "Chargement terminé"
self.listeBornes = testBornes

mais aussi d’autres que tu aurais dans ton algo et que je n’aurais pas vu.

Bonjour,

DispatchQueue.main.async {
                    self.chargementBorbes = true
                    self.ChargementExplication = "Chargement terminé"
                    self.listeBornes = self.testBornes
                }

Ne change rien, par contre j’ai trouvé un lien sur le Net dans mes recherches qui semble traité du problème, si je ne me trompe pas (sous réserve, je ne suis même pas sur).

En tout cas, j’ai le même message dans la console, il faut que je creuse.
Pour les autres, il faut que je les fasse.

Je continu mes recherches. Bon week