๐Ÿ“ Core Location ์™„์ „์ •๋ณต

GPS ์œ„์น˜ ์ถ”์ ๋ถ€ํ„ฐ ์ง€์˜คํŽœ์‹ฑ๊นŒ์ง€. ์œ„์น˜ ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค์˜ ๋ชจ๋“  ๊ฒƒ!

โœจ Core Location์ด๋ž€?

Core Location์€ GPS, Wi-Fi, ์…€๋ฃฐ๋Ÿฌ๋ฅผ ํ™œ์šฉํ•ด ์‚ฌ์šฉ์ž ์œ„์น˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์œ„์น˜ ์ถ”์ , ์ง€์˜คํŽœ์‹ฑ, ๋‚˜์นจ๋ฐ˜ ๋“ฑ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ” ๊ถŒํ•œ ์š”์ฒญ

Info.plist
<!-- ํ•„์ˆ˜: ์‚ฌ์šฉ ๋ชฉ์  ์„ค๋ช… -->

<!-- ์•ฑ ์‚ฌ์šฉ ์ค‘์—๋งŒ ์œ„์น˜ -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>ํ˜„์žฌ ์œ„์น˜๋ฅผ ์ง€๋„์— ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค</string>

<!-- ํ•ญ์ƒ ์œ„์น˜ (๋ฐฑ๊ทธ๋ผ์šด๋“œ) -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>์ง€์˜คํŽœ์‹ฑ ์•Œ๋ฆผ์„ ์œ„ํ•ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์œ„์น˜ ์ ‘๊ทผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค</string>

๐Ÿ“ ํ˜„์žฌ ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ

LocationManager.swift
import CoreLocation

class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
    private let manager = CLLocationManager()

    @Published var location: CLLocation?
    @Published var authorizationStatus: CLAuthorizationStatus?

    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
    }

    // ๊ถŒํ•œ ์š”์ฒญ
    func requestPermission() {
        manager.requestWhenInUseAuthorization()
        // ๋˜๋Š” manager.requestAlwaysAuthorization()
    }

    // ์œ„์น˜ ์ถ”์  ์‹œ์ž‘
    func startUpdating() {
        manager.startUpdatingLocation()
    }

    // ์œ„์น˜ ์ถ”์  ์ค‘์ง€
    func stopUpdating() {
        manager.stopUpdatingLocation()
    }

    // ํ•œ ๋ฒˆ๋งŒ ์œ„์น˜ ๊ฐ€์ ธ์˜ค๊ธฐ (iOS 14+)
    func requestLocation() {
        manager.requestLocation()
    }

    // Delegate - ์œ„์น˜ ์—…๋ฐ์ดํŠธ
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        location = locations.last
        print("์œ„์น˜: \(location!.coordinate.latitude), \(location!.coordinate.longitude)")
    }

    // Delegate - ๊ถŒํ•œ ๋ณ€๊ฒฝ
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        authorizationStatus = manager.authorizationStatus

        switch manager.authorizationStatus {
        case .authorizedWhenInUse, .authorizedAlways:
            startUpdating()
        case .denied, .restricted:
            print("์œ„์น˜ ๊ถŒํ•œ ๊ฑฐ๋ถ€")
        case .notDetermined:
            requestPermission()
        @unknown default:
            break
        }
    }

    // Delegate - ์—๋Ÿฌ
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("์œ„์น˜ ์˜ค๋ฅ˜: \(error.localizedDescription)")
    }
}

๐Ÿ“ฑ SwiftUI ํ†ตํ•ฉ

LocationView.swift
import SwiftUI

struct LocationView: View {
    @StateObject private var locationManager = LocationManager()

