Zombie UIWebView when it's deallocated just after alert() called from javascript - ios

I'm using a UIWebView, and it pops an alert when a button is clicked(from javascript). There is another button (in native side), which closes controller, so deallocs also UIWebView.
The problem is, if I touch the button in UIWebView, and touch to close button before alert is populated, my controller and UIWebView are deallocated, but alert remains on screen. Then if I click any button on alert, application crashes and gives following error:
[UIWebView modalView:didDismissWithButtonIndex:]: message sent to deallocated instance
And this method is called from private method
[UIModalView(Private) _popoutAnimationDidStop:finished:]
I'm using ARC, and my dealloc is like this:
- (void)dealloc {
[_myWebView stopLoading];
_myWebView.delegate = nil;
_myWebView = nil;
}
But this does not solve my problem because I think UIModalView has a reference of my webview as a delegate, and I could not set it to nil because its private.
How can I solve it?
Regards

Find a way to set the delegate on UIAlertView to nil before deallocating UIWebView.

This is an Apple bug in their handling of the alert view. Open a bug report.
In the meantime, here are some workarounds:
Create a category on UIAlertView:
#interface UIAlertView (QuickDismiss) #end
#implementation UIAlertView (QuickDismiss)
- (void)__quickDismiss
{
[self dismissWithClickedButtonIndex:self.cancelButtonIndex animated:NO];
}
#end
Now, in your view controller's dealloc method, call this:
[[UIApplication sharedApplication] sendAction:#selector(__quickDismiss) to:nil from:nil forEvent:nil];
This will dismiss all alert views that are currently open, including the ones displayed by your web view.
If that does not work, you can always iterate all subviews of all UIApplication.sharedApplication.windows objects, checking whether [view.class.description hasPrefix:#"UIAlertView"] is true, and dismissing that. This is a less elegant method than the previous one, and should be last resort.
Good luck.

Finally, I find a great solution which actully works. I use method swizzle to hook UIAlertView Delegate function - (void)didPresentAlertView:(UIAlertView *)alertView
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ([self.delegate respondsToSelector:#selector(didPresentAlertView:)]) {
[self.delegate didPresentAlertView:alertView];
}
if ([self.delegate isKindOfClass: [UIWebView class]]) {
uiWebView = self.delegate;
}
}
I just retain UIWebView instance in this function so that the UIWebView instance as UIAlertView's delegate will not be released before UIAlertView instance being released.

Related

Detect when user left viewController

I have controller (news screen), and i need to detect when user leave it. I tried
- (void)viewWillDisappear:(BOOL)animated
, but the problem is, when user tap share button (share in social networks etc.) that method triggers, but after sharing user is still in news screen, therefore its not work.
I also tried
-(void)willMoveToParentViewController:(UIViewController *)parent {
, but it also trigger when user first enter controller, which is wrong (i need to detect leaving only).
How can i detect when user leave controller, but not trigger when he enter "sharing" pop screen?
These four methods can be used in a view controller's appearance callbacks to determine if it is being presented, dismissed, or added or removed as a child view controller. For example, a view controller can check if it is disappearing because it was dismissed or popped by asking itself in its viewWillDisappear:
method by checking the expression ([self isBeingDismissed] || [self isMovingFromParentViewController]).
- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);
use isMovingFromParentViewController for your scenario
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController){
}
}
Check this it will help you.
UIActivityViewController *conroller=[[UIActivityViewController alloc] initWithActivityItems:#[#"Hello"] applicationActivities:nil];
You can handle the sharing thing in the completion here
[conroller setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError){
if(!activityError)
NSLog(#"Shared");
}];
The completion will tell you that the activity was presented so you can handle the activities you want to handle in the completion like this
[self presentViewController:conroller animated:YES completion:^{
NSLog(#"Activity Appeared"); //Same as viewWillDisappear
}];
Hope this helps.

Close keyboard with Side Menu MMDrawerController

I'm using MMDrawerController as a side menu. I can't find where to close the keyboard in center view when I open the side menu.
I've tried
write [self.view endEditing:YES] in sideMenuViewController's viewWillAppear method.
addObserver in centerViewController which called [self.view endEditing:YES] and postObserver in sideMenuViewController's viewWillAppear method.
It just don't work and cause like this...
Anyone has an idea to fix it?
today I met the same problem :)
I have spend several hours debugging the MMDrawer library and I have found solution of some kind. My solutions contains of two steps:
1) In MMDrawerController.m file - in the implementation of method: -(void)toggleDrawerSide:(MMDrawerSide)drawerSide animated:(BOOL)animated completion:(void (^)(BOOL finished))completion add at the beginning of the method, the following code: [self.centerViewController.view endEditing:true];. With this code you close the keyboard when tapping on menu burger - all views that are subviews for the current centerViewController try to resignFirstResponder.
2) In MMDrawerController.m file - in the implementation of method: -(void)panGestureCallback:(UIPanGestureRecognizer *)panGesture replace the else statement of case UIGestureRecognizerStateBegan with the following code:
else {
//hide keyboard when pan gesture start
[self.centerViewController.view endEditing:true];
self.startingPanRect = self.centerContainerView.frame;
}
And I am giving you the old code - coming from the library just for more detail explanation:
else {
self.startingPanRect = self.centerContainerView.frame;
}
With this code you close the keyboard when starting pan gesture, if such pan gesture is allowed for side menu - all views that are subviews for the current centerViewController try to resignFirstResponder.
P.S. I have tried to put this code in another method, but it behave a little strange because of the animation completion block after the movement of the centerViewController is done. So I think endEditing: in the beginning of the pan gesture is the way to go.
Hope that could help you!
I found the solution
In your CentralView controller
#pragma mark - Button Handlers
-(void)leftDrawerButtonPress:(id)sender{
[self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:nil];
[self.textfield resignFirstResponder];
}
Try this method. It is working for me
You don't have a use observer for this, just have IBOutlet property for your UITextField say "textfield".
[self.textfield resignFirstResponder];
add this line when you sideMenuViewController's viewWillAppear.
For anyone who still has problems with opening the drawer using a gesture, use the following code:
Put the code where you initialize your drawer controllers.
[self.drawerController setGestureCompletionBlock:^(MMDrawerController *drawerController, UIGestureRecognizer *gesture) {
// hide the keyboard when the gesture completes
if(drawerController.openSide == MMDrawerSideLeft) {
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
}
}];

