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
314 changes: 173 additions & 141 deletions src/Uno.UWP/Devices/Geolocation/Geolocator.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,204 +9,236 @@
using Windows.UI.Core;
using Uno.UI.Dispatching;

namespace Windows.Devices.Geolocation
namespace Windows.Devices.Geolocation;

public sealed partial class Geolocator
{
public sealed partial class Geolocator
{
private CLLocationManager _locationManager;
private CLLocationManager _locationManager;

private double _movementThreshold;

partial void PlatformInitialize()
partial void PlatformInitialize()
{
if (NativeDispatcher.Main.HasThreadAccess)
{
if (NativeDispatcher.Main.HasThreadAccess)
_locationManager = new CLLocationManager
{
_locationManager = new CLLocationManager
{
DesiredAccuracy = DesiredAccuracy == PositionAccuracy.Default ? 10 : 1,
};
DesiredAccuracy = DesiredAccuracy == PositionAccuracy.Default ? 10 : 1,
DistanceFilter = _movementThreshold,
};

_locationManager.LocationsUpdated += _locationManager_LocationsUpdated;
_locationManager.LocationsUpdated += _locationManager_LocationsUpdated;

_locationManager.StartUpdatingLocation();
}
else
{
NativeDispatcher.Main.Enqueue(PlatformInitialize, NativeDispatcherPriority.Normal);
}
_locationManager.StartUpdatingLocation();
}

private void _locationManager_LocationsUpdated(object sender, CLLocationsUpdatedEventArgs e)
else
{
BroadcastStatusChanged(PositionStatus.Ready);
_positionChangedWrapper.Event?.Invoke(this, new PositionChangedEventArgs(ToGeoposition(e.Locations.Last())));
NativeDispatcher.Main.Enqueue(PlatformInitialize, NativeDispatcherPriority.Normal);
}
}

partial void StartPositionChanged()
/// <summary>
/// The distance of movement, in meters, relative to the coordinate from the last PositionChanged event,
/// that is required for the Geolocator to raise a PositionChanged event. The default value is 0.
/// </summary>
public double MovementThreshold
{
get => _movementThreshold;
set
{
BroadcastStatusChanged(PositionStatus.Initializing);
_movementThreshold = value;
if (_locationManager != null)
{
if (NativeDispatcher.Main.HasThreadAccess)
{
_locationManager.DistanceFilter = _movementThreshold;
}
else
{
NativeDispatcher.Main.Enqueue(() =>
{
if (_locationManager != null)
{
_locationManager.DistanceFilter = _movementThreshold;
}
}, NativeDispatcherPriority.Normal);
}
}
}
}

internal CLLocationManager LocationManager => _locationManager;
private void _locationManager_LocationsUpdated(object sender, CLLocationsUpdatedEventArgs e)
{
BroadcastStatusChanged(PositionStatus.Ready);
_positionChangedWrapper.Event?.Invoke(this, new PositionChangedEventArgs(ToGeoposition(e.Locations.Last())));
}

public IAsyncOperation<Geoposition> GetGeopositionAsync() => GetGeopositionInternalAsync().AsAsyncOperation();
partial void StartPositionChanged()
{
BroadcastStatusChanged(PositionStatus.Initializing);
}

public Task<Geoposition> GetGeopositionInternalAsync()
internal CLLocationManager LocationManager => _locationManager;

{
if (CoreDispatcher.Main.HasThreadAccess)
{
BroadcastStatusChanged(PositionStatus.Initializing);
var location = _locationManager.Location;
if (location == null)
{
throw new InvalidOperationException("Could not obtain the location. Please make sure that NSLocationWhenInUseUsageDescription and NSLocationUsageDescription are set in info.plist.");
}
public IAsyncOperation<Geoposition> GetGeopositionAsync() => GetGeopositionInternalAsync().AsAsyncOperation();

BroadcastStatusChanged(PositionStatus.Ready);
public Task<Geoposition> GetGeopositionInternalAsync()

return Task.FromResult(ToGeoposition(location));
}
else
{
if (CoreDispatcher.Main.HasThreadAccess)
{
BroadcastStatusChanged(PositionStatus.Initializing);
var location = _locationManager.Location;
if (location == null)
{
return CoreDispatcher.Main.RunWithResultAsync<Geoposition>(
priority: CoreDispatcherPriority.Normal,
task: () => GetGeopositionInternalAsync()
);
throw new InvalidOperationException("Could not obtain the location. Please make sure that NSLocationWhenInUseUsageDescription and NSLocationUsageDescription are set in info.plist.");
}
}

private static Geoposition ToGeoposition(CLLocation location)
=> new Geoposition(
new Geocoordinate(
altitude: location.Altitude,
longitude: location.Coordinate.Longitude,
latitude: location.Coordinate.Latitude,
accuracy: location.HorizontalAccuracy,
altitudeAccuracy: location.VerticalAccuracy,
speed: location.Speed,
point: new Geopoint(
new BasicGeoposition
{
Altitude = location.Altitude,
Latitude = location.Coordinate.Latitude,
Longitude = location.Coordinate.Longitude
}
),
timestamp: (DateTime)location.Timestamp
)
BroadcastStatusChanged(PositionStatus.Ready);

return Task.FromResult(ToGeoposition(location));
}
else
{
return CoreDispatcher.Main.RunWithResultAsync<Geoposition>(
priority: CoreDispatcherPriority.Normal,
task: () => GetGeopositionInternalAsync()
);
}
}

public IAsyncOperation<Geoposition> GetGeopositionAsync(TimeSpan maximumAge, TimeSpan timeout)
=> GetGeopositionAsync();
private static Geoposition ToGeoposition(CLLocation location)
=> new Geoposition(
new Geocoordinate(
altitude: location.Altitude,
longitude: location.Coordinate.Longitude,
latitude: location.Coordinate.Latitude,
accuracy: location.HorizontalAccuracy,
altitudeAccuracy: location.VerticalAccuracy,
speed: location.Speed,
point: new Geopoint(
new BasicGeoposition
{
Altitude = location.Altitude,
Latitude = location.Coordinate.Latitude,
Longitude = location.Coordinate.Longitude
}
),
timestamp: (DateTime)location.Timestamp
)
);

public IAsyncOperation<Geoposition> GetGeopositionAsync(TimeSpan maximumAge, TimeSpan timeout)
=> GetGeopositionAsync();


private static List<CLLocationManager> _requestManagers = new List<CLLocationManager>();
private static List<CLLocationManager> _requestManagers = new List<CLLocationManager>();

public static IAsyncOperation<GeolocationAccessStatus> RequestAccessAsync() => RequestAccessInternalAsync().AsAsyncOperation();
public static IAsyncOperation<GeolocationAccessStatus> RequestAccessAsync() => RequestAccessInternalAsync().AsAsyncOperation();

private static async Task<GeolocationAccessStatus> RequestAccessInternalAsync()
private static async Task<GeolocationAccessStatus> RequestAccessInternalAsync()

{
if (CoreDispatcher.Main.HasThreadAccess)
{
if (CoreDispatcher.Main.HasThreadAccess)
{
var mgr = new CLLocationManager();
var mgr = new CLLocationManager();

lock (_requestManagers)
{
_requestManagers.Add(mgr);
}
lock (_requestManagers)
{
_requestManagers.Add(mgr);
}

try
{
var accessStatus = default(GeolocationAccessStatus);
var tsc = new TaskCompletionSource<CLAuthorizationStatus>();
try
{
var accessStatus = default(GeolocationAccessStatus);
var tsc = new TaskCompletionSource<CLAuthorizationStatus>();

#if __IOS__ || __TVOS__
// Workaround for a bug in Xamarin.iOS https://github.com/unoplatform/uno/issues/4853
var @delegate = new CLLocationManagerDelegate();
// Workaround for a bug in Xamarin.iOS https://github.com/unoplatform/uno/issues/4853
var @delegate = new CLLocationManagerDelegate();

mgr.Delegate = @delegate;
mgr.Delegate = @delegate;

@delegate.AuthorizationChanged += (s, e) =>
@delegate.AuthorizationChanged += (s, e) =>
#else
mgr.AuthorizationChanged += (s, e) =>
mgr.AuthorizationChanged += (s, e) =>
#endif
{
if (e.Status != CLAuthorizationStatus.NotDetermined)
{
if (e.Status != CLAuthorizationStatus.NotDetermined)
{
tsc.TrySetResult(e.Status);
}
};
tsc.TrySetResult(e.Status);
}
};

#if __IOS__ || __TVOS__ //required only for iOS
mgr.RequestWhenInUseAuthorization();
mgr.RequestWhenInUseAuthorization();
#endif

if (CLLocationManager.Status != CLAuthorizationStatus.NotDetermined)
{
accessStatus = TranslateStatus(CLLocationManager.Status);
}

var cLAuthorizationStatus = await tsc.Task;
if (CLLocationManager.Status != CLAuthorizationStatus.NotDetermined)
{
accessStatus = TranslateStatus(CLLocationManager.Status);
}

accessStatus = TranslateStatus(cLAuthorizationStatus);
var cLAuthorizationStatus = await tsc.Task;

//if geolocation is not well accessible, default geoposition should be recommended
if (accessStatus != GeolocationAccessStatus.Allowed)
{
IsDefaultGeopositionRecommended = true;
}
accessStatus = TranslateStatus(cLAuthorizationStatus);

return accessStatus;
}
finally
//if geolocation is not well accessible, default geoposition should be recommended
if (accessStatus != GeolocationAccessStatus.Allowed)
{
lock (_requestManagers)
{
_requestManagers.Remove(mgr);
}
IsDefaultGeopositionRecommended = true;
}

return accessStatus;
}
else
finally
{
return await CoreDispatcher.Main.RunWithResultAsync<GeolocationAccessStatus>(
priority: CoreDispatcherPriority.Normal,
task: () => RequestAccessInternalAsync()
);
lock (_requestManagers)
{
_requestManagers.Remove(mgr);
}
}
}
else
{
return await CoreDispatcher.Main.RunWithResultAsync<GeolocationAccessStatus>(
priority: CoreDispatcherPriority.Normal,
task: () => RequestAccessInternalAsync()
);
}
}

private static GeolocationAccessStatus TranslateStatus(CLAuthorizationStatus status)
private static GeolocationAccessStatus TranslateStatus(CLAuthorizationStatus status)
{
switch (status)
{
switch (status)
{
// 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
// This is for the compatibility with iOS 8 and the introduction of AuthorizedWhenInUse.
// This can be replaced with proper enum values when upgrading to iOS 8.0 SDK.
case (CLAuthorizationStatus)4: // CLAuthorizationStatus.AuthorizedWhenInUse:
case (CLAuthorizationStatus)3: // CLAuthorizationStatus.AuthorizedAlways:
return GeolocationAccessStatus.Allowed;

case CLAuthorizationStatus.NotDetermined:
return GeolocationAccessStatus.Unspecified;

default:
case CLAuthorizationStatus.Restricted:
case CLAuthorizationStatus.Denied:
return GeolocationAccessStatus.Denied;
}
// 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
// This is for the compatibility with iOS 8 and the introduction of AuthorizedWhenInUse.
// This can be replaced with proper enum values when upgrading to iOS 8.0 SDK.
case (CLAuthorizationStatus)4: // CLAuthorizationStatus.AuthorizedWhenInUse:
case (CLAuthorizationStatus)3: // CLAuthorizationStatus.AuthorizedAlways:
return GeolocationAccessStatus.Allowed;

case CLAuthorizationStatus.NotDetermined:
return GeolocationAccessStatus.Unspecified;

default:
case CLAuthorizationStatus.Restricted:
case CLAuthorizationStatus.Denied:
return GeolocationAccessStatus.Denied;
}
}

#if __IOS__ || __TVOS__
private class CLLocationManagerDelegate : NSObject, ICLLocationManagerDelegate
{
public event EventHandler<CLAuthorizationChangedEventArgs> AuthorizationChanged;
private class CLLocationManagerDelegate : NSObject, ICLLocationManagerDelegate
{
public event EventHandler<CLAuthorizationChangedEventArgs> AuthorizationChanged;

[Export("locationManager:didChangeAuthorizationStatus:")]
public void DidChangeAuthorizationStatus(CLLocationManager manager, CLAuthorizationStatus status)
{
AuthorizationChanged?.Invoke(manager, new CLAuthorizationChangedEventArgs(status));
}
[Export("locationManager:didChangeAuthorizationStatus:")]
public void DidChangeAuthorizationStatus(CLLocationManager manager, CLAuthorizationStatus status)
{
AuthorizationChanged?.Invoke(manager, new CLAuthorizationChangedEventArgs(status));
}
#endif
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public uint ReportInterval
}
}
#endif
#if false || __IOS__ || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__
[global::Uno.NotImplemented("__IOS__", "__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")]
#if false || false || __TVOS__ || IS_UNIT_TESTS || __WASM__ || __SKIA__ || __NETSTD_REFERENCE__
[global::Uno.NotImplemented("__TVOS__", "IS_UNIT_TESTS", "__WASM__", "__SKIA__", "__NETSTD_REFERENCE__")]
public double MovementThreshold
{
get
Expand Down
Loading