My view controller is presenting a view via the presentViewController:animated:completion:
method. The view is presented fine.
Then I dismiss this view and re-present it and get the following crash:
*** -[WebBrowser isKindOfClass:]: message sent to deallocated instance 0x1f640ac0
My code is using ARC. Here is the code of my WebBrowser class, a straightforward embedded browser.
WebBrowser.h:
@interface WebBrowser : ITViewController <UIWebViewDelegate, UIAlertViewDelegate>
@property (nonatomic, strong) NSString *URL;
@property (nonatomic, weak) IBOutlet UIWebView *webView;
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner;
- (id)initWithURL:(NSString *)URL;
- (IBAction)dismissView:(id)sender;
@end
WebBrowser.m:
@implementation WebBrowser
- (id)initWithURL:(NSString *)URL_ {
self = [super initWithNibName:@"MyNib" bundle:nil];
if (self) {
self.URL = URL_;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.webView.delegate = self;
if (self.URL) {
[self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.URL]]];
}
}
- (IBAction)dismissView:(id)sender {
self.URL = nil;
[self.webView stopLoading];
self.webView.delegate = nil;
[self dismissViewControllerAnimated:YES completion:NULL];
}
// + some non-related web view delegate stuff
@end
And finally here is how I present the view in my parent view controller:
WebBrowser *browser = [[WebBrowser alloc] initWithURL:URL];
[self presentViewController:browser animated:YES completion:NULL];
I'm running iOS 6 and compiling with ARC.
First I thought this bug was ARC related. Here is my original post:
I've noticed crashes in my app with iOS 6.x when displaying modal view controllers and releasing it when it was working just fine with previous versions of iOS.
Blame me for not using ARC yet (it's my next big step on this project), but for instance, when displaying Game Center leaderboards with the following code, the following steps :
showLeaderboard
shown below, ie displaying a new instance of GKLeaderboardViewController
)then, the following error happens
*** -[GKLeaderboardViewController isKindOfClass:]: message sent to deallocated instance 0x17467120
This is my code:
- (void)showLeaderboard {
if ([[GKLocalPlayer localPlayer] isAuthenticated]) {
GKLeaderboardViewController *lb = [[GKLeaderboardViewController alloc] init];
lb.category = ...;
lb.leaderboardDelegate = self;
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:lb animated:YES];
[lb release];
} else {
...
}
}
- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController{
[self dismissModalViewControllerAnimated:YES];
}
It turns out that removing the [lb release]
instruction solves my problem and, again, that no such crash happens with iOS 5.x.
The same happens with the Game Center achievements view controller, or any other custom view controllers of mine being displayed with presentModalViewController:
.
It also seems that replacing the deprecated-presentModalViewController:
instruction by the new presentViewController:animated:completion:
DOES NOT solve the problem.
I see at least one possible problem:
[self.webView stopLoading];
self.webView.delegate = nil;
In general, it is safer to set delegate
to nil
before calling stopLoading
.
Also, it is definitely safer to use dismissViewControllerAnimated
it the way it is supposed to be used, that is, call it on the presenting controller. Although the documentation states the call is passed to the presenting controller, it's not a good idea to call a method on an object which is being deallocated inside the method.
- (IBAction)dismissView:(id)sender {
// pass the event to the presenting controller
// the presenting controller should call [self dismissViewControllerAnimated:YES completion:NULL];
}
- (void)viewWillDissapear {
[super viewWillDissapear];
//no need to do this, it's done automatically in ARC
//self.URL = nil;
self.webView.delegate = nil;
[self.webView stopLoading];
}
Edit
According to Apple's documentation you SHOULD be dismissing the viewcontroller from the parent (presenting) viewcontroller: http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html#//apple_ref/doc/uid/TP40007457-CH111-SW14
"When it comes time to dismiss a presented view controller, the preferred approach is to let the presenting view controller dismiss it. In other words, whenever possible, the same view controller that presented the view controller should also take responsibility for dismissing it."
Also, can you please clear up which viewcontrollers are presenting which, and how many are overlayed on top of each other. from apples docs:
"If you present several view controllers in succession, and thus build a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack."
So if you think it might have anything to do with delegate methods coming from your webview, you should probably be unsubscribing the view from the delegate property/stopping the webview from loading in viewWillUnload, not in the dismissal IBAction, since that won't necessarily be called.
I have edited this to make it more complete/clear
You need to set the browser view to a instance variable with a strong property before presenting the view. Then set it to nil after dismissing it.
First create a delegate protocol for Modals:
@protocol ModalViewDelegate
/**
Delegation callback for when a modal has been dismissed
*/
- (void)modalDidDismiss:(UIViewController*)viewController;
@end
In your presenting view controllers interface, subscribe to the protocol:
@interface PresentingViewController <ModalViewDelegate>
@property (nonatomic,strong) WebBrowser *browserView;
@end
In your implementation when presenting the view:
WebBrowser *browser = [[WebBrowser alloc] initWithURL:URL];
browser.delegate = self
self.browserView = browser;
[self presentViewController:browser animated:YES completion:NULL];
In your WebBrowser interface:
@interface WebBrowser : ITViewController <UIWebViewDelegate, UIAlertViewDelegate>
@property (nonatomic, strong) NSString *URL;
@property (nonatomic, weak) IBOutlet UIWebView *webView;
@property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner;
@property (nonatomic, weak) id <ModalViewDelegate> delegate;
- (id)initWithURL:(NSString *)URL;
- (IBAction)dismissView:(id)sender;
@end
In your WebBrowser implementation:
- (IBAction)dismissView:(id)sender {
self.URL = nil;
[self.webView stopLoading];
self.webView.delegate = nil;
[self.delegate didDismissBrowser:self];
}
And back in your parent view controller:
- (void)didDismissBrowser:(WebBrowser*)browser
{
if (browser == self.browserView)
{
[self dismissViewControllerAnimated:YES completion:NULL];
self.browserView = nil;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With