UIWebView still active after pressing back button

I'm using a Navigationcontroller which pushes a new Viewcontroller containing a Webview every time a link is clicked. One of the URLs has a javascript function, which calls another function every 60 seconds. It works fine, but when I hit the back button once after being on that web view (containing the javascript), the webview stays active and keeps doing the javascript calls, while going back to the previous webview flawlessly. I can see it doing things through NSLog. Why is that and how can I avoid this?
EDIT:
I have declared my subclass of UIWebView in my Viewcontroller like this:
#property (strong, nonatomic) AFXTWebView *wv;
and this is how I instantiate it (in ViewDidLoad):
[self setWv:[[AFXTWebView alloc] initWithVC:self andURLString:[self.pageConfig objectForKey:xAFXTPageLink]]];
[self.view addSubview:self.wv];
[self.wv loadWebView];
I have also tried setting the webView to nil in viewWillDisappear:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.wv = nil;
}
You have to use the viewWillAppear & viewWillDisappear methods
-(void)viewWillAppear:(BOOL)animated
{
_webView=[[UIWebView alloc]init];
......//do your stuff
}
-(void)viewWillDisappear:(BOOL)animated
{
[_webView removeFromSuperView];
_webView=nil;
}
In your viewWillDisappear load a blank page into the web view.
This sounds like your web view and view controller are not being released, i.e. you have a leak. You should check your code for any unreleased retained/strong references to your view controller or web view.
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
}

