Communitygithub.com

JordanCoin/ios-skills-collection

Implementar, revisar o mejorar las funciones de mapas y ubicación en aplicaciones iOS/macOS usando MapKit y CoreLocation. Úsalo al trabajar con vistas de mapa, anotaciones, marcadores, polilíneas, seguimiento de ubicación del usuario, geocodificación, geocodificación inversa, búsqueda/autocompletado, direcciones y rutas, geocercas, monitoreo de regiones, flujos asíncronos de CLLocationUpdate o flujos de autorización de ubicación. También úsalo al trabajar con mapas, coordenadas, direcciones, lugares, indicaciones, cálculos de distancia o funciones basadas en ubicación en aplicaciones Swift.

¿Qué es ios-skills-collection?

Esta habilidad proporciona una guía completa para construir funciones de mapas y localización en aplicaciones iOS/macOS utilizando MapKit y CoreLocation, con un enfoque en las APIs modernas a partir de iOS 17+. Está diseñada para desarrolladores de Swift que trabajan con SwiftUI y necesitan patrones actualizados para vistas de mapa, anotaciones, seguimiento de ubicación en tiempo real, geocodificación, búsqueda de lugares, direcciones y geocercas. La habilidad incluye ejemplos de código listos para usar, como la integración de vistas `Map` con `MapContentBuilder`, el uso de `CLLocationUpdate.liveUpdates()` para flujos de ubicación asíncronos y `CLMonitor` para monitoreo de regiones. También cubre buenas prácticas, como la gestión de autorización de ubicación con `CLServiceSession` (iOS 18+) y la optimización del rendimiento para evitar el consumo excesivo de batería. El documento está organizado en secciones que abarcan desde lo básico (marcadores y anotaciones) hasta temas avanzados (rutas, geocodificación con iOS 26+ y PlaceDescriptor), e incluye una lista de verificación para revisar código existente y una sección de errores comunes.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

MapKit

Construye funciones basadas en mapas y ubicación para iOS 17+ con SwiftUI, MapKit y las APIs asíncronas modernas de CoreLocation. Usa Map con MapContentBuilder para las vistas, CLLocationUpdate.liveUpdates() para transmitir la ubicación y CLMonitor para geocercas.

Consulta references/mapkit-patterns.md para patrones extendidos de MapKit y references/mapkit-corelocation-patterns.md para patrones de CoreLocation.

Contenido

Flujo de trabajo

1. Agregar un mapa con marcadores o anotaciones

  1. Importa MapKit.
  2. Crea una vista Map con un enlace opcional MapCameraPosition.
  3. Agrega Marker, Annotation, MapPolyline, MapPolygon o MapCircle dentro del closure MapContentBuilder.
  4. Configura el estilo del mapa con .mapStyle().
  5. Agrega controles de mapa con .mapControls { }.
  6. Maneja la selección con un enlace selection:.

2. Rastrear la ubicación del usuario

  1. Agrega NSLocationWhenInUseUsageDescription al Info.plist.
  2. En iOS 18+, crea un CLServiceSession para gestionar la autorización.
  3. Itera CLLocationUpdate.liveUpdates() en una Task.
  4. Filtra las actualizaciones por distancia o precisión antes de actualizar la UI.
  5. Detén la tarea cuando ya no sea necesario el rastreo de ubicación.

3. Buscar lugares

  1. Configura MKLocalSearchCompleter para sugerencias de autocompletado.
  2. Debounce la entrada del usuario (al menos 300ms) antes de establecer la consulta.
  3. Convierte la finalización seleccionada en un MKLocalSearch.Request para obtener resultados completos.
  4. Muestra los resultados como marcadores o en una lista.

4. Obtener direcciones y mostrar una ruta

  1. Crea un MKDirections.Request con el origen y destino MKMapItem.
  2. Establece el transportType (.automobile, .walking, .transit, .cycling).
  3. Espera MKDirections.calculate().
  4. Dibuja la ruta con MapPolyline(route.polyline).

5. Revisar código de mapa/ubicación existente

Revisa la Lista de Verificación al final de este archivo.

Vista de Mapa en 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()
        }
    }
}

Marcador y Anotación

// Marcador globo -- la forma más simple de fijar una ubicación
Marker("Café", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
    .tint(.brown)

// Anotación -- vista SwiftUI personalizada en una coordenada
Annotation("Tú", coordinate: userCoord, anchor: .bottom) {
    Image(systemName: "figure.wave")
        .padding(6)
        .background(.blue.gradient, in: .circle)
        .foregroundStyle(.white)
}

Superposiciones: Polilínea, Polígono, Círculo

Map {
    // Polilínea a partir de coordenadas
    MapPolyline(coordinates: routeCoords)
        .stroke(.blue, lineWidth: 4)

    // Polígono (resaltar área)
    MapPolygon(coordinates: parkBoundary)
        .foregroundStyle(.green.opacity(0.3))
        .stroke(.green, lineWidth: 2)

    // Círculo (radio alrededor de un punto)
    MapCircle(center: storeCoord, radius: 500)
        .foregroundStyle(.red.opacity(0.15))
        .stroke(.red, lineWidth: 1)
}

Posición de la Cámara

MapCameraPosition controla lo que muestra el mapa. Enlázala para permitir la interacción del usuario y mover la cámara programáticamente.

// Centrar en una región
@State private var position: MapCameraPosition = .region(
    MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
)

// Seguir la ubicación del usuario
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)

