I'm using Xamarin.Forms and creating an iOS App who has a background service to get a location each 10 minutes.
The code is working, my problem is when I access the App configuration on an IPad.
It shows the permission for accesss the camera but not to access the current location.
I think that will be a problem when I submit the App for review.
For initialize the location:
this.locMgr = new CLLocationManager();
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
{
locMgr.RequestAlwaysAuthorization(); // works in background
//locMgr.RequestWhenInUseAuthorization (); // only in foreground
}
For getting the location:
if (CLLocationManager.LocationServicesEnabled)
{
if (CLLocationManager.Status==CLAuthorizationStatus.Authorized
|| CLLocationManager.Status==CLAuthorizationStatus.AuthorizedAlways)
{
//set the desired accuracy, in meters
LocMgr.DesiredAccuracy = 1;
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
_currentLocation = (e.Locations[e.Locations.Length - 1]);
};
LocMgr.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs e) =>
{
if (e.Status == CLAuthorizationStatus.Denied
|| e.Status == CLAuthorizationStatus.Restricted)
{
LocMgr.StopUpdatingLocation();
_currentLocation = null;
}
};
LocMgr.StartUpdatingLocation();
}
}
There are something that I forgot?
Did you add these keys to your Info.plist file?
<key>NSLocationAlwaysUsageDescription</key>
<string>Your message goes here</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your message goes here</string>
Related
My project is Xamarin Forms in iOS device, and that using WIFI to work with other devices. I want to know whether it is possible to know the current connected ssid name first. If my app loses the link, can it silently try to reconnect to the WiFi without the user having to do anything?
Here is my logic diagram
You could use DependencyService to get a SSID name, you could refer to Xamarin.Forms DependencyService
In your service, try some code like this:
string ssid = "";
string[] supportedInterfaces;
StatusCode status = CaptiveNetwork.TryGetSupportedInterfaces(out supportedInterfaces);
if ((status = CaptiveNetwork.TryGetSupportedInterfaces(out supportedInterfaces)) == StatusCode.OK)
{
foreach (var item in supportedInterfaces)
{
NSDictionary info;
status = CaptiveNetwork.TryCopyCurrentNetworkInfo(item, out info);
if (status == StatusCode.OK)
{
ssid = info[CaptiveNetwork.NetworkInfoKeySSID].ToString();
}
}
}
In your info.plist, you should add this key:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your Description</string>
Once you get the SSID name (and password), you could use NEHostspotConfiguration to make a connection.
NEHotspotConfiguration hotspotConfiguration = new NEHotspotConfiguration(ssid,pwd,false);
hotspotConfiguration.JoinOnce = true;
NEHotspotConfigurationManager.SharedManager.ApplyConfiguration(hotspotConfiguration, (NSError error) =>
{
if (error != null)
{
if (error?.LocalizedDescription == "already associated.")
{
IsConnected = true;
}
else
{
IsConnected = false;
}
}
else
{
IsConnected = true;
}
});
You may also enable Access WiFi information, Hotspot capability of you App ID at the Apple Developer Portal and add them in your Entitlements.plist. Don't forget to regenerate your provisioning file. Hope my answer could help you~
I am trying to handle geofence intersection triggers when my app is terminated (not background). I want handle the enter/exit triggers from CLLocationManager even if my app is not running. It should wake up my app at the background, do the required processing on enter/exit.
To do this, a lot of places its given that it is possible using background app refresh functionality. I have the following code written but as soon as i terminate the application, it stops hearing the geofence trigger events.
Can anybody please guide me how to handle these events even if application is terminated ?
public async Task StartLocationUpdates()
{
_cts = new CancellationTokenSource();
_taskId = UIApplication.SharedApplication.BeginBackgroundTask("LongRunningTask", OnExpiration);
try
{
if (CLLocationManager.LocationServicesEnabled)
{
LocMgr.DesiredAccuracy = 1;
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
var locations = e.Locations;
};
LocMgr.StartUpdatingLocation();
if (CLLocationManager.LocationServicesEnabled)
{
if (CLLocationManager.Status != CLAuthorizationStatus.Denied)
{
if (CLLocationManager.IsMonitoringAvailable(typeof(CLCircularRegion)))
{
LocMgr.DidStartMonitoringForRegion += (o, e) =>
{
Console.WriteLine("Now monitoring region {0}", e.Region.ToString());
};
LocMgr.RegionEntered += (o, e) =>
{
Instance.Speak("Just entered " + e.Region.ToString());
};
LocMgr.RegionLeft += (o, e) =>
{
Instance.Speak("Just left " + e.Region.ToString());
};
foreach (CLCircularRegion region in RegionList)
{
if (region != null)
{
StopMonitoringRegion(region);
}
LocMgr.StartMonitoring(region);
}
}
else
{
Console.WriteLine("This app requires region monitoring, which is unavailable on this device");
}
LocMgr.Failed += (o, e) =>
{
Console.WriteLine(e.Error);
};
}
else
{
Console.WriteLine("App is not authorized to use location data");
}
}
else
{
Console.WriteLine("Location services not enabled, please enable this in your Settings");
}
}
}
catch (OperationCanceledException)
{
}
}
Thank You in advance.
It seems you have added the privacy keys in the info.plist to enable the location service. If you want to enable it when the app is on background state or terminated, we also should turn on the Background Modes: Capabilities > Background Modes > Location updates.
Then we can initialize the location manager in AppDelegate:
CLLocationManager locationManager;
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
locationManager = new CLLocationManager();
locationManager.Delegate = new MyLocationDelegate();
//enable service when app is on background or terminated
locationManager.PausesLocationUpdatesAutomatically = false;
locationManager.AllowsBackgroundLocationUpdates = true;
locationManager.RequestAlwaysAuthorization();
locationManager.DistanceFilter = 100.0;
locationManager.DesiredAccuracy = 1;
// The important request can awake your app
locationManager.StartMonitoringSignificantLocationChanges();
locationManager.StartUpdatingLocation();
return true;
}
The event LocationsUpdated() will also fire if your location has been changed even your app has been terminated:
public class MyLocationDelegate : CLLocationManagerDelegate
{
public override void LocationsUpdated(CLLocationManager manager, CLLocation[] locations)
{
//Handle your method here
}
}
You can test this with your simulator: Debug > Location > Freeway Drive. Store the locations to your sandbox folder in the event above. Force quit your app, after a few seconds open it, retrieve the file you stored. You will find the whole locations records.
I have been struggling with this for several days and have found many other posts on the subject, but nothing seems to fix the problem I have. I have a feeling this might be simple as I have tried this several times with cut down projects and always get the same failure. I can receive Firebase messages fine in Android, but iOS consistently defeats me.
I am using a phone with iOS 10 as a test. The debug code always gets a token back, but on connecting, an error 'The operation couldn’t be completed. (com.google.fcm error 501.)' is returned. I attach some sample code - this has been bodged around in frustration
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IUNUserNotificationCenterDelegate, IMessagingDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new FirebaseTest.App (""));
RegisterForNotificationFCM();
return base.FinishedLaunching (app, options);
}
void RegisterForNotificationFCM()
{
// Register your app for remote notifications.
if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0))
{
// iOS 10 or later
var authOptions = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Badge | UNAuthorizationOptions.Sound;
UNUserNotificationCenter.Current.RequestAuthorization(authOptions, (granted, error) => {
Console.WriteLine(granted);
});
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.Current.Delegate = this;
// For iOS 10 data message (sent via FCM)
Messaging.SharedInstance.Delegate = this;
}
else
{
// iOS 9 or before
var allNotificationTypes = UIUserNotificationType.Alert | UIUserNotificationType.Badge | UIUserNotificationType.Sound;
var settings = UIUserNotificationSettings.GetSettingsForTypes(allNotificationTypes, null);
UIApplication.SharedApplication.RegisterUserNotificationSettings(settings);
}
UIApplication.SharedApplication.RegisterForRemoteNotifications();
Firebase.Analytics.Loader loader1 = new Firebase.Analytics.Loader();
Firebase.InstanceID.Loader loader2 = new Firebase.InstanceID.Loader();
//Firebase.Core.App.Configure();
Firebase.InstanceID.InstanceId.Notifications.ObserveTokenRefresh((sender, e) =>
{
var newToken = Firebase.InstanceID.InstanceId.SharedInstance.Token;
System.Diagnostics.Debug.WriteLine(newToken);
connectFCM();
});
}
public override void DidEnterBackground(UIApplication uiApplication)
{
Messaging.SharedInstance.ShouldEstablishDirectChannel = false;
}
public override void OnActivated(UIApplication uiApplication)
{
base.OnActivated(uiApplication);
}
public override void RegisteredForRemoteNotifications(UIApplication application, NSData deviceToken)
{
Messaging.SharedInstance.ApnsToken = deviceToken;
connectFCM();
//Firebase.InstanceID.InstanceId.SharedInstance.SetApnsToken(deviceToken, Firebase.InstanceID.ApnsTokenType.Prod);
}
public void DidRefreshRegistrationToken(Messaging msg, string str)
{
}
//Fire when background received notification is clicked
public override void DidReceiveRemoteNotification(UIApplication application, NSDictionary userInfo, Action<UIBackgroundFetchResult> completionHandler)
{
//Messaging.SharedInstance.AppDidReceiveMessage(userInfo);
System.Diagnostics.Debug.WriteLine(userInfo);
// Generate custom event
NSString[] keys = { new NSString("Event_type") };
NSObject[] values = { new NSString("Recieve_Notification") };
var parameters = NSDictionary<NSString, NSObject>.FromObjectsAndKeys(keys, values, keys.Length);
// Send custom event
Firebase.Analytics.Analytics.LogEvent("CustomEvent", parameters);
if (application.ApplicationState == UIApplicationState.Active)
{
System.Diagnostics.Debug.WriteLine(userInfo);
var aps_d = userInfo["aps"] as NSDictionary;
var alert_d = aps_d["alert"] as NSDictionary;
var body = alert_d["body"] as NSString;
var title = alert_d["title"] as NSString;
//debugAlert(title, body);
}
}
[Export("userNotificationCenter:willPresentNotification:withCompletionHandler:")]
public void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action<UNNotificationPresentationOptions> completionHandler)
{
// Do your magic to handle the notification data
System.Console.WriteLine(notification.Request.Content.UserInfo);
}
// Receive data message on iOS 10 devices.
public void ApplicationReceivedRemoteMessage(RemoteMessage remoteMessage)
{
Console.WriteLine(remoteMessage.AppData);
}
private void connectFCM()
{
//Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
//Messaging.SharedInstance.Subscribe("/topics/topic");
Messaging.SharedInstance.Connect((error) =>
{
if (error == null)
{
Messaging.SharedInstance.Subscribe("/topics/topic");
}
System.Diagnostics.Debug.WriteLine(error != null ? "error occured" : "connect success");
});
}
These functions have been removed from many other posts on the same subject as happens when you get more and more desperate. I think the firebase console has been set up okay. I have added the iOS as an app along side the working Android one and have added an authentication key created on the Mac. (surprisingly I still get a token back even without this iOS app as part of the firebase project - not what I expected) A certificate has been created for this app and the provisioning profile has been recreated and old ones deleted.
I'm not sure what else to add - someone please put me out of my misery...
Got there in the end. Things for other people to check on - Moved App.Configure() into connectFCM
private void connectFCM()
{
Firebase.Core.App.Configure();
Messaging.SharedInstance.ShouldEstablishDirectChannel = true;
}
and added the following to info.plist
<key>NSExceptionDomains</key>
<dict>
<key>firebaseio.com</key>
<dict>
<key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
How can i pause application for prevention from running next method unit client does not selected dialog buttons?
For example i am showing location update dialog for accessing location service and i want to pause my application for dialog response
public CLLocation UpdateUserLocation()
{
CLLocation currentLocation = null;
CLLocationManager LocMgr = new CLLocationManager();
if (CLLocationManager.LocationServicesEnabled)
{
if (UIDevice.CurrentDevice.CheckSystemVersion (6, 0))
{
LocMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
currentLocation = e.Locations [e.Locations.Length - 1];
};
}
else
{
LocMgr.UpdatedLocation += (object sender, CLLocationUpdatedEventArgs e) =>
{
currentLocation = e.NewLocation;
};
}
LocMgr.StartUpdatingLocation ();
LocMgr.Failed += (object sender, NSErrorEventArgs e) =>
{
Console.WriteLine (e.Error);
};
}
else
{
currentLocation = null;
Console.WriteLine ("Location services not enabled, please enable this in your Settings");
}
if (currentLocation != null)
{
LocationDetector.Instance.UpdateCurrentArea (new MyLatLng (currentLocation.Coordinate.Latitude, currentLocation.Coordinate.Longitude));
}
return currentLocation;
}
If I am understanding your question correctly.
When you display a dialog box, you are wanting to stop execution of the current method from further executing until the user selects a dialog box response.
Once they have selected a response, you would then like to continue execution of the code in the same function, effectively achieving your 'pause' that you are after.
To achieve this in iOS you can use a TaskCompletionSource.
In the example below it shows a dialog box first, asking the user if they want some coffee and then waits for the user to respond.
Once the user responds, it then continues execution, within the same function, and displays a further message box that is dependent on the selection that the user made.
UIButton objButton1 = new UIButton (UIButtonType.RoundedRect);
objButton1.SetTitle ("Click Me", UIControlState.Normal);
objButton1.TouchUpInside += (async (o2, e2) => {
int intCoffeeDispenserResponse = await ShowCoffeeDispenserDialogBox();
//
switch (intCoffeeDispenserResponse)
{
case 0:
UIAlertView objUIAlertView1 = new UIAlertView();
objUIAlertView1.Title = "Coffee Dispenser";
objUIAlertView1.Message = "I hope you enjoy the coffee.";
objUIAlertView1.AddButton("OK");
objUIAlertView1.Show();
break;
case 1:
UIAlertView objUIAlertView2 = new UIAlertView();
objUIAlertView2.Title = "Coffee Dispenser";
objUIAlertView2.Message = "OK - Please come back later when you do.";
objUIAlertView2.AddButton("OK");
objUIAlertView2.Show();
break;
}
});
//
View = objButton1;
private Task<int> ShowCoffeeDispenserDialogBox()
{
TaskCompletionSource<int> objTaskCompletionSource1 = new TaskCompletionSource<int> ();
//
UIAlertView objUIAlertView1 = new UIAlertView();
objUIAlertView1.Title = "Coffee Dispenser";
objUIAlertView1.Message = "Do you want some coffee?";
objUIAlertView1.AddButton("Yes");
objUIAlertView1.AddButton("No");
//
objUIAlertView1.Clicked += ((o2, e2) => {
objTaskCompletionSource1.SetResult(e2.ButtonIndex);
});
//
objUIAlertView1.Show();
//
return objTaskCompletionSource1.Task;
}
I have code that is used to show a device's location. It works just fine on the emulator and it takes me to the fake location at Microsoft. But it didn't work when I build it into the phone, it showed me the world map. Is this a known bug or I have done something wrong? Here is my code:
private GeoCoordinateWatcher loc = null;
private void button1_Click(object sender, RoutedEventArgs e)
{
if (loc == null)
{
loc = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
loc.StatusChanged += loc_StatusChanged;
}
if (loc.Status == GeoPositionStatus.Disabled)
{
loc.StatusChanged -= loc_StatusChanged;
MessageBox.Show("Location services must be enabled on your phone.");
return;
}
loc.Start();
}
void loc_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
{
if (e.Status == GeoPositionStatus.Ready)
{
Pushpin p = new Pushpin();
p.Template = this.Resources["pinMyLoc"] as ControlTemplate;
p.Location = loc.Position.Location;
mapControl.Items.Add(p);
map1.SetView(loc.Position.Location, 17.0);
loc.Stop();
}
}
}
Instead of using the StatusChanged event, you should use the GeoCoordinateWatcher.PositionChanged event, in from which you should use the GeoPositionChangedEventArgs.Position property, to reflect the changed location.
This is due to my location doesn't support by Bing Map. I couldn't use the Bing Map app installed in my phone neither. Hmm...