Communitygithub.com

iOS 지도 및 위치 기능 스킬 컬렉션

SwiftUI 앱에서 MapKit과 CoreLocation을 사용하여 지도 뷰, 주석, 마커, 폴리라인, 사용자 위치 추적, 지오코딩, 역지오코딩, 검색/자동완성, 길찾기 및 경로, 지오펜싱, 영역 모니터링, CLLocationUpdate 비동기 스트림, 위치 권한 흐름 등 지도와 위치 기반 기능을 구현, 검토 또는 개선합니다.

iOS 지도 및 위치 기능 스킬 컬렉션란 무엇인가요?

이 스킬은 iOS 17+ (SwiftUI)와 macOS 앱에서 MapKit 및 CoreLocation을 사용한 지도 및 위치 기반 기능 구현을 위한 포괄적인 가이드입니다. SwiftUI의 `Map` 뷰, `MapContentBuilder`를 사용한 마커, 주석, 오버레이(폴리라인, 폴리곤, 원) 표현 방법, `CLLocationUpdate.liveUpdates()`를 사용한 현대적인 비동기 위치 업데이트 스트림, CLServiceSession을 통한 iOS 18+ 권한 관리, 지오코딩/역지오코딩, MKLocalSearchCompleter를 활용한 장소 검색 자동완성, MKDirections를 사용한 길찾기 및 경로 표시, ETA 계산 등을 다룹니다. 또한 iOS 26+에서 사용 가능한 MKGeocodingRequest/MKReverseGeocodingRequest 및 PlaceDescriptor와 같은 최신 API도 포함되어 있어, 개발자가 플랫폼 별 최신 기능을 활용할 수 있도록 돕습니다. 일반적인 실수와 코드 리뷰 체크리스트를 제공하여, 더 효율적이고 안정적인 위치 기반 앱을 개발하도록 안내합니다. Cursor, Claude Code, Codex 등 다양한 AI 에이전트에서 사용할 수 있습니다.

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

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

MapKit

SwiftUI MapKit 및 현대적인 CoreLocation 비동기 API로 iOS 17+를 대상으로 하는 지도 기반 및 위치 인식 기능을 구축합니다. 뷰에는 Map과 MapContentBuilder를, 위치 스트리밍에는 CLLocationUpdate.liveUpdates()를, 지오펜싱에는 CLMonitor를 사용하세요.

확장된 MapKit 패턴은 references/mapkit-patterns.md를, CoreLocation 패턴은 references/mapkit-corelocation-patterns.md를 참조하세요.

목차

워크플로우

1. 마커 또는 주석이 있는 지도 추가

  1. MapKit 임포트.
  2. 선택적 MapCameraPosition 바인딩과 함께 Map 뷰 생성.
  3. MapContentBuilder 클로저 내에 Marker, Annotation, MapPolyline, MapPolygon, 또는 MapCircle 추가.
  4. .mapStyle()로 지도 스타일 구성.
  5. .mapControls { }로 지도 컨트롤 추가.
  6. selection: 바인딩으로 선택 처리.

2. 사용자 위치 추적

  1. Info.plist에 NSLocationWhenInUseUsageDescription 추가.
  2. iOS 18+에서 CLServiceSession을 생성하여 권한 관리.
  3. Task에서 CLLocationUpdate.liveUpdates() 순회.
  4. UI 업데이트 전 거리 또는 정확도별로 업데이트 필터링.
  5. 위치 추적이 더 이상 필요하지 않으면 태스크 중지.

3. 장소 검색

  1. 자동완성 제안을 위해 MKLocalSearchCompleter 구성.
  2. 사용자 입력 디바운스 (최소 300ms) 후 쿼리 설정.
  3. 선택된 완성 항목을 MKLocalSearch.Request로 변환하여 전체 결과 얻기.
  4. 결과를 마커 또는 목록으로 표시.

4. 길찾기 및 경로 표시

  1. 출발지와 목적지 MKMapItem으로 MKDirections.Request 생성.
  2. transportType 설정 (.automobile, .walking, .transit, .cycling).
  3. MKDirections.calculate() 대기.
  4. MapPolyline(route.polyline)로 경로 그리기.

5. 기존 지도/위치 코드 검토

이 파일 끝에 있는 리뷰 체크리스트를 실행합니다.

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와 Annotation

// 풍선 마커 -- 위치를 고정하는 가장 간단한 방법
Marker("Cafe", systemImage: "cup.and.saucer.fill", coordinate: cafeCoord)
    .tint(.brown)

// Annotation -- 특정 좌표의 커스텀 SwiftUI 뷰
Annotation("You", coordinate: userCoord, anchor: .bottom) {
    Image(systemName: "figure.wave")
        .padding(6)
        .background(.blue.gradient, in: .circle)
        .foregroundStyle(.white)
}

오버레이: 폴리라인, 폴리곤, 원

