UINavigationcontroller popViewcontroller does not work at second try - ios

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.

Related

webViewDidFinishLoad not working?

In my app, I am trying to make a splash image appear as my UIWebView loads so it is not just a blank screen. However my webViewDidFinishLoad method will not work. This means that the splash image appears but does not disappear from the screen once the UIWebView has loaded.
My code for the method is:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
[loadingImageView removeFromSuperview];
}
Any help on why the method will not work would be appreciated greatly.
My .h:
#interface ViewController : UIViewController
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
My ViewDidLoad and webViewDidFinishLoading:
- (void)viewDidLoad {
UIWebView *mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
[super viewDidLoad];
}
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSLog(#"content loading finished");
// Remove loading image from view
[loadingImageView removeFromSuperview];
}
Hi probably you do not set proper delegate.
This is small code tip for you.
-(void)viewDidLoad {
mWebView = [[UIWebView alloc] init];
mWebView.delegate = self;
mWebView.scalesPageToFit = YES;
}
-(void)webViewDidFinishLoad:(UIWebView *)webView {
[loadingImageView removeFromSuperview];
NSLog(#"finish");
}
In you're .h file add.
#interface MyView: UIViewController <UIWebViewDelegate> {
UIWebView *webView;
}
Code fixes.
For .h file
#interface ViewController : UIViewController<UIWebViewDelegate>
-(IBAction)makePhoneCall:(id)sender;
#property (nonatomic, strong) IBOutlet UIWebView *webView;
#property(nonatomic, strong) UIImageView *loadingImageView;
#end
For .m file
- (void)viewDidLoad
{
[super viewDidLoad];
webView.delegate = self;
//**************** Set website URL for UIWebView
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.sleafordpizza.com/food"]]];
//**************** Add Static loading image to prevent white "flash" ****************/
UIImage *loadingImage = [UIImage imageNamed:#"LittleItalyLogo.png"];
loadingImageView = [[UIImageView alloc] initWithImage:loadingImage];
loadingImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"LittleItalyLogo.png"],
nil];
[self.view addSubview:loadingImageView];
}
At certain times, this delegate method actually never gets fired. I have had severe problems with the same thing in some of my projects.
At one occasion, I actually had to solve it with a timer, checking the state of the web view every second or so to see if I could proceed.
In that particular case, I just needed a certain element to be present. Still, the view did not trigger the finish loading event, due to external script errors being injected.
So, I just started a trigger when the web view begun loading, then called a method every now and then to see if the web view contained the element in question.
- (void)methodCalledByTimer {
if (<I still do not have what I need>) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
You could also check if the web view is actually loading, if that is absolutely necessary:
- (void)methodCalledByTimer {
if (self.webView.isLoading) {
//The web view has not yet finished loading; keep checking
} else {
//The web view has finished loading; stop the timer, hide spinners and proceed
}
}
Then, naturally, I'd check for the finishedLoading event as well, just to be sure. Remember to also implement the webView:didFailLoadWithError: method as well.
When waiting for a web page to finish loading, there are some things to keep in mind.
For instance, do you really need it to stop loading, or is there anything else you can do? In my case, I needed an element. Being able to properly execute a script is another thing that may be required.
Second, is the loading page using any external resources? I once had external script errors causing the webViewDidFinishLoad: method to not being called at all. If I removed the external scripts, it worked.
Third, if the page is using external resources, you are exposed not only to the loading capacity of your own resources, but that of the external resources as well. Tracking scripts, ads etc...if one resource provider is delivering content sloooowly (or not at all), you could page could be stuck in loading state forever.
So, I'd go with checking for something else. :)
I see you aren't handling errors. If there is an error, all subsequent delegate calls will not happen. I was surprised to find that this is true when the webview uses a plugin too. It calls this error method telling you that the webview handed off to the delegate, in my case the movie player.
implement this and see if that is it.
-(void) webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (error.code == 204) {
//request was handled by a plugin instead of by the webview directly
...
}
else
{
NSLog(#"didFailLoadWithError. ERROR: %#", error);
}
}
I was able to do all the remaining loading work in this method instead of the webviewdidfinishLoad

Method called only in viewDidApper but not in viewDidLoad, why?

I created a method that sets the title of a button based on a value.
This method needs to be called when opening the viewController and maybe refreshed when the controller appears again.
So i created the method and I called that method in viewDidLoad and viewDidApper but it seems to be called only when I change page and turn back to the view controller.
Why?
My code is
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self controlloRichieste];
......
}
-(void)viewDidAppear:(BOOL)animated{
[self controlloRichieste];
}
-(void)controlloRichieste{
//Numero richieste di contatto
NSString *numeroRichieste = #"1";
if([numeroRichieste isEqual:#"0"]){
[_labelRequestNumber setTitle:#"Nessuna" forState:UIControlStateNormal];
} else {
_labelRequestNumber.titleLabel.text = numeroRichieste;
_labelRequestNumber.tintColor = [UIColor redColor];
}
//Fine Numero richieste di contatto
}
You can also move that code to viewWillAppear so that it gets called each time it appears.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self controlloRichieste];
}
I see the problem now, try the other way around
-(void)controlloRichieste{
//Numero richieste di contatto
NSString *numeroRichieste = #"1";
if([numeroRichieste isEqual:#"0"]){
[_labelRequestNumber setTitle:#"Nessuna" forState:UIControlStateNormal];
} else {
_labelRequestNumber.tintColor = [UIColor redColor];
[[_labelRequestNumber titleLabel]setText:numeroRichieste];
}
//Fine Numero richieste di contatto
}
Change set the button color, before you change its titleLabel's text
I created a demo PROJECT for you, hope it's helpful!
When you open view first time the viewDidLoad is called and the viewDidAppeare.
The viewDidAppeare is called every time when the view is opened, when you push or present other view controller and go back to the maine one viewDidAppeare is called.
You should call:
[super viewDidAppear:animated];
The viewDidLoad is called just when the view is loaded and after that when it's deallocated and it needs to be allocated again. So mostly when you push or present other view controller and go back to the maine one viewDidLoad is not called.

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"]]];
}

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

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.

Another (!) Activity Indicator Issue

Well, after reading a bunch of SO posts on this issue, I still can't fix problems with my activity indicator. This indicator is in a view in a tab under the control of its own view controller. It has a view with a UIWebView which loads a local html page just fine. The view is loaded with initWithNibName and then awakeFromNib. Here's the part I think is relevant:
#implementation HelpViewController
#synthesize webView = _webView;
#synthesize back = _back;
#synthesize forward = _forward;
#synthesize aI = _aI;
- (void) viewDidLoad {
_aI = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_aI stopAnimating];
// Now add the web view
NSString *filePath = [[NSBundle mainBundle]
pathForResource:#"Help"
ofType:#"html"];
[self.view addSubview:_webView];
_webView.delegate = self;
NSURL* fileURL = [NSURL fileURLWithPath:filePath];
NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
[_webView loadRequest:request];
[super viewDidLoad];
}
-(void) webViewDidStartLoad:(UIWebView *)webView {
[_aI startAnimating];
_back.enabled = NO;
_forward.enabled = NO;
}
-(void) webViewDidFinishLoad:(UIWebView *)webView {
[_aI stopAnimating];
if (webView.canGoBack) {
_back.enabled = YES;
_back.highlighted = YES;
}
if (webView.canGoForward) {
_forward.enabled = YES;
_forward.highlighted = YES;
}
}
The navigation buttons work fine. The activity indicator was placed in the nib, but in the main view, not on/over/under the webView. In the attributes, I have Hides When Stopped checked. If I check Animating the indicator is always visible and animated, no matter how I navigate through the UIWebView.. If I uncheck Animating, Hidden is automatically checked for me. In this case, the indicator never shows up. So it's either always on or always off.
I've read quite a bit about cases where you need to have the indicator on a different thread. I'm not sure, but I don't think that applies here (I load a local html page but allow users to navigate off and then back to the local page). But, I seem to have some disconnect; perhaps it is the fact that the indicator is in the main view but the pages are in the webView? Or I'm not calling things in the right methods. Or who knows... Thanks for any help!
You overwrite _aI in your viewDidLoad and never place it in your view hierarchy, so the object you are sending the messages to is never visible and the activity indicator placed in interface build will never change its state, so thats why its either always animating or hidden.

Resources