This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Force unwrapping with ! is prohibited in this codebase. ALWAYS use safe optional handling:
let location = locationManager.location! // Crash if nil
let coordinate = location.coordinate! // Crash if nilUse guard let for early returns:
guard let location = locationManager.location else {
print("Location unavailable")
return
}
// Safe to use location hereUse optional chaining:
if let coordinate = location?.coordinate {
// Use coordinate safely
}Use nil coalescing for defaults:
let accuracy = location?.horizontalAccuracy ?? 0.0Why this matters: Force unwraps cause immediate crashes when encountering nil values. Safe optional handling ensures the library remains stable and provides better error handling for consumers.
Position is a Swift 6-ready, actor-based location positioning library for iOS and macOS with modern async/await APIs and AsyncSequence support. The library provides thread-safe location services with Swift concurrency.
- iOS 16.0+ / macOS 13.0+
- Swift 6.0+ (also supports Swift 5 mode)
- Xcode 16.0+
# Build for debug
swift build
# Build for release
swift build -c release
# Run tests
swift test
# Clean build
swift package clean# Lint sources
swiftlint lint Sources/
# Auto-fix linting issues
swiftlint --fix Sources/# iOS Simulator
xcodebuild -scheme Position -destination 'platform=iOS Simulator,name=iPhone 15'
# macOS
xcodebuild -scheme Position -destination 'platform=macOS'-
Position.swift (Sources/Position.swift:77-900+)
- Main actor-based class that manages all location services
- Provides async/await APIs for one-shot location requests
- Implements AsyncSequence streams for continuous updates (location, heading, authorization, floor, visit, errors)
- Maintains backwards compatibility with deprecated observer pattern
-
DeviceLocationManager.swift (Sources/DeviceLocationManager.swift)
- Internal actor that wraps CLLocationManager
- Handles platform-specific location services
- Manages delegation from CoreLocation to Position actor
- Implements battery and background state monitoring
-
CoreLocation+Additions.swift (Sources/CoreLocation+Additions.swift)
- Extensions for CLLocation, CLPlacemark, CLLocationCoordinate2D
- Geospatial calculation utilities (distance, bearing, coordinate at bearing)
- vCard generation for location sharing
- Pretty formatting methods for locations and addresses
Position follows strict Swift 6 concurrency patterns:
- Actor Isolation: Position is an actor, ensuring thread-safe access to all location services
- Async/Await: All public methods are async and must be called with await
- AsyncSequence: Reactive streams for continuous updates without callbacks
- @preconcurrency: Used for CoreLocation imports to handle API evolution
- Sendable: All data types passed across actors conform to Sendable
AsyncSequence Streams:
locationUpdates: Location changesheadingUpdates: Compass heading (iOS only)authorizationUpdates: Permission status changesfloorUpdates: Indoor floor level changesvisitUpdates: Significant location visitserrorUpdates: Location errors
- iOS 16.0+ / macOS 13.0+
- Swift 6.0+ (also supports Swift 5 mode)
- Xcode 16.0+
- Platform-specific features:
- Heading updates: iOS only
- Battery monitoring: iOS only
- Background updates: Full on iOS, limited on macOS
Position as an actor:
public actor Position {
// All methods are actor-isolated
public func currentLocation() async throws -> CLLocation {
// Thread-safe by default
}
}Calling actor methods:
let position = Position()
let location = try await position.currentLocation() // Must use awaitOne-shot location request:
do {
let location = try await position.currentLocation()
print("Location: \(location.coordinate)")
} catch {
print("Error: \(error)")
}AsyncSequence for continuous updates:
Task {
for await location in position.locationUpdates {
print("New location: \(location.coordinate)")
}
}Authorization handling:
let status = try await position.requestWhenInUseLocationAuthorization()
switch status {
case .allowedWhenInUse:
print("Authorized")
case .denied:
print("Access denied")
default:
print("Other status: \(status)")
}CRITICAL: All long-running tasks MUST be cancellable to prevent memory leaks.
class LocationViewModel {
private var locationTask: Task<Void, Never>?
deinit {
locationTask?.cancel() // Critical: Cancel on deinit
}
func startMonitoring() {
locationTask?.cancel() // Cancel previous task
locationTask = Task {
for await location in position.locationUpdates {
guard !Task.isCancelled else { return }
// Handle location update
}
}
}
}Position uses domain-specific errors:
public enum Error: Swift.Error, Sendable {
case restricted
case notDetermined
case denied
case authorizationFailure
case locationFailure
}Error handling pattern:
do {
let location = try await position.currentLocation()
} catch Position.Error.denied {
print("Location access denied")
} catch Position.Error.restricted {
print("Location access restricted")
} catch {
print("Other error: \(error)")
}let location = try await position.currentLocation()for await location in position.locationUpdates {
// Handle location
}await position.startUpdating()
let status = await position.locationServicesStatus// Request authorization and wait for result
let status = try await position.requestWhenInUseLocationAuthorization()
// Or request without waiting
await position.requestWhenInUseLocationAuthorization()Task {
async let location = position.currentLocation()
async let heading = position.currentHeading()
let (loc, head) = try await (location, heading)
print("Location: \(loc), Heading: \(head)")
}let location = locationManager.location! // Will crash if nilguard let location = locationManager.location else { return }position.startUpdating() // Compiler errorawait position.startUpdating() // Correctposition.getCurrentLocation { location in
// Old callback pattern
}let location = try await position.currentLocation()func startMonitoring() {
Task {
for await location in position.locationUpdates {
// Memory leak if task never cancelled
}
}
}private var task: Task<Void, Never>?
func startMonitoring() {
task = Task { ... }
}
deinit {
task?.cancel()
}- Location services require proper simulator/device setup
- Use iOS Simulator's Debug > Location menu for testing
- Available test locations: Apple, City Run, Freeway Drive, etc.
Required usage descriptions:
NSLocationWhenInUseUsageDescription: "We need your location to..."NSLocationAlwaysAndWhenInUseUsageDescription: "We need your location always to..."UIBackgroundModes: Includelocationfor background updates
- Consider mocking CLLocationManager for unit tests
- Test authorization state transitions
- Verify AsyncSequence behavior
- Test error handling paths
The project uses SwiftLint with custom rules (.swiftlint.yml):
Disabled rules:
identifier_name- Allows short variable names likeidline_length- No line length restrictionsfunction_body_length- Large functions allowed for actor implementationsfile_length- Large files allowed
Enabled opt-in rules:
empty_count- Use.isEmptyinstead ofcount == 0empty_string- Use.isEmptyinstead of== ""modifier_order- Consistent modifier orderingconvenience_type- Detect types that should be enums
- Maintain actor isolation - All public API must be actor-safe
- Use async/await - No completion handlers
- Provide AsyncSequence - For continuous data streams
- Add error cases - To Position.Error enum if needed
- Update deprecations - Mark old observer pattern methods as deprecated
- Test on both platforms - iOS and macOS if applicable
- Never break actor isolation - Don't add non-isolated methods
- Preserve async/await APIs - Don't revert to callbacks
- Maintain Sendable conformance - All types crossing actors must be Sendable
- Keep @preconcurrency imports - For CoreLocation compatibility
- Test authorization flows - Especially edge cases like .allowedWhenInUse
- No force unwrapping (!)
- All actor methods use async/await
- Long-running tasks are cancellable
- Error handling uses Position.Error enum
- Optional handling is safe (guard let, if let, ??)
- SwiftLint passes with no warnings
- Both iOS and macOS builds succeed
- Authorization edge cases handled (.allowedWhenInUse vs .allowedAlways)
- Request minimum necessary authorization (WhenInUse vs Always)
- Provide clear usage descriptions in Info.plist
- Stop location updates when not needed to save battery
- Handle authorization changes gracefully
- Only request if absolutely necessary
- Explain clearly to users why always-on location is needed
- Implement battery-aware accuracy adjustments
- Respect user's authorization downgrades (Always β WhenInUse)
- Use appropriate accuracy for use case
- Stop updates when not needed
- Use significant location changes for background
- Implement battery-aware accuracy reduction
- Cancel AsyncSequence iterations when done
- Store Task references and cancel in deinit
- Use weak self in long-running closures
- Clean up continuations properly
- Prefer
distanceFilterover processing every update - Use
pausesLocationUpdatesAutomaticallywhere appropriate - Consider
activityTypefor better system optimization - Batch location updates when possible