Screen corruption after dismissing modal UIImagePickerController subclass - iphone

I am having trouble dismissing a modal view controller containing a UIImagePickerController (ZBarScannerController). For some reason, after dismissing the controller after scanning a bar code with the iphone camera, the view controller always leaves a rectangle of stale graphical data over the same area as the ZBarScannerController's tool bar. The corrupt data is always a portion of whatever image the camera was seeing at the moment.
An image of the problem (corrupt area in red rectangle):
It is impossible to remove that rectangle of corrupt screen data except by backgrounding / killing the app. Also, if I specify NO when I dismiss the modal picker, the OS will remove the view controller while still displaying the controller on screen, causing crashes if I interact with any controls on the modal view. How can I go about fixing the problem?
Code for dismissing the controller:
- (void) imagePickerController: (UIImagePickerController*) reader
didFinishPickingMediaWithInfo: (NSDictionary*) info
{
id<NSFastEnumeration> results =
[info objectForKey:ZBarReaderControllerResults];
for (ZBarSymbol *oSymbol in results) {
//process result
//Ensure that QR code is decoded
zbar_symbol_type_t type = oSymbol.type;
if (type == ZBAR_QRCODE) {
//Get Barcode Data
NSString *dataStr = oSymbol.data;
[self processCommand:dataStr];
}
}
//[reader dismissModalViewControllerAnimated:NO];
[[m_oReaderController parentViewController] dismissModalViewControllerAnimated:YES];
}
Code for making the controller:
- (void)onQRCameraActivate:(id)sender {
IPOProofAppDelegate *oAppDelegate = (IPOProofAppDelegate *) [[UIApplication sharedApplication] delegate];
if (m_oReaderController == nil) {
m_oReaderController = [[ZBarReaderViewController alloc] init];
m_oReaderController.readerDelegate = self;
ZBarImageScanner *oScanner = m_oReaderController.scanner;
[oScanner setSymbology:0 config:ZBAR_CFG_ENABLE to:0];
[oScanner setSymbology:ZBAR_QRCODE config:ZBAR_CFG_ENABLE to:1];
}
[self.navigationController presentModalViewController:m_oReaderController animated:YES];
}

Related

iOS7 / iOS6 Conditional Rotation Portrait / Landscape for different sections of App