    var body: some View {
        VStack(spacing: 20) {
            if let location = locationManager.location {
                Text("๐Ÿ“ ํ˜„์žฌ ์œ„์น˜")
                    .font(.headline)

                Text("์œ„๋„: \(location.coordinate.latitude)")
                Text("๊ฒฝ๋„: \(location.coordinate.longitude)")
                Text("๊ณ ๋„: \(location.altitude)m")
                Text("์ •ํ™•๋„: \(location.horizontalAccuracy)m")
            } else {
                Text("์œ„์น˜ ์ •๋ณด ์—†์Œ")

                Button("์œ„์น˜ ๊ถŒํ•œ ์š”์ฒญ") {
                    locationManager.requestPermission()
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .padding()
    }
}

๐ŸŽฏ ์ง€์˜คํŽœ์‹ฑ (Geofencing)

Geofencing.swift
import CoreLocation

class GeofenceManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()

    override init() {
        super.init()
        manager.delegate = self
    }

    // ์ง€์˜คํŽœ์Šค ์ถ”๊ฐ€ (ํŠน์ • ์ง€์—ญ ๋ชจ๋‹ˆํ„ฐ๋ง)
    func startMonitoring(
        identifier: String,
        center: CLLocationCoordinate2D,
        radius: CLLocationDistance
    ) {
        // ๊ถŒํ•œ ์ฒดํฌ
        guard manager.authorizationStatus == .authorizedAlways else {
            manager.requestAlwaysAuthorization()
            return
        }

        // ์›ํ˜• ์ง€์—ญ ์ƒ์„ฑ
        let region = CLCircularRegion(
            center: center,
            radius: radius,  // ๋ฏธํ„ฐ ๋‹จ์œ„ (์ตœ๋Œ€ 400m)
            identifier: identifier
        )

        region.notifyOnEntry = true  // ์ง„์ž… ์‹œ ์•Œ๋ฆผ
        region.notifyOnExit = true  // ํ‡ด์ถœ ์‹œ ์•Œ๋ฆผ

        manager.startMonitoring(for: region)
    }

    // ์ง€์˜คํŽœ์Šค ์ œ๊ฑฐ
    func stopMonitoring(identifier: String) {
        for region in manager.monitoredRegions {
            if region.identifier == identifier {
                manager.stopMonitoring(for: region)
            }
        }
    }

    // Delegate - ์ง€์—ญ ์ง„์ž…
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("โœ… \(region.identifier) ์ง€์—ญ์— ์ง„์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค")
        // ๋กœ์ปฌ ์•Œ๋ฆผ ํ‘œ์‹œ
        sendNotification(title: "์ง€์—ญ ์ง„์ž…", body: "\(region.identifier)์— ๋„์ฐฉํ–ˆ์Šต๋‹ˆ๋‹ค")
    }

    // Delegate - ์ง€์—ญ ํ‡ด์ถœ
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        print("๐Ÿšช \(region.identifier) ์ง€์—ญ์„ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค")
        sendNotification(title: "์ง€์—ญ ํ‡ด์ถœ", body: "\(region.identifier)์„ ๋– ๋‚ฌ์Šต๋‹ˆ๋‹ค")
    }