Map {
    // 좌표로부터 폴리라인
    MapPolyline(coordinates: routeCoords)
        .stroke(.blue, lineWidth: 4)

    // 폴리곤 (영역 강조)
    MapPolygon(coordinates: parkBoundary)
        .foregroundStyle(.green.opacity(0.3))
        .stroke(.green, lineWidth: 2)

    // 원 (특정 지점 주변 반경)
    MapCircle(center: storeCoord, radius: 500)
        .foregroundStyle(.red.opacity(0.15))
        .stroke(.red, lineWidth: 1)
}

카메라 위치

MapCameraPosition은 지도에 표시되는 내용을 제어합니다. 바인딩하여 사용자가 상호 작용하거나 프로그래밍 방식으로 카메라를 이동할 수 있게 하세요.

// 영역 중심
@State private var position: MapCameraPosition = .region(
    MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 37.334, longitude: -122.009),
        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
    )
)

// 사용자 위치 따라가기
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)

// 특정 카메라 각도 (3D 원근)
@State private var position: MapCameraPosition = .camera(
    MapCamera(centerCoordinate: applePark, distance: 1000, heading: 90, pitch: 60)
)

// 특정 항목 프레임
position = .item(MKMapItem.forCurrentLocation())
position = .rect(MKMapRect(...))

지도 스타일

.mapStyle(.standard)                                        // 기본 도로 지도
.mapStyle(.standard(elevation: .realistic, showsTraffic: true))
.mapStyle(.imagery)                                         // 위성
.mapStyle(.imagery(elevation: .realistic))                  // 3D 위성
.mapStyle(.hybrid)                                          // 위성 + 레이블
.mapStyle(.hybrid(elevation: .realistic, showsTraffic: true))

지도 상호 작용 모드

.mapInteractionModes(.all)           // 기본: 이동, 확대/축소, 회전, 피치
.mapInteractionModes(.pan)           // 이동만
.mapInteractionModes([.pan, .zoom])  // 이동 및 확대/축소
.mapInteractionModes([])             // 정적 지도 (상호 작용 없음)

지도 선택

@State private var selectedMarker: MKMapItem?

Map(selection: $selectedMarker) {
    ForEach(places) { place in
        Marker(place.name, coordinate: place.coordinate)
            .tag(place.mapItem)     // 태그는 선택 유형과 일치해야 함
    }
}
.onChange(of: selectedMarker) { _, newValue in
    guard let item = newValue else { return }
    // 선택에 반응
}

CoreLocation 현대 API

CLLocationUpdate.liveUpdates() (iOS 17+)

CLLocationManagerDelegate 콜백을 단일 비동기 시퀀스로 대체합니다. 각 반복은 선택적 CLLocation을 포함하는 CLLocationUpdate를 생성합니다.

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 }
                // 수평 정확도로 필터링
                guard location.horizontalAccuracy < 50 else { continue }
                await MainActor.run {
                    self.currentLocation = location
                }
            }
        }
    }

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

CLServiceSession (iOS 18+)

기능의 수명 동안 권한 요구 사항을 선언합니다. 위치 서비스가 필요한 동안 세션에 대한 참조를 유지하세요.

// 사용 중 권한, 전체 정확도 선호
let session = CLServiceSession(
    authorization: .whenInUse,
    fullAccuracyPurposeKey: "NearbySearchPurpose"
)
// `session`을 저장 속성으로 유지하고 사용이 끝나면 해제합니다.

iOS 18+에서 CLLocationUpdate.liveUpdates()와 CLMonitor는 명시적으로 생성하지 않으면 암시적 CLServiceSession을 사용합니다. .always 권한 또는 전체 정확도가 필요할 때 명시적으로 생성하세요.

권한 흐름

// Info.plist 키 (필수):
// NSLocationWhenInUseUsageDescription
// NSLocationAlwaysAndWhenInUseUsageDescription (.always가 필요한 경우에만)

// 권한 확인 및 거부된 경우 설정으로 안내
struct LocationPermissionView: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        ContentUnavailableView {
            Label("위치 접근 거부됨", systemImage: "location.slash")
        } description: {
            Text("이 기능을 사용하려면 설정에서 위치 접근을 활성화하세요.")
        } actions: {
            Button("설정 열기") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    openURL(url)
                }
            }
        }
    }
}

지오코딩

CLGeocoder (iOS 8+)

let geocoder = CLGeocoder()

// 순방향 지오코딩: 주소 문자열 -> 좌표
let placemarks = try await geocoder.geocodeAddressString("1 Apple Park Way, Cupertino")
if let location = placemarks.first?.location {
    print(location.coordinate) // CLLocationCoordinate2D
}

// 역방향 지오코딩: 좌표 -> 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 및 MKReverseGeocodingRequest (iOS 26+)

더 풍부한 데이터를 가진 MKMapItem과 유연한 주소 서식을 위한 MKAddress / MKAddressRepresentations를 반환하는 새로운 MapKit 네이티브 지오코딩입니다.

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

