Message Sent to Deallocated instance when going to background - iphone

I am using ARC and testing on iOS7 (iPhone 5S).
I have a navigation controller (a) with a pushed UIViewController (b). b presents a modal NavigationViewController (c) via a manual segue. c then pushes a UIViewController (d).
So now we have 2 navigation stacks, one root and one modal.
I would like to dismiss the modal NavigationViewController (c) and all of its children whenever I go to background.
In UIViewController (b) I have the following code:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(enteredBackground:)
name:UIApplicationDidEnterBackgroundNotification object:nil];
}
-(void)enteredBackground:(NSNotification *)notification{
if (self.presentedViewController) {
[self dismissViewControllerAnimated:NO completion:nil];
}
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
This works fine if I am on C's rootViewController when I go to background, but crashes when I am on D.
2013-10-20 22:49:49.008 MyApp[2596:60b] *** -[UIView release]: message sent to deallocated instance 0x17036aa40
How do I fix this? It seems like a view in the modal navigation stack is being sent dealloc and is already released. After looking in the debugger, the modal nvaigationcontroller (c) itself seems to still exist.

i have made an test sample and checked the same scenario , it is'nt crashing anywhere . Please check for any releases on object of type UIView in your ViewController C and D. Or can you post the code where you are doing all the stuff you mentioned above .

Related

ModalViewController not in the window hierarchy

After a long period of trying and searching the Internet and StackOverflow I could not find the answer I need.
My Problem is a warning:
Warning: Attempt to present <PAPasscodeViewController: 0x81cc8a0> on <ServerViewController: 0x75864b0> whose view is not in the window hierarchy!
I am using a Custom Control called "PAPasscodeViewController", the thing itself is working but when I try to display the view on the ApplicationDidBecomeActive method it displays this warning in the Consoleoutput.
I am using this method to keep track of the Notification:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(wait)
name:UIApplicationDidBecomeActiveNotification object:nil];
my -(void)wait is containing this:
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
NSLog(#"waiting");
[self performSelector:#selector(enterPasscode) withObject:nil afterDelay:.3f];
The App is having a RootViewController and a GeneralViewController, if the User already downloaded a Config package the GeneralViewController gets pushed in after starting as a modalView like this (in the viewDidLoad):
GeneralView = [[GeneralViewController alloc] initWithNibName:#"GeneralViewController" bundle:[NSBundle mainBundle]];
GeneralView.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentViewController:GeneralView animated:YES completion:nil];
In the GeneralView the User has more Options to open new Views through Buttons and they get pushed in through IBActions like the GeneralViewController.
So the Problem is occuring when this happens:
I am starting the App, Config Package was already installed and the User wants to track his ServerData on "ServerViewController", so he touches the appropriate Button for this.
Then the View gets pushed in (its now RootViewController (managed by AppDelegate)-> GeneralViewController(pushed in)-> ServerViewController(pushed in)), everything works fine.
Now when the User goes back to his Homescreen I am closing the modal View "ServerViewController" as follows:
-(void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(close:)
name:UIApplicationDidEnterBackgroundNotification object:nil];}
-(IBAction)close:(id)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}
so when the App enters Backgrounding the ServerViewController gets dismissed, after resuming the App the GeneralViewController is now the active View Controller, and in his -(void)viewDidAppear I am calling the -(void)wait from above, it calls this function:
- (void)enterPasscode{
PAPasscodeViewController *passcodeViewController = [[PAPasscodeViewController alloc] initForAction:PasscodeActionEnter];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
passcodeViewController.backgroundView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds style:UITableViewStyleGrouped];
}
passcodeViewController.delegate = self;
passcodeViewController.passcode = [configArray objectAtIndex:3];
passcodeViewController.simple = YES;
[self presentViewController:passcodeViewController animated:YES completion:nil];
}
This is working fine but then I am getting the annoying warning from above (here again so you dont have to scroll up)
Warning: Attempt to present <PAPasscodeViewController: 0x75d1c10> on <ServerViewController: 0x818b390> whose view is not in the window hierarchy!
actually this is confusing because I think my GeneralViewController is now the Active ViewController again...
Maybe I am just blinded by the simplicity of this Problem... but maybe someone here can help me understand.
I did NOT find any solution in any other post here in StackOverflow or other sites concerning that exact problem!
You have to remove self as observer when your ServerViewController is not shown. Add the following code to ServerViewController:
- (void)viewDidDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

