iOS permission alert issue - ios

I have a view that:
Creates an observer for UIApplicationDidBecomeActiveNotification with invokes a selector
Sequentially asks the user for permissions to: use the camera, location & receiving push notifications.
The view has three UIButtons with state depending on each permission state, which navigate the user to settings if permissions for anything were rejected
Tapping a button which represents a permission with rejected state navigates the user to settings
Once each alert hides, using the observer action, next alert is triggered and all button states are updated to reflect any changes
Once all permissions are granted it pushes next view with the rest of the signup/in flow.
The problem is: on some devices, when running the app from a clean state (app removed and reinstalled), permissions for location & notifications are set to rejected by default, as if the user was presented an alert that was rejected.
I couldn't pinpoint any rational issue behind this, except for leftover settings from some outdated build that don't get deleted when installing a new one. This view seems to be the only place that can possibly trigger these alerts.
Did anyone have a similar issue and can suggest anything?

I would suggest you to try to check for states of location services and notification services before asking user to use it. Since if user is going to disable these the moment you ask him for permission, he will need to go to the settings and enable it there. You should try to detect if user has disabled location/notification/camera.
For camera use:
func accessToCamera(granted: #escaping (() -> Void)) {
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeAudio)
if status == .authorized {
granted()
} else if status == .denied {
self.cameraPermissionAlert()
} else if status == .notDetermined {
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (accessAllowed) in
if accessAllowed {
granted()
} else {
self.cameraPermissionAlert()
}
})
} else if status == .restricted {
self.cameraPermissionAlert()
}
} else {
print("Camera not available on this device")
}
}
func cameraPermissionAlert() {
let alert = UIAlertController(title: "Access to camera not available", message: "Please enable access to camera in order to use this feature", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default, handler: { (action) in
if let url = URL(string: UIApplicationOpenSettingsURLString) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil))
if let top = UIApplication.topViewController() { // This is extension to UIApplication that finds top view controller and displays it
top.present(alert, animated: true, completion: nil)
}
}
For remote notifications you can use something like this:
Determine on iPhone if user has enabled push notifications
And for location services:
Check if location services are enabled
In both of these cases you can detect if this is disabled or not by user and present user with alert controller that has open settings functionality.

Related

SwiftyStoreKit IAP Purchasing but not remembering it's settings after launch

