How to detect user giving microphone permission on iOS? - ios

So the thing is that I need to call some function after user gives (or declines) a permission to use the microphone.
I already saw this:
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
if (granted) {
// Microphone enabled code
[self someFunction];
}
else {
// Microphone disabled code
}
}];
However, this works only to detect current state.
If the current state is "no" and popup shows and user gives the permission - the function will not be called. That's because in the moment of executing this the permission was "no" and until we run the code next time the function will not be called.
What I want to do is to call a function after the user pressed either "allow" or "decline".
Anyone knows how to do this?
EDIT:
Forgot to mention it has to be iOS 7.0 up compatible solution.

A method of AVAudioSession introduced in iOS 8 is recordPermission. This returns an enum named AVAudioSessionRecordPermission. You could use a switch to determine whether the permission alert has been presented to the user or not. This way you only call requestRecordPermission when it has not been presented to the user, so the permission block can assume it is being executed after the user allows or disallows permission for the first time.
An example would be something like -
AVAudioSessionRecordPermission permissionStatus = [[AVAudioSession sharedInstance] recordPermission];
switch (permissionStatus) {
case AVAudioSessionRecordPermissionUndetermined:{
[[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
// CALL YOUR METHOD HERE - as this assumes being called only once from user interacting with permission alert!
if (granted) {
// Microphone enabled code
}
else {
// Microphone disabled code
}
}];
break;
}
case AVAudioSessionRecordPermissionDenied:
// direct to settings...
break;
case AVAudioSessionRecordPermissionGranted:
// mic access ok...
break;
default:
// this should not happen.. maybe throw an exception.
break;
}

If use has not yet given your permission, do the following:
First, show the popup dialogue
Run your code in OP
-
if([[AVAudioSession sharedInstance] respondsToSelector:#selector(requestRecordPermission)]) {
[[AVAudioSession sharedInstance] requestRecordPermission];
// Now run your function
}

Related

Pop ups are not showing

I am developing an app that needs to access both the user's location and calendar, a pop up should appear but for some reason it is not showing and I keep getting denied, here is the code for the calendar access :
- (void)askAuthorization
{
EKAuthorizationStatus authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
BOOL needsToRequestAccessToEventStore = (authorizationStatus == EKAuthorizationStatusNotDetermined);
if (needsToRequestAccessToEventStore) {
[self.store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
// Access granted
NSLog(#"User granted access");
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate userDidAuthorizeCalendarReadingsWithError:error];
});
} else {
// Denied
NSLog(#"User did not grant access. To grant access, go to Settings > Privacy > Calendars > VirtualAssitant");
[self.delegate userDidNotAuthorizeCalendarReadingsWithError:error];
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate userDidAuthorizeCalendarReadingsWithError:error];
});
}
}];
}
}
I am not getting a pop up and AuthorizationStatusForEntityType:EKEntityTypeEvent value is "denied"!
As for the location I went to settings-> privacy-> location, found it set to never for my application and I had to manually change it to always.
does anyone know why or has been through the same problem and can help me.
I tried cleaning the build folder and reseting Location and Privacy settings but when I rebuild my application the same thing happen I don't get a pop up asking for permission and the values are set to denied!
The permission pop up for any system access like camera, photo album, location, contacts, etc... will only appear once (for each access type).
Once it has been presented and then accepted or denied then it will never appear again.
Because it has come back with the permission "denied" this means it has already appeared and you tapped on "do not allow".
The only way to change the permission in the app after that is to go into the app settings and change it manually.
You can force the permission pop-up to appear again by deleting the app from your device and then installing it again.

iOS Admob ad starts playing with lock screen buttons - BUG?

