Communitygithub.com

JordanCoin/ios-skills-collection

Implémentez, révisez ou améliorez les fonctionnalités de cartes et de localisation dans les applications iOS/macOS avec MapKit et CoreLocation.

Qu'est-ce que ios-skills-collection ?

Ce skill rassemble les meilleures pratiques et des exemples de code prêts à l'emploi pour intégrer des fonctionnalités de cartes et de localisation dans des applications iOS/macOS avec SwiftUI. Utilisant le framework MapKit et l'API asynchrone moderne de CoreLocation, il est conçu pour les développeurs ciblant iOS 17 et versions ultérieures. Il couvre en détail l'utilisation de la vue `Map` avec `MapContentBuilder`, le suivi de la localisation utilisateur avec `CLLocationUpdate.liveUpdates()`, le géocodage, la recherche de lieux (autocomplétion et recherche complète), les itinéraires et le calcul d'ETA, ainsi que la géolocalisation (`CLMonitor`). Le document fournit également une checklist de révision pour évaluer la qualité du code existant, des sections dédiées aux erreurs courantes et à leur correction, et des liens vers des fichiers de références externes pour des patterns MapKit et CoreLocation plus avancés. Il est particulièrement utile pour les agents qui doivent implémenter, réviser ou améliorer des fonctionnalités de cartes et de géolocalisation dans des applications Swift, en s'appuyant sur les API modernes et la syntaxe async/await.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/JordanCoin/ios-skills-collection/tree/HEAD/skills/dpearson2699--mapkit

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

MapKit

Créez des fonctionnalités de cartes et de localisation pour iOS 17+ avec SwiftUI, MapKit et les API asynchrones modernes de CoreLocation. Utilisez Map avec MapContentBuilder pour les vues, CLLocationUpdate.liveUpdates() pour le flux de localisation, et CLMonitor pour le géorepérage.

Consultez references/mapkit-patterns.md pour les patterns MapKit avancés et references/mapkit-corelocation-patterns.md pour les patterns CoreLocation.

Sommaire

Flux de travail

1. Ajouter une carte avec des marqueurs ou annotations

  1. Importez MapKit.
  2. Créez une vue Map avec une liaison optionnelle MapCameraPosition.
  3. Ajoutez Marker, Annotation, MapPolyline, MapPolygon ou MapCircle dans la closure MapContentBuilder.
  4. Configurez le style de la carte avec .mapStyle().
  5. Ajoutez des contrôles de carte avec .mapControls { }.
  6. Gérez la sélection avec une liaison selection:.

2. Suivre la localisation de l'utilisateur

  1. Ajoutez NSLocationWhenInUseUsageDescription à Info.plist.
  2. Sur iOS 18+, créez une CLServiceSession pour gérer l'autorisation.
  3. Itérez CLLocationUpdate.liveUpdates() dans une Task.
  4. Filtrez les mises à jour par distance ou précision avant de mettre à jour l'interface.
  5. Arrêtez la tâche lorsque le suivi de localisation n'est plus nécessaire.

3. Rechercher des lieux

  1. Configurez MKLocalSearchCompleter pour les suggestions d'autocomplétion.
  2. Débouncez l'entrée utilisateur (au moins 300 ms) avant de définir la requête.
  3. Convertissez la complétion sélectionnée en MKLocalSearch.Request pour obtenir les résultats complets.
  4. Affichez les résultats sous forme de marqueurs ou dans une liste.

4. Obtenir des directions et afficher un itinéraire

  1. Créez une MKDirections.Request avec une source et une destination MKMapItem.
  2. Définissez transportType (.automobile, .walking, .transit, .cycling).
  3. Attendez le résultat de MKDirections.calculate().
  4. Dessinez l'itinéraire avec MapPolyline(route.polyline).

5. Réviser du code existant de carte/localisation

Parcourez la Checklist de Révision à la fin de ce fichier.

Vue Carte SwiftUI (iOS 17+)

import MapKit
import SwiftUI

struct PlaceMap: View {
    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        Map(position: $position) {
            Marker("Apple Park", coordinate: applePark)
            Marker("Infinite Loop", systemImage: "building.2",
                   coordinate: infiniteLoop)
        }
        .mapStyle(.standard(elevation: .realistic))
        .mapControls {
            MapUserLocationButton()
            MapCompass()
            MapScaleView()
        }
    }
}

Marqueur et Annotation

