Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 33 additions & 28 deletions Examples/Packages/iOS/Sources/ExampleMap/Atoms.swift
Original file line number Diff line number Diff line change
@@ -1,42 +1,28 @@
import Atoms
import CoreLocation

struct LocationManagerAtom: ValueAtom, Hashable {
func value(context: Context) -> LocationManagerProtocol {
let manager = CLLocationManager()
let delegate = LocationManagerDelegate()

manager.delegate = delegate
manager.desiredAccuracy = kCLLocationAccuracyBest
context.addTermination(manager.stopUpdatingLocation)
context.keepUntilTermination(delegate)
delegate.onChange = {
context.reset(AuthorizationStatusAtom())
}
final class LocationObserver: NSObject, ObservableObject, CLLocationManagerDelegate {
let manager: LocationManagerProtocol

return manager
deinit {
manager.stopUpdatingLocation()
}
}

struct CoordinateAtom: ValueAtom, Hashable {
func value(context: Context) -> CLLocationCoordinate2D? {
let manager = context.watch(LocationManagerAtom())
return manager.location?.coordinate
init(manager: LocationManagerProtocol) {
self.manager = manager
super.init()
manager.delegate = self
}
}

struct AuthorizationStatusAtom: ValueAtom, Hashable {
func value(context: Context) -> CLAuthorizationStatus {
let manager = context.watch(LocationManagerAtom())
return manager.authorizationStatus
}
}
convenience override init() {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest

private final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate {
var onChange: (() -> Void)?
self.init(manager: manager)
}

func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
onChange?()
objectWillChange.send()

switch manager.authorizationStatus {
case .authorizedAlways, .authorizedWhenInUse:
Expand All @@ -57,3 +43,22 @@ private final class LocationManagerDelegate: NSObject, CLLocationManagerDelegate
print(error.localizedDescription)
}
}

struct LocationObserverAtom: ObservableObjectAtom, Hashable {
func object(context: Context) -> LocationObserver {
LocationObserver()
}
}

struct CoordinateAtom: ValueAtom, Hashable {
func value(context: Context) -> CLLocationCoordinate2D? {
let observer = context.watch(LocationObserverAtom())
return observer.manager.location?.coordinate
}
}

struct AuthorizationStatusAtom: ValueAtom, Hashable {
func value(context: Context) -> CLAuthorizationStatus {
context.watch(LocationObserverAtom().select(\.manager.authorizationStatus))
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import CoreLocation

protocol LocationManagerProtocol {
protocol LocationManagerProtocol: AnyObject {
var delegate: CLLocationManagerDelegate? { get set }
var desiredAccuracy: CLLocationAccuracy { get set }
var location: CLLocation? { get }
var authorizationStatus: CLAuthorizationStatus { get }

func stopUpdatingLocation()
}

extension CLLocationManager: LocationManagerProtocol {}
Expand All @@ -14,4 +16,9 @@ final class MockLocationManager: LocationManagerProtocol {
var desiredAccuracy = kCLLocationAccuracyKilometer
var location: CLLocation? = nil
var authorizationStatus = CLAuthorizationStatus.notDetermined
var isUpdatingLocation = true

func stopUpdatingLocation() {
isUpdatingLocation = false
}
}
54 changes: 47 additions & 7 deletions Examples/Packages/iOS/Tests/ExampleMapTests/ExampleMapTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,68 @@ import XCTest

@MainActor
final class ExampleMapTests: XCTestCase {
func testLocationObserverAtom() {
let atom = LocationObserverAtom()
let context = AtomTestContext()
let manager = MockLocationManager()

context.override(atom) { _ in
LocationObserver(manager: manager)
}

context.watch(atom)

XCTAssertNotNil(manager.delegate)
XCTAssertTrue(manager.isUpdatingLocation)

context.unwatch(atom)

XCTAssertFalse(manager.isUpdatingLocation)
}

func testCoordinateAtom() {
let atom = CoordinateAtom()
let context = AtomTestContext()
let locationManager = MockLocationManager()
let manager = MockLocationManager()

context.override(LocationManagerAtom()) { _ in locationManager }
context.override(LocationObserverAtom()) { _ in
LocationObserver(manager: manager)
}

locationManager.location = CLLocation(latitude: 1, longitude: 2)
manager.location = CLLocation(latitude: 1, longitude: 2)

XCTAssertEqual(context.watch(atom)?.latitude, 1)
XCTAssertEqual(context.watch(atom)?.longitude, 2)
}

func testAuthorizationStatusAtom() {
func testAuthorizationStatusAtom() async {
let atom = AuthorizationStatusAtom()
let locationManager = MockLocationManager()
let manager = MockLocationManager()
let context = AtomTestContext()
let observer = LocationObserver(manager: manager)

context.override(LocationManagerAtom()) { _ in locationManager }
context.override(LocationObserverAtom()) { _ in
observer
}

locationManager.authorizationStatus = .authorizedWhenInUse
manager.authorizationStatus = .authorizedWhenInUse

XCTAssertEqual(context.watch(atom), .authorizedWhenInUse)

manager.authorizationStatus = .authorizedAlways

Task {
observer.objectWillChange.send()
}

await context.waitUntilNextUpdate()

XCTAssertEqual(context.watch(atom), .authorizedAlways)

observer.objectWillChange.send()
let didUpdate = await context.waitUntilNextUpdate(timeout: 1)

// Should not update if authorizationStatus is not changed.
XCTAssertFalse(didUpdate)
}
}