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.
Related
So, it is pretty clear question I think. This question is about monitoring ajax requests. I tried it but didn't work as I wanted.
How can I call a function in my native iOS Obj-C application every time a request is sent, received and the browsing link is changed in WKWebView?
Ok, I found a way.
You can create a new class (let's call it MyURLProtocol) which has NSURLProtocol as subclass. Then add this function to MyURLProtocol:
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
NSLog(#"URL = %#", request.URL.absoluteString);
return NO;
}
This function will be called each time your webview makes a request. And then you need to register this protocol with the loading system. In your Appdelegate.m file include your class and add/replace didFinishLaunchingWithOptions function with this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[NSURLProtocol registerClass:[MyURLProtocol class]];
return YES;
}
All set. Now you can edit canInitWithRequest function and do what you want with the request.
After visiting a share url on a website, viewers are prompted to open the link in my application. If the application is running, it works perfectly.
However, if the application is closed, the viewDidLoad and viewWillAppear methods are not called on my ViewController, so I am not able to open the desired ViewController.
Does anyone know how to allow to get the viewDidLoad function to run if the app is launched from the openURL function?
I currently have:
AppDelegate:
-(BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
if (!url) { return NO; }
NSString *party_url = [NSString stringWithFormat:#"%#%#/", PARTYURL, url.lastPathComponent];
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:#"WebViewNotification" object:party_url]];
return YES;
}
ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
appDelegate = [AppDelegate getDelegate];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(webViewNotification:) name:#"WebViewNotification" object:nil];
}
- (void)webViewNotification:(NSNotification *)notification {
NSString *url = [notification object];
PartyViewController *partyViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PartyViewController"];
partyViewController.partyUrl = url;
[self.navigationController pushViewController:partyViewController animated:YES];
}
Again, this works fine if the app was previously opened. However, if it is closed, this does not work. Thank you!
When your app launched from a link without any other operation you don't have a presented ViewController contains this code in your views stacks. so this code will never been executed. consider directly present partyViewController in your CustomTabBarController.
First, thanks to everyone that takes the time to answer questions. I have gotten so many quick answers to problems over the years from StackOverflow.
I'm new to Object C and iOS programming, but starting with what I think should be a super simple app. It receives a push notification (which works fine) and redirects to a webpage when it has figured out its appid.
The problem is that the while I can get my UIWebView to loadRequest in the viewDidLoad, the same code will not execute in another function.
Here's the code:
AppDelegate.m
// UFAppDelegate.m
#import "UFAppDelegate.h"
#import "UFViewController.h"
#implementation UFAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Add registration for remote notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
// ....
NSString *urlString = #"http://www.google.com";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(#"Navigating to URL: %#", url);
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
}
#end
ViewController.h:
// UFViewController.h
#import <UIKit/UIKit.h>
#interface UFViewController : UIViewController
- (void)handleOpenURL:(NSURL *)url;
#end
ViewController.m:
// UFViewController.m
#import "UFViewController.h"
#interface UFViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *UFHWebView;
- (void)handleOpenURL:(NSURL *)url;
#end
#implementation UFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"UFViewController.viewDidLoad started");
// This block that assigns the url and loads it works perfectly here... but not down below.
NSString *urlString = #"http://search.yahoo.com/";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.viewDidLoad completed");
}
- (void)handleOpenURL:(NSURL *)url
{
NSLog(#"UFViewController.handleOpenURL started");
NSLog(#"url = %#",url);
// The below loadRequest does not load!
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.handleOpenURL completed");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"Error - %#", error);
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return YES;
}
#end
When run as coded here, the yahoo page shows from the loadRequest in the didload, but the handleOpenURL one still does not fire. If I comment out the loadRequest out of viewDidLoad, a blank page shows up and the handleOpenURL still does not fire.
Here's the debug sequence. viewDidLoad fires and completes before receiving the AppID and manually firing handleOpenURL:
2013-12-12 15:28:32.606 UF[5896:60b] UFViewController.viewDidLoad started
2013-12-12 15:28:32.608 UF[5896:60b] UFViewController.viewDidLoad completed
2013-12-12 15:28:32.765 UF[5896:60b] Navigating to URL: http://www.google.com
2013-12-12 15:28:32.769 UF[5896:60b] UFViewController.handleOpenURL started
2013-12-12 15:28:32.773 UF[5896:60b] url = http://www.google.com
2013-12-12 15:28:32.775 UF[5896:60b] UFViewController.handleOpenURL completed
Any help appreciated!
Are you sure you have just one instance of UFViewController here?
-didRegisterForRemoteNotificationsWithDeviceToken initializes a new web view controller in a local variable, but I can't see how it's getting shown to the user - you aren't doing anything with the vc once it's created.
Here's what I think might be happening:
App starts. You have an instance of UFViewController in a storyboard. It loads, its -viewDidLoad is called, the log statements print and the webview loads correctly.
A little later, the -didRegisterForRemoteNotificationsWithDeviceToken: method is called. This instantiates a UFViewController, and calls the -handleOpenUrl method on it. The -handleOpenURL log methods fire. However, this controller has not been loaded into the view hierarchy, so its view is never instantiated, and its -viewDidLoad is never called.
This means that the webview that the -loadRequest: is being called on is nil. In Objective-C, it is valid to send a message to nil- unlike most other languages, no error or exception will be thrown. Instead, absolutely nothing happens, hence no web load.
The UFViewController in -handleOpenUrl is only referred to by a local variable within the method, so when the method completes, the last strong reference to it vanishes and it is released.
To check this is correct:
-NSLog(#"%#",[self description]); in your vc methods - this will log the address of the objects and tell you whether they are the same.
-NSLog the web controller's view, to check whether it's nil in handleOpenURL
To solve your problem, I suggest using an NSNotification posted by the AppDelegate and listened for by the UFViewController that is fully instantiated. So in the vc you'd do:
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(handleRegistration:) name:#"HandleRegistrationNotification" object:nil];
and then implement the handleRegistration method:
-(void)handleRegistration:(NSNotification*)notification
{
//your handleOpenUrl code
}
and also
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"HandleRegistrationNotification" object:nil];
}
(this bit is important because otherwise you may get a crash when the vc is dealloc'd)
Then, in the -didRegisterForRemoteNotifications: method:
[[NSNotificationCenter defaultCenter]postNotificationName:#"HandleRegistrationNotification" object:nil];
If you need to pass any information through the notification (eg urls) you can use the object parameter - this is accessible in the method called on the vc via the object property of the NSNotification parameter.
Full docs on notifications in Objective-C can be found here.
Some minor points - it's generally a bad idea to call your view controllers things like theView rather than theViewController since other people (or you in 6 months time) will end up thinking they're UIView s. Also the webview property should not start with a capital letter- that's generally only used for class names.
This should be called, "How to call a storyboard viewcontroller method from the appdelegate". And the answer is:
in AppDelegate.m:
#import "UFViewController.h"
...
UFViewController *rootViewController = (UFViewController*)self.window.rootViewController;
[rootViewController handleOpenURL:url];
In my code snippets above, this means replacing these lines
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
with the ones above. The difference is my original code instantiates a new UFViewController object into a local variable, while the correct code just gets a handle to the view controller already created in the storyboard and calls its method.
I have setup a test application using Admob Mediation service. I have an issue that when an error occurs and is handled by a method if I hide the banner View it causes no further ad requests to occur, possibly due to this 'hidden status'.
What I can do is sleep within the error method for a certain amount of time then request again, however this isn't the best method... I am guessing this would lock up some process and potentially other user input whilst sleepng? I am not sure of this as the app only includes ads so cannot test.
Here are my methods...
- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error;
{
NSLog(#"Failed to receive ad with error: %#", [error localizedFailureReason]);
bannerView_.hidden = YES;
sleep(59);
[bannerView_ loadRequest:[self createRequest]];
}
- (void)adViewDidReceiveAd:(GADBannerView *)view;
{
NSLog(#"Ad Received");
bannerView_.hidden = NO;
}
I am looking for the best way to either:
1. Hide the view when no ad is returned, but ensure requests continue and the ad view is shown again once an ad is received.
2. Use a loop in the error method to handle requesting again until successful and not locking up anything else.
Only being tested on simulator at the moment, if any difference is made.
I would suggest you to use performSelector to make an asynchronous call instead of using sleep because sleep will block your thread. So this is implemented as in the 1st way you mentioned.
- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error;
{
NSLog(#"Failed to receive ad with error: %#", [error localizedFailureReason]);
bannerView_.hidden = YES;
[self performSelector:#selector(repeatAdRequest) withObject:nil afterDelay:60.0];
}
- (void)adViewDidReceiveAd:(GADBannerView *)view;
{
NSLog(#"Ad Received");
bannerView_.hidden = NO;
}
-(void) repeatAdRequest
{
[bannerView_ loadRequest:[self createRequest]];
}
make addview a subview of another blank uiview.
In adview didfail, hide the view and not the adview
and in recieve show the view again.
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];