I have a music streaming app in which I use lock screen controls to play/pause/next the song.
I have Admob Interstitial ads in my app.
However when I use the lock screen controls, it gets passed down to the video ad as well because of which the video ad starts playing along with my app's music. Is there any way to prevent this?
Here's how I am handling the lock screen controls. I don't interact with the ads in any of this code but still the control gets passed down to admob's video player:
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
////NSLog(#"CustomApp:remoteControlReceivedWithEvent:%#", event.description);
if (event.type == UIEventTypeRemoteControl)
{
switch (event.subtype)
{
case UIEventSubtypeRemoteControlPlay:
// play the video
dispatch_async(dispatch_get_main_queue(), ^{
[[[SoundEngine sharedInstance] audioPlayer] resume];
//[[SoundEngine sharedInstance] setLockScreenElapsedTime];
});
break;
case UIEventSubtypeRemoteControlPause:
// pause the video
dispatch_async(dispatch_get_main_queue(), ^{
[[[SoundEngine sharedInstance] audioPlayer] pause];
//[[SoundEngine sharedInstance] setLockScreenElapsedTime];
});
break;
case UIEventSubtypeRemoteControlNextTrack:
// to change the video
dispatch_async(dispatch_get_main_queue(), ^{
[[SoundEngine sharedInstance] nextClicked];
//[[SoundEngine sharedInstance] setLockScreenElapsedTime];
});
break;
case UIEventSubtypeRemoteControlPreviousTrack:
// to play the privious video
dispatch_async(dispatch_get_main_queue(), ^{
[[SoundEngine sharedInstance] prevClicked];
//[[SoundEngine sharedInstance] setLockScreenElapsedTime];
});
break;
default:
break;
}
}
}
I also recently encountered an AdMob interstitial video ads playing after I called load, without ever calling present. In addition, when I did present the ads, toggling the mute switch did not work.
The versions of AdMob SDK were 7.19.1 and 7.20.0.
In the end I identified it to be an AdMob issue, but it was not so obvious. The portion that was causing the error was registering UserAgent for UserDefauts. In particular the following lines cause the problem.
let userAgent : String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9"
UserDefaults.standard.register(defaults: ["UserAgent" : userAgent])
Probably AdMob touches user agent stored in UserDefaults when loading ads. However, I absolutely needed that custom user agent so am still unsure what I can do, but at least we know how AdMob SDK fails us in this case.

iOS 8 Touch ID error "User interaction is required."

I have been working on integrating Touch ID support into an app I am working on. It is however acting very inconsistent. One of the common issues I am seeing is on a fresh app launch it works as expected, but then on backgrounding the app, and bringing it to the foreground I am getting an error back from
evaluatePolicy:localizedReason:reply:
It does not even make a lot of sense (I never see the touchid alert)
Error Domain=com.apple.LocalAuthentication Code=-1004 "User interaction is required." UserInfo=0x171470a00 {NSLocalizedDescription=User interaction is required.}
I have tried presenting the touchid alert when the app is already running, when its just foregrounded, does not seem to matter. Its broken on every time after the initial app launch.
Anyone else running into this?
For reference, here is the code I am using:
if (_useTouchId && [LAContext class]) {
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
_didPresentTouchId = YES;
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:#"Use your Touch ID to open *****" reply:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
if (success) {
_isClosing = YES;
[self hide];
if (_successBlock) {
_successBlock();
}
}
else if (error && error.code != -2 && error.code != -3 && error.code != -1004) {
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"Authentication failed, please enter your Pin" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil] show];
}
else {
if (error) {
DDLogError(#"TouchID error: %#", error.description);
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, .6 * NSEC_PER_SEC), dispatch_get_main_queue(), ^ {
[self keyboardButtonTouched];
});
}
});
}];
}
}
Usually PIN view controllers are pushed before entering background in:
- (void)applicationDidEnterBackground:(UIApplication *)application
So app's inner information won't appear when paging through app preview images (home button double tap). I guess you are doing something similar.
The problem is that LocalAuthentication's new API requires the calling viewController to be visible.
This is why you shouldn't call your "showTouchID" function before resigning to background. Instead call "showTouchID" function when entering foreground:
- (void)applicationWillEnterForeground:(UIApplication *)application
And it should work.
Don't forget to call it also when app is first launched (in which case ..willEnterForeground will not get called).
#hetzi answer really helped me, but I have more to add on this.
Basically this error happens when your app is woken up from background and somewhere on your code you are asking for Touch ID (my case is the local authentication type, I haven't tested with the keychain type). There's no way the user can interact with Touch ID prompted while the app is running on background, hence the error message.
User interaction is required.
The reasons my app was coming from background were: Push Notifications or Apple Watch.
My fix is doing something like this on the viewDidLoad method of my initial VC:
if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) {
[self promptForTouchID];
}
I've used != because, when your app first launches it is in the UIApplicationStateInactive state. And that state doesn't generate a Touch ID error because the prompt will appear.
I also call [self promptForTouchID] on a notification of UIApplicationWillEnterForegroundNotification, but since you know that the app will enter foreground, there's no need to check here.

Detecting active AVAudioSessions on iOS device

