Communitygithub.com

JordanCoin/ios-skills-collection

Implemente, revise ou aprimore funcionalidades de mapas e localização em apps iOS/macOS usando MapKit e CoreLocation. Use ao trabalhar com Map views, anotações, marcadores, polilinhas, rastreamento de localização do usuário, geocodificação, geocodificação reversa, busca/autocomplete, direções e rotas, geofencing, monitoramento de regiões, streams assíncronos CLLocationUpdate ou fluxos de autorização de localização. Também use ao trabalhar com mapas, coordenadas, endereços, lugares, direções, cálculos de distância ou funcionalidades baseadas em localização em apps Swift.

O que é ios-skills-collection?

Este skill fornece um guia abrangente para desenvolvedores que estão criando funcionalidades de mapas e localização em aplicativos iOS e macOS usando as APIs modernas do MapKit e CoreLocation, com foco no iOS 17+. Ele cobre desde a configuração básica de uma view `Map` com SwiftUI, incluindo marcadores, anotações e sobreposições (polilinhas, polígonos, círculos), até o gerenciamento avançado da câmera com `MapCameraPosition`. O skill também aborda as APIs assíncronas mais recentes do CoreLocation, como `CLLocationUpdate.liveUpdates()` para streaming de localização e `CLServiceSession` para gerenciamento de autorização no iOS 18+. Funcionalidades de busca (com `MKLocalSearchCompleter` para autocomplete e `MKLocalSearch` para busca completa), geocodificação (com `CLGeocoder` e as novas `MKGeocodingRequest`/`MKReverseGeocodingRequest`), cálculo de direções e rotas (incluindo rotas de bicicleta no iOS 26+) também são detalhadas. Além disso, o skill inclui uma lista de erros comuns, um checklist de revisão e referências para padrões estendidos, tornando-se um recurso essencial para implementar, revisar ou melhorar qualquer funcionalidade relacionada a mapas e localização em apps Swift.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

MapKit

Construa funcionalidades baseadas em mapas e localização com foco no iOS 17+ usando SwiftUI, MapKit e as APIs assíncronas modernas do CoreLocation. Use Map com MapContentBuilder para as views, CLLocationUpdate.liveUpdates() para streaming de localização e CLMonitor para geofencing.

Consulte references/mapkit-patterns.md para padrões estendidos do MapKit e references/mapkit-corelocation-patterns.md para padrões do CoreLocation.

Conteúdo

Fluxo de Trabalho

1. Adicionar um mapa com marcadores ou anotações

  1. Importe MapKit.
  2. Crie uma view Map com um binding opcional de MapCameraPosition.
  3. Adicione Marker, Annotation, MapPolyline, MapPolygon ou MapCircle dentro do bloco MapContentBuilder.
  4. Configure o estilo do mapa com .mapStyle().
  5. Adicione controles de mapa com .mapControls { }.
  6. Gerencie a seleção com um binding selection:.

2. Rastrear a localização do usuário

  1. Adicione NSLocationWhenInUseUsageDescription ao Info.plist.
  2. No iOS 18+, crie um CLServiceSession para gerenciar a autorização.
  3. Itere sobre CLLocationUpdate.liveUpdates() em uma Task.
  4. Filtre as atualizações por distância ou precisão antes de atualizar a UI.
  5. Pare a task quando o rastreamento de localização não for mais necessário.

3. Buscar lugares

  1. Configure MKLocalSearchCompleter para sugestões de autocomplete.
  2. Faça debounce da entrada do usuário (pelo menos 300ms) antes de definir a consulta.
  3. Converta a conclusão selecionada em um MKLocalSearch.Request para obter resultados completos.
  4. Exiba os resultados como marcadores ou em uma lista.

4. Obter direções e exibir uma rota

  1. Crie um MKDirections.Request com origem e destino MKMapItem.
  2. Defina transportType (.automobile, .walking, .transit, .cycling).
  3. Aguarde o resultado de MKDirections.calculate().
  4. Desenhe a rota com MapPolyline(route.polyline).

