Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Danneman101

macrumors 6502
Original poster
Aug 14, 2008
361
1
I've been using the following method with sdk 3.2 to open the safari browser from within my app:

Code:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://www.mypage.com"]];

Upgrading to 4.3.1 however, I encounter a curious problem.

It opens safari without any problems.

But when I return to my application, the webview that contained the link that via shouldStartLoadWithRequest triggered the above code is now unresponsive.

Any ideas what causes this, and how to solve it?
 
After some further research it seems to have to do with the app not being closed completely after opening another application. It just keeps running in the background, and when it reappears the webpage is unresponsive due to the fact that the function that should remove the progressview is not called.

I can think of a couple of solutions:

1. Force the app to quit after having called the sharedApplication : openURL-function. But how do I force my app to quit programmatically?

2. Remove the progressview. This makes the app look bad, though.

3. Remove the progressview when the app receives focus again (without having been closed). But what function is used to detect this event?
 
Close app programmatically?

How do you close an app programmatically? I can't seem to find such a simple function...
 
You can't. There is no supported API. There is a private one I used to use if, for any reason, my app could not read an internal plist but Apple's scanning tool found that usage and rejected my app on the last updated...
 
Yes, there is a way using supported public APIs.

Set the "Application does not run in background" plist key so that your app can't run in the background. Then launch Safari by sending it a URL using UIApplication sharedApplication openURL.

The OS will then close your app for you before bringing Safari to the foreground.
 
Yes, there is a way using supported public APIs.

Set the "Application does not run in background" plist key so that your app can't run in the background. Then launch Safari by sending it a URL using UIApplication sharedApplication openURL.

The OS will then close your app for you before bringing Safari to the foreground.

Beautiful solution - not even a line of code required :D Thanks a lot :)
 
Well, UIApplication sharedApplication openURL would seem to be a line of code.

You haven't said why you want this. One common solution is to put up an alert with no buttons on it. The alert tells the user why the app can't proceed and the user then hits the home button. But it is correct that this solution won't be so good if the app just goes to the background and can come back later in the same state. Depends on why it can't proceed.
 
The web page you tell Safari to go to could contain an image of (or HTML for) your informational alert dialog. Then your already out of your app.
 
After some further research it seems to have to do with the app not being closed completely after opening another application. It just keeps running in the background, and when it reappears the webpage is unresponsive due to the fact that the function that should remove the progressview is not called.

You would really need to give details of how you're setting up this progressview...

3. Remove the progressview when the app receives focus again (without having been closed). But what function is used to detect this event?

This sounds like the right solution... I wouldn't mess around with shutting your app down, it's not how users will expect an iOS 4 application to behave. Even better if you're opening the link in Safari, you know that you shouldn't be loading the webpage yourself. So before you kick off to Safari do the cleanup.

But again any advice you get here is just going te be guess work because we don't really know what you're doing. And I don't mean any offense here, I'm guilty of myself. You get so wrapped up in what you're doing you think... Well this is obviously what I'm doing, everyone will understand it.
 
You would really need to give details of how you're setting up this progressview...



This sounds like the right solution... I wouldn't mess around with shutting your app down, it's not how users will expect an iOS 4 application to behave. Even better if you're opening the link in Safari, you know that you shouldn't be loading the webpage yourself. So before you kick off to Safari do the cleanup.

But again any advice you get here is just going te be guess work because we don't really know what you're doing. And I don't mean any offense here, I'm guilty of myself. You get so wrapped up in what you're doing you think... Well this is obviously what I'm doing, everyone will understand it.

Yes, I know - sorry about being less than clear on this. The problem is that the code handling this is so convoluted and spread trough so many code-pages that I find it really hard to distill the function to anything that is postable on the forum.

I'll give it a shot, though, because as you say the expected behavior is that the app should be kept open in the background:

The progressview is a subview that is loaded on top of the page each time the webview is loaded with a new local html-file:


