How can I gracefully handle denied authorization in AVCaptureDeviceInput.init? - swift

The documentation for AVCaptureDeviceInput.init(device:) documents its parameters as:
device
The device from which to capture input.
outError
If an error occurs during initialization, upon return contains an NSError object describing the problem.
This outError out-parameter is, in Swift, represented as a thrown Error. I can catch and display this like so:
do {
let deviceInput = try AVCaptureDeviceInput(device: device)
// ...
}
catch {
print("Error: \(error)")
}
There is one specific case I want to handle gracefully: when the user has denied authorization for application to use the camera. In this case, I get the following output:
Error: Error Domain=AVFoundationErrorDomain Code=-11852 "Cannot use FaceTime HD Camera (Built-in)" UserInfo={NSLocalizedFailureReason=This app is not authorized to use FaceTime HD Camera (Built-in)., AVErrorDeviceKey=<AVCaptureDALDevice: 0x100520a60 [FaceTime HD Camera (Built-in)][0x8020000005ac8514]>, NSLocalizedDescription=Cannot use FaceTime HD Camera (Built-in)}
I need to distinguish this error type from other unexpected errors, like so:
do {
let deviceInput = try AVCaptureDeviceInput(device: device)
// ...
}
catch AVError.Code.applicationIsNotAuthorizedToUseDevice {
// Expected error, handle gracefully
errorMessageBox(errorText: "You have denied authorization to access your camera. Fix this in System Preferences > Security & Privacy.")
}
catch {
// Unexpected errors
errorMessageBox("Error: \(error)")
}
This is pseudocode and does not compile. I know that the error code -11852 is AVError.Code.applicationIsNotAuthorizedToUseDevice. However, I don't know how to get the error code out of the opaque error object in order to test it.
What is the specific type of the error thrown by AVCaptureDeviceInput.init(device:)? How do I extract the AVError.Code from it in order to handle this specific error?

There are two possible approaches. One is to check before you even attempt, e.g.
if AVCaptureDevice.authorizationStatus(for: .video) == .denied {
offerToOpenSettings()
return
}
The other approach is to catch the not authorized error:
let input: AVCaptureDeviceInput
do {
input = try AVCaptureDeviceInput(device: camera)
} catch AVError.applicationIsNotAuthorizedToUseDevice {
offerToOpenSettings()
return
} catch {
print("some other error", error)
return
}
Note, that’s catching AVError.applicationIsNotAuthorizedToUseDevice, not AVError.Code.applicationIsNotAuthorizedToUseDevice.
If, for example, this was an iOS app, you could have a function to offer to redirect the user to settings app:
func offerToOpenSettings() {
guard
let settings = URL(string: UIApplication.openSettingsURLString),
UIApplication.shared.canOpenURL(settings)
else { return }
let alert = UIAlertController(title: nil, message: "Would you like to open Settings to enable permission to use the camera?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
UIApplication.shared.open(settings)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
Note, since this is potentially presenting an alert, you don’t want to trigger this in viewDidLoad (which is too early in the process), but rather viewDidAppear.
Or, on macOS, maybe something like:
func offerToOpenSettings() {
let preferences = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera")!
let alert = NSAlert()
alert.messageText = #"The camera is disabled. Please go to the “Camera” section in Security System Preferences, and enable this app."#
alert.addButton(withTitle: "System Preferences")
alert.addButton(withTitle: "Cancel")
if alert.runModal() == .alertFirstButtonReturn {
NSWorkspace.shared.open(preferences)
}
}

Related

swift webrtc can not force audio to speaker

before ios16, my webrtc call work fine, but after upgrade to ios16, i can not hear anything, and can not talk to other. Here is my code:
func speakerOn() {
audioQueue.async { [weak self] in
guard let self = self else { return }
self.rtcAudioSession.lockForConfiguration()
do {
try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.mixWithOthers])
try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
try self.rtcAudioSession.overrideOutputAudioPort(.speaker)
try self.rtcAudioSession.setActive(true)
} catch let error {
debugPrint("Couldn't force audio to speaker: \(error)")
}
self.rtcAudioSession.unlockForConfiguration()
}
}
here is the problem i got:
"Couldn't force audio to speaker: Error Domain=NSOSStatusErrorDomain Code=1701737535 "Session activation failed" UserInfo={NSLocalizedDescription=Session activation failed}"

Firebase Functions in swift does not return anything if there is no Internet