검색

MKLocalSearchCompleter (자동완성)

@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 (전체 검색)

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
}

// 자연어 쿼리로 특정 영역 내 검색
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
}

길찾기

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
}

지도에 경로 표시

@State private var route: MKRoute?

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

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
}

자전거 길찾기 (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+)

Place ID 없이 좌표나 주소로 풍부한 장소 참조를 생성합니다. 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
}

일반적인 실수

하지 마세요: 처음부터 .authorizedAlways를 요청하세요 — 사용자는 광범위한 권한을 신뢰하지 않습니다. 하세요: .requestWhenInUseAuthorization()으로 시작하고, 사용자가 백그라운드 기능을 활성화할 때만 .always로 에스컬레이션하세요.

하지 마세요: iOS 17+에서 단순 위치 가져오기에 CLLocationManagerDelegate를 사용하세요. 하세요: 더 깔끔하고 간결한 코드를 위해 CLLocationUpdate.liveUpdates() 비동기 스트림을 사용하세요.

하지 마세요: 지도/뷰가 보이지 않을 때 위치 업데이트를 계속 실행하세요 (배터리 소모). 하세요: SwiftUI에서 .task { }를 사용하여 사라질 때 업데이트가 자동으로 취소되도록 하세요.

하지 마세요: CLPlacemark 속성을 강제 언래핑하세요 — 모두 선택 사항입니다. 하세요: nil 병합 사용: placemark.locality ?? "알 수 없음".

하지 마세요: MKLocalSearchCompleter 쿼리 업데이트마다 MKLocalSearch.start()를 실행하세요. 하세요: 사용자가 완성 항목을 선택한 후에만 전체 검색을 실행하고, 입력 중에는 MKLocalSearchCompleter.delegate 결과에 의존하세요.

리뷰 체크리스트

정확성

  • 모든 런타임 시나리오에서 맵이 올바른 좌표를 표시합니까?
  • 영역 및 스팬이 적절합니까 (너무 확대/축소되지 않음)?
  • 지오코딩/역지오코딩이 네트워크 오류를 정상적으로 처리합니까?
  • 검색 결과가 예상과 일치합니까 (결과 유형, 필터링)?
  • 경로가 정확합니까 (시작/끝 지점, 중간 경유지)?

권한

  • Info.plist에 NSLocationWhenInUseUsageDescription이 있습니까?
  • 백그라운드 위치가 사용되는 경우에만 NSLocationAlwaysAndWhenInUseUsageDescription이 있습니까?
  • 앱이 승인되지 않은 상태와 거부된 상태를 적절히 처리합니까?
  • iOS 18+에서 기능별로 CLServiceSession이 사용됩니까?
  • 앱이 승인 상태를 확인하고 필요할 때 설정으로 안내합니까?

성능

  • 위치 업데이트가 거리/정확도 임계값으로 필터링됩니까?
  • 지도가 더 이상 필요하지 않을 때 업데이트가 중지됩니까 (.task 범위)?
  • 검색 쿼리가 디바운스됩니까 (300ms 이상)?
  • 많은 수의 주석이 있는 경우 주석 클러스터링이 사용됩니까?
  • 배터리 소모를 방지하기 위해 과도한 지도 새로고침을 피합니까?

보안

  • 위치 데이터가 불필요하게 지속/공유되지 않습니까?
  • 위치 권한이 민감한 데이터가 아닌 기능 동작에만 사용됩니까?
  • 전체 정확도가 필요한 경우 fullAccuracyPurposeKey가 Info.plist에 있습니까?
  • iOS 18+에서 CLServiceSession 권한이 적절한 최소 범위로 설정됩니까?

접근성

  • 모든 주석/마커에 접근성 레이블이 있습니까?
  • 지도 컨트롤에 VoiceOver 힌트가 있습니까 (.accessibilityHint)?
  • 축소된 지도가 접근성 요소를 숨기지 않습니까?
  • 사용자 지정 어노테이션 뷰에 .accessibilityElement(children: .contain) 또는 .combine이 있습니까?

오류 처리

  • 네트워크 오류 (지오코딩, 검색, 길찾기)가 정상적으로 처리됩니까?
  • 위치 서비스를 사용할 수 없을 때 사용자에게 표시가 됩니까?
  • 지도 타일 로딩 실패가 (가능한 경우) 처리됩니까?
  • 지역 모니터링 실패가 호출됩니까?

유지보수성

  • 지도/위치 로직이 SwiftUI 뷰와 분리되어 있습니까?
  • 장소 검색, 경로 계산과 같은 반복 작업에 재사용 가능한 함수가 사용됩니까?
  • 매직 넘버 (좌표, 반경)가 상수로 정의됩니까?
  • 플랫폼 버전 가용성이 #available로 확인됩니까?

참고 자료

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.

관련 스킬