Skip to content

Commit e1f1f8a

Browse files
Merge pull request #21974 from unoplatform/copilot/add-geolocator-movement-threshold
[iOS] Add support for `Geolocator.MovementThreshold`
2 parents 731f057 + 53bf2c1 commit e1f1f8a

File tree

2 files changed

+175
-143
lines changed

2 files changed

+175
-143
lines changed

src/Uno.UWP/Devices/Geolocation/Geolocator.iOSmacOS.cs

Lines changed: 173 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -9,204 +9,236 @@
99
using Windows.UI.Core;
1010
using Uno.UI.Dispatching;
1111

12-
namespace Windows.Devices.Geolocation
12+
namespace Windows.Devices.Geolocation;
13+
14+
public sealed partial class Geolocator
1315
{
14-
public sealed partial class Geolocator
15-
{
16-
private CLLocationManager _locationManager;
16+
private CLLocationManager _locationManager;
17+
18+
private double _movementThreshold;
1719

18-
partial void PlatformInitialize()
20+
partial void PlatformInitialize()
21+
{
22+
if (NativeDispatcher.Main.HasThreadAccess)
1923
{
20-
if (NativeDispatcher.Main.HasThreadAccess)
24+
_locationManager = new CLLocationManager
2125
{
22-
_locationManager = new CLLocationManager
23-
{
24-
DesiredAccuracy = DesiredAccuracy == PositionAccuracy.Default ? 10 : 1,
25-
};
26+
DesiredAccuracy = DesiredAccuracy == PositionAccuracy.Default ? 10 : 1,
27+
DistanceFilter = _movementThreshold,
28+
};
2629

27-
_locationManager.LocationsUpdated += _locationManager_LocationsUpdated;
30+
_locationManager.LocationsUpdated += _locationManager_LocationsUpdated;
2831

29-
_locationManager.StartUpdatingLocation();
30-
}
31-
else
32-
{
33-
NativeDispatcher.Main.Enqueue(PlatformInitialize, NativeDispatcherPriority.Normal);
34-
}
32+
_locationManager.StartUpdatingLocation();
3533
}
36-
37-
private void _locationManager_LocationsUpdated(object sender, CLLocationsUpdatedEventArgs e)
34+
else
3835
{
39-
BroadcastStatusChanged(PositionStatus.Ready);
40-
_positionChangedWrapper.Event?.Invoke(this, new PositionChangedEventArgs(ToGeoposition(e.Locations.Last())));
36+
NativeDispatcher.Main.Enqueue(PlatformInitialize, NativeDispatcherPriority.Normal);
4137
}
38+
}
4239

43-
partial void StartPositionChanged()
40+
/// <summary>
41+
/// The distance of movement, in meters, relative to the coordinate from the last PositionChanged event,
42+
/// that is required for the Geolocator to raise a PositionChanged event. The default value is 0.
43+
/// </summary>
44+
public double MovementThreshold
45+
{
46+
get => _movementThreshold;
47+
set
4448
{
45-
BroadcastStatusChanged(PositionStatus.Initializing);
49+
_movementThreshold = value;
50+
if (_locationManager != null)
51+
{
52+
if (NativeDispatcher.Main.HasThreadAccess)
53+
{
54+
_locationManager.DistanceFilter = _movementThreshold;
55+
}
56+
else
57+
{
58+
NativeDispatcher.Main.Enqueue(() =>
59+
{
60+
if (_locationManager != null)
61+
{
62+
_locationManager.DistanceFilter = _movementThreshold;
63+
}
64+
}, NativeDispatcherPriority.Normal);
65+
}
66+
}
4667
}
68+
}
4769

48-
internal CLLocationManager LocationManager => _locationManager;
70+
private void _locationManager_LocationsUpdated(object sender, CLLocationsUpdatedEventArgs e)
71+
{
72+
BroadcastStatusChanged(PositionStatus.Ready);
73+
_positionChangedWrapper.Event?.Invoke(this, new PositionChangedEventArgs(ToGeoposition(e.Locations.Last())));
74+
}
4975

50-
public IAsyncOperation<Geoposition> GetGeopositionAsync() => GetGeopositionInternalAsync().AsAsyncOperation();
76+
partial void StartPositionChanged()
77+
{
78+
BroadcastStatusChanged(PositionStatus.Initializing);
79+
}
5180

52-
public Task<Geoposition> GetGeopositionInternalAsync()
81+
internal CLLocationManager LocationManager => _locationManager;
5382

54-
{
55-
if (CoreDispatcher.Main.HasThreadAccess)
56-
{
57-
BroadcastStatusChanged(PositionStatus.Initializing);
58-
var location = _locationManager.Location;
59-
if (location == null)
60-
{
61-
throw new InvalidOperationException("Could not obtain the location. Please make sure that NSLocationWhenInUseUsageDescription and NSLocationUsageDescription are set in info.plist.");
62-
}
83+
public IAsyncOperation<Geoposition> GetGeopositionAsync() => GetGeopositionInternalAsync().AsAsyncOperation();
6384

64-
BroadcastStatusChanged(PositionStatus.Ready);
85+
public Task<Geoposition> GetGeopositionInternalAsync()
6586

66-
return Task.FromResult(ToGeoposition(location));
67-
}
68-
else
87+
{
88+
if (CoreDispatcher.Main.HasThreadAccess)
89+
{
90+
BroadcastStatusChanged(PositionStatus.Initializing);
91+
var location = _locationManager.Location;
92+
if (location == null)
6993
{
70-
return CoreDispatcher.Main.RunWithResultAsync<Geoposition>(
71-
priority: CoreDispatcherPriority.Normal,
72-
task: () => GetGeopositionInternalAsync()
73-
);
94+
throw new InvalidOperationException("Could not obtain the location. Please make sure that NSLocationWhenInUseUsageDescription and NSLocationUsageDescription are set in info.plist.");
7495
}
75-
}
7696

77-
private static Geoposition ToGeoposition(CLLocation location)
78-
=> new Geoposition(
79-
new Geocoordinate(
80-
altitude: location.Altitude,
81-
longitude: location.Coordinate.Longitude,
82-
latitude: location.Coordinate.Latitude,
83-
accuracy: location.HorizontalAccuracy,
84-
altitudeAccuracy: location.VerticalAccuracy,
85-
speed: location.Speed,
86-
point: new Geopoint(
87-
new BasicGeoposition
88-
{
89-
Altitude = location.Altitude,
90-
Latitude = location.Coordinate.Latitude,
91-
Longitude = location.Coordinate.Longitude
92-
}
93-
),
94-
timestamp: (DateTime)location.Timestamp
95-
)
97+
BroadcastStatusChanged(PositionStatus.Ready);
98+
99+
return Task.FromResult(ToGeoposition(location));
100+
}
101+
else
102+
{
103+
return CoreDispatcher.Main.RunWithResultAsync<Geoposition>(
104+
priority: CoreDispatcherPriority.Normal,
105+
task: () => GetGeopositionInternalAsync()
96106
);
107+
}
108+
}
97109

98-
public IAsyncOperation<Geoposition> GetGeopositionAsync(TimeSpan maximumAge, TimeSpan timeout)
99-
=> GetGeopositionAsync();
110+
private static Geoposition ToGeoposition(CLLocation location)
111+
=> new Geoposition(
112+
new Geocoordinate(
113+
altitude: location.Altitude,
114+
longitude: location.Coordinate.Longitude,
115+
latitude: location.Coordinate.Latitude,
116+
accuracy: location.HorizontalAccuracy,
117+
altitudeAccuracy: location.VerticalAccuracy,
118+
speed: location.Speed,
119+
point: new Geopoint(
120+
new BasicGeoposition
121+
{
122+
Altitude = location.Altitude,
123+
Latitude = location.Coordinate.Latitude,
124+
Longitude = location.Coordinate.Longitude
125+
}
126+
),
127+
timestamp: (DateTime)location.Timestamp
128+
)
129+
);
130+
131+
public IAsyncOperation<Geoposition> GetGeopositionAsync(TimeSpan maximumAge, TimeSpan timeout)
132+
=> GetGeopositionAsync();
100133

101134

102-
private static List<CLLocationManager> _requestManagers = new List<CLLocationManager>();
135+
private static List<CLLocationManager> _requestManagers = new List<CLLocationManager>();
103136

104-
public static IAsyncOperation<GeolocationAccessStatus> RequestAccessAsync() => RequestAccessInternalAsync().AsAsyncOperation();
137+
public static IAsyncOperation<GeolocationAccessStatus> RequestAccessAsync() => RequestAccessInternalAsync().AsAsyncOperation();
105138

106-
private static async Task<GeolocationAccessStatus> RequestAccessInternalAsync()
139+
private static async Task<GeolocationAccessStatus> RequestAccessInternalAsync()
107140

141+
{
142+
if (CoreDispatcher.Main.HasThreadAccess)
108143
{
109-
if (CoreDispatcher.Main.HasThreadAccess)
110-
{
111-
var mgr = new CLLocationManager();
144+
var mgr = new CLLocationManager();
112145

113-
lock (_requestManagers)
114-
{
115-
_requestManagers.Add(mgr);
116-
}
146+
lock (_requestManagers)
147+
{
148+
_requestManagers.Add(mgr);
149+
}
117150

118-
try
119-
{
120-
var accessStatus = default(GeolocationAccessStatus);
121-
var tsc = new TaskCompletionSource<CLAuthorizationStatus>();
151+
try
152+
{
153+
var accessStatus = default(GeolocationAccessStatus);
154+
var tsc = new TaskCompletionSource<CLAuthorizationStatus>();
122155

123156
#if __IOS__ || __TVOS__
124-
// Workaround for a bug in Xamarin.iOS https://github.com/unoplatform/uno/issues/4853
125-
var @delegate = new CLLocationManagerDelegate();
157+
// Workaround for a bug in Xamarin.iOS https://github.com/unoplatform/uno/issues/4853
158+
var @delegate = new CLLocationManagerDelegate();
126159

127-
mgr.Delegate = @delegate;
160+
mgr.Delegate = @delegate;
128161

129-
@delegate.AuthorizationChanged += (s, e) =>
162+
@delegate.AuthorizationChanged += (s, e) =>
130163
#else
131-
mgr.AuthorizationChanged += (s, e) =>
164+
mgr.AuthorizationChanged += (s, e) =>
132165
#endif
166+
{
167+
if (e.Status != CLAuthorizationStatus.NotDetermined)
133168
{
134-
if (e.Status != CLAuthorizationStatus.NotDetermined)
135-
{
136-
tsc.TrySetResult(e.Status);
137-
}
138-
};
169+
tsc.TrySetResult(e.Status);
170+
}
171+
};
139172

140173
#if __IOS__ || __TVOS__ //required only for iOS
141-
mgr.RequestWhenInUseAuthorization();
174+
mgr.RequestWhenInUseAuthorization();
142175
#endif
143176

144-
if (CLLocationManager.Status != CLAuthorizationStatus.NotDetermined)
145-
{
146-
accessStatus = TranslateStatus(CLLocationManager.Status);
147-
}
148-
149-
var cLAuthorizationStatus = await tsc.Task;
177+
if (CLLocationManager.Status != CLAuthorizationStatus.NotDetermined)
178+
{
179+
accessStatus = TranslateStatus(CLLocationManager.Status);
180+
}
150181

151-
accessStatus = TranslateStatus(cLAuthorizationStatus);
182+
var cLAuthorizationStatus = await tsc.Task;
152183

153-
//if geolocation is not well accessible, default geoposition should be recommended
154-
if (accessStatus != GeolocationAccessStatus.Allowed)
155-
{
156-
IsDefaultGeopositionRecommended = true;
157-
}
184+
accessStatus = TranslateStatus(cLAuthorizationStatus);
158185

159-
return accessStatus;
160-
}
161-
finally
186+
//if geolocation is not well accessible, default geoposition should be recommended
187+
if (accessStatus != GeolocationAccessStatus.Allowed)
162188
{
163-
lock (_requestManagers)
164-
{
165-
_requestManagers.Remove(mgr);
166-
}
189+
IsDefaultGeopositionRecommended = true;
167190
}
191+
192+
return accessStatus;
168193
}
169-
else
194+
finally
170195
{
171-
return await CoreDispatcher.Main.RunWithResultAsync<GeolocationAccessStatus>(
172-
priority: CoreDispatcherPriority.Normal,
173-
task: () => RequestAccessInternalAsync()
174-
);
196+
lock (_requestManagers)
197+
{
198+
_requestManagers.Remove(mgr);
199+
}
175200
}
176201
}
202+
else
203+
{
204+
return await CoreDispatcher.Main.RunWithResultAsync<GeolocationAccessStatus>(
205+
priority: CoreDispatcherPriority.Normal,
206+
task: () => RequestAccessInternalAsync()
207+
);
208+
}
209+
}
177210

178-
private static GeolocationAccessStatus TranslateStatus(CLAuthorizationStatus status)
211+
private static GeolocationAccessStatus TranslateStatus(CLAuthorizationStatus status)
212+
{
213+
switch (status)
179214
{
180-
switch (status)
181-
{
182-
// These two constants are set by value based on https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/c/tdef/CLAuthorizationStatus
183-
// This is for the compatibility with iOS 8 and the introduction of AuthorizedWhenInUse.
184-
// This can be replaced with proper enum values when upgrading to iOS 8.0 SDK.
185-
case (CLAuthorizationStatus)4: // CLAuthorizationStatus.AuthorizedWhenInUse:
186-
case (CLAuthorizationStatus)3: // CLAuthorizationStatus.AuthorizedAlways:
187-
return GeolocationAccessStatus.Allowed;
188-
189-
case CLAuthorizationStatus.NotDetermined:
190-
return GeolocationAccessStatus.Unspecified;
191-
192-
default:
193-
case CLAuthorizationStatus.Restricted:
194-
case CLAuthorizationStatus.Denied:
195-
return GeolocationAccessStatus.Denied;
196-
}
215+
// These two constants are set by value based on https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/c/tdef/CLAuthorizationStatus
216+
// This is for the compatibility with iOS 8 and the introduction of AuthorizedWhenInUse.
217+
// This can be replaced with proper enum values when upgrading to iOS 8.0 SDK.
218+
case (CLAuthorizationStatus)4: // CLAuthorizationStatus.AuthorizedWhenInUse:
219+
case (CLAuthorizationStatus)3: // CLAuthorizationStatus.AuthorizedAlways:
220+
return GeolocationAccessStatus.Allowed;
221+
222+
case CLAuthorizationStatus.NotDetermined:
223+
return GeolocationAccessStatus.Unspecified;
224+
225+
default:
226+
case CLAuthorizationStatus.Restricted:
227+
case CLAuthorizationStatus.Denied:
228+
return GeolocationAccessStatus.Denied;
197229
}
230+
}
198231

199232
#if __IOS__ || __TVOS__
200-
private class CLLocationManagerDelegate : NSObject, ICLLocationManagerDelegate
201-
{
202-
public event EventHandler<CLAuthorizationChangedEventArgs> AuthorizationChanged;
233+
private class CLLocationManagerDelegate : NSObject, ICLLocationManagerDelegate
234+
{
235+
public event EventHandler<CLAuthorizationChangedEventArgs> AuthorizationChanged;
203236

204-
[Export("locationManager:didChangeAuthorizationStatus:")]
205-
public void DidChangeAuthorizationStatus(CLLocationManager manager, CLAuthorizationStatus status)
206-
{
207-
AuthorizationChanged?.Invoke(manager, new CLAuthorizationChangedEventArgs(status));
208-
}
237+
[Export("locationManager:didChangeAuthorizationStatus:")]
238+
public void DidChangeAuthorizationStatus(CLLocationManager manager, CLAuthorizationStatus status)
239+
{
240+
AuthorizationChanged?.Invoke(manager, new CLAuthorizationChangedEventArgs(status));
209241
}
210-
#endif
211242
}
243+
#endif
212244
}

src/Uno.UWP/Generated/3.0.0.0/Windows.Devices.Geolocation/Geolocator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public uint ReportInterval
2222
}
2323
}
2424
#endif
25-
#if false || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__
26-
[global::Uno.NotImplemented("__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")]
25+
#if false || false || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__
26+
[global::Uno.NotImplemented("__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")]
2727
public double MovementThreshold
2828
{
2929
get

0 commit comments

Comments
 (0)