spb/focale Public
Swift 100%
1//2// PlaceProvider.swift3// Focale4//5// Author: Simon-Pierre Boucher6// Contact: contact@spboucher.ai7//8// Turns the device location into a coarse PlaceRef (locality, never raw9// coordinates — CLAUDE.md §4). Everything stays on-device; CLGeocoder10// lookups are throttled and cached.11//1213import CoreLocation14import Foundation15import Observation1617@MainActor18@Observable19final class PlaceProvider: NSObject, CLLocationManagerDelegate {2021 private(set) var currentPlace: PlaceRef?2223 private let manager = CLLocationManager()24 private let geocoder = CLGeocoder()25 private var lastGeocodedLocation: CLLocation?2627 override init() {28 super.init()29 manager.delegate = self30 manager.desiredAccuracy = kCLLocationAccuracyHundredMeters31 }3233 func startIfAuthorized() {34 switch manager.authorizationStatus {35 case .notDetermined:36 manager.requestWhenInUseAuthorization()37 case .authorizedWhenInUse, .authorizedAlways:38 manager.startUpdatingLocation()39 default:40 break41 }42 }4344 func stop() {45 manager.stopUpdatingLocation()46 }4748 nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {49 Task { @MainActor in self.startIfAuthorized() }50 }5152 nonisolated func locationManager(53 _ manager: CLLocationManager,54 didUpdateLocations locations: [CLLocation]55 ) {56 guard let location = locations.last else { return }57 Task { @MainActor in self.geocodeIfNeeded(location) }58 }5960 private func geocodeIfNeeded(_ location: CLLocation) {61 // Re-geocode only after moving ~500 m; place names don't change faster.62 if let previous = lastGeocodedLocation,63 location.distance(from: previous) < 500 { return }64 lastGeocodedLocation = location6566 Task {67 guard let placemark = try? await geocoder.reverseGeocodeLocation(location).first68 else { return }69 currentPlace = PlaceRef(70 name: placemark.areasOfInterest?.first,71 locality: placemark.locality,72 countryCode: placemark.isoCountryCode73 )74 }75 }76}77