I was searching for the Xamarin implementation of How to open settings programmatically
Vito-ziv answered it for objective C - what is the correct way to do this in C# for iOS in Xamarin Studio?
For current devices this is only possible in ios8 (ios9 not available at time of writing) (It used to be possible before ios5 apparently - see this blog post from Adrian Stevens at Xamarin - shout out to him for the inspiration for this answer)
To do it in ios8, I did it like this:
var settingsString = UIKit.UIApplication.OpenSettingsUrlString;
var url = new NSUrl (settingsString);
UIApplication.SharedApplication.OpenUrl (url);
Where the above code was called from a click event via delegate class in a UIAlertView click.
Since I am supporting ios7 too, to handle ios7 devices I did this, where the HandleLocationAuthorisation method is called when deciding whether to present a view controller - the user on ios8 and above can choose to go to the settings directly, whereas the user on ios7 has to go there manually.
This example below is checking for location services, but with trivial changes could easily be changed to check for other types of settings.
public bool HandleLocationAuthorisation ()
{
if (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedAlways) {
return true;
} else {
UIAlertView uiAlert;
//iOS 8 and above can redirect to settings from within the app
if (UIDevice.CurrentDevice.CheckSystemVersion(8,0)) {
uiAlert = new UIAlertView
("Location Services Required",
"",
null,
"Return To App","Open Settings");
uiAlert.Delegate = new OpenSettingsFromUiAlertViewDelegate();
uiAlert.Message = "Authorisation to use your location is required to use this feature of the app.";
//ios7 and below has to go there manually
} else {
uiAlert = new UIAlertView
("Location Services Required",
"Authorisation to use your location is required to use this feature of the app. To use this feature please go to the settings app and enable location services",
null,
"Ok");
}
uiAlert.Show ();
return false;
}
}
For completeness, here is the code for the event delgate referenced above:
public class OpenSettingsFromUiAlertViewDelegate : UIAlertViewDelegate {
public override void Clicked (UIAlertView alertview, nint buttonIndex)
{
if (buttonIndex == 1) {
var settingsString = UIKit.UIApplication.OpenSettingsUrlString;
var url = new NSUrl (settingsString);
UIApplication.SharedApplication.OpenUrl (url);
}
}
}
Hope this will help you. This is working in iPhone not sure about working on iPad.
var url = new NSUrl("prefs:root=Settings");
UIApplication.SharedApplication.OpenUrl(url);
Related
Hello stackoverflowers,
I am working on a project that is built using MVVMCross and Xamarin for iOS and Android. I have found out that the project uses a quite old version of MVVMCross (4.4.0) and I am trying to bring it up to the current one (6.4). I thought it's a good idea to first upgrade to 5.7 and on a later stage, when I have the navigation switched to the new form etc, I will bump up to 6++. I have sucessfully run the android version to 5.7, however, the iOS version uses a customPresenter, that I don't quite know how to transform to the new Presenter introduced in 5.1. I think my custom presenter is based on https://github.com/MvvmCross/MvvmCross-Samples/tree/master/XPlatformMenus/XPlatformMenusTabs.iOS which hasn't been updated in a while.
In my MvxTabPresenter that subclasses MvxIosViewPresenter, the show function is no longer overridable. In addition IMvxModalIosView doesnt seem to exist anymore.
public override void Show(IMvxIosView view)
{
if (view is IMvxModalIosView)
{
if (this._currentModalViewController != null)
{
return;
}
var currentModalViewController = view as MvxViewController;
this._currentModalViewController = currentModalViewController;
currentModalViewController.ModalPresentationStyle = UIModalPresentationStyle.Popover;
CurrentTopViewController.AddChildViewController(currentModalViewController);
currentModalViewController.View.Frame = CurrentTopViewController.View.Bounds.Inset(10, 10);
currentModalViewController.View.Alpha = 0;
CurrentTopViewController.View.Add(currentModalViewController.View);
currentModalViewController.DidMoveToParentViewController(CurrentTopViewController);
UIView.Animate(0.25, () =>
{
currentModalViewController.View.Alpha = 1;
});
//this.PresentModalViewController(currentModalViewController, true);
return;
}
if (view is HomeView)
{
if (this.CurrentTopViewController is MvxTabBarViewController)
{
TabBarPresenter.SelectedIndex = 0;
return;
}
public override void CloseModalViewController()
{
if (this._currentModalViewController != null)
{
this._currentModalViewController.DismissModalViewController(true);
_currentModalViewController.WillMoveToParentViewController(null);
_currentModalViewController.View.RemoveFromSuperview();
_currentModalViewController.RemoveFromParentViewController();
this._currentModalViewController = null;
return;
}
base.CloseModalViewController();
}
}
Also this is no longer overridable from the superclass.
Any suggestions on how to approach this?
Kind regards,
V
As you may see in the MvxIosViewPresenter now the mvx attributes are registered with the action that should be called.
So, firstly you should inherit from MvxIosViewPresenter. Then, for modal you should override ShowModalViewController.
I suggest you to read the docs, the MvxIosViewPresenter and MvxAttributeViewPresenter files on the repo to check out how it works.
HIH
I'm trying to implement Keychain from start to finish for my Xamarin Application. At the moment I have the initial bit that will display the native prompt `Touch ID to Use Password'. That works successfully using the bit of code below.
public class TouchId : ITouchID
{
public Task<bool> AuthenticateUserIDWithTouchID()
{
var taskSource = new TaskCompletionSource<bool>();
var context = new LAContext();
if (context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError AuthError))
{
var replyHandler = new LAContextReplyHandler((success, error) => {
taskSource.SetResult(success);
});
context.EvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, "Logging with touch ID", replyHandler);
};
return taskSource.Task;
}
}
My question is how do I display the native prompt asking if I would like to save to Keychain.
I've created a basic one using xamarin's Display Alert, but it doesn't quite look as nice as the native.
I am trying to observe if a screenshot is taken while using my App on Iphone. If a screenshot is taken while using my App, I would like that screenshot to be deleted.
I also understand that during deletion, user needs to give permission for deletion.
I used an Observer method successfully to check if a screenshot is taken while using my app.
I am stuck at a point where I need to access that screenshot and delete it, of course with user permission.
```public override void OnActivated(UIApplication application)
{
try
{
// Start observing screenshot notification
if (_screenshotNotification == null)
{
_screenshotNotification = NSNotificationCenter.DefaultCenter.AddObserver(UIApplication.UserDidTakeScreenshotNotification,
(NSNotification n) => {
Console.WriteLine("UserTookScreenshot");
var photosOptions = new PHFetchOptions();
photosOptions.SortDescriptors = new NSSortDescriptor[] { new
NSSortDescriptor("creationDate", false) };
photosOptions.FetchLimit = 1;
var photo = PHAsset.FetchAssets(photosOptions);
Console.WriteLine(photo);
var filePath = photo.Path;
System.IO.File.Delete(filePath);
n.Dispose();
}
);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
}```
I know the above code does not work with deleting the current screenshot taken while using my App. It gives a general idea on what I want to achieve.
How can I delete the screenshot taken while using my APP from Iphone instantly (with user permission)? I would also like to know if it is possible.
Yes it is possible, you obviously have to properly manage the permissions for the image. In order to do this, first you have to add an observer to detect the screenshot as shown here
First: declare an NSObject variable on your AppDelegate. In the example below I added _screenshotNotification.
Second: On the AppDelegate’s OnActivated (app moving to active state), add code to start observing the UIApplication.UserDidTakeScreenshotNotification and then do something once the notification is posted.
Third: On the AppDelegate’s OnResignActivation (app moving to inactive state), add code to remove the observer.
Then you have to actually find the file & delete, so you in the page you are trying to do it, just add the Foundation & Photos using statements and then try this. (I converted the Swift from here but haven't tested it)
var fetchOptions = new PHFetchOptions();
fetchOptions.SortDescriptors[0] = new Foundation.NSSortDescriptor("creationDate", true);
var fetchResult = PHAsset.FetchAssets(PHAssetMediaType.Image, fetchOptions);
if (fetchResult != null)
{
var lastAsset = (fetchResult.LastObject as PHAsset);
var arrayToDelete = new PHAsset[1] { lastAsset };
PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() => { PHAssetChangeRequest.DeleteAssets(arrayToDelete); },
async (bool success, NSError errorMessage) => { }); //Handle Completion Here Appropriately
}
I don't think it is possible.
From iOS 11, when you take a screenshot, the snap minimizes itself in the bottom left corner of the screen. From here, save or delete the screenshot is decided by the user.(as the image below)
You can read more information form this article: how-take-screenshot-iphone-or-ipad-ios-11
Here comes the problem how do you know whether user has save the screenshot or not?
If the user saved, you can delete the screenShot form the photoLibrary.
While if the user dose not save the screenshot, what your deleted is not the screenshot.
Try this:
func didTakeScreenshot() {
self.perform(#selector(deleteAppScreenShot), with: nil, afterDelay: 1, inModes: [])
}
#objc func deleteAppScreenShot() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors?[0] = Foundation.NSSortDescriptor(key: "creationDate", ascending: true)
let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
guard let lastAsset = fetchResult.lastObject else { return }
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.deleteAssets([lastAsset] as NSFastEnumeration)
} completionHandler: { (success, errorMessage) in
if !success, let errorMessage = errorMessage {
print(errorMessage.localizedDescription)
}
}
}
I am currently using UWP Toolkit to navigate among app pages. There is a page that is being used for initializing and opening RaspberryPi GPIO pins. The following error occurs after navigating away from that page then trying to navigate back to it again.
The process cannot access the file because it is being used by another process .\r\n\r\nPin ' is currently opened in an incompatible sharing mode. Make sure this pin is not already in use by this application or another application
I can see that the constructor is being called each time the page is visited and hence there is an attempt to open pins that are already opened. What is the best way to overcome this issue?
You may add NavigationCacheMode = NavigationCacheMode.Required; to the ctor of the page so your app will not create a new instance of it when you navigate there.
What I always do is let a class deal with managing pins so that your user code can request pins to act on.
public class IO
{
private readonly GpioController _gpioController;
private readonly Dictionary<int, GpioPin> _pins;
public IO(GpioController gpioController)
{
_gpioController = gpioController;
_pins = new Dictionary<int, GpioPin>();
}
public GpioPin OpenPin(int pin, GpioSharingMode mode)
{
if (_pins.ContainsKey(pin))
{
var gpioPin = _pins[pin];
if (gpioPin.SharingMode == mode)
{
return gpioPin;
}
throw new ArgumentException($"Pin '{pin}' is already configured in mode '{gpioPin.SharingMode}'");
}
else
{
var gpioPin = _gpioController?.OpenPin(pin, mode);
_pins[pin] = gpioPin;
return gpioPin;
}
}
}
Then my viewmodels simply request a pin as follows
public MainViewModel()
{
_io = ServiceContainer.Instance.Get<IO>();
_brakingPin = _io.OpenPin(4, GpioSharingMode.Exclusive);
_io.SetDriveMode(_brakingPin, GpioPinDriveMode.Output);
_io.Write(_brakingPin, GpioPinValue.Low);
}
I'm having troubles using the facebook binding from here ( https://github.com/mono/monotouch-bindings/tree/master/facebook ) and the problem is that the authorize ( login ) function doesn't work on the device. On the simulator it's working perfect, but from the device instead of the webbrowser login window it launches the official facebook app ( installed on the phone ).
The same thing happens with the sample provided with the binding.
Any ideeas how can I use the browser to login ( if I unninstall the official facebook app it works ok on the device also ) instead of the facebook app?
The code I use:
class SessionDelegate : FBSessionDelegate
{
AppDelegate container;
NSAction onLogin;
public NSAction OnLogin {
get {
return this.onLogin;
}
set {
onLogin = value;
}
}
public SessionDelegate (AppDelegate container)
{
this.container = container;
}
public override void DidNotLogin (bool cancelled)
{
Console.WriteLine("did not login");
//container.SaveAuthorization ();
if( OnLogin != null ) OnLogin.Invoke();
}
public override void DidLogin ()
{
Console.WriteLine("login !");
container.SaveAuthorization ();
if( OnLogin != null ) OnLogin.Invoke();
}
public override void DidLogout ()
{
Console.WriteLine("logout !");
container.ClearAuthorization();
}
}
And:
var sessionDelegate = new SessionDelegate (this);
facebook = new Facebook (LocalSettings.FacebookAppId, sessionDelegate);
var defaults = NSUserDefaults.StandardUserDefaults;
if (defaults ["FBAccessTokenKey"] != null && defaults ["FBExpirationDateKey"] != null)
{
facebook.AccessToken = defaults ["FBAccessTokenKey"] as NSString;
facebook.ExpirationDate = defaults ["FBExpirationDateKey"] as NSDate;
}
and for login:
facebook.Authorize(new string [] { "email", "publish_stream", "read_friendlists", "user_photos" });
Okay, so I found the answer myself. The problem was in the facebook developer app settings, the iOS Bundle ID didn't was the same with the one in monodevelop identifier :)
This functionality is the developer's intend. First I don't understand why you don't want this and Second this is a open-source library. you can walk through the code and find the piece of code that makes it. Then change it to what you want. I am now searching for this in this library. As long as I got the answer I reply it. But my guess is that they have used UIApplication.SharedApplication.* to open up Facebook app if it is available.
Sincerely yours,
Peyman Mortazavi
FWIW: you'll get the same error if you are in the sandbox and try to logon with a real user.