.h-file
Code:
@interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate, UIWebViewDelegate, AVAudioPlayerDelegate, PopupTextSizeDelegate> {
	IBOutlet UIView *progressAlert;
}
@property (nonatomic, retain) UIView *progressAlert;


.m-file
Code:
@synthesize progressAlert;


-(void)webViewDidStartLoad:(UIWebView *)webView 
{
	// ----------------------
	// SHOW:	PROGRESS VIEW
	// ----------------------
	progressAlert = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
	progressAlert.frame = CGRectMake(0.0f, 0.0f, 1100.0f, 1100.0f);	
	[self.webView addSubview:progressAlert];
	progressAlert.backgroundColor = [UIColor whiteColor];
	[progressAlert release];

	// Also an activityview loaded here, but not important for this example I gather...
}


- (void)webViewDidFinishLoad:(UIWebView *)webView
{
	// ----------------------
	// HIDE:	PROGRESS VIEW
	// ----------------------
	@try {
		[self performSelector:@selector(fn_HideProgressView) withObject:nil afterDelay:0.01];
	}
	@catch (NSException * e) {}
	@finally {}
}


-(void) fn_HideProgressView
{
	// ---------------------
	// Stop:	ProgressView
	// ---------------------
	if (self.progressAlert)
	{
		[self.webView sendSubviewToBack:progressAlert];	
	}
}

The strange thing with the code is that when the app returns from having been in the background, all the functions for closing the progressview are called (even the "[self.webView sendSubviewToBack:progressAlert];" code).

Even when I try reloading a new local html-page into the webview the progressview is persistent.

This seems to indicate that perhaps I loose the handle for the current progressview as the app is pushed to the background, and when returned I can't access it. I.e. the "sendSubviewToBack"-function is not reaching the actual progressview.

This was not a problem in previous versions of the sdk, though...

The obvious solution would be to remove the progressview before opening safari, but that should already be done by the time a user clicks on a link, since fn_HideProgressView is called by webViewDidStartLoad. So I am uncertain why it appears again when the app regains focus.
 
You can't. There is no supported API. There is a private one I used to use if, for any reason, my app could not read an internal plist but Apple's scanning tool found that usage and rejected my app on the last updated...


Hang the main thread so the big bad watch dog comes and eats your app. Isn't that kinda supported or unsupported or silly.

^^ note this is not good advice, clearly a joke.
 
Hang the main thread so the big bad watch dog comes and eats your app. Isn't that kinda supported or unsupported or silly.

^^ note this is not good advice, clearly a joke.

That might well work. As would intentionally writing to a memory address you don't own :p
 
Code:
-(void)webViewDidStartLoad:(UIWebView *)webView 
{
	// ----------------------
	// SHOW:	PROGRESS VIEW
	// ----------------------
	progressAlert = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
	progressAlert.frame = CGRectMake(0.0f, 0.0f, 1100.0f, 1100.0f);	
	[self.webView addSubview:progressAlert];
	progressAlert.backgroundColor = [UIColor whiteColor];
	[progressAlert release];

	// Also an activityview loaded here, but not important for this example I gather...
}

This may be your problem. You're not implicitly retaining your progressAlert. There are two ways to set a pointer, using the designated setter

Code:
self.progressAlert = ...;
[self setProgressAlert:...];

and assigning the pointer directly

Code:
progressAlert = ...;

By using retain with your property and synthesizing the setter, the memory for the object is automatically retained when assigning it. Here the memory is retained by adding the progressAlert to the subview but maybe something funky is happening elsewhere in your code when going to the background and coming back that your pointer to it gets lost in the shuffle. What if you tried using your setter rather than assigning the pointer directly? An easy way to test this would be to add a break point or NSLog to your code when you check if the progressAlert is nil and see what the value actually is.

This is the main reason I always use different names for my iVars and my properties... (eg progressAlert_ for the iVar and progressAlert for the property). That way I can easily follow what is happening without having to look for self all the time, and if I do make a mistake, the compiler will whine at me.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.