I am trying to integrate a very basic single IAP in my game, here I am calling from my GameScene
let alert = UIAlertController(title: "Upgrade", message: "Would you like to remove ads?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Remove Ads", style: .default, handler: { action in
print("Pressed Remove Ads")
GameViewController().buytheIAP()
}))
alert.addAction(UIAlertAction(title: "Restore Purchases", style: .default, handler: { action in
print("Pressed Restore")
GameViewController().restoretheIAP()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { action in
print("Pressed Cancel")
}))
view?.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
Those methods are called correctly, and refer to these inside GameViewController.swift;
func buytheIAP(){
iAPHelper.purchase()
print("OK Lets upgrade")
}
func restoretheIAP(){
iAPHelper.restorePurchase()
print("OK Lets restore")
}
func restoreDidSucceed() {
UserDefaults.setValue(true, forKey: iAPHelper.productID)
//this should have something like hide banner etc.
bannerView.isHidden = true
}
func purchaseDidSucceed() {
UserDefaults.setValue(true, forKey: iAPHelper.productID)
//as above this should have something like hide banner etc.
bannerView.isHidden = true
print("Purchased upgrade ENJOYYYYYYYY")
}
func nothingToRestore() {
}
func paymentCancelled() {
}
The test IAP goes through, it gets the correct information from the app store, and I use my sandbox details to purchase, it goes through correctly with a successful purchase message. However, the bannerView does not hide, and more importantly, upon restarting the game again, everything is forgotten and the game thinks nothing has been purchased. I am guessing it has to be some sort of check that is missing.
I have this in my viewDidLoad
if userDefaults.bool(forKey: iAPHelper.productID) {
bannerView.isHidden = true
print("It is purchased, so DO NOT show the ads")
} else{
bannerView.adSize = getAdaptiveSize()
bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
bannerView.delegate = self
bannerView.rootViewController = self
bannerView.load(GADRequest())
addBannerViewToView(bannerView)
print("Not purchased, so show the ads")
}
And it ALWAYS shows print("Not purchased, so show the ads")
The IAPHelper file, for purchasing is;
func purchase() {
SwiftyStoreKit.purchaseProduct(productID, quantity: 1, atomically: true) { [self] result in
switch result {
case .success:
delegate?.purchaseDidSucceed()
print("OK It's purchased")
case .error(let error):
switch error.code {
case .unknown: print("Unknown error. Please contact support")
case .clientInvalid: print("Not allowed to make the payment")
case .paymentCancelled:
delegate?.paymentCancelled()
case .paymentInvalid: print("The purchase identifier was invalid")
case .paymentNotAllowed: print("The device is not allowed to make the payment")
case .storeProductNotAvailable: print("The product is not available in the current storefront")
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
default: print((error as NSError).localizedDescription)
}
}
}
}
}
And the log DOES show print("OK It's purchased") after the initial purchase - so I am struggling to see what is going wrong.
The IAP delegate functions are not (guaranteed to be) called on the UI/main thread, that's why your view doesn't hide.
Don't you see some iOS warning saying that you try to set a UIView property on non-main thread?
The fact that your custom purchase info was not saved in UserDefaults could be caused by killing the app prematurely from Xcode.
(UserDefaults does take of saving what's being set during normal app flow.)
OK, so I figured the answer, there was a missing line in my AppDelegate.swift section didFinishLaunchingWithOptions
UserDefaults.standard.register(defaults: ["adRemove" : false])
I renamed userDefaults.bool(forKey: iAPHelper.productID) to make it easier to use / understand. So in my original post that has been replaced by UserDefaults.standard.register(defaults: ["adRemove" : false]) instead.
You can then use this anywhere to check, such as;
if !userDefaults.bool(forKey: "adRemove") {
// Do something here
}
Hope this helps anyone in future with the same question!

IOS share extension show alert if user is not logged in?

I had added share extension to upload file.
But i want to stop open share extension when user is not logged in application and show alert similar like Messenger application from facebook.
Facebook messenger app
How can i do this
Note : I know how to do check is User logged in or not using app groups. But I want to check before open the share extension and show alert. in my case its first open the share extension and then i am showing alert. I want to check before open the share extension
Rather than directly open your custom view for share extension you could use alert first to check if user logged in or not, then if user has logged in, you can proceed to present your custom view with animation.
you can do this by adding below method on ShareViewController: SLComposeServiceViewController.
override func viewDidLoad() {
// check if user logged in or not here and if not below code will be executed.
self.loginAlertController = UIAlertController(title: "Please launch application from the home screen before continuing.", message: nil, preferredStyle: .alert)
let onOk = UIAlertAction(title: "OK", style: .destructive) { alert in
self.extensionContext?.cancelRequest(withError: NSError(domain: "loging", code: 0, userInfo: nil))
}
loginAlertController!.addAction(onOk)
present(loginAlertController!, animated: true, completion: nil)
}
You should make use of App groups to communicate between the main app and extension app.(Sharing Data: NSUserDefaults and App Groups
)
You can add data in main app like this :-
let mySharedDefaults = UserDefaults(suiteName: "group.yourValue")
mySharedDefaults?.set(false, forKey: "isLoggedIn")
Then you can get data like this in your extension
let mySharedDefaults = UserDefaults(suiteName: "group.yourValue")
if let isLoggedIn = mySharedDefaults?.value(forKey: "isLoggedIn") as? Bool {
if !isLoggedIn {
showAlert()
}
}
Refer this for implementing app groups

Alert Box Asking User to Grant Location Privileges is not showing

I am just getting started with SWIFT programming on iOS. However I have good experience in other languages such as JAVA, C# and PHP. That said, I am running into a little bit of a problem. I am creating a simple application that will show a user information based on their geographical location. I am following the tutorial listed here.
However, since a lot of things have changed between Swift 2.x and 3.x I have had to modify his code. Not too hard and so far I have gotten it to compile. However, I am never asked by the application to allow me to use the device's location.
I have followed the directions Apple listed here about putting "NSLocationAlwaysUsageDescription" in the Info.plist. However I am never prompted. The relevant piece of code that should be showing this alert is listed below.
Any help would be greatly appreciated. Thanks.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 1. status is not determined
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestAlwaysAuthorization()
}
// 2. authorization were denied
else if CLLocationManager.authorizationStatus() == .denied {
let controller = UIAlertController(title: "Error!", message: "Location services were previously denied. Please enable location services for this app in Settings.", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "Dismiss", style: .destructive) { (action) in
print("Dismiss button tapped!")
}
controller.addAction(alertAction)
}
// 3. we do have authorization
else if CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager.startUpdatingLocation()
}
}
Your code is working fine you just need to present your alert:
self.present(controller, animated: true, completion: nil)
And also take a look at my answer here how you can use switch for this types of code.