There is a function that is triggered after the AccountKit authorization, it calls the Firebase Function to validate the token on Facebook and returns a userId if everything is confirmed, and registers the user if he has not yet been registered.
It works fine when Internet is available, but while offline - Firebase function does not return or throw any errors or at least nil results, and I would like it to return an error such as No internet connection or ANYTHING that could be catched.
Digging web and APIReference brought no results. Does the call of firebase function really not return anything in such cases (offline)?
func checkUserCredentials(phoneNumber: String, FBId: String, Token: String) {
functions.httpsCallable("checkUserCredentials").call(["phone":"\(phoneNumber)", "FBId":"\(FBId)", "Token":"\(Token)"])
{ (result, error) in
if let error = error as NSError?
{
if error.domain == FunctionsErrorDomain
{
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
}
}
if let userDoc = (result?.data as? [String: Any])?["userID"] as? String
{
DispatchQueue.main.async(execute: { self.performSegue(withIdentifier: "StartTheApp", sender: self) })
}
} }
I recommend checking for a network connection before making any network request. That way you're not dependent on the vagaries of whichever library you're using to talk to the network.
I use Reachability to check for a network connection before performing any requests (which I then perform using Alamofire). Below is a sample function to check for network:
import Reachability
...
func networkIsReachable(shouldShowAlert: Bool) -> Bool {
if let reachability: Reachability = Reachability(), reachability.connection != .none {
return true
}
if shouldShowAlert {
let alertController = UIAlertController(title: "Error", message: "No internet connection.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
present(alertController, animated: true, completion: nil)
}
return false
}
Since I'm using this function all throughout my codebase, I even moved it into an extension so as not to violate DRY principle.
Updating your code to use this function would look like this:
func checkUserCredentials(phoneNumber: String, FBId: String, Token: String) {
guard let networkIsReachable(shouldShowAlert: true) else {
// network is not reachable, and user has been shown an error message
return
}
// now perform network request
// ...
}

Cannot modify user Parse

Trying to save logged in parse user's value, it only works for the first time but when i close the app and reopen it, it doesn't work again.
This is the save code I'm using which seems alright
PFUser.current()["about"] = textfield.text
PFUser.current().saveInBackground()
and this is the error i get when trying to save the objects to current user.
PFKeychainStore failed to set object for key 'currentUser', with error: -34018
or
cannot modify user objectIDxx
This started happening after i installed parse server instead of parse.com
Were you using "revocable sessions" before? If not, parse-server requires you to use them. You can check out the migration tutorial here.
You'll need to add this after you initialize parse:
[PFUser enableRevocableSessionInBackground]
And then you will need to re-login a user if you get an 'invalid session' error from parse.
// Swift
class ParseErrorHandlingController {
class func handleParseError(error: NSError) {
if error.domain != PFParseErrorDomain {
return
}
switch (error.code) {
case kPFErrorInvalidSessionToken:
handleInvalidSessionTokenError()
... // Other Parse API Errors that you want to explicitly handle.
}
private class func handleInvalidSessionTokenError() {
//--------------------------------------
// Option 1: Show a message asking the user to log out and log back in.
//--------------------------------------
// If the user needs to finish what they were doing, they have the opportunity to do so.
//
// let alertView = UIAlertView(
// title: "Invalid Session",
// message: "Session is no longer valid, please log out and log in again.",
// delegate: nil,
// cancelButtonTitle: "Not Now",
// otherButtonTitles: "OK"
// )
// alertView.show()
//--------------------------------------
// Option #2: Show login screen so user can re-authenticate.
//--------------------------------------
// You may want this if the logout button is inaccessible in the UI.
//
// let presentingViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
// let logInViewController = PFLogInViewController()
// presentingViewController?.presentViewController(logInViewController, animated: true, completion: nil)
}
}
// In all API requests, call the global error handler, e.g.
let query = PFQuery(className: "Object")
query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// Query Succeeded - continue your app logic here.
} else {
// Query Failed - handle an error.
ParseErrorHandlingController.handleParseError(error)
}
}

Signing Out of Firebase in Swift