Problem: A have an App that uses both Landscape mode (locked) and Portrait Mode (locked) for different parts of the app. Now I have a working solution however it doesn't seem correct and does have it's own problems.
Optimally I would love to force a orientation change. Thinking even about doing a view transformation if needed.
Basic flow of App:
HomeView (Portrait) (which has a few sub pushed views that are also portrait and locked to that).
LandscapeView (Landscape) (which has 5 pushed subviews that are also landscape)
Note:
HomeView has a link to LandscapeView
LandscapeView can go back to HomeView
At the end of the LandscapeView subviews it returns to the HomeView
Basic Image showing how this looks with the different view orientations. (The lines indicate flow of app, orientation of the images indicate how each screen should be )
Currently using the below implementation to call / set if the view is in portrait mode or landscape mode by [setLockedToPortait:YES] (for portrait view) etc.
This in term makes the query for what interface orientation to use from iOS if the device is rotated.
Now for the case of going to the LandscapeView, I show a temporary view over the top of the normal view asking to use to rotate their phone to landscape. (A temporary view is also shown when returning to the HomeView from a landscape view)
So once the user has rotated their device, it will trigger the correct orientation and then the temporary view will hide.
If the user then rotates their phone back to portrait at this point it will still be locked to landscape so will not trigger another view rotation (also no temp view will appear or anything)
Current Implementation Code::
// ---------------------- NavigationController (subclass of UINavigationController)
#interface NavigationController () {
BOOL isOrientationPortrait;
}
#end
#implementation NavigationController {
UIDeviceOrientation lastAccepted;
UIDeviceOrientation lastKnown;
}
-(void)setLockedToPortait:(BOOL)isLocked {
isOrientationPortrait = isLocked;
}
-(UIDeviceOrientation) getCurrentOrientation {
UIDeviceOrientation orientate = [[UIDevice currentDevice] orientation];
if(orientate == 0) { // needed for simulator
orientate = (UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation;
}
return orientate;
}
// Deprecated in iOS6, still needed for iOS5 support.
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
{
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastKnownOrientation:orientation];
if(isOrientationPortrait == YES) {
if([self isLastKnownPortrait] == YES) {
[self setLastAcceptedOrientation:orientation];
return YES;
} else {
return NO;
}
} else {
if([self isLastKnownLandscape] == YES) {
[self setLastAcceptedOrientation:orientation];
return YES;
} else {
return NO;
}
}
}
// iOS6/7 support
- (BOOL)shouldAutorotate
{
// find out the current device orientation
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastKnownOrientation:orientation];
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if(isOrientationPortrait == YES) {
if([self isLastKnownPortrait] == YES)
{
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastAcceptedOrientation:orientation];
}
return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
} else {
if([self isLastKnownLandscape] == YES)
{
UIDeviceOrientation orientation = [self getCurrentOrientation];
[self setLastAcceptedOrientation:orientation];
}
return (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight );
}
}
-(void)setLastAcceptedOrientation:(UIDeviceOrientation)orient {
lastAccepted = orient;
}
-(void)setLastKnownOrientation:(UIDeviceOrientation)orient {
lastKnown = orient;
}
-(BOOL)isLastKnownPortrait {
return UIDeviceOrientationIsPortrait(lastKnown);
}
-(BOOL)isLastKnownLandscape {
return UIDeviceOrientationIsLandscape(lastKnown);
}
-(BOOL)isLastAcceptedPortrait {
return UIDeviceOrientationIsPortrait(lastAccepted);
}
-(BOOL)isLastAcceptedLandscape {
return UIDeviceOrientationIsLandscape(lastAccepted);
}
Current Problems:
Device rotations are always required after a view has loaded for the user going to Landscape mode from Portrait and vice versa.
If the user has the device orientation locked, this will not work at all.
When transitioning back from Landscape mode, and the user has already rotated their device to Portrait (in the last landscape view), the Portrait view's interface will be locked to a 'Landscape' layout until the user re-rotates their device (so currently I am just showing the overlay to rotate the device, but it is already rotated… very annoying for the user). Massive issue right now with the above implementation.
Would love to be able to:
Force an orientation change on the phone for the current view.
Set a preferred layout for a view which is forced between push/pops of views.
I've looked a lot at the other solutions on here and on the Apple Dev forums, however none seem to cover this problem, or still this orientation bug between the two views exists as well.
Thanks for any help or pointers! No advice will be discounted :D
--
Edit::
Solution Found thanks to #leo-natan!!
So instead of trying to force a change of orientation on the views. Just push a new modal view. This forces a change. You still need to above orientation code for managing rotations.
So what I have now in my HomeViewController:
LandscapeViewController * viewController = [[[LandscapeViewController ViewController alloc] init] autorelease];
UINib * nib = [UINib nibWithNibName:#"NavigationController" bundle:nil];
NavigationController *navController = [[nib instantiateWithOwner:nil options:nil] objectAtIndex:0];
[navController initWithRootViewController:viewController];
[self presentViewController:navController animated:YES completion:^{
// completion
}];
So it is necessary to re-add a new navigation controller for this modal view. Also note above 'presentViewController' is the new way of pushing Modal views.
Implemented this overloaded method for the managing of the view controller:
-(id)initWithRootViewController:(UIViewController *)rootViewController {
self = [super initWithRootViewController:rootViewController];
if(self){
}
return self;
}
Note: The above is not using storyboards. The problem may be solved by using storyboards and modally showing a view in the same fashion.
See my answer here, including a test project.
Basically, orientation can only be forced to change when presenting a view controller modally. For example, media playback in some apps. If you wish to transition from a view controller that can only be presented in portrait to a view controller that is only presented in landscape, you will need a modal presentation. Push will not work.

iPhone Camera App - how to stay in Camera view?

I've implemented the camera inside of my app with the default showsCameraControls = YES, and the issue I am having is when the user confirms that the image is a keeper, it dismisses the camera with [self.delegate didFinishWithCamera]. I would like to remain in the Camera view until the user is done taking photos. Without [self.delegate didFinishWithCamera], the app hangs after the user confirms they want to keep the photo and never returns back to the live camera feed. How do I remain in the camera view? Your help is appreciated!
#implementation PHFPhotoOverlayVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.imagePickerController = [[UIImagePickerController alloc] init];
self.imagePickerController.delegate = self;
}
return self;
}
- (void)setupImagePicker:(UIImagePickerControllerSourceType)sourceType
{
self.imagePickerController.sourceType = sourceType;
if (sourceType == UIImagePickerControllerSourceTypeCamera)
{
self.imagePickerController.mediaTypes =
[NSArray arrayWithObjects:(NSString *) kUTTypeImage, nil];
self.imagePickerController.showsCameraControls = YES;
#if false
if ([[self.imagePickerController.cameraOverlayView subviews] count] == 0)
{
CGRect overlayViewFrame = self.imagePickerController.cameraOverlayView.frame;
CGRect newFrame = CGRectMake(0.0,
CGRectGetHeight(overlayViewFrame) -
self.view.frame.size.height - 10.0,
CGRectGetWidth(overlayViewFrame),
self.view.frame.size.height + 10.0);
self.view.frame = newFrame;
[self.imagePickerController.cameraOverlayView addSubview:self.view];
}
#endif
}
}
#pragma mark -
#pragma mark UIImagePickerControllerDelegate
- (void) imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
self.imagePickerController.mediaTypes =
[NSArray arrayWithObjects:(NSString *) kUTTypeImage, nil];
self.imagePickerController.showsCameraControls = YES;
UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
if (self.delegate)
[self.delegate didTakePicture:image];
[self.delegate didFinishWithCamera];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[self.delegate didFinishWithCamera];
}
#end
In order to achieve this, you will have to implement your own method for taking a picture, since this default button causes the view to dismiss itself.
Obviously, you can do this by setting showsCameraControls=NO, but then you'd have to recreate all the other functionality that you still want.
I've never done this, but it should be possible to leave showsCameraControls=YES and use the cameraOverlayView to simply layer an identical "Take Picture" button over the existing one. If you do that, you just need to have that button call the method -takePicture from the instance of your UIImagePickerViewController.
Hopefully that's enough, feel free to comment and ask for any clarification.
You're going to need to implement a custom camera - check out Apple's SquareCam example
You'll be using AVFoundation and will be able to implement any kind of custom behavior when a photo is taken (including just staying on that camera page) - you'll even have to save it to the Photo Library yourself (the SquareCam example shows you how to do this). This also means that if you need the crop/resize controls, you'll have to create them yourself, as well as a picker view (grid gallery view) if you want the user to be able to review their photos after taking them.
It's probably around intermediate level stuff - took me a few days to implement, but if your app is in any way focused on photos, this is definitely the way to go. Gives you complete control of the camera UI and behavior.
Oh, and yeah you'll have to implement the flash button, shutter button, shutter effect, tap to focus, and anything else yourself too. AVFoundation basically gives you a straight pipe to the camera lens (figuratively).