    func sendNotification(title: String, body: String) {
        // UNUserNotificationCenter ์‚ฌ์šฉ
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let geofence = GeofenceManager()
geofence.startMonitoring(
    identifier: "์ง‘",
    center: CLLocationCoordinate2D(latitude: 37.5665, longitude: 126.9780),
    radius: 100  // 100๋ฏธํ„ฐ
)

๐Ÿงญ ๋‚˜์นจ๋ฐ˜ (Heading)

Compass.swift
import CoreLocation

class CompassManager: NSObject, CLLocationManagerDelegate, ObservableObject {
    private let manager = CLLocationManager()

    @Published var heading: Double = 0  // 0-360๋„

    override init() {
        super.init()
        manager.delegate = self
        manager.headingFilter = 5  // 5๋„ ์ด์ƒ ๋ณ€๊ฒฝ ์‹œ๋งŒ ์—…๋ฐ์ดํŠธ
    }

    func startUpdatingHeading() {
        if CLLocationManager.headingAvailable() {
            manager.startUpdatingHeading()
        }
    }

    func stopUpdatingHeading() {
        manager.stopUpdatingHeading()
    }

    // Delegate - ๋ฐฉํ–ฅ ์—…๋ฐ์ดํŠธ
    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        heading = newHeading.trueHeading  // ์ง„๋ถ ๊ธฐ์ค€
        // newHeading.magneticHeading - ์ž๋ถ ๊ธฐ์ค€

        print("๋ฐฉํ–ฅ: \(heading)ยฐ \(getDirection(heading))")
    }

    func getDirection(_ heading: Double) -> String {
        switch heading {
        case 0..<22.5: return "๋ถ"
        case 22.5..<67.5: return "๋ถ๋™"
        case 67.5..<112.5: return "๋™"
        case 112.5..<157.5: return "๋‚จ๋™"
        case 157.5..<202.5: return "๋‚จ"
        case 202.5..<247.5: return "๋‚จ์„œ"
        case 247.5..<292.5: return "์„œ"
        case 292.5..<337.5: return "๋ถ์„œ"
        default: return "๋ถ"
        }
    }
}

๐Ÿ“ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ

Distance.swift
import CoreLocation

// ๋‘ ์ขŒํ‘œ ์‚ฌ์ด ๊ฑฐ๋ฆฌ (๋ฏธํ„ฐ)
func calculateDistance(from: CLLocation, to: CLLocation) -> CLLocationDistance {
    return from.distance(from: to)
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
let seoul = CLLocation(latitude: 37.5665, longitude: 126.9780)
let busan = CLLocation(latitude: 35.1796, longitude: 129.0756)

let distance = calculateDistance(from: seoul, to: busan)
print("๊ฑฐ๋ฆฌ: \(distance / 1000)km")  // ์•ฝ 325km

โš™๏ธ ์ •ํ™•๋„ ์„ค์ •

Accuracy.swift
let manager = CLLocationManager()

// ์ •ํ™•๋„ ์„ค์ • (๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ โ†”๏ธ ์ •ํ™•๋„ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„)
manager.desiredAccuracy = kCLLocationAccuracyBest  // ์ตœ๊ณ  ์ •ํ™•๋„
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters  // 10m
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters  // 100m
manager.desiredAccuracy = kCLLocationAccuracyKilometer  // 1km
manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers  // 3km

// ์ตœ์†Œ ์ด๋™ ๊ฑฐ๋ฆฌ ์„ค์ • (์ด๋ณด๋‹ค ์ ๊ฒŒ ์ด๋™ํ•˜๋ฉด ์—…๋ฐ์ดํŠธ ์•ˆ ํ•จ)
manager.distanceFilter = 10  // 10๋ฏธํ„ฐ

// ๋ฐฐํ„ฐ๋ฆฌ ์ ˆ์•ฝ ๋ชจ๋“œ (iOS 9+)
manager.allowsBackgroundLocationUpdates = false  // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์—…๋ฐ์ดํŠธ ๋„๊ธฐ

๐Ÿ”‹ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์œ„์น˜ ์ถ”์ 

BackgroundLocation.swift
// 1. Info.plist์— ์ถ”๊ฐ€
// NSLocationAlwaysAndWhenInUseUsageDescription

// 2. Capabilities ์„ค์ •
// Xcode > Signing & Capabilities > Background Modes
// โœ… Location updates ์ฒดํฌ

// 3. ์ฝ”๋“œ ์„ค์ •
let manager = CLLocationManager()
manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false

// Always ๊ถŒํ•œ ์š”์ฒญ
manager.requestAlwaysAuthorization()

// โš ๏ธ ์ฃผ์˜: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์œ„์น˜๋Š” ๋ฐฐํ„ฐ๋ฆฌ๋ฅผ ๋งŽ์ด ์†Œ๋ชจํ•ฉ๋‹ˆ๋‹ค!
// ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•˜์„ธ์š”.

๐Ÿ“Š ์œ„์น˜ ์ •ํ™•๋„ ํ™•์ธ

AccuracyCheck.swift
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last else { return }

    // ์ˆ˜ํ‰ ์ •ํ™•๋„ (์ž‘์„์ˆ˜๋ก ์ •ํ™•)
    print("์ˆ˜ํ‰ ์ •ํ™•๋„: \(location.horizontalAccuracy)m")

    // ์ˆ˜์ง ์ •ํ™•๋„ (๊ณ ๋„)
    print("์ˆ˜์ง ์ •ํ™•๋„: \(location.verticalAccuracy)m")

    // ์ •ํ™•๋„๊ฐ€ ๋‚ฎ์œผ๋ฉด ๋ฌด์‹œ
    if location.horizontalAccuracy > 100 {
        print("์ •ํ™•๋„ ๋„ˆ๋ฌด ๋‚ฎ์Œ, ๋ฌด์‹œ")
        return
    }

    // ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ ๋ฌด์‹œ (5์ดˆ ์ด์ƒ)
    if Date().timeIntervalSince(location.timestamp) > 5 {
        print("์˜ค๋ž˜๋œ ์œ„์น˜ ๋ฐ์ดํ„ฐ, ๋ฌด์‹œ")
        return
    }

    // ์œ ํšจํ•œ ์œ„์น˜ ๋ฐ์ดํ„ฐ ์‚ฌ์šฉ
    print("โœ… ์ •ํ™•ํ•œ ์œ„์น˜: \(location.coordinate)")
}

โš ๏ธ ๋ฐฐํ„ฐ๋ฆฌ ์ฃผ์˜!
- ์œ„์น˜ ์ถ”์ ์€ ๋ฐฐํ„ฐ๋ฆฌ๋ฅผ ๋งŽ์ด ์†Œ๋ชจํ•ฉ๋‹ˆ๋‹ค
- ์ ์ ˆํ•œ ์ •ํ™•๋„์™€ distanceFilter ์„ค์ •
- ๋ถˆํ•„์š”ํ•  ๋•Œ๋Š” stopUpdatingLocation() ํ˜ธ์ถœ
- ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์œ„์น˜๋Š” ๊ผญ ํ•„์š”ํ•  ๋•Œ๋งŒ

๐Ÿ’ก HIG ์ฒดํฌ๋ฆฌ์ŠคํŠธ
โœ… ์œ„์น˜ ์‚ฌ์šฉ ๋ชฉ์  ๋ช…ํ™•ํžˆ ์„ค๋ช…
โœ… ์ ์ ˆํ•œ ๊ถŒํ•œ๋งŒ ์š”์ฒญ (WhenInUse vs Always)
โœ… ๊ถŒํ•œ ๊ฑฐ๋ถ€ ์‹œ ์•ฑ ์ •์ƒ ์ž‘๋™
โœ… ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ ์ตœ์†Œํ™”
โœ… ์ •ํ™•๋„ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„ ๊ณ ๋ ค

๐Ÿ“ฆ ํ•™์Šต ์ž๋ฃŒ

๐Ÿ’ป
GitHub ํ”„๋กœ์ ํŠธ
๐ŸŽ
Apple HIG ์›๋ฌธ
๐Ÿ“–
Apple ๊ณต์‹ ๋ฌธ์„œ