I am using local notifications in my app, before presenting user with new notification screen I want to check the authorisation status first. I am using shouldPerformSegue(identifier:, sender:) -> Bool method, so that if the notifications are not authorised by user, the scene where the user configures and saves a new notification is not presented:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "AddReminder" {
// Check to see if notifications are authorised for this app by the user
let isAuthorised = NotificationsManager.checkAuthorization()
if isAuthorised {
print(isAuthorised)
return true
}
else {
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) {
(action: UIAlertAction) in
}
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsURL = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsURL) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (success) in
print("Settings opened: \(success)")
})
}
}
alert.addAction(cancelAction)
alert.addAction(settingsAction)
present(alert, animated: true, completion: nil)
print(isAuthorised)
return false
}
}
// By default, transition
return true
}
Here is the method I use for authorisation check:
static func checkAuthorization() -> Bool {
// var isAuthorised: Bool
var isAuthorised = true
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined:
self.requestAuthorization(completionHandler: { (success) in
guard success else { return }
})
print("Reached .notDetermined stage")
case .authorized:
isAuthorised = true
case .denied:
isAuthorised = false
}
}
//print("Last statement reached before the check itself")
return isAuthorised
}
I figured that the last statement in the above function (return isAuthorized) returned before the body of UNUserNotificationCenter.current().getNotificationSettings{} is executed, therefore it always returns whatever isAuthorized is configured to, at the very beginning of the method.
Question:
Could you please suggest how I could check for authorisation using I better way, since my way does not even work.
This is only my first IOS app, so I am relatively new to IOS development; any help would be greatly appreciated.
If anyone having similar problem, then instead of using getNotificationsSettings(){} method, which will be computed after the enclosing method is returned; we could use different approach, i.e. getting currentUserNotificationSettings, which is notification settings of our app. Then check if current settings contain .aler, .sound and etc. If the answer is YES, then we could be sure that the applications has its notifications enabled.
Related
In my scenario, User will get an alert for receiving Notification in application. If the user clicks on "Don't Allow" UILabel is updated with "Not enabled". If the user wants to change the notification,User will be navigated to application setting page to change the notification permission status.
func checkNotificationPermission(){
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]){
(granted, error) in
if granted == true {
DispatchQueue.main.async {
print("notificaation access true")
self.notificationAccessLbl?.text = "Enabled"
}
}
else {
DispatchQueue.main.async {
self.notificationAccessLbl?.text = "Not enabled"
}
}
}
UIApplication.shared.registerForRemoteNotifications() }
But when the user comes back to application, The UILabel is not getting updated when the user comes to application from Setting page.
for Updating the UILabel in application after the user comes from setting page to application. I Have Called
func checkNotificationPermission()
to update UILabel Value in ViewDidAppear() Method and I register the a function in applicationwillbecomeactive method() Kindly help me in this.
I have switch in setting page in application which allows user to enable disable push and that will be send on server but before that user must have allowed push from settings page of Device. here is my solution
I have created global object
var isPushEnabledFromSettings = false {
didSet {
// you can set label value here in main queue
}
}
and one method to check it
func isPushPermissionGiven (permission:#escaping (Bool) -> ()) {
if #available(iOS 10.0, *) {
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: {settings in
switch settings.authorizationStatus {
case .notDetermined:
permission(false)
case .denied:
permission(false)
case .authorized:
permission(true)
}
})
} else {
// Fallback on earlier versions
if UIApplication.shared.isRegisteredForRemoteNotifications {
permission(true)
} else {
permission(false)
}
}
}
and in view did load added these lines
self.isPushPermissionGiven { (permission) in
self.isPushEnabledFromSettings = permission
}
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationDidBecomeActive, object: nil, queue: .main) {[weak self] (notificaiont) in
guard let strongSelf = self else {return }
strongSelf.isPushPermissionGiven { (permission) in
DispatchQueue.main.async {
strongSelf.isPushEnabledFromSettings = permission
}
}
}
Now I have switch in setting page which allows user to enable disable push
#objc func switchChanged (sender:UISwitch) {
guard self.isPushEnabledFromSettings else {
AppDelegate.sharedDelegate.navigateUesrToSettings(withMessage: "Please grant push permission from settings")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
sender.setOn(false, animated: false)
})
return
}
}
func navigateUesrToSettings (withTitle title:String = "YourApp", withMessage message:String) {
let alertController = UIAlertController (title: title, message: message, preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let _ = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
self.navigate(To: UIApplicationOpenSettingsURLString)
}
alertController.addAction(settingsAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
AppDelegate.sharedDelegate.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
Hope it is helpful to you :)
I have sample app with a button. On click button should be taking the user to push notification service for my app to be able to disable them or enable.
I know how to get to the general setting with this sample code but I think for notification probably you need some additional parameters like bundleId.
My question is more about URL for push notification for my app not only get to general setup as this is shown on code sample below
Sample code:
#IBAction func pushNotificationSettings(button: UIButton) {
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
print("Settings opened: \(success)") // Prints true
})
} else {
// Fallback on earlier versions
}
}
}
For IOS 10 or above
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
if settings.authorizationStatus == .authorized {
// Notifications are allowed
}
else {
// Either denied or notDetermined
let alertController = UIAlertController(title: nil, message: "Do you want to change notifications settings?", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Settings", style: .default) { (action:UIAlertAction) in
if let appSettings = NSURL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(appSettings as URL, options: [:], completionHandler: nil)
}
}
let action2 = UIAlertAction(title: "Cancel", style: .cancel) { (action:UIAlertAction) in
}
alertController.addAction(action1)
alertController.addAction(action2)
self.present(alertController, animated: true, completion: nil)
}
}
In the sign-up process for my app, I have two function - first checks the user's entered handle to see if it's already taken, and then the second function sets the rest of their values:
#IBAction func setupDoneButtonPressed(_ sender: Any) {
checkHandle()
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
}
The checkHandle function seems to be doing it's job, in that it checks the database then prints the "else" condition print statement - however I don't see the alert, and the program simply segues into the app.
If that handle is already in use, I need to program to halt and not move on to setUserInfo and then segue into the app. I'd like to display the alert that I have in there, then allow the user to try again with a different handle.
This is the checkHandle function:
func checkHandle() {
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
let handleRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users")
handleRef.queryOrdered(byChild: "handle").queryEqual(toValue: "\(handle)").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value is NSNull) {
print("handle not in use") // handle not found
userRef.child("handle").setValue(handle)
} else {
print("Handle already in use. Value: \(snapshot.value)") // handle is in use
let alertController = UIAlertController(title: "Handle Taken", message: "Please choose a different handle", preferredStyle: UIAlertControllerStyle.actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
})
}
}
}
What can I do to ensure that the sign-up process stops in the case of an already-existing handle?
You can solve it like that:
First make checkHandle() a function that takes completion like checkHandle(completion: #escaping (Bool) -> Void)
Then inside this function invoke completion(true) if the if condition is met and completion(false) if it is not. Then in your button handler use checkHandle() like that:
checkHandle { [weak self] success in
guard success else { return }
self?.setUserInfo()
self?.performSegue(withIdentifier: "setupToChat", sender: nil)
}
your code is just continuing processing and not taking any conditational action. I would suggest that you make your checkHandle function return a boolean value and then respond accordingly.
func checkHandle(handle: String) -> Bool {
someBool = // some logic to see if handle exists.
return someBool
}
#IBAction func setupDoneButtonPressed(_ sender: Any) {
if checkHandle() {
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
} else {
// present an error, or make suggestions etc
}
}
#IBAction func setupDoneButtonPressed(_ sender: Any) {
if checkHandle()
{
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
}
}
func checkHandle() -> Bool {
let isAvailable = false
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
let handleRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users")
handleRef.queryOrdered(byChild: "handle").queryEqual(toValue: "\(handle)").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value is NSNull) {
print("handle not in use") // handle not found
userRef.child("handle").setValue(handle)
isAvailable = true
} else {
print("Handle already in use. Value: \(snapshot.value)") // handle is in use
let alertController = UIAlertController(title: "Handle Taken", message: "Please choose a different handle", preferredStyle: UIAlertControllerStyle.actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
isAvailable = false
}
})
}
}
if isAvailable
{
return true
}
return false
}
I am trying to make an iOS app using MVVM architecture. Now what I want is that if an observable fails, I will show the user a prompt asking if he wants to cancel or retry. This should be simple enough with retryWhen but retry is never called. Here my code:
.retryWhen({ (errorObservable: Observable<Error>) -> Observable<Error> in
return promptFor("test", cancelAction: RetryResult.cancel, actions: [RetryResult.retry])
.flatMap { action -> Observable<Error> in
switch action {
case .retry:
return errorObservable
case .cancel:
return errorObservable.flatMap { Observable.error($0) }
}
}
})
prompt is just a method I took from RxSwiftExample and can be found in the repo, but for ease here it is:
func promptFor<Action : CustomStringConvertible>(_ message: String, cancelAction: Action, actions: [Action]) -> Observable<Action> {
#if os(iOS)
return Observable.create { observer in
let alertView = UIAlertController(title: "RxExample", message: message, preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: cancelAction.description, style: .cancel) { _ in
observer.on(.next(cancelAction))
})
for action in actions {
alertView.addAction(UIAlertAction(title: action.description, style: .default) { _ in
observer.on(.next(action))
})
}
(UIApplication.shared.delegate as! AppDelegate).window?.rootViewController?.present(alertView, animated: true, completion: nil)
return Disposables.create {
alertView.dismiss(animated:false, completion: nil)
}
}
#elseif os(macOS)
return Observable.error(NSError(domain: "Unimplemented", code: -1, userInfo: nil))
#endif
can anyone explain why this is not working? or even offer a solution?
Ultimately, what I want to have is one function (or probably a function within a separate class) that prompts the user to authenticate via TouchID, then passcode and if either of these are successful then returns a true boolean.
I've figured out the authentication mostly however I can't get the function to return a boolean, here's roughly what I have so far:
The authenticate user function:
func authenticateUser() -> Bool {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your places."
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("touchID authentication succesful")
} else {
switch policyError!.code {
case LAError.UserFallback.rawValue:
print("User selected to enter password.")
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
default:
print("Authentication failed! :(")
}
}
})
} else {
print(error?.localizedDescription)
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
}
return true
}
It's just set to return true for now for testing purposes. However I'd like to have it return true whenever there's a successful authentication. I can't place the return within the context.evaluatePolicy because it's inside the block method. Is there another way to do what I want? Or am I going about this in totally the wrong manner?
Also, for reference here is my showPasswordAlert function:
func showPasswordAlert() {
let alertController = UIAlertController(title: "Passcode", message: "Please enter your passcode.", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in
if let textField = alertController.textFields?.first as UITextField? {
if let passcode = self.keychainWrapper.myObjectForKey("v_Data") as? String {
if textField.text == passcode {
print("Authentication successful! :) ")
} else {
}
}
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Enter passcode..."
textField.textAlignment = NSTextAlignment.Center
textField.secureTextEntry = true
textField.keyboardType = UIKeyboardType.NumberPad
}
self.presentViewController(alertController, animated: true, completion: nil)
}
So in my head what I'm thinking is: showPasswordAlert could also return a true boolean to authenticateUser and then this would in turn return a true boolean to where the authenticateUser function is being called. I know there's a simpler way to do that but I'd just like to get it working for now.
So after much trial and error I've come up with possibly what is the best solution for me at the moment.
It seems that since evaluatePolicy and co. are run asynchronously you can't return variables from them or access variables. You can however, call selectors from inside these blocks (why this is I have no idea).
So my current solution as of writing this post is to call the authenticate function as such:
func authenticateUserWithAction(actionSelector: Selector, object: AnyObject){}
I pass it an action (declared elsewhere in the class, but basically what you want to do if authentication is successful) and an object. The object is just incase the action requires something to be passed to the function. So in my app for example, after authentication a viewController is presented and an object on that viewController is set to an object in the original viewController. This object is passed in the authenticate function.
From within the authenticate user function I can call to an authenticateUserWithPasscode(actionSelector: Selector, object: AnyObject) that takes in the same action and object as the original authenticate function.
The action and object are passed down the chain until the user is authenticated and they are performed.
Pretty hacky code overall but it seems to be working fine for me.
Also had this problem, I ended up making a struct called Authentication which has a static function authenticate which gets passed the view controller from where you're calling it and a callback function:
import LocalAuthentication
import UIKit
struct Authentication {
static func authenticate(viewController: UIViewController, callback:
#escaping (Bool) -> ()) {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Please Authenticate"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) {
[weak viewController] success, authenticationError in
guard let viewController = viewController else {
return
}
DispatchQueue.main.async {
if success {
callback(true)
} else {
let ac = UIAlertController(title: "Authentication failed", message: "Please try again", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
} else {
let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
Then calling it:
Authentication.authenticate(viewController: parentViewController, callback: {
[weak self] (authenticated: Bool) in
if authenticated {
self?.yourFunctionHere()
}
})