Why is 'CLLocationManager.locationServicesEnabled()' true by default?

I have noticed the following with a plain, completely new project in Xcode.
If, in the ViewController.swift file I import CoreLocation, and then in the viewDidLoad method I add...
print(CLLocationManager.locationServicesEnabled())
..., when the app runs in simulator Xcode prints out true. I would have thought that location services would be disabled by default, but as you can see for yourself, the opposite is true. If I wanted I could add some more code to gather location information about the user, and all this without ever having to ask for permission.
Can anybody explain why this is?
As far as I know, CLLocationManager.locationServicesEnabled() will return whether location Services are enabled on the device, not just for that one app. So even if location Services are disabled for that app, if they are enabled for the device, I think that will still return true, as per the documentation: https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/locationServicesEnabled
In my app, I set it up like this:
//check if location services are enabled at all
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
//check if services disallowed for this app particularly
case .Restricted, .Denied:
print("No access")
var accessAlert = UIAlertController(title: "Location Services Disabled", message: "You need to enable location services in settings.", preferredStyle: UIAlertControllerStyle.Alert)
accessAlert.addAction(UIAlertAction(title: "Okay!", style: .Default, handler: { (action: UIAlertAction!) in UIApplication.sharedApplication().openURL(NSURL(string:UIApplicationOpenSettingsURLString)!)
}))
presentViewController(accessAlert, animated: true, completion: nil)
//check if services are allowed for this app
case .AuthorizedAlways, .AuthorizedWhenInUse:
print("Access! We're good to go!")
//check if we need to ask for access
case .NotDetermined:
print("asking for access...")
manager.requestAlwaysAuthorization()
}
//location services are disabled on the device entirely!
} else {
print("Location services are not enabled")
}
Good luck!
Swift 3.1 function to return status and error message
func isLocationEnabled() -> (status: Bool, message: String) {
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
return (false,"No access")
case .authorizedAlways, .authorizedWhenInUse:
return(true,"Access")
}
} else {
return(false,"Turn On Location Services to Allow App to Determine Your Location")
}
}

Change push notifications programmatically in Swift

I have an app where I have implemented push notifications. I check with the user to allow remote notifications with:
let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(settings)
UIApplication.sharedApplication().registerForRemoteNotifications()
I also store the response in a DB with the device token.
I have also implemented a UISwitch in a settings page to enable/disable push notifications however I have only been able to enable/disable this in the DB column.
My problem is that if a user selected Don't Allow in the initial request, I cannot enable push notifications in the phone settings so even if I set the value to enable in the DB, the notification will never reach the phone as the phone settings are still set to disable.
Is there a way in Swift 2 to change the push notification in the phone setting from within the app instead of the user having to go in to the settings to change? Or is it completely redundant to have a UISwitch allow the user to toggle on/off push notifications?
There isn't any way you can change push notifications permission status from program. Also, prompt asking user to allow push notifications can not be shown again and again. You can refer this https://developer.apple.com/library/ios/technotes/tn2265/_index.html.
The first time a push-enabled app registers for push notifications, iOS asks the user if they wish to receive notifications for that app. Once the user has responded to this alert it is not presented again unless the device is restored or the app has been uninstalled for at least a day.
So using UISwitch to toggle permission status doesn't make any sense unless you use switch status to turn on/off remote notifications from your server.
Updated with swift 4 :
func switchChanged(sender: UISwitch!) {
print("Switch value is \(sender.isOn)")
if(sender.isOn){
print("on")
UIApplication.shared.registerForRemoteNotifications()
}
else{
print("Off")
UIApplication.shared.unregisterForRemoteNotifications()
}
}
if you are using FireBase to send push notifications to your devices , you can use topic subscription to enable push notification in subscribed devices and unsubscribe users from the topic when you don't want the user to receive push notification in those devices that have been unsubscribed.
to subscribe a user to a topic simply import Firebase, then use this method:
Messaging.messaging().subscribe(toTopic: "topicName")
and to unsubscribe a user use:
Messaging.messaging().unsubscribe(fromTopic: "topicName")
if settings.authorizationStatus != .authorized{
// Either denied or notDetermined
// Ask the user to enable it in settings.
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
// add your own
UNUserNotificationCenter.current().delegate = self
let alertController = UIAlertController(title: appName, message: "Please enable notifications in settings and try again.", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
})
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(settingsAction)
DispatchQueue.main.async {
(UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
}

Resources