UIImagePickerController won't stop location services - iphone

Basically what I am trying to accomplish is an augmented reality application. I have a map view and the augmented reality view.
When the user only looks at the map view and then returns to the previous page in the UINavigation stack all location services are stopped and the arrow toolbar notification dissapears. It is when the user leaves the map view and the UIImagePickerController is presented modally the location services notification will remain even after the user presses the button that is responsible for stopping all location services and popping the current view.
I know it is not my CLLocationManager causing the problem because as I said the error doesn't occur when the UIImagePicker is never placed on the screen. My thoughts are that the location services used for geolocating or whatever the camera uses them for is not stopping even though i dimiss the modal view before popping the current view.
For the life of me I can't figure out why they arent stopping, if anyone might know why it would be a huge help.
Here is the code that I have right now in the method that is called to prepare for popping the view from the UINavigation stack
[_locationManager setDelegate:nil];
[_locationManager stopUpdatingLocation];
[_locationManager stopUpdatingHeading];
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
if (_imagePickerOn){
[self dismissModalViewControllerAnimated:YES];
_imagePickerOn = FALSE;
}
--EDIT--
Heres the method where I present the image picker, very basic:
- (IBAction) cameraButtonPressed{
_imagePickerOn = TRUE;
[self presentModalViewController:_imagePicker animated:NO];
}

I assume you’re allocating the image picker controller in advance elsewhere. That’s probably the problem—when its view goes offscreen (as you dismiss it), it’s not getting deallocated, so it’s still in memory and still presumably using its location manager. It’s a bug, but not your bug, so you’re not going to be able to do much about it with your current setup.
A more common pattern is to only allocate things like UIImagePickerController when you’re about to present them and to release them immediately after the call to -presentModalViewController:animated:. It can make your UI a little less responsive, especially when allocating complicated view controller (I’m not sure if the image picker controller qualifies as such), but you get the benefit of reduced memory usage and—hopefully—of no longer using location services when you don’t want them.

Related

UIImagePickerController in custom view doesn't show camera

I'm trying to display the content of the camera in a custom view. What I just want to achieve is to have custom buttons to take pictures in order to take more than one photo at a single time.
It should work out of the box, theoretically, but in practice sometimes it happens that if I dismiss my custom view controller and then I re-open it "quickly", the UIImagePickerController just shows a blank (black, actually) content. The funny thing is that if you try to take a picture, the camera actually is enabled and the shutter opens and you can collect the image. The only issue seems to be related to displaying the live-content into a specific UIView.
This is the code I use for displaying it:
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
[imagePickerController setSourceType:UIImagePickerControllerSourceTypeCamera];
[imagePickerController setShowsCameraControls:NO];
[imagePickerController setEditing:NO];
[imagePickerController setNavigationBarHidden:YES];
[imagePickerView addSubview:[imagePickerController view]];
[imagePickerController viewWillAppear:YES];
I don't like that viewWillAppear method call but it is the only way I found in order to show it.
imagePickerView is, indeed, the view that I have previously created to place the picker into.
By digging a little bit the problem myself, I noticed that if I wait a couple of seconds before re-opening my custom view controller, the picker shows up normally.
By taking a look into the console it seems that the picker (or the camera resource associated to it) is actually released after a while but this is just a guess.
Any clue? Thanks
You definitely need that viewWillAppear call, and likely more. Whenever a UIImagePickerController is presented by one of the "indirect" presentation methods (like a modal presentation, or being pushed on a navigation stack), it's automatically sent all of the appropriate display related notifications: viewWillAppear:, viewDidAppear:, viewWillDisappear:, and viewDidDisappear:.
Internally UIImagePickerController uses these notifications to take appropriate initialization actions, like the shutter effect. You don't know how it uses them, you just have to be sure it gets them.
When you present the UIImagePickerController directly by adding it's view as a subview, you deprive it of automatically receiving these notifications. From the View Controller Programming Guide:
If you incorporate a view
controller’s view into your hierarchy
by other means (by adding it as a
subview to some other view perhaps),
the system assumes you want to manage
the view yourself and does not send
messages to the associated view
controller object.
This isn't necessarily bad, it just means you need to shoulder the responsibility for those messages yourself. I haven't seen this exact issue that you're having with display of the picker, but my first attempt at a fix would be to ensure that each of those 4 display related notifications are sent to the picker controller at the appropriate time, especially the disappearing ones, if you aren't already doing so.