I'm trying to figure out if this is possible - my app activates an audio session that is initialized as:
[[[AVAudioSession alloc] init] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&error];
I would like to be able to understand when an additional audio session that originated from another app or the OS is playing.
I know about the ability to implement the delegate methods beginInterruption: and endInterruption but these won't get invoked because of the AVAudioSessionCategoryOptionMixWithOthers option I'm using.
Is there a way to achieve this without using private API?
Thanks in advance.
The way you manage your application's Audio Session has had some significant changes since iOS 6.0, and deserves a brief mention first. Before iOS 6.0 you would make use of AVAudioSession and AudioSessionServices classes, incorporating delegation and property listening respectively. From iOS 6.0 onwards use AVAudioSession class and incorporate notifications.
The following is for iOS 6.0 onwards.
To tell if other audio outside your applications sandbox is playing use -
// query if other audio is playing
BOOL isPlayingWithOthers = [[AVAudioSession sharedInstance] isOtherAudioPlaying];
// test it with...
(isPlayingWithOthers) ? NSLog(#"other audio is playing") : NSLog(#"no other audio is playing");
As for interruption handling you'll need to observe AVAudioSessionInterruptionNotification and AVAudioSessionRouteChangeNotification. So in the class that manages your audio session you could put something like the following - this should be called once at the start of the application lifecycle and don't forget to remove observer in the dealloc method of the same class.
// ensure we already have a singleton object
[AVAudioSession sharedInstance];
// register for notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(interruption:)
name:AVAudioSessionInterruptionNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(routeChange:)
name:AVAudioSessionRouteChangeNotification
object:nil];
And finally add the following selectors interruption: and routeChange: - these will receive a NSNotification object that has a property called userInfo of type NSDictionary that you read to assist any conditionals your application has.
- (void)interruption:(NSNotification*)notification {
// get the user info dictionary
NSDictionary *interuptionDict = notification.userInfo;
// get the AVAudioSessionInterruptionTypeKey enum from the dictionary
NSInteger interuptionType = [[interuptionDict valueForKey:AVAudioSessionInterruptionTypeKey] integerValue];
// decide what to do based on interruption type here...
switch (interuptionType) {
case AVAudioSessionInterruptionTypeBegan:
NSLog(#"Audio Session Interruption case started.");
// fork to handling method here...
// EG:[self handleInterruptionStarted];
break;
case AVAudioSessionInterruptionTypeEnded:
NSLog(#"Audio Session Interruption case ended.");
// fork to handling method here...
// EG:[self handleInterruptionEnded];
break;
default:
NSLog(#"Audio Session Interruption Notification case default.");
break;
} }
And similarly...
- (void)routeChange:(NSNotification*)notification {
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonUnknown:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonUnknown");
break;
case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
// a headset was added or removed
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonNewDeviceAvailable");
break;
case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
// a headset was added or removed
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
break;
case AVAudioSessionRouteChangeReasonCategoryChange:
// called at start - also when other audio wants to play
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonCategoryChange");//AVAudioSessionRouteChangeReasonCategoryChange
break;
case AVAudioSessionRouteChangeReasonOverride:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonOverride");
break;
case AVAudioSessionRouteChangeReasonWakeFromSleep:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonWakeFromSleep");
break;
case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
NSLog(#"routeChangeReason : AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory");
break;
default:
break;
} }
There is no need to poll anything as long as you check the state of your applications audio session say for example in the viewDidLoad of your root view controller, at the start of you apps lifecycle. Any changes from there onwards to your applications audio session will be known via these two main notifications. Replace the NSLog statements with what ever your code needs to do based on the cases contained in the switch.
You can find more information about AVAudioSessionInterruptionTypeKey and AVAudioSessionRouteChangeReasonKey in the AVAudioSession class reference documentation.
My apologies for the long answer but I think Audio Session management in iOS is rather fiddly and Apple's Audio Session Programming Guide, at the time of writing this, does not include code examples using notifications for interruption handling.
You can check if other audio is playing like this:
UInt32 otherAudioIsPlaying;
UInt32 propertySize = sizeof (otherAudioIsPlaying);
AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &propertySize, &otherAudioIsPlaying );
[self handleIfAudioIsPlaying: otherAudioIsPlaying];
Then you can add a loop and check every X second if something changed.

iOS 7 UIImagePickerController Camera No Image

For some reason the first time I open the UIImagePickerController in camera mode on my app it comes up blank. I have to close and reopen that view to get the camera feed to start working. I'm using the standard code that works in iOS 6 perfectly for camera capture. From the sample below I'm firing the capturePhoto: method. Anyone else running into this jenkiness with the iOS 7 camera? I checked the Apple dev forums but its near impossible to find answers there.
- (IBAction)capturePhoto:(id)sender {
[self doImagePickerForType:UIImagePickerControllerSourceTypeCamera];
}
- (void)doImagePickerForType:(UIImagePickerControllerSourceType)type {
if (!_imagePicker) {
_imagePicker = [[UIImagePickerController alloc] init];
_imagePicker.mediaTypes = #[(NSString*)kUTTypeImage];
_imagePicker.delegate = self;
}
_imagePicker.sourceType = type;
[self presentViewController:_imagePicker animated:YES completion:nil];
}
I'm also using UIImagePickerController and ran into the same issue with a blank screen. I'd like to expand a little on what klaudz mentioned regarding iOS 7 authorization for the camera.
Reference:
https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVCaptureDevice_Class/Reference/Reference.html
"Recording audio always requires explicit permission from the user; recording video also requires user permission on devices sold in certain regions."
Here is some code fragments you can start with to check to see if you have permission for the camera and request it if your app hadn't previously requested it. If you are denied due to an earlier request, your app may need to put up a notice to the user to go into settings to manually enable access as klaudz pointed out.
iOS 7 example
NSString *mediaType = AVMediaTypeVideo; // Or AVMediaTypeAudio
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
// This status is normally not visible—the AVCaptureDevice class methods for discovering devices do not return devices the user is restricted from accessing.
if(authStatus == AVAuthorizationStatusRestricted){
NSLog(#"Restricted");
}
// The user has explicitly denied permission for media capture.
else if(authStatus == AVAuthorizationStatusDenied){
NSLog(#"Denied");
}
// The user has explicitly granted permission for media capture, or explicit user permission is not necessary for the media type in question.
else if(authStatus == AVAuthorizationStatusAuthorized){
NSLog(#"Authorized");
}
// Explicit user permission is required for media capture, but the user has not yet granted or denied such permission.
else if(authStatus == AVAuthorizationStatusNotDetermined){
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
// Make sure we execute our code on the main thread so we can update the UI immediately.
//
// See documentation for ABAddressBookRequestAccessWithCompletion where it says
// "The completion handler is called on an arbitrary queue."
//
// Though there is no similar mention for requestAccessForMediaType, it appears it does
// the same thing.
//
dispatch_async(dispatch_get_main_queue(), ^{
if(granted){
// UI updates as needed
NSLog(#"Granted access to %#", mediaType);
}
else {
// UI updates as needed
NSLog(#"Not granted access to %#", mediaType);
}
});
}];
}
else {
NSLog(#"Unknown authorization status");
}
In iOS 7, an app could access the camera before getting authorize of the user.
When an app accesses the camera the first time, iOS show an alert view to ask user.
Users could also set the authorize in Settings--Privacy--Camera--[Your app's name].
The camera will stay in a black blank view if the switch is off.
If you call the camera by using AVCaptureDeviceInput, you can check like:
NSError *inputError = nil;
AVCaptureDeviceInput *captureInput =
[AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:&inputError];
if (inputError &&
inputError.code == AVErrorApplicationIsNotAuthorizedToUseDevice)
{
// not authorized
}
If you call by using UIImagePickerController, I am still looking for a way to check whether got the authorize.
I tried these two methods:
[UIImagePickerController isSourceTypeAvailable:]
[UIImagePickerController isCameraDeviceAvailable:]
but they did't work that they all returned YES.
UPDATE
Thanks for Scott's expanding. [AVCaptureDevice authorizationStatusForMediaType:] is a better way to check.
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (authStatus == AVAuthorizationStatusAuthorized) {
// successful
} else {
// failed, such as
// AVAuthorizationStatusNotDetermined
// AVAuthorizationStatusRestricted
// AVAuthorizationStatusNotDetermined
}
But remember to check the iOS version, because [AVCaptureDevice authorizationStatusForMediaType:] and AVAuthorizationStatus are available above iOS 7.
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
// code for AVCaptureDevice auth checking
}
I experienced the exact same problem, and tried every solution on the Internet with no luck. But finally I found out it was the background thread prevented the camera preview to show up. If you happen to have background thread running while trying to open the camera as I do, try to block the background thread and see what happens. Hope you can get around it.
I came across this control AQPhotoPicker. It's quite easy to use, and hopefully it will help you

Resources