I am attempting to sign out of the Firebase API, but I can't seem to figure out how to handle any errors that may occur.
The Firebase pod provides a method for signing out:
FIRAuth.auth()?.signOut()
It is marked with throws, so I have wrapped it in a do/try/catch block in a method to test the signing out process:
do {
try FIRAuth.auth()?.signOut()
} catch (let error) {
print((error as NSError).code)
}
I see that the signOut method is marked with throws in the Firebase pod, but I don't see how it can handle any errors asynchronously. I have tried entering Airplane Mode, which triggers a network error in my code everywhere else that a network request takes place, but with the signOut method, that error isn't caught because I have no completion handler to execute from. All of the other authentication methods from the Firebase pods have a completion handler, in which I am able to handle errors.
Here is the documentation for the signOut method from the Firebase pod:
/** #fn signOut:
#brief Signs out the current user.
#param error Optionally; if an error occurs, upon return contains an NSError object that
describes the problem; is nil otherwise.
#return #YES when the sign out request was successful. #NO otherwise.
#remarks Possible error codes:
- #c FIRAuthErrorCodeKeychainError Indicates an error occurred when accessing the keychain.
The #c NSLocalizedFailureReasonErrorKey field in the #c NSError.userInfo dictionary
will contain more information about the error encountered.
*/
open func signOut() throws
Do you have any suggestions for an appropriate way to handle the signing out of a user when I don't have a completion handler that allows me to check for an error?
You can catch the error like this
do
{
try Auth.auth().signOut()
}
catch let error as NSError
{
print(error.localizedDescription)
}
Edited from Milli's answer to add sending user back to initial page of the app.
// log out
func logout(){
do
{
try Auth.auth().signOut()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let IntroVC = storyboard.instantiateViewController(withIdentifier: "IntroVC") as! introVC
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController = IntroVC
}
catch let error as NSError
{
print(error.localizedDescription)
}
}
An error is highly unlikely to occur but it's never good to assume anything. By the sound of the documentation, it wipes out your keychain which is the only way you'd be able to log back into your firebase application. From trying logging out of my own firebase app I was surprise that 0 errors occured. Here is the original code.
#IBAction func logOutTapped(_ sender: Any) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
if Utility.hasFacebook {
let login = FBSDKLoginManager()
login.logOut()
}
if Utility.hasTwitter {
Twitter.sharedInstance().sessionStore.logOutUserID((Twitter.sharedInstance().sessionStore.session()?.userID)!)
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginVC")
self.present(initialViewController, animated: false)
}
Anyways if you really want a completion handler then here's something I tossed up quickly
func logOut(completion:#escaping(_ errorOccured: Bool) -> Void) {
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
completion(true)
}
completion(false)
}

Swift Promise Kit and throws

So I'm using PromiseKit in my latest swift app to do most of the networking code, along with Alamofire. I'm trying to setup my promises to throw when my returns aren't what I desire - here's what the code looks like:
`
do{
firstly({
try DoStuff.doStuff()
}).then({ response in
self.array = response
}).error { error in
throw Error.GeneralError
print(error)
}
firstly({
try DoOtherThing.otherThing()
}).then({ response in
self.stuff = response
}).error{ error in
throw TransactionError.GeneralError
print(error)
}
} catch {
let alertController = UIAlertController(title: "Network Error", message: "Network error, please try again", preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in
//
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true) {
//
}
}
`
This code works just hunky dory if I don't have the 'throw' statements in there - if I just print the error, or put my alert controller code in there, works as expected. But when I add the throw, I get an compiler red flag on the 'error' line that says Cannot call value of non function type 'ErrorType' Any thoughts? Thanks
The way you would do this with PromiseKit would be something like:
let stuff = firstly {
try DoStuff.doStuff()
}.then { response in
self.array = response
}
let otherStuff = firstly {
try DoOtherThing.otherThing()
}.then { response in
self.stuff = response
}
when(fulfilled: stuff, otherStuff).catch { _ in
let alertController = UIAlertController(title: "Network Error", message: "Network error, please try again", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) { (action) in
//
}
alertController.addAction(OKAction)
self.present(alertController, animated: true) {
//
}
}
In the above, I'm assuming that doStuff() and doOtherThing() are both synchronous functions that throw on error. As such, it doesn't make a lot of sense to wrap them in promises unless you are using the results to feed an asynchronous task and then using the result from that.
I think your understanding of do/catch isn't quite right.
Do/Catch is a synchronous operation only, so to catch the throw, the error must be thrown whilst in the do block. In this case, all you're doing inside the do block is setting up the promise. Should the error condition ever be reached, it will be executed asynchronously in a different context - outside of your do catch block and so cannot be caught.
EDIT:
To make it clearer why you are getting the error, here is the method signature for error in PromiseKit:
func error(policy policy: ErrorPolicy = .AllErrorsExceptCancellation, _ body: (ErrorType) -> Void)
The 'body' closure is not declared as throwing so therefore you cannot throw to exit that context. To throw, it would need to be declared like this:
func error(policy policy: ErrorPolicy = .AllErrorsExceptCancellation, _ body: (ErrorType) throws -> Void)
But it can't because it executes it asynchronously.