UIActivityViewController not dismissing

I'm trying to add a custom UIActivity to a UIActivityController. When I click on the item, it presents the view controller I want it to, but when I finish with that view controller, the original UIActivityViewController is still there. My question is, how and where do I dismiss the activity view controller? This is the code in my custom UIActivity.
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems{
self.activityTitle = #"Text Editor";
self.activityType = #"TextEdit";
self.activityImage = [UIImage imageNamed:#"Icon.png"];
if (activityItems) return YES;
else return NO;
}
- (void)prepareWithActivityItems:(NSArray *)activityItems{
for (NSString *path in activityItems) {
if ([path lastPathComponent]) {
self.file = path;
}
}
}
- (UIViewController *)activityViewController{
ACTextEditController *actec = [[ACTextEditController alloc] initWithFile:_file];
return actec;
}
EDIT
I've tried doing this, and I know it is called because I tried logging something in it and it was called
- (void)activityDidFinish:(BOOL)completed{
if (completed) {
[self.activityViewController dismissViewControllerAnimated:YES completion:nil];
}
}
however, it's still there when the view controller dismisses. Why?
Here's a working example:
http://www.apeth.com/iOSBook/ch26.html#_activity_view
Notice that we end up by calling activityDidFinish:. That is what tears down the original interface. It may be that that is the step you are omitting. If not, just compare what you're doing with what I'm doing, since what I'm doing works (on iPhone).

ZBar API Embedded Scanner Blur issue

I am using ZBar iPhone SDK in one of my projects (iOS SDK 5.1 ,XCode 4.4.1 and device running iOS 5.5.1). I am using the embedded scanner from the examples provided in the SDk itself.
Now the issue which I am facing is that I successfully scan a bar code and move to another view controller ( using navigation controller). When I come back (pop the second view controller) the scanner i.e the ZBarReaderView doesn't scan the subsequent bar codes , infact the overlay shows a blur image of the scanned barcode and is never able to scan it properly.
This is what all I have implemented . In BarScannerViewController.h I have declared
ZBarReaderView* readerView;
with property
#property (nonatomic , retain) IBOutlet UIImageView* imgvScannedBarCode;
Now this is connected to one of the views in xib.
Finally I use set up the required methods as follows -
- (void)viewDidLoad {
[super viewDidLoad];
// the delegate receives decode results
readerView.readerDelegate = self;
[readerView start];
}
- (void) viewDidAppear: (BOOL) animated {
// run the reader when the view is visible
[activityIndicatorScanning startAnimating];
[readerView start];
}
- (void) viewWillDisappear: (BOOL) animated {
[activityIndicatorScanning stopAnimating];
[readerView stop];
}
With all this set up when I scan any bar code say EAN123 for the first time I get the call back in
- (void) readerView: (ZBarReaderView*) view
didReadSymbols: (ZBarSymbolSet*) syms
fromImage: (UIImage*) img
{
// do something useful with results
ZBarSymbol *symbol = nil;
for(symbol in syms) {
barCodeFound = YES;
break;
}
// EXAMPLE: do something useful with the barcode data
NSLog(#"%#",symbol.data);
}
but on subsequent runs (After I push a view and come back on this screen again) I get blurred view.
Am I missing something here ? Any help/Suggestion/Comments would be helpful.
Here's the code that I use to start (and endlessly restart) the scanner. Interestingly, I note that I never stop the scan, but it works very reliably.
- (void) startScan
{
ZBarReaderViewController *reader = [ZBarReaderViewController new];
reader.readerDelegate = self;
ZBarImageScanner *scanner = reader.scanner;
[scanner setSymbology: ZBAR_I25
config: ZBAR_CFG_ENABLE
to: 0];
// present and release the controller
[self presentViewController:reader animated:YES completion:nil]; // Modal
[reader release];
}
I could solve the Blur issue by reconfiguring the SDK in my project. I followed the embedded scanner example as provided on ZBarSDk. I guess I might have missed some essential settings while configuring it earlier.

iOS: AutoRotating between NIBs

My universal app is a single full screen view. Pressing a button flips to reveal a settings page:
- (void) showSettings
{
FlipsideViewController * flipsideVC = [FlipsideViewController alloc];
NSString * settingsNib;
if ( isIPad() )
settingsNib = isCurrentlyPortrait() ? #"settings_iPad_portrait" : #"settings_iPad_landscape";
else
settingsNib = #"settings_iPhone";
[flipsideVC initWithNibName: settingsNib
bundle: nil ];
flipsideVC.delegatePointingToMainVC = self;
flipsideVC.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController: flipsideVC
animated: YES ];
[flipsideVC release];
}
and the settings page invokes the delegate method: I recreate the main view in light of the changed settings, and flip back.
- (void) settingsDidQuit:(FlipsideViewController *) flipsideVC
{
[self createOrRecreateWheelView];
[self dismissModalViewControllerAnimated: YES];
}
But what if the user rotates the iPad on the settings page? Apple decrees that my app must handle this. But how to do this? can I dynamically load a new XIB for the settings page?
I can't see a way to do that, so my attempted solution is to catch the rotation within the settings view, ...
- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) oldInterfaceOrientation
{
[self.delegatePointingToMainVC settingsOrientationChanged];
}
...and call back to the main view controller, which dissolves the settings view controller and recreates it in light of the current orientation.
- (void) settingsOrientationChanged
{
[self dismissModalViewControllerAnimated: YES];
[self showSettings];
}
There is a trivial problem straight away -- didRotateFromInterfaceOrientation gets triggered automatically when the settings page loads. I can prevent this by setting a boolean to false in init, and modifying thus:
- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) oldInterfaceOrientation
{
if (initialized)
[self.delegatePointingToMainVC settingsOrientationChanged];
initialized = true;
}
problem with this approach is that I navigate to the settings page, rotate the device, and it momentarily shows the correct settings page, before flicking back to my main view.
I think there is a threading problem here. But maybe my whole approach is wrong. Can somebody suggest a better solution?
I'm not sure I understand the problem. You want the settings view (loaded from NIB) to autorotate? You should just return YES for the orientation you want the autorotation to be performed in the shouldAutorotateToInterfaceOrientation: and set the autoresizing mask of the views inside the XIB accordingly to your needs.
There's no need to call back the main view controller and tell him to push a new settings view controller. The rotation behavior of the views is determined by the autoresizing mask properties of each view and the implementation of shouldAutorotateToInterfaceOrientation: method of the associated view controller and just that. If want to do more advanced animations, though, you can set up and manage them in the willRotateToInterfaceOrientation:duration: and didRotateFromInterfaceOrientation: methods.