// Balloon marker -- façon la plus simple d'épingler un lieu
Marker("Cafe", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
    .tint(.brown)

// Annotation -- Vue SwiftUI personnalisée à une coordonnée
Annotation("You", coordinate: userCoord, anchor: .bottom) {
    Image(systemName: "figure.wave")
        .padding(6)
        .background(.blue.gradient, in: .circle)
        .foregroundStyle(.white)
}

Superpositions : Polyligne, Polygone, Cercle

Map {
    // Polyligne à partir de coordonnées
    MapPolyline(coordinates: routeCoords)
        .stroke(.blue, lineWidth: 4)

    // Polygone (zone en surbrillance)
    MapPolygon(coordinates: parkBoundary)
        .foregroundStyle(.green.opacity(0.3))
        .stroke(.green, lineWidth: 2)

    // Cercle (rayon autour d'un point)
    MapCircle(center: storeCoord, radius: 500)
        .foregroundStyle(.red.opacity(0.15))
        .stroke(.red, lineWidth: 1)
}

Position de la Caméra

MapCameraPosition contrôle ce que la carte affiche. Liez-la pour permettre à l'utilisateur d'interagir et pour déplacer la caméra par programmation.

// Centrer sur une région
@State private var position: MapCameraPosition = .region(
    MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
)

// Suivre la localisation de l'utilisateur
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)

// Angle de caméra spécifique (perspective 3D)
@State private var position: MapCameraPosition = .camera(
    MapCamera(centerCoordinate: applePark, distance: 1000, heading: 90, pitch: 60)
)

// Cadrer des éléments spécifiques
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))

Style de Carte

.mapStyle(.standard)                                        // Carte routière par défaut
.mapStyle(.standard(elevation: .realistic, showsTraffic: true))
.mapStyle(.imagery)                                         // Satellite
.mapStyle(.imagery(elevation: .realistic))                  // Satellite 3D
.mapStyle(.hybrid)                                          // Satellite + étiquettes
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))

Modes d'Interaction de la Carte

.mapInteractionModes(.all)           // Par défaut : panoramique, zoom, rotation, inclinaison
.mapInteractionModes(.pan)           // Panoramique uniquement
.mapInteractionModes([.pan, .zoom])  // Panoramique et zoom
.mapInteractionModes([])             // Carte statique (aucune interaction)

Sélection sur la Carte

@State private var selectedMarker: MKMapItem?

Map(selection: $selectedMarker) {
    ForEach(places) { place in
        Marker(place.name, coordinate: place.coordinate)
            .tag(place.mapItem)     // Le tag doit correspondre au type de sélection
    }
}
.onChange(of: selectedMarker) { _, newValue in
    guard let item = newValue else { return }
    // Réagir à la sélection
}

API CoreLocation Moderne

CLLocationUpdate.liveUpdates() (iOS 17+)

Remplacez les callbacks CLLocationManagerDelegate par une simple séquence asynchrone. Chaque itération produit une CLLocationUpdate contenant une CLLocation optionnelle.

import CoreLocation

@Observable
final class LocationTracker: @unchecked Sendable {
    var currentLocation: CLLocation?
    private var updateTask: Task<Void, Never>?

    func startTracking() {
        updateTask = Task {
            let updates = CLLocationUpdate.liveUpdates()
            for try await update in updates {
                guard let location = update.location else { continue }
                // Filtrer par précision horizontale
                guard location.horizontalAccuracy < 50 else { continue }
                await MainActor.run {
                    self.currentLocation = location
                }
            }
        }
    }

    func stopTracking() {
        updateTask?.cancel()
        updateTask = nil
    }
}

CLServiceSession (iOS 18+)

Déclarez les exigences d'autorisation pour la durée de vie d'une fonctionnalité. Conservez une référence à la session tant que vous avez besoin des services de localisation.

// Autorisation lors de l'utilisation avec préférence de précision totale
let session = CLServiceSession(
    authorization: .whenInUse,
    fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// Conservez `session` comme propriété stockée ; libérez-la une fois terminé.

Sur iOS 18+, CLLocationUpdate.liveUpdates() et CLMonitor prennent une CLServiceSession implicite si vous n'en créez pas explicitement. Créez-en une explicitement lorsque vous avez besoin d'une autorisation .always ou d'une précision totale.

Flux d'Autorisation

// Clés Info.plist (obligatoires) :
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription (uniquement si .always est nécessaire)

// Vérifier l'autorisation et guider l'utilisateur vers les Réglages en cas de refus
struct LocationPermissionView: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        ContentUnavailableView {
            Label("Accès à la localisation refusé", systemImage: "location.slash")
        } description: {
            Text("Activez l'accès à la localisation dans les Réglages pour utiliser cette fonctionnalité.")
        } actions: {
            Button("Ouvrir les Réglages") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    openURL(url)
                }
            }
        }
    }
}

Géocodage

CLGeocoder (iOS 8+)

let geocoder = CLGeocoder()

// Géocodage direct : chaîne d'adresse -> coordonnées
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
    print(location.coordinate) // CLLocationCoordinate2D
}

