I am trying to get the location. I implement everything but nothing works.
This is my class:
public class Best2 : CLLocationManagerDelegate
{
public CLLocationManager _locationManager;
private Int32 _numEvento;
public Best2()
{
}
protected Best2(NSObjectFlag t) : base(t)
{
}
protected internal Best2(IntPtr handle) : base(handle)
{
}
public Best2(Int32 paramEvento)
{
_numEvento = paramEvento;
_locationManager = new CLLocationManager ();
_locationManager.PausesLocationUpdatesAutomatically = false;
_locationManager.DesiredAccuracy = CLLocation.AccurracyBestForNavigation;
if (UIDevice.CurrentDevice.CheckSystemVersion (9, 0)) {
_locationManager.AllowsBackgroundLocationUpdates = true;
}
_locationManager.Delegate = this;
StartLocationUpdates();
}
public void StartLocationUpdates ()
{
if (CLLocationManager.LocationServicesEnabled) {
_locationManager.RequestLocation();
System.Diagnostics.Debug.WriteLine(
String.Format(
"FA&)#2 RequestLocation() Data:D {0}"
, DateTime.UtcNow
)
);
}
}
public override void Failed (CLLocationManager manager, Foundation.NSError error)
{
System.Diagnostics.Debug.WriteLine(
String.Format(
"FA&)#2 Failed() Data:D {0}"
, DateTime.UtcNow
)
);
}
public override void UpdatedLocation (CLLocationManager manager, CLLocation newLocation, CLLocation oldLocation)
{
System.Diagnostics.Debug.WriteLine(
String.Format(
"FA&)#2 UpdatedLocation() Data:D {0}"
, DateTime.UtcNow
)
);
}
I call this way:
Best2 position = new Best2(1);
Based in the apple documentation, I think it should call the method Failed if not works and UpdatedLocation if works. But nothing is calling.
I already try to put the class receiving events, but it doesn't work either.
According to the documentation about Request Location we know that if we want to use location in iOS we must follow the steps below:
Add the NSLocationWhenInUseUsageDescription key and the NSLocationAlwaysAndWhenInUseUsageDescription key to your Info.plist file.
If your app supports iOS 10 and earlier, add the NSLocationAlwaysUsageDescription key to your Info.plist file.
Then before RequestLocation() we need to call RequestAlwaysAuthorization() or RequestWhenInUseAuthorization() to get user's authentication.
At last we can get the location but you misunderstand the event:
I think it should call the method Failed if not works and
UpdatedLocation if works.
If it works the event LocationsUpdated() will fire not UpdatedLocation().
Related
I have a Xamarin Forms app that needs to call a web page that contains a custom Bing Map and on the web page it needs to be able to get the users current location.
The app already has the required "app" permissions for both Android and iOS and I can get the users location internally in the app without issues.
When I call my external web page that has a custom Bing Map, it does allow me to get the users location in both Android and iOS BUT on iOS it asks the user "website... Would Like To Use Your Current Location. Don't Allow / OK" each time they visit the web page from inside my Xamarin app.
I found the following articles that help point me in the right directions but I just don't understand enough of iOS to piece it together.
How to prevent WKWebView to repeatedly ask for permission to access location? -- This looks like what I need but it is in iOS and does not use WkWebViewRender which is required for newer iOS apps.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview -- This shows me how to add "script" to the iOS side but I can't get the DidReceiveScriptMessage method to fire.
Here is my current implementation of WkWebViewRender.
public class HybridWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
public HybridWebViewRenderer() : this(new WKWebViewConfiguration())
{
}
const string JavaScriptFunctionTest =
"navigator.geolocation.getCurrentPosition = function(success, error, options) {window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');};";
WKUserContentController userController;
public HybridWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
try
{
userController = config.UserContentController;
var script = new WKUserScript(new NSString(JavaScriptFunctionTest), injectionTime: WKUserScriptInjectionTime.AtDocumentEnd, isForMainFrameOnly: true);
userController.AddUserScript(script);
userController.AddScriptMessageHandler(this, "locationHandler");
}
catch (System.Exception ex)
{
}
}
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
var msg = message.Body.ToString();
System.Diagnostics.Debug.WriteLine(msg);
}
}
And here is the only javascript functions in my html page.
function StartTracking() {
//Add a pushpin to show the user's location.
userPin = new Microsoft.Maps.Pushpin(map.getCenter(), { visible: true });
map.entities.push(userPin);
//Watch the users location.
watchId = navigator.geolocation.watchPosition(UsersLocationUpdated);
}
function UsersLocationUpdated(position) {
var loc = new Microsoft.Maps.Location(
position.coords.latitude,
position.coords.longitude);
//Update the user pushpin.
userPin.setLocation(loc);
userPin.setOptions({ visible: true });
//Center the map on the user's location.
map.setView({ center: loc });
}
function StopTracking() {
// Cancel the geolocation updates.
navigator.geolocation.clearWatch(watchId);
//Remove the user pushpin.
map.entities.clear();
}
https://gist.github.com/hayahyts/2c369563b2e9f244356eb6228ffba261 is so close to what I need but I must be doing something wrong.
The DidReceiveScriptMessage does not get called if I use
const string JavaScriptFunction =
"navigator.geolocation.getCurrentPosition " +
" = function(success, error, options) " +
"{window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');};";
BUT DidReceiveScriptMessage does get called if I use
const string JavaScriptFunction =
"function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}";
So I'm not sure what is wrong just yet but it must be with the locationHandler replacement code.
Renderer namespace with ExportRenderer attribute:
[assembly: ExportRenderer(typeof(WebView), typeof(MyNamespace.MyWebViewRenderer))]
namespace MyNamespace
{
Renderer declaration with reference to IWKScriptMessageHandler:
public class MyWebViewRenderer : WkWebViewRenderer, IWKScriptMessageHandler
{
Renderer fields:
const string MessageHandlerName = "locationHandler";
const string JavaScriptFunction =
"navigator.geolocation.getCurrentPosition = function(success, error, options) {window.webkit.messageHandlers.locationHandler.postMessage('getCurrentPosition');};";
//const string HtmlSource = "html containing navigator.geolocation.getCurrentPosition"
Renderer SetupScripts in OnElementChanged override:
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
SetupScripts(Configuration);
//LoadHtmlString(HtmlSource, null); //example loading page
}
}
void SetupScripts(WKWebViewConfiguration wkWebViewConfig)
{
wkWebViewConfig.UserContentController.AddScriptMessageHandler(this, MessageHandlerName);
var jsFunction = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false);
wkWebViewConfig.UserContentController.AddUserScript(jsFunction);
}
Renderer implementation of IWKScriptMessageHandler:
//IWKScriptMessageHandler
public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
//should be invoked when getCurrentPosition in HtmlSource is invoked
}
Try to create a new class which inherit from WKScriptMessageHandler to handle the DidReceiveScriptMessage method .
Modify your code as below
Change the Handler
userController.AddScriptMessageHandler(new myClass(), "invokeAction");
Remove WKScriptMessageHandler interface from HybridWebViewRenderer .
public class HybridWebViewRenderer : WkWebViewRenderer
Create a subclass of WKScriptMessageHandler .
public class myClass : WKScriptMessageHandler
{
public override void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message)
{
//_webview.InvokeAction(message.Body.ToString());
}
}
Check the similar issue : https://forums.xamarin.com/discussion/169893/wkscriptmessagehandler-is-not-firing .
i am new to Xamarin, i have a code to get GPS location, which is working fine for IOS 6 but for IOS 8 it is not fetching the long. and lat.
//Check for getting current lat/long
CLLocationCoordinate2D toLocation;
private CLLocationManager locationManager;
if (!CLLocationManager.LocationServicesEnabled) {
AppDelegate.currLat = "";
AppDelegate.currLong = "";
gpsFlag = false;
} else {
gpsFlag = true;
locationManager = new CLLocationManager();
locationManager.StartUpdatingLocation();
if (locationManager.Location != null) {
toLocation = locationManager.Location.Coordinate;
AppDelegate.currLat = Convert.ToString (toLocation.Latitude);
AppDelegate.currLong = Convert.ToString (toLocation.Longitude);
}
locationManager.StopUpdatingLocation ();
}
i am using the Universal Template to create Application in Xamarin. i have R&D for IOS 8 GPS location, i got to know that i need to add "NSLocationAlwaysUsageDescription" to info.plist, but i am not getting how to add this.
getting-gps-location-using-core-location-in-ios-8
Please help me how can get the GPS location for IOS 8
public class LocationHelper
{
private static bool _isTracking;
public static bool IsTracking { get { return _isTracking; } }
private static string _longitude;
private static string _latitude;
private static DateTime _lastUpdated;
public static event EventHandler LocationUpdated;
public static CLLocationManager LocationManager { private set; get; }
public static void StartLocationManager(double distanceFilter, double accuracy)
{
LocationManager = new CLLocationManager();
if (LocationManager.RespondsToSelector(new MonoTouch.ObjCRuntime.Selector("requestWhenInUseAuthorization")))
LocationManager.RequestWhenInUseAuthorization();
LocationManager.DistanceFilter = CLLocationDistance.FilterNone;
LocationManager.DesiredAccuracy = accuracy;
LocationManager.LocationsUpdated += LocationManager_LocationsUpdated;
LocationManager.StartUpdatingLocation();
_isTracking = true;
System.Diagnostics.Debug.WriteLine("Location manager started ");
}
public static void StopLocationManager()
{
if (LocationManager != null)
{
LocationManager.LocationsUpdated -= LocationManager_LocationsUpdated;
LocationManager = null;
_isTracking = false;
}
}
public static void Refresh()
{
LocationManager.StopUpdatingLocation();
LocationManager.StartUpdatingLocation();
}
private static void LocationManager_LocationsUpdated(object sender, CLLocationsUpdatedEventArgs e)
{
if (LocationUpdated != null)
LocationUpdated(null, null);
UpdateLocation(e.Locations[e.Locations.Length - 1]);
}
private static void UpdateLocation(CLLocation location)
{
_longitude = location.Coordinate.Longitude.ToString();
_latitude = location.Coordinate.Latitude.ToString();
_lastUpdated = DateTime.Now;
}
public static LocationResult GetLocationResult()
{
return new LocationResult(_latitude, _longitude, _lastUpdated);
}
public class LocationResult
{
public DateTime UpdatedTime { private set; get; }
public string Latitude { private set; get; }
public string Longitude { private set; get; }
public LocationResult(string latitude, string longitude, DateTime updated)
{
UpdatedTime = updated;
Latitude = latitude;
Longitude = longitude;
}
}
}
This works in iOS8I'm using this static class, every time before taking coordinates I'm calling Refresh() I'm cant find thread where i found this solution but this causes to return location immediately, and then call to GetLocationResult() to get location and when u finished with locationManager call StopLocationManager()
When you create a new instance of CLLocationmanager it is extremely important that you call the following:
manager = new CLLocationManager();
1.) manager.RequestWhenInUseAuthorization (); //This is used if you are doing anything in the foreground.
2.) manager.RequestAlwaysAuthorization ();// This is used if you want to scan in the background
Of course these are iOS 8 only methods so makes sure to do a version check with: UIDevice.CurrentDevice.CheckSystemVersion (8, 0);
This isn’t enough though to prompt your user for access to location. You will need to now modify your Info.plist! You will need to add a new string entry called NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription. Then you can specify the message you want to prompt your user with when attempting to get location
I try to create a location-tracking app. App should work in background. So, I switch on properties "Enable background modes", "location updates" and added parameter "NSLocationAlwaysUsageDescription" to the source.
On ios7 app works fine, but on ios8 it suspends in background after few minutes (How app should work: I send a request every time when new location is received, and if I can see this request on the server it means that app is working).
I downloaded xamarin.mobile component with location functionality and used it instead of my class for geolocation. App also suspends in the background.
I created Objective-C app with the same functionality and tested it on the same device. Result - app works fine (as expected).
So, maybe app still needs some setting or I'm missing something?
public class LocationUpdatedEventArgs : EventArgs
{
CLLocation location;
public LocationUpdatedEventArgs(CLLocation location)
{
this.location = location;
}
public CLLocation Location
{
get { return location; }
}
}
protected CLLocationManager locMgr;
public event EventHandler<LocationUpdatedEventArgs> LocationUpdated = delegate { };
public GeoLocationService_iOS()
{
this.locMgr = new CLLocationManager();
LocationUpdated += SaveLocation;
locMgr.AuthorizationChanged += (object sender, CLAuthorizationChangedEventArgs e) =>
{
//CheckStatus();
};
if (locMgr.RespondsToSelector(new Selector("requestAlwaysAuthorization")))
{
locMgr.RequestAlwaysAuthorization();
}
locMgr.DistanceFilter = 1;
locMgr.DesiredAccuracy = CLLocation.AccuracyBest;
locMgr.LocationsUpdated += (object sender, CLLocationsUpdatedEventArgs e) =>
{
// fire our custom Location Updated event
this.LocationUpdated(this, new LocationUpdatedEventArgs(e.Locations[e.Locations.Length - 1]));
};
locMgr.StartUpdatingLocation();
}
public void SaveLocation(object sender, LocationUpdatedEventArgs e)
{
SendLoc();
}
Adding
locMgr.PausesLocationUpdatesAutomatically = false;
solved my problem.
Im just trying to get a really simple POC of my multiplatform app finding ibeacons in a crossplatform xamarin solution. I've got the android side of things going but just hitting issues with the iOS side of things.
I've got the following hacked up code in the AppDelegate class (this is just to first muck around with it, i realize this isnt where it should reside):
[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
UIWindow window;
static readonly string uuid = "B9407F30-F5F8-466E-AFF9-25556B57FE6D";
static readonly string monkeyId = "Monkey";
CBPeripheralManager peripheralMgr;
BTPeripheralDelegate peripheralDelegate;
CLLocationManager locationMgr;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
myAppManagerApp.Init(typeof(myAppManagerApp).Assembly);
Forms.Init();
// FormsMaps.Init();
UINavigationBar.Appearance.BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0);
UINavigationBar.Appearance.TintColor = UIColor.Blue;
UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes()
{
TextColor = UIColor.White
});
window.RootViewController = BuildView();
window.MakeKeyAndVisible();
var monkeyUUID = new NSUuid(uuid);
var beaconRegion = new CLBeaconRegion(monkeyUUID, monkeyId);
//power - the received signal strength indicator (RSSI) value (measured in decibels) of the beacon from one meter away
var power = new NSNumber(-59);
NSMutableDictionary peripheralData = beaconRegion.GetPeripheralData(power);
peripheralDelegate = new BTPeripheralDelegate();
peripheralMgr = new CBPeripheralManager(peripheralDelegate, DispatchQueue.DefaultGlobalQueue);
peripheralMgr.StartAdvertising(peripheralData);
locationMgr = new CLLocationManager();
locationMgr.RegionEntered += (object sender, CLRegionEventArgs e) =>
{
if (e.Region.Identifier == monkeyId)
{
var notification = new UILocalNotification() { AlertBody = "There's a monkey hiding nearby!" };
UIApplication.SharedApplication.PresentLocationNotificationNow(notification);
}
};
locationMgr.DidStartMonitoringForRegion += locationMgr_DidStartMonitoringForRegion;
locationMgr.MonitoringFailed += locationMgr_MonitoringFailed;
locationMgr.StartMonitoring(beaconRegion);
locationMgr.StartRangingBeacons(beaconRegion);
locationMgr.DidRangeBeacons +=locationMgr_DidRangeBeacons;
return true;
}
private void locationMgr_DidRangeBeacons(object sender, CLRegionBeaconsRangedEventArgs e)
{
throw new NotImplementedException();
}
private void locationMgr_MonitoringFailed(object sender, CLRegionErrorEventArgs e)
{
throw new NotImplementedException();
}
private void locationMgr_DidStartMonitoringForRegion(object sender, CLRegionEventArgs e)
{
int i = 0;
//throw new NotImplementedException();
}
static UIViewController BuildView()
{
var root = new pgeRoot();
var controller = root.CreateViewController();
return controller;
}
Ive chopped most of the code out of the Find the monkey sample. Either way the DidRangeBeacons or RegionEntered events never fire. I'm using estimote iBeacons so i dont know whether that makes a difference?
Any ideas on what im missing here? Is there a permission or setting i need to put into the plist?
Thanks
In iOS 8 you need to explicitly ask for permission to use Location Services - it's the CLLocationManager's RequestAlwaysAuthorization (for monitoring) and RequestWhenInUseAuthorization (for ranging) methods. You also need an appropriate (NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription) entry in the Info.plist file of your iOS app, though I'm not entirely surely how to do this in Xamarin.
I created a very simple test app to try and reverse geocode my current lat/long into an address.
Here is the code for my ViewModel:
namespace LoginProductsMVVM.Core.ViewModels
{
public class ProductDetailViewModel
: MvxViewModel
{
public void Init(Product product)
{
Product = product;
}
private Product _product;
public Product Product
{
get { return _product; }
set { _product = value;
RaisePropertyChanged (() => Product); }
}
private string _latitude;
public string Latitude{
get { return _latitude; }
set { _latitude = value; RaisePropertyChanged(() => Latitude); }
}
private string _longitude;
public string Longitude{
get { return _longitude; }
set { _longitude = value; RaisePropertyChanged(() => Longitude); }
}
private string _address;
public string Address{
get { return _address; }
set { _address = value; RaisePropertyChanged(() => Address); }
}
private IMvxGeoLocationWatcher _watcher;
public IMvxGeoLocationWatcher Watcher
{
get
{
_watcher = Mvx.Resolve<IMvxGeoLocationWatcher> ();
return _watcher;
}
}
public ProductDetailViewModel(IMvxGeoLocationWatcher watcher)
{
_watcher = watcher;
_watcher.Start (new MvxGeoLocationOptions (), OnLocation, OnError);
}
void OnLocation (MvxGeoLocation location)
{
Latitude = location.Coordinates.Latitude.ToString();
Longitude = location.Coordinates.Longitude.ToString();
// Android Location specific stuff
var activity = Mvx.Resolve<IMvxAndroidCurrentTopActivity> ().Activity;
Geocoder geocdr = new Geocoder (activity.BaseContext);
IList<Address> addresses = geocdr.GetFromLocation (double.Parse(Latitude), double.Parse(Longitude), 1);
addresses.ToList().ForEach ((addr) => Address += addr.ToString() + "\r\n\r\n");
}
void OnError (MvxLocationError error)
{
Mvx.Error ("Seen location error {0}", error);
}
}
}
I have a break point in my OnLocation method but it never gets in there. Am I missing something for this to work properly on Android? It appears to work just fine for iOS...
Per Odahan here:
Well... I investigated a bit more : The problem is known and can be seen on many devices. This is not a MvvmCross problem. Short answer : the device needs to be rebooted and all is working like a charm... It seems Google sent some updates that are causing the problem.
Here is a thread speaking about this problem and a similar one concerning the GeoCode class :
https://code.google.com/p/android/issues/detail?id=38009
So : can be closed, MvvmCross is ok but others can face this bug so my explanations and the link here.
When you say it never fires 'success', does the success actually ever occur?
There could be lots of things going wrong in the GPS code - eg your app might not have privilege, your phone might have A-GPS or GPS disabled, or you might even be running on a location-less emulator - all of this is possible from your description.
It'a also worth noting that Xamarin.Android has long standing issues with hitting breakpoints - so it's better to add trace than to rely on breakpoint hitting :/
Perhaps try running though N=8 from http://mvvmcross.wordpress.com/ - does that help at all? (N=9 is also worth watching as it shows one way to allow multiple viewmodels to use the geowatcher)