์์น ์๋น์ค ๋ฐ ์ง์คํ์ฑ ๊ฐ์ด๋. ์ด ๋ฌธ์๋ฅผ ์ฝ๊ณ Core Location ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
Core Location์ ๊ธฐ๊ธฐ์ ์์น, ๊ณ ๋, ๋ฐฉํฅ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ํ๋ ์์ํฌ์ ๋๋ค. GPS, Wi-Fi, ์ ๋ฃฐ๋ฌ, ๋น์ฝ์ ํ์ฉํด ์์น๋ฅผ ํ์ ํฉ๋๋ค.
import CoreLocation<!-- ์ฑ ์ฌ์ฉ ์ค ์์น -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>ํ์ฌ ์์น๋ฅผ ์ง๋์ ํ์ํ๊ธฐ ์ํด ํ์ํฉ๋๋ค.</string>
<!-- ํญ์ ์์น (๋ฐฑ๊ทธ๋ผ์ด๋) -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์น ๊ธฐ๋ฐ ์๋ฆผ์ ๋ณด๋ด๊ธฐ ์ํด ํ์ํฉ๋๋ค.</string>@Observable
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
var location: CLLocation?
var authorizationStatus: CLAuthorizationStatus = .notDetermined
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
}
func requestPermission() {
manager.requestWhenInUseAuthorization()
}
func startUpdating() {
manager.startUpdatingLocation()
}
func stopUpdating() {
manager.stopUpdatingLocation()
}
// MARK: - Delegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location = locations.last
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
authorizationStatus = manager.authorizationStatus
}
}switch manager.authorizationStatus {
case .notDetermined:
// ์์ง ์์ฒญ ์ ํจ
manager.requestWhenInUseAuthorization()
case .restricted, .denied:
// ์ค์ ์ผ๋ก ์ ๋
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
case .authorizedWhenInUse:
// ์ฑ ์ฌ์ฉ ์ค๋ง ํ์ฉ
manager.startUpdatingLocation()
case .authorizedAlways:
// ํญ์ ํ์ฉ (๋ฐฑ๊ทธ๋ผ์ด๋ ๊ฐ๋ฅ)
manager.startUpdatingLocation()
@unknown default:
break
}// ์ต๊ณ ์ ํ๋ (๋ฐฐํฐ๋ฆฌ ์๋ชจ ๋์)
manager.desiredAccuracy = kCLLocationAccuracyBest
// ๋ค๋น๊ฒ์ด์
์ฉ
manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
// 10๋ฏธํฐ ์ ํ๋
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
// 100๋ฏธํฐ ์ ํ๋ (๋ฐฐํฐ๋ฆฌ ์ ์ฝ)
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
// ํฌ๋ก๋ฏธํฐ ์ ํ๋
manager.desiredAccuracy = kCLLocationAccuracyKilometer
// ์ต์ ์ด๋ ๊ฑฐ๋ฆฌ (๋ฏธํฐ)
manager.distanceFilter = 10 // 10m ์ด๋ ์๋ง๋ค ์
๋ฐ์ดํธimport SwiftUI
import CoreLocation
// MARK: - Location Manager
@Observable
class LocationManager: NSObject, CLLocationManagerDelegate {
private let manager = CLLocationManager()
var location: CLLocation?
var placemark: CLPlacemark?
var authorizationStatus: CLAuthorizationStatus = .notDetermined
var isLoading = false
var error: Error?
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = 10
authorizationStatus = manager.authorizationStatus
}
func requestPermission() {
manager.requestWhenInUseAuthorization()
}
func requestLocation() {
isLoading = true
manager.requestLocation() // ๋จ์ผ ์์น ์์ฒญ
}
func startContinuousUpdates() {
manager.startUpdatingLocation()
}
func stopUpdates() {
manager.stopUpdatingLocation()
}
private func reverseGeocode(_ location: CLLocation) {
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { [weak self] placemarks, error in
self?.placemark = placemarks?.first
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
isLoading = false
guard let newLocation = locations.last else { return }
// ์ ํ๋ ํํฐ๋ง
guard newLocation.horizontalAccuracy > 0 && newLocation.horizontalAccuracy < 100 else { return }
location = newLocation
reverseGeocode(newLocation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
isLoading = false
self.error = error
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
authorizationStatus = manager.authorizationStatus
if authorizationStatus == .authorizedWhenInUse || authorizationStatus == .authorizedAlways {
requestLocation()
}
}
}
// MARK: - View
struct LocationView: View {
@State private var locationManager = LocationManager()
var body: some View {
NavigationStack {
VStack(spacing: 24) {
// ๊ถํ ์ํ
StatusBadge(status: locationManager.authorizationStatus)
// ํ์ฌ ์์น
if let location = locationManager.location {
VStack(spacing: 8) {
Text("ํ์ฌ ์์น")
.font(.headline)
Text("\(location.coordinate.latitude, specifier: "%.4f"), \(location.coordinate.longitude, specifier: "%.4f")")
.font(.system(.body, design: .monospaced))
if let placemark = locationManager.placemark {
Text(formatAddress(placemark))
.foregroundStyle(.secondary)
}
Text("์ ํ๋: \(Int(location.horizontalAccuracy))m")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
} else if locationManager.isLoading {
ProgressView("์์น ํ์ธ ์ค...")
}
// ๋ฒํผ
VStack(spacing: 12) {
if locationManager.authorizationStatus == .notDetermined {
Button("์์น ๊ถํ ์์ฒญ") {
locationManager.requestPermission()
}
.buttonStyle(.borderedProminent)
} else if locationManager.authorizationStatus == .authorizedWhenInUse ||
locationManager.authorizationStatus == .authorizedAlways {
Button("ํ์ฌ ์์น ์๋ก๊ณ ์นจ") {
locationManager.requestLocation()
}
.buttonStyle(.bordered)
} else {
Button("์ค์ ์์ ๊ถํ ํ์ฉ") {
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
}
.buttonStyle(.bordered)
}
}
Spacer()
}
.padding()
.navigationTitle("์์น")
}
}
func formatAddress(_ placemark: CLPlacemark) -> String {
[placemark.locality, placemark.thoroughfare, placemark.subThoroughfare]
.compactMap { $0 }
.joined(separator: " ")
}
}
struct StatusBadge: View {
let status: CLAuthorizationStatus
var body: some View {
HStack {
Image(systemName: icon)
Text(text)
}
.font(.caption)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(color.opacity(0.2))
.foregroundStyle(color)
.clipShape(Capsule())
}
var icon: String {
switch status {
case .authorizedAlways, .authorizedWhenInUse: return "checkmark.circle.fill"
case .denied, .restricted: return "xmark.circle.fill"
default: return "questionmark.circle.fill"
}
}
var text: String {
switch status {
case .authorizedAlways: return "ํญ์ ํ์ฉ"
case .authorizedWhenInUse: return "์ฑ ์ฌ์ฉ ์ค ํ์ฉ"
case .denied: return "๊ฑฐ๋ถ๋จ"
case .restricted: return "์ ํ๋จ"
case .notDetermined: return "๊ถํ ํ์"
@unknown default: return "์ ์ ์์"
}
}
var color: Color {
switch status {
case .authorizedAlways, .authorizedWhenInUse: return .green
case .denied, .restricted: return .red
default: return .orange
}
}
}func setupGeofence(center: CLLocationCoordinate2D, radius: Double, identifier: String) {
let region = CLCircularRegion(
center: center,
radius: radius,
identifier: identifier
)
region.notifyOnEntry = true
region.notifyOnExit = true
manager.startMonitoring(for: region)
}
// Delegate
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("์ง์
: \(region.identifier)")
// ๋ก์ปฌ ์๋ฆผ ๋ฑ
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("์ดํ: \(region.identifier)")
}// 1. Capabilities: Background Modes โ Location updates ์ฒดํฌ
// 2. Info.plist: NSLocationAlwaysAndWhenInUseUsageDescription
func enableBackgroundLocation() {
manager.allowsBackgroundLocationUpdates = true
manager.pausesLocationUpdatesAutomatically = false
manager.showsBackgroundLocationIndicator = true // ํ๋ ๋ฐ ํ์
}func startHeadingUpdates() {
if CLLocationManager.headingAvailable() {
manager.startUpdatingHeading()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let trueHeading = newHeading.trueHeading // ์ง๋ถ ๊ธฐ์ค (0-360)
let magneticHeading = newHeading.magneticHeading // ์๋ถ ๊ธฐ์ค
print("๋ฐฉํฅ: \(trueHeading)ยฐ")
}let seoul = CLLocation(latitude: 37.5665, longitude: 126.9780)
let busan = CLLocation(latitude: 35.1796, longitude: 129.0756)
let distance = seoul.distance(from: busan) // ๋ฏธํฐ ๋จ์
print("์์ธ-๋ถ์ฐ: \(distance / 1000) km") // ~325 km-
๊ถํ ์์ฒญ ํ์ด๋ฐ
- ์ฑ ์์ ์ ๋ฐ๋ก ์์ฒญ โ
- ๊ธฐ๋ฅ ์ฌ์ฉ ์ง์ ์ ์์ฒญ โ
- ์ ํ์ํ์ง ์ค๋ช UI ์ถ๊ฐ
-
๋ฐฐํฐ๋ฆฌ ์ต์ ํ
- ํ์ํ ๋๋ง
startUpdatingLocation() - ๋จ์ผ ์์ฒญ์
requestLocation()์ฌ์ฉ distanceFilter์ ์ ํ ์ค์
- ํ์ํ ๋๋ง
-
์ ํ๋ vs ๋ฐฐํฐ๋ฆฌ
kCLLocationAccuracyBest: GPS ์ฌ์ฉ, ๋ฐฐํฐ๋ฆฌ ๋ง์ด ์๋ชจkCLLocationAccuracyHundredMeters: Wi-Fi/Cell, ์ ์ฝ
-
์๋ฎฌ๋ ์ดํฐ ํ ์คํธ
- Features โ Location โ Custom Location
- ๋๋ GPX ํ์ผ๋ก ๊ฒฝ๋ก ์๋ฎฌ๋ ์ด์