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
Related
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.
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
}
We've all been there. You want to take photos within your app, or access photos, microphone, contacts, etc... But first iOS must prompt the user for permission. In many cases the user will deny access.
If your app detects that the user has denied access, you can navigate the user to the App's privacy settings with this:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
Handy. However....
I've noticed that if you do convince the user to toggle the switch to on, that the app does not detect the changes.
Consider this code. The user is immediately prompted for permission to access the camera (this only shows the first time that app is run). Suppose the user denied permission. Next they decide that they did want to enable camera access after all. No problem. The user taps on the button which brings up the privacy panel. The user changes the switch to allow access. Then the user switches back to the app. The block fires for UIApplicationDidBecomeActiveNotification which reads the permission again. However it does not reflect the user's changes (still reads as Denied).
If the app is purged from memory and run again, it will properly read the state.
Not all permissions behave this way. For instance CoreLocation seems to detect the user's changes. I've also found a way to detect changes for Notifications. But for Contacts, Calendars, Camera, Microphone, Core Motion (and more) the changes are not detected until the app is terminated and run again.
Any ideas?
#import "ViewController.h"
#import AVFoundation;
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self printPermission];
}];
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
[self printPermission];
}];
}
-(void)printPermission{
dispatch_async(dispatch_get_main_queue(), ^{
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(status == AVAuthorizationStatusNotDetermined){
NSLog(#"VWWPermissionStatusNotDetermined");
self.view.backgroundColor = [UIColor whiteColor];
} else if(status == AVAuthorizationStatusAuthorized){
NSLog(#"VWWPermissionStatusAuthorized");
self.view.backgroundColor = [UIColor greenColor];
} else if(status == AVAuthorizationStatusDenied) {
NSLog(#"VWWPermissionStatusDenied");
self.view.backgroundColor = [UIColor redColor];
} else if(status == AVAuthorizationStatusRestricted) {
NSLog(#"VWWPermissionStatusRestricted");
self.view.backgroundColor = [UIColor redColor];
}
});
}
- (IBAction)buttonAction:(id)sender {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}
#end
So, this turned out to be a bug related to iOS 9b1.
Permission detection works fine on iOS 8.
I did learn that you need to check the permission on the main queue. If you do that, it will reflect updates.
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.
I have the following code that no longer works in iOS 7 for changing the sessionPreset of a capture session while it is still running (but not capturing video). This worked on iOS 6.x, but on 7 the video preview freezes.
I've tried removing begin/commit configuration, as well as locking the input device and unlocking after. I've also tried calling beginSession again after the commitConfiguration, but this causes the device to start capturing video.
Is this a bug or am I doing something wrong?
- (void)setVideoCaptureSessionPreset:(NSString *)videoCaptureSessionPreset
{
if (_captureSession.sessionPreset == videoCaptureSessionPreset) return;
if (![_captureSession canSetSessionPreset:videoCaptureSessionPreset])
{
TFLog(#"%s Device cannot set preset to: %#", __PRETTY_FUNCTION__, videoCaptureSessionPreset);
return;
}
[_captureSession beginConfiguration];
_captureSession.sessionPreset = videoCaptureSessionPreset;
[_captureSession commitConfiguration];
}
Edit: Not only does this cause the video preview to freeze, it causes the entire phone lock up (background no longer displayed on home screen, app cannot restart, and eventually the entire phone has to be hard-reset).
In iOS 7 and up, the solution is to use the new AVCaptureDevice activeFormat API and set the session preset to AVCaptureSessionPresetInputPriority. When you change the active format, you have to wrap your startRunning in the lockForConfiguration like so:
if ([_videoCaptureDevice lockForConfiguration:nil])
{
_videoCaptureDevice.activeFormat = format
[_captureSession startRunning];
[_videoCaptureDevice unlockForConfiguration];
}