UIKeyboardDidHideNotification error application crashing

I'm using a UIView. The application is using ARC. UIView is used in multiple view controllers. In UIView a listener to the UIKeyboardDidHideNotification is added. The listener works fine with some view controllers and with other view controllers it crashes the application. Especially when I use in the second view contoller after using in the first. The error is
* -[appname keyboardWillHide]: message sent to deallocated instance 0xb9c2760
In some scenarios the listener is getting called twice.
The code i have added in the uiview drawrect method is:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide) name:UIKeyboardWillHideNotification object:nil];
the code for listener is
-(void)keyboardWillHide
{
if(boolisViewlifted)
{
CGRect newFrame=self.frame;
newFrame=CGRectMake(self.frame.origin.x, self.frame.origin.y+250, self.frame.size.width, self.frame.size.height);
self.frame=newFrame;
boolisViewlifted=false;
}
}
The uiview appears on top of the calling view controller. Please let me know what causes this error and how to solve it.
Your view is getting unloaded because of memory warnings. You need to override dealloc method & remove observer for all notifications in all views where you added observer for notifications.
//do add in all views
-(void)dealloc
{
//[super dealloc];//As you are using ARC don't call super's dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Call Selector method from another Class - NSNotificationCenter

I would like to know how I can call the selector which is in another class when notification is posted.I am on tabbarcontroller.
The FirstViewController, SecondViewController are tab bar items
Inside `FirstViewController` I have the following
-(void)viewdidload
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
}
- (void)productPurchased:(NSNotification *)notification {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *productIdentifier = (NSString *) notification.object;
NSLog(#"Purchased: %#", productIdentifier);
[appDelegate.myDownloadablePoemsArray addObject:productIdentifier];
[self.tabBarController setSelectedIndex:3];
}
- (void)productPurchaseFailed:(NSNotification *)notification {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
SKPaymentTransaction * transaction = (SKPaymentTransaction *) notification.object;
if (transaction.error.code != SKErrorPaymentCancelled) {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"Error!"
message:transaction.error.localizedDescription
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil] autorelease];
[alert show];
}
}
The above code is working fine. Now what the issue is, I want to call the same selector method from my another view say for example I have a view controller named SecondViewController, in that I am adding the same notification observer.
but the selector method is not called in the FirstViewController.
Inside SecondViewController I have the following
-(void)viewdidload
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
}
But I want to call the selecor methods from FirstViewController;
Please let me know , is that possible ? And how can I do that?
Thanks a lot
in the SecondViewController change the self as observer to the pointer of the FirstViewController, because the instance of FirsViewController has the methods.
inside SecondViewController.m you must use these lines:
- (void)viewdidload {
[[NSNotificationCenter defaultCenter] addObserver:firstViewController selector:#selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:firstViewController selector: #selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];
}
BUT! AND THIS IS THE POINT.
if the FirstViewController is already a valid and loaded view controller in the memory with the methods as you've mentioned above, and it is an observer already for these notifications in the NSNotificatioCenter, you don't need to add again it to the NSNotificationCenter because the FirstViewController can receive and will receive the desired notification still. (it is just not shown, because an other view controller covers it.)
if the FirstViewController is not exists yet when the SecondViewController is, you cannot reach any instance method called from an another class because the FirstViewController was not instantiated before, and you cannot add it to the NSNotificationCenter as well.
CONCLUSION
it would be better to isolate the purchase callbacks into a third class what you can use for every independent view controller, according to the spirit of the OOP and MVC.
If your view controllers are the roots of tab-bar controllers, once they are loaded the first time, they stay around unless manually replaced.
Thus, when you install the notification handler in the first controller, unless you remove the notification handler, it will still get them, even when the second controller is onscreen.
Now, it may get unloaded due to memory pressure, or by the custom tab-bar-controller code. However, it's highly unusual for a tab-bar controller to deallocate one of its view controllers, so your installed notification handlers will be around until you cancel them.
In fact, if both view controllers register for the notifications, then they will both get them.
You are registering in viewDidLoad so the first one will get registered immediately, as it will be loaded and displayed as the initial controller. It will continue to receive those notifications.
When the second one loads, it will also register. Now both view controllers are receiving the notifications. When you go back to the first view controller, they will both still be getting the notifications.

I expect the initial view controller of my storyboard to be the current controller

If the application was previously in the background, when applicationDidBecomeActive is called, I expect the initial view controller of my storyboard to be the current controller.
I used:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self.window makeKeyAndVisible];
}
When I restart app ,loginAciton inside rootViewController still be called ,but could not present the next controller . No errors like nothing happened.
- (IBAction)loginAciton:(id)sender
{
id controller = [self.storyboard instantiateViewControllerWithIdentifier:#"Navigation"];
[self presentModalViewController:controller animated:YES];
}
Why?
PS. My rootViewController is not a UINavigationController.
Thanks for any replies.
A much better way is to add UIApplicationExitsOnSuspend to your Info.plist and set it to YES.
I got it !
if you wanna the initial view controller of your storyboard to be the current controller every time , you can try it :
- (void)applicationDidEnterBackground:(UIApplication *)application
{
exit(EXIT_SUCCESS);
}
You can use the notification too, put this code in applicationDidBecomeActive:
[[NSNotificationCenter defaultCenter] postNotificationName:#"appActivated" object:nil];
and add observer in that current view ...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateView:)
name:#"appActivated"
object:nil];
and don't forget to remove the observer in dealloc: of your current view...
[[NSNotificationCenter defaultCenter] removeObserver:self];
may this will help you..