5. Revisar código de mapa/localização existente

Percorra o Checklist de Revisão no final deste arquivo.

SwiftUI Map View (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()
        }
    }
}

Marker e Annotation

// Marcador de balão -- a maneira mais simples de fixar um local
Marker("Cafe", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
    .tint(.brown)

// Annotation -- view SwiftUI personalizada em uma coordenada
Annotation("You", coordinate: userCoord, anchor: .bottom) {
    Image(systemName: "figure.wave")
        .padding(6)
        .background(.blue.gradient, in: .circle)
        .foregroundStyle(.white)
}

Sobreposições: Polyline, Polygon, Circle

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

    // Polygon (destaque de área)
    MapPolygon(coordinates: parkBoundary)
        .foregroundStyle(.green.opacity(0.3))
        .stroke(.green, lineWidth: 2)

    // Circle (raio em torno de um ponto)
    MapCircle(center: storeCoord, radius: 500)
        .foregroundStyle(.red.opacity(0.15))
        .stroke(.red, lineWidth: 1)
}

Posição da Câmera

MapCameraPosition controla o que o mapa exibe. Vincule-o para permitir que o usuário interaja e para mover a câmera programaticamente.

// Centralizar em uma região
@State private var position: MapCameraPosition = .region(
    MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
)

// Seguir a localização do usuário
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)

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

// Enquadrar itens específicos
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))

Estilo do Mapa

.mapStyle(.standard)                                        // Mapa rodoviário padrão
.mapStyle(.standard(elevation: .realistic, showsTraffic: true))
.mapStyle(.imagery)                                         // Satélite
.mapStyle(.imagery(elevation: .realistic))                  // Satélite 3D
.mapStyle(.hybrid)                                          // Satélite + rótulos
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))

Modos de Interação do Mapa

.mapInteractionModes(.all)           // Padrão: pan, zoom, rotacionar, inclinar
.mapInteractionModes(.pan)           // Apenas pan
.mapInteractionModes([.pan, .zoom])  // Pan e zoom
.mapInteractionModes([])             // Mapa estático (sem interação)

Seleção no Mapa

@State private var selectedMarker: MKMapItem?

Map(selection: $selectedMarker) {
    ForEach(places) { place in
        Marker(place.name, coordinate: place.coordinate)
            .tag(place.mapItem)     // A tag deve corresponder ao tipo de seleção
    }
}
.onChange(of: selectedMarker) { _, newValue in
    guard let item = newValue else { return }
    // Reagir à seleção
}

API Moderna do CoreLocation

CLLocationUpdate.liveUpdates() (iOS 17+)

Substitua os callbacks do CLLocationManagerDelegate por uma única sequência assíncrona. Cada iteração produz um CLLocationUpdate contendo um 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 precisão horizontal
                guard location.horizontalAccuracy < 50 else { continue }
                await MainActor.run {
                    self.currentLocation = location
                }
            }
        }
    }

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

CLServiceSession (iOS 18+)

Declare os requisitos de autorização para a vida útil de um recurso. Mantenha uma referência para a sessão enquanto precisar dos serviços de localização.

// Autorização de uso em primeiro plano com preferência de precisão total
let session = CLServiceSession(
    authorization: .whenInUse,
    fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// Mantenha `session` como uma propriedade armazenada; libere-a quando terminar.

No iOS 18+, CLLocationUpdate.liveUpdates() e CLMonitor usam um CLServiceSession implícito se você não criar um explicitamente. Crie um explicitamente quando precisar de autorização .always ou precisão total.

Fluxo de Autorização

// Chaves Info.plist (obrigatórias):
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription (apenas se .always for necessário)

// Verificar autorização e guiar o usuário para Ajustes quando negada
struct LocationPermissionView: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        ContentUnavailableView {
            Label("Acesso à Localização Negado", systemImage: "location.slash")
        } description: {
            Text("Ative o acesso à localização nos Ajustes para usar este recurso.")
        } actions: {
            Button("Abrir Ajustes") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    openURL(url)
                }
            }
        }
    }
}

Geocodificação

