i open a url within a Webview and whilst loading i show a spinner in a alert view.
If the url tapped opens an internal app such as iTunes how can i detect the web view has left my app so that when the user returns the dialog has been dismissed.
i have used didFailLoadWithError and this didn't work?
Any ideas?
Thanks dan
FIXED- i forgot to set the delegate Doh!
UIWebViewDelegate method webView:shouldStartLoadWithRequest:navigationType: will ask your webview's delegate if it is allowed to open every single url before trying to open it. You could check for url type in that method and return NO if it's not http or https, then present an alert for the user and let them chose if they want to open it or stay within your app, or just log that app was left to open another one and return YES;
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *urlString = [NSString stringWithFormat:#"%#", request.URL];
NSArray *urlComponents = [urlString componentsSeparatedByString:#"://"];
NSString *urlType = [urlComponents objectAtIndex:0];
if ([urlType isEqualToString:#"http"] || [urlType isEqualToString:#"https"]) {
// present an alert or do whatever you want with this url...
return NO;
}
return YES;
}
First of all, using an alert view to display loading progress in a web view is probably a bad idea, because it blocks all user interaction until it finishes loading.
You already have code that allows the web view to handle certain URLs with the built-in apps, such as iTunes (it doesn't do that on its own), so I guess when you use [[UIApplication sharedApplication] openURL:...] to open the external URL, you could easily hide the spinner there as well.
You could use applicationWillResignActive to detect when the app goes into an inactive state. It goes in your app delegate:
- (void)applicationWillResignActive:(UIApplication *)application {
//close your UIWebView here
}
If you can't access your UIWebView from the delegate, you could register for the UIApplicationDidEnterBackgroundNotification notification from your UIViewController. Make sure you un-register at some point.
//register
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeWebView) name:UIApplicationDidEnterBackgroundNotification object:nil];
//un-register
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
Related
I have a little game with a timer.
I'm implementing adMob to monetize and I am not able to restart timer/ads after user clicks on the banner and come back to the app.
The flow is:
1 - game start
2 - show ads
3 - click on banner and pause timer
4 - oper safari
5 - click "back to my app" link/button (iOS feature)
6 - back to the app and restar timer (problem here)
I had implemented all adMob events method (and insert restar timer code) but I can't get out of this issue.
The code work because it worked with iAds (I'm migrating to adMob).
Any help is appreciated.
Thank you
EDIT:
here is the code:
/// Tells the delegate an ad request loaded an ad.
- (void)adViewDidReceiveAd:(GADBannerView *)adView {
NSLog(#"adViewDidReceiveAd");
self.pauseTimer = NO;
}
/// Tells the delegate an ad request failed.
- (void)adView:(GADBannerView *)adView
didFailToReceiveAdWithError:(GADRequestError *)error {
NSLog(#"adView:didFailToReceiveAdWithError: %#", [error localizedDescription]);
self.pauseTimer = NO;
}
/// Tells the delegate that a full screen view will be presented in response
/// to the user clicking on an ad.
- (void)adViewWillPresentScreen:(GADBannerView *)adView {
NSLog(#"adViewWillPresentScreen");
self.pauseTimer = NO;
}
/// Tells the delegate that the full screen view will be dismissed.
- (void)adViewWillDismissScreen:(GADBannerView *)adView {
NSLog(#"adViewWillDismissScreen");
self.pauseTimer = NO;
}
/// Tells the delegate that the full screen view has been dismissed.
- (void)adViewDidDismissScreen:(GADBannerView *)adView {
NSLog(#"adViewDidDismissScreen");
self.pauseTimer = NO;
}
/// Tells the delegate that a user click will open another app (such as
/// the App Store), backgrounding the current app.
- (void)adViewWillLeaveApplication:(GADBannerView *)adView {
NSLog(#"adViewWillLeaveApplication");
self.pauseTimer = YES;
}
In this VC create a property to store this
#property (nonatomic) BOOL didGoToSafari;
- (void)adViewWillLeaveApplication:(GADBannerView *)adView {
NSLog(#"adViewWillLeaveApplication");
self.pauseTimer = YES;
self.didGoToSafari = YES;
}
In the VC that you show right before the ad would show in viewWillAppear or viewDidAppear you should put this code
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(applicationDidBecomeActiveNotification:)
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
And then after viewDidAppear or viewWillAppear, write this function
- (void)applicationDidBecomeActiveNotification:(NSNotification *)notification {
if (self.didGoToSafari = YES){
self.pauseTimer = NO;
self.didGoToSafari = NO;
}
}
In viewWillDisappear
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
Basically what you're doing is listening to see if the app became active again. If it did, check to see if it's coming back from Safari. It's not perfect because you could feasibly be using the app, user goes to Safari and then doesn't go back to or close the game. They could then use Safari later and then go back to the game and it would start running again. There probably some control flow in the AppDelegate you could use to code around this, but in general this code should do it.
EDIT: As per your comment about understanding it, here's the full explanation.
You are using NSNotification to listen for when the app returns to an active state. UIApplicationDidBecomeActiveNotification is automatically called when your app becomes active (it's an app delegate method). When it does, the method (void)applicationDidBecomeActiveNotification gets called automatically and the methods in that method get called. You have a boolean flag to see if the app is returning from Safari because your app could return from any other app if user switched to another app when the ad got pushed. In the end, you remove your VC as an observer to avoid memory leaks.
I've implemented a Notification Center Extension that provides a button to launch its containing app. Depending on the state of the widget I want to navigate to one of two different locations in the app when this button is pressed. I was able to obtain this behavior and it works well while the app is in memory, however, if the app has to be launched for the first time when this button is pressed it does not navigate to the correct location.
I know the reason why this occurs. When I parse the URL to determine where to go in the app delegate, I post a notification and provide information in the userInfo dictionary. I subscribe to this notification in the View Controller that needs to update its UI based on the information in the userInfo dictionary. The problem is, I subscribe to this notification in viewWillAppear. This notification is posted before viewWillAppear is called on this View Controller when the app is launched for the first time, therefore the UI does not update and it behaves as if the app was launched from the home screen.
My question is, is there a different method I can place the code to subscribe to this notification that's early enough in the lifecycle, or is there a better approach to respond to launching from a URL scheme?
Some simplified code:
//AppDelegate.m:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
if ([[url scheme] isEqualToString:#"app name"]) {
if ([[url resourceSpecifier] isEqualToString:#"//tab1"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:DidLaunchWithURLScheme object:nil userInfo:#{#"info": #"first"}];
} else if ([[url query] isEqualToString:#"//tab2"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:DidLaunchWithURLScheme object:nil userInfo:#{#"info": #"second"}];
}
return YES;
}
return NO;
}
//viewWillAppear in View Controller:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didLaunchAppWithURLInfo:)
name:DidLaunchWithURLScheme
object:nil];
- (void)didLaunchAppWithURLInfo:(NSNotification *)notification {
//does get called if app was already in memory
//does not get called if app was fresh launched
//do something here like select a different tab based on the userInfo
}
I have a UIWebView that is handling an OAuth authentication flow in my app. The callback URL that the OAuth service uses points to a custom URL scheme I've set up in my app, let's call it mycustomurlscheme.
So once the user authenticates successfully, the OAuth service redirects them to the URL mycustomurlscheme://oauth/success?oauth_token=xxxxxxxxxxxxxxxxxxxx&oauth_verifier=xxxxxxxxxxxxxxxxxxxx. When the UIWebView requests this URL, the app behaves correctly; namely, the application:openURL:sourceApplication:annotation: method in my app delegate gets called, and I dismiss the UIWebView and continue on with the app.
However, I am logging load failures in the UIWebView (using webView:didFailLoadWithError:), and when the above process happens, I see the following in the console:
Error Domain=WebKitErrorDomain Code=102 "Frame load interrupted" UserInfo=0xa1a4810 {NSErrorFailingURLKey=mycustomurlscheme://oauth/success?oauth_token= xxxxxxxxxxxxxxxxxxxx&oauth_verifier=xxxxxxxxxxxxxxxxxxxx, NSErrorFailingURLStringKey=mycustomurlscheme://oauth/success?oauth_token= xxxxxxxxxxxxxxxxxxxx&oauth_verifier=xxxxxxxxxxxxxxxxxxxx, NSLocalizedDescription=Frame load interrupted}
I tried implementing the following UIWebViewDelegate method, thinking that intercepting the request would avoid the error, but the error still happens:
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request
navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:#"mycustomurlscheme"]) {
[[UIApplication sharedApplication] openURL:request.URL];
return NO;
} else {
return YES;
}
}
This code then causes the following code in my app delegate to run:
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
NSNotification *notification = [NSNotification notificationWithName:kDSApplicationLaunchedWithURLNotification object:nil userInfo:#{kApplicationLaunchOptionsURLKey: url}];
[[NSNotificationCenter defaultCenter] postNotification:notification];
return YES;
}
This notification is picked up by the following block that continues the loading & launching of the app, post-authorization:
self.applicationLaunchNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kDSApplicationLaunchedWithURLNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
NSURL *url = [notification.userInfo valueForKey:kApplicationLaunchOptionsURLKey];
currentRequestToken.verifier = [DSAPIParametersFromQueryString([url query]) valueForKey:#"oauth_verifier"];
[[DSAPIManager sharedManager] acquireAccessTokenWithRequestToken:currentRequestToken success:^(DSAPIOAuth1Token * accessToken) {
[self finishLogin];
} failure:^(NSError *error) {
[self removeAsObserver];
[self showAlert:NSLocalizedString(#"Could not authorize the app. Please try again later.", nil)];
}];
}];
The finishLogin and removeAsObserver methods look like this:
- (void)finishLogin
{
[self removeAsObserver];
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self performSegueWithIdentifier:#"PostLoginSegue" sender:self];
}];
}
- (void)removeAsObserver
{
if (self.applicationLaunchNotificationObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:self.applicationLaunchNotificationObserver];
self.applicationLaunchNotificationObserver = nil;
}
}
Is it safe to ignore this error message? Anyone know why it's happening? My hunch is that the UIWebView thinks the URL has an invalid scheme, but the phone knows it's ok. If that's the case is there any way to tell the UIWebView (or the WebKit engine) that this scheme is valid?
The error here is actually the Frame load interrupted nothing to do with the url scheme. Using custom url scheme is perfectly possible and doesn't create any errors. I have them in many of my apps and have never had a problem with them.
I believe your issue is because of the [webView stoploading]; technically it will stop loading when you return NO so the [webView stopLoading]; is just causing an interruption in the process. Remove this line and let it continue to return NO and see what happens I am 99.9% sure this is the reason.
So your code would look like
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([request.URL.scheme isEqualToString:#"mycustomurlscheme"]) {
// Dismiss the view controller, continue loading the app, etc.
return NO;
}
return YES; // No need for the else statement if this is the only thing to happen if it fails the if statement.
}
UPDATE
Based don comments removing [webView stopLoading]; didn't fix your issue. If this is the case and you make it into that if statement I suspect that something else is going on. Have you shared all your code? With this // Dismiss the view controller, continue loading the app, etc. in your code I suspect that you haven't shared everything in which case we can't help.
Check if there is anything for "UserAgent" in"standardUserDefaults". There are chances that UserAgent is getting appended in request URL. Or check for any "UserAgent" sent in url request.
The notification I receive contains an url to open in a UIWebView. But I cannot access the UIWebview from my AppDelegate (this is where is receive the notification).
-(void) application:(UIApplication *)application didReceiveRemoteNotification(NSDictionary* ) userInfo{
// handle notification (this works)
//HERE I want to call my UIWebView and load the url I got.
}
One way to do this is to post a notification that your UIWebView receives. Check out the NSNotificationCenter Class Reference or this example on SO.
In my app I have used the following, this is when the UIWebView is already on the screen:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSString *urlString = [userInfo objectForKey:#"url"];
if( [self.window.rootViewController isKindOfClass:[UINavigationController class]] ){
UINavigationController *currentNavigationController = (UINavigationController*)self.window.rootViewController;
if( [currentNavigationController.visibleViewController isKindOfClass:[NHCallVC class]] ){
SomeViewController *currentViewController = (SomeViewController*)currentNavigationController.visibleViewController;
[currentViewController.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
}
}
}
The structure with the UINavigationController seems complex, but this was needed for the way I set up my app. This can be different in your app.
The idea is to get the viewcontroller that is opened and load a URL on the UIWebView. The code is assuming the UIViewController with the UIWebView is currently open. The code should be altered if you want to navigate to the correct UIViewController before opening the url in the UIWebView.
Place an observer to the view where UIWebView resides such as:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(recievedNotification:) name:#"ReceivedNotification" object:nil];
Write the appropriate code within recievedNotification function which changes the UIWebView's target url.
And post a notification in didReceiveRemoteNotification function in APP Delegate such as:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReceivedNotification" object:nil];
Good luck.
Good day,
Through the use of an UIWebview I have now a working method to show a youtube video within my app (using the tag, finding the play button within the webview and firing the touch event on that).
Works like a charm. The video pops up and plays. However I would like to recieve an event when the video ends or the user clicks the done button.
On the internet I have found that there is an event: MPAVControllerItemPlaybackDidEndNotification where you can listen to. However this one does not get called.
After some further research I found that for Youtube Videos embedded through UIWebView another notification was called ( UIMoviePlayerControllerDidExitFullscreenNotification ). Unfortunately that one does not work either anymore. ( found it here )
Does anyone have any idea how I can do some action after the video is done playing or has been dismissed?
Thanks
Use the UIMoviePlayerControllerWillExitFullscreenNotification for getting notified once the user tapped on the DONE button. The UIMoviePlayerControllerDidExitFullscreenNotification seems indeed to be omitted on iOS6.
Mind that ...Did... vs. ...Will... difference!
For more on that subject, once again check my updated answer within that posting you referenced in your question.
Let's look at this scenario :
In your view, you have a button. When it's clicked, you want to play the video directly.
In order, to do so, you open the webview as a modal view of your view :
[self presentModalViewController:videoWebView animated:NO];
For your webview, you should use Youtube API to integrate and autoplay the video. See the proposed working example here : https://stackoverflow.com/a/15538968
You'll see that the video is launched in a modal view of your webview view. One way to detect when the video is dismissed (when the "done" button has been clicked) is to use the viewDidAppear on your webview view class. In this method you will then dismiss the webview view too but...when this view is launched at first, you don't want to dismiss it. You can add a boolean property to avoid that.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (_videoLaunched) {
[self dismissModalViewControllerAnimated:YES];
}
}
In the viewDidLoad method, set this property to NO and in the webViewDidFinishLoad method (delegate method of webview) set it to YES.
I think it answers one part of your question. Concerning the detection of the end of the video you have to modify you YT_Player.html file to listen to state changes.
ytPlayer = new YT.Player('media_area', {height: '100%', width: '100%', videoId: 'SbPFDcspRBA',
events: {'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange}
function onPlayerStateChange(e) {
var result = JSON.parse(event.data);
if (result.info == 0) { // Video end
window.location = "videomessage://end";
}
}
});
You will then catch the event in your webview view and dismiss it like this :
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
if ([[url scheme] isEqualToString:#"videomessage"]) {
[self dismissModalViewControllerAnimated:YES];
return YES;
}
return YES;
}
What you need here is something like this:
- (void)playerWillExitFullscreen:(NSNotification *)notification
{
//do something...
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerWillExitFullscreen:)
name:#"MPMoviePlayerWillExitFullscreenNotification" object:nil];