// Ángulo de cámara específico (perspectiva 3D)
@State private var position: MapCameraPosition = .camera(
    MapCamera(centerCoordinate: applePark, distance: 1000, heading: 90, pitch: 60)
)

// Encuadrar elementos específicos
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))

Estilo del Mapa

.mapStyle(.standard)                                        // Mapa de carreteras por defecto
.mapStyle(.standard(elevation: .realistic, showsTraffic: true))
.mapStyle(.imagery)                                         // Satélite
.mapStyle(.imagery(elevation: .realistic))                  // Satélite 3D
.mapStyle(.hybrid)                                          // Satélite + etiquetas
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))

Modos de Interacción del Mapa

.mapInteractionModes(.all)           // Por defecto: desplazar, zoom, rotar, inclinar
.mapInteractionModes(.pan)           // Solo desplazar
.mapInteractionModes([.pan, .zoom])  // Desplazar y zoom
.mapInteractionModes([])             // Mapa estático (sin interacción)

Selección en el Mapa

@State private var selectedMarker: MKMapItem?

Map(selection: $selectedMarker) {
    ForEach(places) { place in
        Marker(place.name, coordinate: place.coordinate)
            .tag(place.mapItem)     // La etiqueta debe coincidir con el tipo de selección
    }
}
.onChange(of: selectedMarker) { _, newValue in
    guard let item = newValue else { return }
    // Reaccionar a la selección
}

API Moderna de CoreLocation

CLLocationUpdate.liveUpdates() (iOS 17+)

Reemplaza los callbacks de CLLocationManagerDelegate con una única secuencia asíncrona. Cada iteración produce un CLLocationUpdate que contiene un CLLocation opcional.

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 }
                // Filtrar por precisión horizontal
                guard location.horizontalAccuracy < 50 else { continue }
                await MainActor.run {
                    self.currentLocation = location
                }
            }
        }
    }

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

CLServiceSession (iOS 18+)

Declara los requisitos de autorización durante la vida útil de una función. Mantén una referencia a la sesión mientras necesites servicios de ubicación.

// Autorización de uso mientras la aplicación está en uso con preferencia de precisión completa
let session = CLServiceSession(
    authorization: .whenInUse,
    fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// Mantén `session` como una propiedad almacenada; libérala cuando termines.

En iOS 18+, CLLocationUpdate.liveUpdates() y CLMonitor toman un CLServiceSession implícito si no creas uno explícitamente. Crea uno explícito cuando necesites autorización .always o precisión completa.

Flujo de Autorización

// Claves Info.plist (requeridas):
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription (solo si se necesita .always)

// Verificar la autorización y guiar al usuario a Configuración cuando se deniega
struct LocationPermissionView: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        ContentUnavailableView {
            Label("Acceso a Ubicación Denegado", systemImage: "location.slash")
        } description: {
            Text("Habilita el acceso a la ubicación en Configuración para usar esta función.")
        } actions: {
            Button("Abrir Configuración") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    openURL(url)
                }
            }
        }
    }
}

Geocodificación

CLGeocoder (iOS 8+)

let geocoder = CLGeocoder()

// Geocodificación directa: cadena de dirección -> coordenadas
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
    print(location.coordinate) // CLLocationCoordinate2D
}

// Geocodificación inversa: coordenadas -> lugar
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 y MKReverseGeocodingRequest (iOS 26+)

Nueva geocodificación nativa de MapKit que devuelve MKMapItem con datos más ricos y MKAddress / MKAddressRepresentations para un formato de dirección flexible.

@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
}

Búsqueda

MKLocalSearchCompleter (Autocompletado)

@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 (Búsqueda Completa)

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
}

// Búsqueda por consulta en lenguaje natural dentro de una región
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
}

Direcciones

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
}

Mostrar Ruta en el Mapa

@State private var route: MKRoute?

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

Cálculo de 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
}

Direcciones para Ciclistas (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+)

Crea referencias de lugar enriquecidas a partir de coordenadas o direcciones sin necesidad de un ID de lugar. Requiere 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
}

Errores Comunes

NO: Solicites .authorizedAlways por adelantado: los usuarios desconfían de los permisos amplios. SÍ: Comienza con .requestWhenInUseAuthorization(), escala a .always solo cuando el usuario active una función en segundo plano.

NO: Uses CLLocationManagerDelegate para obtener ubicaciones simples en iOS 17+. SÍ: Usa el flujo asíncrono CLLocationUpdate.liveUpdates() para un código más limpio y conciso.