Does a UIAlertView's Delegate have to be a view controller?

I've got an issue where an object that's creating a UIAlertView is sending a message back to its delegate, but the delegate object is crashing.
I've got it working OK in a couple of other instances, but what I'm trying to do in this case (and what's different from other similar questions) is that the object that instantiates the alert, and which acts as its delegate is not itself a view, but rather an object that is instantiated within a view. To wit, in the parent view:
#implementation
*MyCustomObject customObject;
-(void)viewDidLoad {
customObject = [[MyCustomObject alloc] init];
}
#end
And in the custom object:
-(void)DoCoolThings {
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Do You Want To Do Cool Things"
message:#"...description of cool things..."
delegate:self
cancelButtonTitle:#"No Thanks"
otherButtonTitles:#"HELLS YES", nil];
[message show];
}
and
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 1) {
[self DoCoolThings];
} else {
[self GoAndCry];
}
}
When I do the alert view within a viewcontroller object, everything is fine. Doing it within this sub-object which itself has no view allows me to create the alert, but that object--which shouldn't be getting de-allocated based on the scoping--doesn't seem to want to continue to act as a delegate.
The error I'm getting is indeed a de-allocation message, but I feel strongly that this is not the problem because if I remove the alert, all the other stuff--specifically, it's a wrapper for a storekit purchase process--works fine, and all those delegate methods work happily.
I've got a solution which will allow me to move the Alert into the parent view's methods, but I was hoping not to have to. Is this limitation real, or is it my imagination? IE am I doing something else wrong?

UINavigationcontroller popViewcontroller does not work at second try

I encountered a little problem with my app. It is a simple problem but i can not find out what is the cause of crash.
I have a simple viewcontroller with only a webview (created and linked in storyboard) (ARC enabled):
#implementation BPActivateController
#synthesize mainWebView;
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/test.php?code=%#", BASE_URL, [[NSUserDefaults standardUserDefaults] objectForKey:#"uniqueIdentifier"]]];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[mainWebView loadRequest:requestObj];
[super viewDidLoad];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *html = [webView stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"];
if ([html rangeOfString:#"<h1>Not Found</h1>"].location != NSNotFound)
{
[self.navigationController popViewControllerAnimated:YES];
NSLog(#"pop the view controller");
}
}
- (void)viewDidUnload
{
[self setMainWebView:nil];
[super viewDidUnload];
}
The first time the view opens the popViewControllerAnimated is called and the user is redirected back to the last viewcontroller. But when the view is opened again, gives me a EXEC_BAD_ACCESS after the popViewControllerAnimated is called. When i enable the Zombie code gives me the following trace:
2012-06-04 11:51:12.717 actusmedicus[410:707] pop the view controller
2012-06-04 11:51:12.720 actusmedicus[410:707] *** -[BPActivateController respondsToSelector:]: message sent to deallocated instance 0xc6d95b0
I have tried several things but still is is not clear what selector is being called. I suspect that the webview is still busy..
Anyone has an idea that gets me back on track?
[EDIT]
The EXEC_BAD_ACCESS is fixed by setting the delegate of the webview to nil.
But the real problem is still there, the first time the view is pushed on the UINavigation stack, the web view does load and after a 404 the popViewControllerAnimated is successfully executed. But the second time we push the same view on the UINavigation stack (the same way we did the first time) The popViewControllerAnimated does not do anything. I have checked if it is nil but that does not seem to be the problem..
My first idea was that it runs in another thread and the UINavigationcontroller does not exist there. I ruled out that possibility with the help of performSelectorOnMainThread.
Finally i added a button to call popViewControllerAnimated manually and that works every time, so why doesn't it work when i call it programmatically?
I suspect that the webview is still busy..
If so then in your dealloc call the stopLoading method.
You should also set the delegate to nil in the dealloc.
Make sure if you are not segueing twice to the destination view controller. In my case, my code fired the segue twice. So I had to click back button twice to come back to my main controller.

Resources