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
- SwiftUI Map View (iOS 17+)
- API Moderna do CoreLocation
- Geocodificação
- Busca
- Direções
- PlaceDescriptor (iOS 26+)
- Erros Comuns
- Checklist de Revisão
- Referências
Fluxo de Trabalho
1. Adicionar um mapa com marcadores ou anotações
- Importe
MapKit. - Crie uma view
Mapcom um binding opcional deMapCameraPosition. - Adicione
Marker,Annotation,MapPolyline,MapPolygonouMapCircledentro do blocoMapContentBuilder. - Configure o estilo do mapa com
.mapStyle(). - Adicione controles de mapa com
.mapControls { }. - Gerencie a seleção com um binding
selection:.
2. Rastrear a localização do usuário
- Adicione
NSLocationWhenInUseUsageDescriptionao Info.plist. - No iOS 18+, crie um
CLServiceSessionpara gerenciar a autorização. - Itere sobre
CLLocationUpdate.liveUpdates()em umaTask. - Filtre as atualizações por distância ou precisão antes de atualizar a UI.
- Pare a task quando o rastreamento de localização não for mais necessário.
3. Buscar lugares
- Configure
MKLocalSearchCompleterpara sugestões de autocomplete. - Faça debounce da entrada do usuário (pelo menos 300ms) antes de definir a consulta.
- Converta a conclusão selecionada em um
MKLocalSearch.Requestpara obter resultados completos. - Exiba os resultados como marcadores ou em uma lista.
4. Obter direções e exibir uma rota
- Crie um
MKDirections.Requestcom origem e destinoMKMapItem. - Defina
transportType(.automobile,.walking,.transit,.cycling). - Aguarde o resultado de
MKDirections.calculate(). - 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
MKLocalSearchCompletertê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: .realistice fundos escuros? - O recurso de localização usa a API assíncrona moderna em vez do delegado legado?
Referências
- Padrões Estendidos do MapKit
- Padrões do CoreLocation
- Apple: MapKit | Apple Developer Documentation
- Apple: CoreLocation | Apple Developer Documentation
- WWDC 2023: O que há de novo no MapKit
- WWDC 2024: O que há de novo no CoreLocation
Este skill foi inspirado por ios-skills-collection.