NO: Mantengas las actualizaciones de ubicación ejecutándose cuando el mapa/vista no es visible (drena la batería). SÍ: Usa .task { } en SwiftUI para que las actualizaciones se cancelen automáticamente al desaparecer.

NO: Desenvuelvas forzosamente las propiedades de CLPlacemark (todas son opcionales). SÍ: Usa coalescencia nil: placemark.locality ?? "Desconocido".

NO: Disparar MKLocalSearch con cada pulsación de tecla; satura el límite de la API. SÍ: Debounce con Combine en al menos 300ms.

NO: Dibujar una ruta sin manejar errores en el cálculo de direcciones (carreteras cortadas, sin ruta, etc.). SÍ: Manejar MKDirections.Error y mostrar un mensaje al usuario.

NO: Ignorar el argumento fullAccuracyPurposeKey en CLServiceSession. SÍ: Proporcionar una clave de propósito definida en Info.plist en NSLocationTemporaryUsageDescriptionDictionary.

NO: Depender de la segmentación de carreteras de MKRoute (puede no coincidir con el camino real). SÍ: Para navegación paso a paso, usa MKDirections.calculateETA() en su lugar.

Lista de Verificación

Fundamentos de la Vista de Mapa

  • ¿Se usan vistas SwiftUI Map (iOS 17+) en lugar de MKMapView mediante UIViewRepresentable?
  • ¿Se utiliza MapContentBuilder para marcadores, anotaciones y superposiciones?
  • ¿Se usan Marker y Annotation adecuadamente? (Marker para etiqueta simple, Annotation para vistas personalizadas)
  • ¿Están mapControls agregados (brújula, escala, botón de ubicación)?
  • ¿Se configuró mapStyle (estándar, imagen, híbrido, elevación)?
  • ¿Se está pasando MapCameraPosition para control programático de la cámara?
  • ¿Están los modos de interacción restringidos cuando se desea?

Autorización y Privacidad

  • ¿Están NSLocationWhenInUseUsageDescription (y .always si aplica) en Info.plist?
  • ¿Están las descripciones de autorización escritas para el usuario final?
  • ¿Se usa CLServiceSession en iOS 18+ para definir los requisitos de autorización?
  • ¿Se guía al usuario a Configuración cuando se deniega la autorización de ubicación?
  • ¿Se solicita .always solo cuando el usuario activa una función en segundo plano?

Rendimiento de la Ubicación

  • ¿Se usan CLLocationUpdate.liveUpdates() en un Task en lugar del delegado?
  • ¿Se filtran las actualizaciones por horizontalAccuracy antes de actualizar la UI?
  • ¿Se detiene el rastreo de ubicación cuando la vista desaparece?
  • ¿Se usan desiredAccuracy y activityType cuando se crea un CLServiceSession?
  • ¿Se evita la actualización de la UI a 60fps para actualizaciones de ubicación? (20fps es suficiente)

Búsqueda y Autocompletado

  • ¿Se debouncea la entrada de búsqueda (mín. 300ms) antes de establecer queryFragment?
  • ¿Se configuró resultTypes en MKLocalSearchCompleter (.address, .pointOfInterest, etc.)?
  • ¿Se maneja la superación del límite de tasa de MKLocalSearch?
  • ¿Se filtra region en las solicitudes de búsqueda geográfica?
  • ¿Se muestran en el mapa los resultados como marcadores después de la selección?

Direcciones y Enrutamiento

  • ¿Se verificó que el origen/destino existe antes de calcular las direcciones?
  • ¿Se configuró transportType (automóvil, caminar, transporte público, bicicleta) según corresponda?
  • ¿Se manejan los casos de error (sin ruta, restricciones de tráfico, etc.)?
  • ¿Se limita la línea de ruta al ancho y color adecuados?
  • ¿Se usa calculateETA() en lugar de calculate() si solo se necesita el tiempo de viaje?

Precisión y Confiabilidad

  • ¿Se manejan los opcionales de CLPlacemark de forma segura con coalescencia nil?
  • ¿Se proporciona una alternativa cuando la geocodificación falla o no devuelve resultados?
  • ¿Están las escalas del mapa limitadas a un rango razonable?
  • ¿Se usa MKMapItemRequest con PlaceDescriptor en lugar de geocodificación para coordenadas?
  • ¿Se realizan pruebas de la función en diferentes configuraciones regionales y tipos de datos de ubicación?

Geocercas (usando CLMonitor)

  • ¿Se usa CLMonitor en lugar del obsoleto CLCircularRegion + startMonitoring(for:)?
  • ¿Se llama CLMonitor.StreamedEvents para eventos de geocerca en vivo?
  • ¿Se maneja la autorización de ubicación .always para la entrega de eventos en segundo plano?
  • ¿Se define un radio mínimo razonable (al menos 100m)?
  • ¿Se limpian las geocercas cuando la función ya no está en uso?

Referencias

Ver también: references/mapkit-patterns.md para patrones extendidos de MapKit y references/mapkit-corelocation-patterns.md para patrones de CoreLocation.

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 relacionados