playing youtube video inside uiwebview. How to handle the "done" button?

I have a uiwebview that plays a youtube video. How can I handle the done button action?
Right now, when I tap the done button it changes back to my app main menu (not the menu that was supposed to dismiss to) and it just freezes. Can anyone help me please?
Ps: the menu where the uiwebview is located, was previously presented modally.
The YouTube plug-in player is itself a modal view controller. It is returning to its presentingViewController when the done button is pressed. Its presentingViewController is not your modal view controller but is instead the viewController that called [presentModalViewController:animated:] to present your modal view controller. Since the original modal view controller is still active, the app behaves badly.
To fix the problem,
1) Track whether the modal view controller has been presented but not dismissed.
2) In the viewDidAppear method of the presenting view controller, if the modal view controller was presented and not dismissed, dismiss and present it again.
For example, in controller that is presenting the modal web view controller:
- (void) presentModalWebViewController:(BOOL) animated {
// Create webViewController here.
[self presentModalViewController:webViewController animated:animated];
self.modalWebViewPresented = YES;
}
- (void) dismissModalWebViewController:(BOOL) animated {
self.modalWebViewPresented = NO;
[self dismissModalViewControllerAnimated:animated];
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.modalWebViewPresented) {
// Note: iOS thinks the previous modal view controller is displayed.
// It must be dismissed first before a new one can be displayed.
// No animation is needed as the YouTube plugin already provides some.
[self dismissModalWebViewController:NO];
[self presentModalWebViewController:NO];
}
}
This thread is very useful and help me to find the problem!
The answer of lambmj works fine, but I found a better way.
In presenting view controller:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.presentedViewController) {
UIViewController *vc = self.presentedViewController;
[vc dismissModalViewControllerAnimated:NO];
[self presentModalViewController:vc
animated:NO];
}
}
Hope this helps!
#Gdx Wu
#lambmj
Thanks for your methods, they work fine. But there is some small problem that after clicking the done button & jumping directly to the presenting view controller, we need to dismiss the presented modal view controller and present it again, which would bring some dither(like flash) between these view controller switches.
Based on this, I highly recommend #IsaacCisneros 's method which would switch seamlessly.
Simply remove UIWebView when it enters full screen; add back UIWebView when it exit full screen. Sample code below assuming a UIViewController with subview of UIWebView, and your UIWebView should have youtube iframe.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Add observer for "Done" button click
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerWillExitFullscreen:)
name:#"UIMoviePlayerControllerWillExitFullscreenNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerDidEnterFullscreen:)
name:#"UIMoviePlayerControllerDidEnterFullscreenNotification"
object:nil];
}
- (void)viewDidDisappear:(BOOL)animated {
// Remove observers for "Done" button click
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIMoviePlayerControllerWillExitFullscreenNotification" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIMoviePlayerControllerDidEnterFullscreenNotification" object:nil];
}
- (void)playerWillExitFullscreen:(NSNotification *)notification {
// Before exit full screen, add back UIWebView that have been removed earlier
[self.view addSubview:self.webView];
}
- (void)playerDidEnterFullscreen:(NSNotification *)notification {
if (self.presentingViewController) { // UIWebView is presenting the build-in movie player controller
[self.webView removeFromSuperview]; // Built-in movie player controller is already entering full screen mode
}
}