CLGeocoder (iOS 8+)

let geocoder = CLGeocoder()

// Geocodificação direta: string de endereço -> coordenadas
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
    print(location.coordinate) // CLLocationCoordinate2D
}

// Geocodificação reversa: coordenadas -> 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 e MKReverseGeocodingRequest (iOS 26+)

Nova geocodificação nativa do MapKit que retorna MKMapItem com dados mais ricos e MKAddress / MKAddressRepresentations para formatação flexível de endereços.

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

Busca

MKLocalSearchCompleter (Autocomplete)

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

// Buscar por consulta em linguagem natural dentro de uma região
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
}

Direções

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
}

Exibir Rota no Mapa

@State private var route: MKRoute?

Map {
    if let route {
        MapPolyline(route.polyline)
            .stroke(.blue, lineWidth: 5)
    }
    Marker("Início", coordinate: startCoord)
    Marker("Fim", 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
}

Direções para Bicicleta (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+)

Crie referências ricas de lugares a partir de coordenadas ou endereços sem precisar de um Place ID. Requer 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
}

Erros Comuns

NÃO FAÇA: Solicitar .authorizedAlways de imediato — os usuários desconfiam de permissões amplas. FAÇA: Comece com .requestWhenInUseAuthorization(), evolua para .always apenas quando o usuário ativar um recurso em segundo plano.

NÃO FAÇA: Usar CLLocationManagerDelegate para buscas simples de localização no iOS 17+. FAÇA: Use o stream assíncrono CLLocationUpdate.liveUpdates() para um código mais limpo e conciso.

NÃO FAÇA: Manter as atualizações de localização em execução quando o mapa/view não estiver visível (consome bateria). FAÇA: Use .task { } no SwiftUI para que as atualizações sejam canceladas automaticamente ao desaparecer.

NÃO FAÇA: Fazer force-unwrap das propriedades de CLPlacemark — todas são opcionais. FAÇA: Use nil-coalescing: placemark.locality ?? "Desconhecido".

NÃO FAÇA: Disparar MKLocalSearchCompleter a cada caractere digitado sem debounce. FAÇA: Use Task.sleep(nanoseconds:) ou Combine para debounce da entrada do usuário (300ms é um bom padrão).

NÃO FAÇA: Ignorar o tratamento de erros em CLLocationUpdate.liveUpdates() — a sequência pode lançar erros de autorização. FAÇA: Envolva o loop for try await em um bloco do/catch e trate CLError adequadamente.

NÃO FAÇA: Assumir que o usuário sempre tem o Bluetooth ligado ao usar geofencing com CLMonitor. FAÇA: Verificar CLLocationManager.isMonitoringAvailable(for: CLRegion.self) e o estado do Bluetooth.

Checklist de Revisão

  • O app solicita autorização de localização apenas quando necessário?
  • O fluxo de autorização inclui um estado de negação com mensagem amigável e link para Ajustes?
  • CLLocationUpdate.liveUpdates() é cancelado quando a view some?
  • Os dados de localização são filtrados por precisão antes de serem usados?
  • transportType é definido explicitamente ao solicitar direções?
  • Os resultados do MKLocalSearchCompleter têm debounce para evitar chamadas excessivas à API?
  • Marcadores e anotações têm coordenadas válidas e tags de seleção corretas?
  • A posição da câmera do mapa é restaurada ao navegar de volta?
  • O tratamento de erros é aplicado a todas as chamadas assíncronas (geocodificação, busca, direções)?
  • CLServiceSession (iOS 18+) é liberado quando não for mais necessário?
  • O app lida com mudanças de autorização em tempo de execução (ex.: usuário nega nos Ajustes)?
  • As sobreposições do mapa têm níveis de opacidade razoáveis para não obstruir o conteúdo subjacente?
  • Os estilos de mapa são testados com elevation: .realistic e fundos escuros?
  • O recurso de localização usa a API assíncrona moderna em vez do delegado legado?

Referências


Este skill foi inspirado por ios-skills-collection.

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.

Habilidades Relacionadas