// Géocodage inverse : coordonnées -> placemark
let location = CLLocation(latitude: 37.3349, longitude: -122.0090)
let placemarks = try await geocoder.reverseGeocodeLocation(location)
if let placemark = placemarks.first {
    let address = [placemark.name, placemark.locality, placemark.administrativeArea]
        .compactMap { $0 }
        .joined(separator: ", ")
}

MKGeocodingRequest et MKReverseGeocodingRequest (iOS 26+)

Nouveau géocodage natif MapKit qui renvoie un MKMapItem avec des données plus riches et MKAddress / MKAddressRepresentations pour un formatage flexible des adresses.

@available(iOS 26, *)
func reverseGeocode(location: CLLocation) async throws -> MKMapItem? {
    guard let request = MKReverseGeocodingRequest(location: location) else {
        return nil
    }
    let mapItems = try await request.mapItems
    return mapItems.first
}

@available(iOS 26, *)
func forwardGeocode(address: String) async throws -> [MKMapItem] {
    guard let request = MKGeocodingRequest(addressString: address) else { return [] }
    return try await request.mapItems
}

Recherche

MKLocalSearchCompleter (Autocomplétion)

@Observable
final class SearchCompleter: NSObject, MKLocalSearchCompleterDelegate {
    var results: [MKLocalSearchCompletion] = []
    var query: String = "" { didSet { completer.queryFragment = query } }

    private let completer = MKLocalSearchCompleter()

    override init() {
        super.init()
        completer.delegate = self
        completer.resultTypes = [.address, .pointOfInterest]
    }

    func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
        results = completer.results
    }

    func completer(_ completer: MKLocalSearchCompleter, didFailWithError error: Error) {
        results = []
    }
}

MKLocalSearch (Recherche Complète)

func search(for completion: MKLocalSearchCompletion) async throws -> [MKMapItem] {
    let request = MKLocalSearch.Request(completion: completion)
    request.resultTypes = [.pointOfInterest, .address]
    let search = MKLocalSearch(request: request)
    let response = try await search.start()
    return response.mapItems
}

// Recherche par requête en langage naturel dans une région
func searchNearby(query: String, region: MKCoordinateRegion) async throws -> [MKMapItem] {
    let request = MKLocalSearch.Request()
    request.naturalLanguageQuery = query
    request.region = region
    let search = MKLocalSearch(request: request)
    let response = try await search.start()
    return response.mapItems
}

Itinéraires

func getDirections(from source: MKMapItem, to destination: MKMapItem,
                   transport: MKDirectionsTransportType = .automobile) async throws -> MKRoute? {
    let request = MKDirections.Request()
    request.source = source
    request.destination = destination
    request.transportType = transport
    let directions = MKDirections(request: request)
    let response = try await directions.calculate()
    return response.routes.first
}

Afficher un Itinéraire sur la Carte

@State private var route: MKRoute?

Map {
    if let route {
        MapPolyline(route.polyline)
            .stroke(.blue, lineWidth: 5)
    }
    Marker("Start", coordinate: startCoord)
    Marker("End", coordinate: endCoord)
}
.task {
    route = try? await getDirections(from: startItem, to: endItem)
}

Calcul d'ETA

func getETA(from source: MKMapItem, to destination: MKMapItem) async throws -> TimeInterval {
    let request = MKDirections.Request()
    request.source = source
    request.destination = destination
    let directions = MKDirections(request: request)
    let response = try await directions.calculateETA()
    return response.expectedTravelTime
}

Itinéraires Cyclables (iOS 26+)

@available(iOS 26, *)
func getCyclingDirections(to destination: MKMapItem) async throws -> MKRoute? {
    let request = MKDirections.Request()
    request.source = MKMapItem.forCurrentLocation()
    request.destination = destination
    request.transportType = .cycling
    let directions = MKDirections(request: request)
    let response = try await directions.calculate()
    return response.routes.first
}

PlaceDescriptor (iOS 26+)

Créez des références de lieux riches à partir de coordonnées ou d'adresses sans avoir besoin d'un ID de lieu. Nécessite import GeoToolbox.

@available(iOS 26, *)
func lookupPlace(name: String, coordinate: CLLocationCoordinate2D) async throws -> MKMapItem {
    let descriptor = PlaceDescriptor(
        representations: [.coordinate(coordinate)],
        commonName: name
    )
    let request = MKMapItemRequest(placeDescriptor: descriptor)
    return try await request.mapItem
}

Erreurs Courantes

À NE PAS FAIRE : Demander .authorizedAlways par défaut — les utilisateurs se méfient des autorisations trop larges. À FAIRE : Commencez par .requestWhenInUseAuthorization(), passez à .always uniquement lorsque l'utilisateur active une fonctionnalité en arrière-plan.

