๐ 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)
โ
๊ถํ ๊ฑฐ๋ถ ์ ์ฑ ์ ์ ์๋
โ
๋ฐฐํฐ๋ฆฌ ์๋ชจ ์ต์ํ
โ
์ ํ๋ ํธ๋ ์ด๋์คํ ๊ณ ๋ ค