Preventing view from unloading in iOS SDK?

I've built an app that uses a UITableView inside a UINavigationController, inside a UITabBarController. Every entry in the UITableView opens up a view that contains some basic text, buttons, but most importantly, an MPMoviePlayerController that plays audio when started. A user can click this MPMoviePlayerController and continue to browse around the rest of the app (different tabs, or moving back in the navcontroller, opening other views from the tableview) and continue to hear the audio.
I'd like the user to be able to return to the view with the active MPMoviePlayerController at any time. I understand how I would go about allowing the user to return to a certain view from any view, but I'm struggling with how to prevent that view from being reloaded when the user tries accessing the same view.
Is there any way I can save a view in memory? Or save the active MPMoviePlayerController as some type of global object, so that I can at least access that from anywhere?
I appreciate any and all help. Thanks!
I'd recommend you create a property for the MPMoviePlayerController in your app's UIApplicationDelegate (which you can then access from anywhere in the code with [UIApplication sharedApplication].delegate but you will need to cast to your UIApplicationDelegate subclass).
When you come to enter the screen which plays content, check whether your movie player property in the app delegate is nil, if it is create it, otherwise re-use it.
Don't forget to release the reference to your MPMoviePlayerController when the media stops playing, or when the media has already stopped and you get a memory warning or when your app shuts down.
The down side of this approach is it causes coupling between most of your view controllers and your app delegate. You could mitigate this with the use of a protocol however.
You should simply retain it. Like this [myView retain] and keep a pointer to it in where you need. When you want myView to appear, just add it as a subview to current visible view like[myController.view addSubview:myView].
Hope that will help, Good luck!
I've found that even adding a retain doesn't do the trick. I've actually found the best success with overriding the setView (since part of unloading the view involves calling setView:nil. I have a BOOL that gets set the FIRST time the VC loads and once thats set it will never allow setView to be called again.
- (void) setView: (UIView*) view{
NSLog(#"MainViewController: setView");
// this is our attempt to stop iOS from unloading our view.. when iOS tries to unload your view they call setView:nil.. so, no!
if(!viewDidAppear) [super setView:view];
}
A little bit of a hack, but you can override setView: in your subclass so that it never allows to set the view to nil:
-(void)setView:(UIView *)view
{
if (view == nil) return;
[super setView:view];
}

How to recreate UIViewController stack?

I'm writing a 'load' feature for my current iPhone app.
Practically what I want to do is get the user back where s/he left off. To do so I want to recreate all the UIViewControllers appeared before.
My problem is the UIViewControllers won't appear if I simply call [[self navigationController] pushViewController:newController animated:YES];.
The same code works fine when invoked as an event handler, like after touching a button. But if I do same without an even (for ex. in viewDidLoad) the new view controller's loadView method won't get called.
My actual code (with some pseudo elements):
- (void)viewDidLoad {
[super viewDidLoad];
if (loading)
[self onSomeEvent];
}
- (void)onSomeEvent {
UIViewController *newController = //init....;
[[self navigationController] pushViewController:newController animated:YES];
[newController release];
}
I guess viewDidLoad is not the right place to do such a call, but then what is?
I'm not sure that spreading your "load" feature all accross your controllers is the best way to achieve it.
I would rather put it in the init of your application, in the applicationDidFinishLauching part. Then you have a centralized place where you restore the previous state (easier to handle IMHO).
Let's suppose you want to implement some more advanced restore feature like:
displaying a splash UIView with an activity indicator to indicate the user that you're restoring previous state
restore the stack of your controllers in the navigation controller
remove the splash
it's easier to have all this code in the applicationDidFinishLauching : it's all managed at one point.
Morever, when restoring the old state to your navigation controller, you can avoid using the transitions and use animated:NO instead of YES when pushing your controllers. It will be easier to handle from your perspective and the restore time may be decreased if you remove time needed to achieve transitions.

How to avoid data lose when UIImagePickerController unloads my controller?

I am using UIImagePickerController to take a photo from the camera. However, the I find that randomly my calling controller (the one that is shown before the UIImagePickercontroller is shown) gets unloaded. I logged the viewDidUnload, and indeed it does get called. When the camera is done and dismissed, my controller's viewDidLoad will be called, unfortunately all the state is now gone. Things like text entered or things selected will be gone.
Obviously this is something to do with running out of memory. But is this behavior normal? Should I be handling it? This is NOT typically how modalViewController works. Usually you show it and dismiss it, everything should be intact.
What is a good way to avoid data lost in this case? Should I have to write a bunch of code to save the full state?
what's happening is that your view controller's didReceiveMemoryWarning is being called, and then since your view isn't visible, it's being unloaded.
the solution is that any data that wants to be persistent should be stored in your view controller rather than in your view.
you can prevent this from happening by providing an implementation of didReceiveMemoryWarning in your UIViewController class that doesn't call [super didReceiveMemoryWarning] which is where the unloading of the view happens, but it's still a good thing to try to understand what's going on.
It's really not advisable to override -didReceiveMemoryWarning so that UIViewController can't deallocate the view because then you may run into a "real" problem of low memory and your app will be forced to quit by the system.
What you really should do is store the text and other bits of entered data from the view in your -didReceiveMemoryWarning method (making sure to call super). Then in -viewDidLoad you can put the text and other bits back into the UI.
This will lighten the memory usage and reduce the likelihood of your app being forced to quit because there's no more memory left. All the while, the user won't know it ever disappeared!
My solution to the same issue described above was to declare an instance variable in my view controller which stores the contents of the UITextView should the view get unloaded. Thus in my viewWillDissapear method I save the contents of UITextView.text into my instance variable and in my viewWillAppear method I restore the contents.
- (void)viewWillAppear:(BOOL)animated
{
if (messageTextString != nil)
{
textEdit.text = messageTextString;
}
[super viewWillAppear:animated];
}
(void)viewWillDisappear:(BOOL)animated
{
self.messageTextString = textEdit.text;
[super viewWillDisappear:animated];
}

How to safely shut down a loading UIWebView in viewWillDisappear?

I have a view containing a UIWebView which is loading a google map (so lots of javascript etc). The problem I have is that if the user hits the 'back' button on the nav bar before the web view has finished loading, it is not clear to me how to tidily tell the web view to stop loading and then release it, without getting messages sent to the deallocated instance. I'm also not sure that a web view likes its container view disappearing before it's done (but I've no choice if the user hits the back button before it's loaded).
In my viewWillDisappear handler I have this
map.delegate=nil;
[self.map stopLoading];
this seems to handle most cases OK, as nil'ing the delegate stops it sending the didFailLoadWithError to my view controller. However if I release the web view in my view's dealloc method, sometimes (intermittently) I will still get a message sent to the deallocated instance, which seems to be related to the javascript running in the actual page, e.g.:
-[UIWebView webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:]: message sent to deallocated instance 0x4469ee0
If I simply don't release the webview, then I don't get these messages though I guess I'm then leaking the webview.
If I don't send the 'stopLoading' message, and simply release the webview within viewWillDisappear, then I see messages like this:
/SourceCache/WebCore/WebCore-351.9.42/wak/WKWindow.c:250 WKWindowIsSuspendedWindow: NULL window.
Possibly related, I sometimes (again totally intermittent) get an ugly heisenbug where clicking the back button on some other view's navbar will pop the title, but not the view. In other words I get left with the title of view n on the stack, but the view showing is still view n+1 (the result is you're trapped on this screen and cannot get back to the root view - you can go the other direction, i.e. push more views and pop back to the view that didn't pop corrrectly, just not to the root view. The only way out is to quit the app). At other times the same sequence of pushes and pops on the same views works fine.
This particular one is driving me nuts. I think it may be related to the view disappearing before the web view is loaded, i.e. in this case I suspect it may scribble on memory and confuse the view stack. Or, this could be completely unrelated and a bug somewhere else (i've never been able to reproduce it in debug build mode, it only happens with release build settings when I can't watch it with gdb :-). From my debug runs, I don't think I'm over-releasing anything. And I only seem to be able to trigger it if at some point I have hit the view that has the web view, and it doesn't happen immediately after that.
A variation on this should fix both the leaking and zombie issues:
- (void)loadRequest:(NSURLRequest *)request
{
[self retain];
if ([webView isLoading])
[webView stopLoading];
[webView loadRequest:request];
[self release];
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
[self retain];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[self release];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
[self release];
}
- (void)viewWillDisappear
{
if ([webView isLoading])
[webView stopLoading];
}
- (void)dealloc
{
[webView setDelegate:nil];
[webView release];
[super dealloc];
}
There's a few ways to handle it, but this should work. You want the didFailLoadWithError message, it's what tells you it's stopped.
Set a flag isLeaving=YES;
Send the Webview a stopLoading.
In didFailLoadWithError:, check for the error you get when
the webview stops:
if ((thiserror.code == NSURLErrorCancelled) && (isLeaving==YES)) {
[otherClass performSelector:#selector(shootWebview) withObject:nil withDelay:0]
}
release the webView in shootWebview:
variations:
if you want to be cavalier about it, you can do the performSelector:withObject:withDelay: with a delay of [fillintheblank], call it 10-30 seconds without the check and you'll almost certainly get away with it, though I don't recommend it.
You can have the didFailLoadWithError set a flag and clean it up somewhere else.
or my favorite, maybe you don't need to dealloc it all when you leave. Won't you ever display that view container again? why not keep it around reuse it?
Your debug being different from release issue, you might want to check your configuration to make sure that it's exactly the same. Bounty was on the reproducible part of the question, right? ;-).
--
Oh wait a second, you might be taking a whole View container down with the WebView. You can do a variation on the above and wait to release the whole container in shootWebView.
The UINavigationController bug you're describing in the second part of your post might be related to your handling of memory warnings. I've experienced this phenomenon and I"ve been able to reproduce it on view n in the stack by simulating a memory warning while viewing view (n+1) in the stack.
UIWebView is a memory eater, so getting memory warnings wouldn't be surprising when it's used as part of a view hierarchy.
A simple release message in dealloc ought to be enough.
Your second problem sounds like a prematurely deallocated view, but I can't say much without seeing some code.
I had a similar problem to this using a UIWebView in OS3 - this description was a good starting point, however I found than simply nil'ing out the web view delegate before releasing the webView solved my problem.
Reading the sample code (the accepted answer - above) - it seems like a lot of overkill. E.g. [webView release] and webView = nil lines do exactly the same thing given the way the author describes the variable is declared (so you don't need both). I'm also not fully convinced by all the retain and release lines either - but I guess your mileage will vary.
Possibly related, I sometimes (again
totally intermittent) get an ugly
heisenbug where clicking the back
button on some other view's navbar
will pop the title, but not the view.
In other words I get left with the
title of view n on the stack, but the
view showing is still view n+1 (the
result is you're trapped on this
screen and cannot get back to the root
view - you can go the other direction,
i.e. push more views and pop back to
the view that didn't pop corrrectly,
just not to the root view. The only
way out is to quit the app). At other
times the same sequence of pushes and
pops on the same views works fine.
I have the same problem, when I'm use navigation controller with view controllers in stack > 2 and current view controller index > 2, if an memoryWarning occurs in this momens, it raises the same problems.
There is inly 1 solution, which I found after many experiments with overriding pop and push methods in NavigationController, with the stack of view controllers, with views and superviews for stacked ViewControllers, etc.
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface FixedNavigationController :
UINavigationController <UINavigationControllerDelegate>{
}
#end
#import "FixedNavigationController.h"
static BOOL bugDetected = NO;
#implementation FixedNavigationController
- (void)viewDidLoad{
[self setDelegate:self];
}
- (void)didReceiveMemoryWarning{
// FIX navigationController & memory warning bug
if([self.viewControllers count] > 2)
bugDetected = YES;
}
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
// FIX navigationController & memory warning bug
if(bugDetected){
bugDetected = NO;
if(viewController == [self.viewControllers objectAtIndex:1]){
[self popToRootViewControllerAnimated:NO];
self.viewControllers = [self.viewControllers arrayByAddingObject:viewController];
}
}
}
#end
It works fine for 3 view controllers in stack.