À NE PAS FAIRE : Utiliser CLLocationManagerDelegate pour des récupérations simples de localisation sur iOS 17+. À FAIRE : Utilisez le flux asynchrone CLLocationUpdate.liveUpdates() pour un code plus propre et concis.

À NE PAS FAIRE : Garder les mises à jour de localisation actives lorsque la carte/vue n'est pas visible (épuise la batterie). À FAIRE : Utilisez .task { } dans SwiftUI pour que les mises à jour s'annulent automatiquement lors de la disparition.

À NE PAS FAIRE : Forcer le déballage des propriétés CLPlacemark — elles sont toutes optionnelles. À FAIRE : Utilisez la coalescence nulle : placemark.locality ?? "Inconnu".

À NE PAS FAIRE : Déclencher ``

[La suite du contenu a été tronquée pour la traduction]

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

JordanCoin/ios-skills-collection

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries. Use when the user asks about GitHub issues, pull requests, workflows, or wants to interact with GitHub repositories from the command line — including tasks like check CI status, create PR, list issues, or query the GitHub API.

JordanCoin/ios-skills-collection

Create media playback experiences using AVKit. Use when adding video players with AVPlayerViewController, enabling Picture-in-Picture, routing media with AirPlay, using SwiftUI VideoPlayer views, configuring transport controls, displaying subtitles and closed captions, or integrating AVFoundation playback with system UI.

JordanCoin/ios-skills-collection

Integrate and optimize Core ML models in iOS apps for on-device machine learning inference. Covers model loading (.mlmodelc, .mlpackage), predictions with auto-generated classes and MLFeatureProvider, compute unit configuration (CPU, GPU, Neural Engine), MLTensor, VNCoreMLRequest, MLComputePlan, multi-model pipelines, and deployment strategies. Use when loading Core ML models, making predictions, configuring compute units, or profiling model performance.

JordanCoin/ios-skills-collection

Display and manipulate PDF documents using PDFKit. Use when embedding PDFView to show PDF files, creating or modifying PDFDocument instances, adding annotations (highlights, notes, signatures), extracting text with PDFSelection, navigating pages, generating thumbnails, filling PDF forms, or wrapping PDFView in SwiftUI.

JordanCoin/ios-skills-collection

Implement, review, or improve in-app tips and onboarding using Apple's TipKit framework. Use when adding feature discovery tooltips, onboarding flows, contextual tips, first-run experiences, coach marks, or working with Tip protocol, TipView, popoverTip, tip rules, tip events, or feature education UI.

JordanCoin/ios-skills-collection

When the user wants to optimize their Google Play Store listing — title, short description, full description, keywords, ratings, or Play Store-specific features. Use when the user mentions "Google Play", "Android", "Play Store", "Play Console", "short description", "full description indexed", "Google Play ASO", or wants Google Play-specific keyword, creative, or ratings strategy. For iOS App Store optimization, see aso-audit and metadata-optimization.

JordanCoin/ios-skills-collection

When the user wants to implement, optimize, or use App Clips for app discovery and conversion. Use when the user mentions "App Clip", "app clip code", "mini app", "instant app", "App Clip card", "App Clip link", "no download required", "instant experience", or wants to understand how App Clips appear in App Store search. For general App Store discoverability, see aso-audit. For marketing campaigns, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to plan a launch strategy for a new app or major update. Also use when the user mentions "app launch", "launch plan", "launch checklist", "pre-launch", "launch day", or "how to launch my app". For ongoing ASO after launch, see aso-audit. For paid acquisition during launch, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to analyze their own app's actual performance data from App Store Connect — real downloads, revenue, IAP, subscriptions, trials, or country breakdowns synced via Appeeky Connect. Use when the user asks about "my downloads", "my revenue", "how is my app performing", "ASC data", "sales and trends", "my subscription numbers", "App Store Connect metrics", or wants to compare periods or top markets. For third-party app estimates, see app-analytics. For subscription analytics depth, see monetization-strategy.

JordanCoin/ios-skills-collection

When the user wants a full ASO health audit, review their App Store listing quality, or diagnose why their app isn't ranking. Also use when the user mentions "ASO audit", "ASO score", "why am I not ranking", "listing review", or "optimize my app store page". For keyword-specific research, see keyword-research. For metadata writing, see metadata-optimization.

JordanCoin/ios-skills-collection

Always use when working on any iOS, macOS, Swift, SwiftUI, Xcode, or Apple platform project. Routes to the correct specialized skills based on development phase and task context. This collection contains 210 skills from 28 authors covering the full iOS lifecycle.

Skills associés