in iOS 14 when permission is accepted and right after the pop-up is closed
await Permissions.RequestAsync
await Permissions.RequestAsync
never returns, or throws an exception the first time called.
in iOS 14 when permission is accepted and right after the pop-up is closed
await Permissions.RequestAsync
await Permissions.RequestAsync
returns PermissionStatus.Granted.
Same for me.
Currently use this workaround:
while (true)
{
if (await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>() == PermissionStatus.Granted)
break;
Permissions.RequestAsync<Permissions.LocationWhenInUse>();
await Task.Delay(1000);
}
Was just about to report this.
Is there an ETA on a fix? iOS 14 is already out so I think these kinds of fixes should be top priority.
Thanks.
I dug in some and the CLLocationManager.AuthorizationChanged event does not appear to be called after permission is granted (or denied).
Interestingly, the CLLocationManagerDelegate.AuthorizationChanged method does get called if I use the delegate pattern instead (at least in my rudimentary re-implementation I experimented with).
I am leaning towards this being an issue in Xamarin.iOS proper or possibly iOS, but who knows 🤷♂️
The PermissionsPlugin also has this issue.
I dug in some and the
CLLocationManager.AuthorizationChangedevent does not appear to be called after permission is granted (or denied).Interestingly, the CLLocationManagerDelegate.AuthorizationChanged` method does get called if I use the delegate pattern instead (at least in my rudimentary re-implementation I experimented with).
I am leaning towards this being an issue in Xamarin.iOS proper or possibly iOS, but who knows 🤷♂️
The PermissionsPlugin also has this issue.
Thanks for the research. Do you think the best course of action would be to report it a bug to Xamarin.iOS so they can have a look at it, or do you think the plugin should be changed to use the delegate pattern instead?
Same for me.
Currently use this workaround:while (true) { if (await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>() == PermissionStatus.Granted) break; Permissions.RequestAsync<Permissions.LocationWhenInUse>(); await Task.Delay(1000); }
I think something like this would be better:
```c#
public async Task
where TPermission : Permissions.BasePermission, new()
{
// temporary fix for https://github.com/xamarin/Essentials/issues/1390
if (Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.iOS &&
DeviceInfo.Version.Major >= 14)
{
Permissions.RequestAsync
PermissionStatus status;
while ((status = await Permissions.CheckStatusAsync
await Task.Delay(50);
return status;
}
return await Permissions.RequestAsync<TPermission>();
}
```
Yep, same here.
Need to debug through this one, i have seen this on my other one. Apparently in iOS 14 they have changed the callbacks :(
Maybe there is some new api that ios 14 expects you to use? How would you do this in objc/swift?
I have temporary solved my problem with this code
`public class PermissionService : IPermissionService
{
public PermissionService()
{
}
LocationCheck showTrackingMap;
public async Task<PermissionStatus> RequestPermissionsAsync()
{
PermissionStatus permission = PermissionStatus.Unknown;
showTrackingMap = new LocationCheck((s, ev) =>
{
if ((ev as LocationCheck.LocationCheckEventArgs).Allowed)
{
permission = PermissionStatus.Granted;
}
else
{
permission = PermissionStatus.Denied;
}
showTrackingMap.Dispose();
});
while (permission == PermissionStatus.Unknown)
{
await Task.Delay(10);
}
return permission;
}
}
public class LocationCheck : NSObject, ICLLocationManagerDelegate
{
public class LocationCheckEventArgs : EventArgs
{
public readonly bool Allowed;
public LocationCheckEventArgs(bool Allowed)
{
this.Allowed = Allowed;
}
}
CLLocationManager locationManager;
EventHandler locationStatus;
public LocationCheck(EventHandler locationStatus)
{
this.locationStatus = locationStatus;
Initialize();
}
public LocationCheck(NSObjectFlag x) : base(x) { Initialize(); }
public LocationCheck(IntPtr handle) : base(handle) { Initialize(); }
public LocationCheck(IntPtr handle, bool alloced) : base(handle, alloced) { Initialize(); }
public void Initialize()
{
locationManager = new CLLocationManager
{
Delegate = this
};
locationManager.RequestAlwaysAuthorization();
}
[Export("locationManager:didChangeAuthorizationStatus:")]
public void AuthorizationChanged(CLLocationManager manager, CLAuthorizationStatus status)
{
switch (status)
{
case CLAuthorizationStatus.AuthorizedAlways:
case CLAuthorizationStatus.AuthorizedWhenInUse:
locationStatus.Invoke(locationManager, new LocationCheckEventArgs(true));
break;
case CLAuthorizationStatus.Denied:
case CLAuthorizationStatus.Restricted:
locationStatus.Invoke(locationManager, new LocationCheckEventArgs(false));
break;
}
}
protected override void Dispose(bool disposing)
{
locationStatus = null;
locationManager.Delegate = null;
locationManager.Dispose();
base.Dispose(disposing);
}
}`
Here's my workaround. I wasn't seeing CLLocationManagerDelegate.AuthorizationChanged in my own implementation, whether I subclass the base CLLocationManagerDelegate like this, or implement ICLLocationManagerDelegate with an Export like @Mustafa-ah, so I'm falling back to polling if it's never called.
public class LocationPermissionService : CLLocationManagerDelegate
{
private TaskCompletionSource<PermissionStatus> _tcs;
private CLLocationManager _manager;
public LocationPermissionService(CLLocationManager manager)
{
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
_manager.Delegate = this;
}
public override void AuthorizationChanged(CLLocationManager manager, CLAuthorizationStatus status)
{
// This doesn't seem to be called.
_tcs?.TrySetResult(ToPermissionStatus(status));
}
public async Task<PermissionStatus> RequestAlwaysPermission(CancellationToken cancelToken = default)
{
if (!CLLocationManager.LocationServicesEnabled) return PermissionStatus.Disabled;
if (CLLocationManager.Status != CLAuthorizationStatus.NotDetermined) return ToPermissionStatus(CLLocationManager.Status);
_tcs = new TaskCompletionSource<PermissionStatus>();
_manager.RequestAlwaysAuthorization();
// Due to iOS bug, we have to poll for the status. Cool!
// Let's poll every half second for 10 seconds, and also await the callback just in case it works
var delegateTask = _tcs.Task.WaitAsync(cancelToken);
for (int i = 0; i < 20; i++)
{
var delayTask = Task.Delay(500, cancelToken);
var awaitedTask = await Task.WhenAny(delegateTask, delayTask);
if (awaitedTask == delegateTask)
{
return await delegateTask;
}
else if (CLLocationManager.Status != CLAuthorizationStatus.NotDetermined)
{
return ToPermissionStatus(CLLocationManager.Status);
}
}
// Timed out or maybe the prompt was dismissed, just return what we know
return ToPermissionStatus(CLLocationManager.Status);
}
public PermissionStatus GetPermissionStatus(CancellationToken cancelToken = default)
{
if (!CLLocationManager.LocationServicesEnabled) return PermissionStatus.Disabled;
return ToPermissionStatus(CLLocationManager.Status);
}
private PermissionStatus ToPermissionStatus(CLAuthorizationStatus status) => status switch
{
CLAuthorizationStatus.AuthorizedAlways => PermissionStatus.Granted,
CLAuthorizationStatus.AuthorizedWhenInUse => PermissionStatus.Granted,
CLAuthorizationStatus.Denied => PermissionStatus.Denied,
CLAuthorizationStatus.NotDetermined => PermissionStatus.Unknown,
CLAuthorizationStatus.Restricted => PermissionStatus.Denied,
_ => PermissionStatus.Unknown
};
}
@StefAnglr CLLocationManagerDelegate.AuthorizationChanged is deprecated as of iOS 14, but it's replaced by CLLocationManagerDelegate.DidChangeAuthorization. For me that seems to be called reliably, so no polling needed. Of course you need to handle both to support iOS < 14 too.
What I'm seeing is when requesting location, on iOS 14 the "Allow Location" prompt is show very briefly, then it instantly goes away before I'm able to click "Use location while in App"
Is this issue related? Below is my simple code. Wasn't sure if its somehow related to this:
https://stackoverflow.com/questions/7888896/current-location-permission-dialog-disappears-too-quickly
https://github.com/xamarin/Essentials/issues/1066
while (true)
{
if (await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>() == PermissionStatus.Granted)
break;
await AppHelper.RequestAsync_Fixed<Permissions.LocationWhenInUse>();
}
var request = new GeolocationRequest(GeolocationAccuracy.Medium)
{
Timeout = TimeSpan.FromSeconds(30)
};
var location = await Geolocation.GetLocationAsync(request);
Video showing the issue:
@msjogren has done the work to resolve this and I will try get a preview out as soon as possible.
You can test the fix in the nuget from the artifacts here:
https://dev.azure.com/xamarin/public/_build/results?buildId=30010&view=results
I'm going to just make sure that VS2017 is not broken with this, and then I'll merge and then the build will go out to the preview feed: https://aka.ms/xamarin-essentials-ci/index.json
Is there any way to determine if location is granted as "precise" or not? And second question whether it is planned at all to be in xamarin.essentials?
@RomanNakonechnyi could you open a new issue with that?
@RomanNakonechnyi could you open a new issue with that?
https://github.com/xamarin/Essentials/issues/1523 here it is. Please, confirm if it makes sense.
Most helpful comment
I think something like this would be better:
```c# RequestAsync_Fixed()(); // don't await as it won't return on iOS 14 until https://github.com/xamarin/Essentials/issues/1390 is fixed()) == PermissionStatus.Unknown)
public async Task
where TPermission : Permissions.BasePermission, new()
{
// temporary fix for https://github.com/xamarin/Essentials/issues/1390
if (Xamarin.Forms.Device.RuntimePlatform == Xamarin.Forms.Device.iOS &&
DeviceInfo.Version.Major >= 14)
{
Permissions.RequestAsync
PermissionStatus status;
while ((status = await Permissions.CheckStatusAsync
await Task.Delay(50);
return status;
}
}
```