可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
We have:
(1) Facebook API-based web application with Facebook OAuth functionality (“the FB web app”)
(2) UIWebView
-based browser on iPad (“the Browser”)
Our objective is to open the Facebook Login page to sign in to the FB web app (1) inside the UIWebView-based Browser (2) on iPad.
There is a somewhat similar issue here:
http://facebook.stackoverflow.com/questions/11337285/no-longer-able-to-login-to-ios-app-via-oauth-the-page-requested-was-not-found
However, the issue of that question happens after the user enters login and password into the Facebook form. Our problem is that we cannot get the Facebook login form displayed in the first place. Changing the app type to from “Web” to “Native/Desktop”, as suggested in that question, did not help.
Steps:
1. Open our web page (simple HTML page) with this UIWebView
Browser
2. Click on “FB web app” launch button on this page
3. OnClick JavaScript tries to initiate OAuth, which should open the login screen of Facebook to sign in to the FB web app
Current outcome (issue):
On iOS 5.+ and iOS 6.+ devices
- Our web page stays unchanged
- Facebook login page is NOT shown (our web page is still displayed)
On iOS 4.3 (works as expected):
- the Facebook login page is opened in the same UIWebView
object of the Browser (replaces our web page)
Expected outcome:
- Facebook login page is displayed, and the user can enter Facebook login & password
- Works on iOS 5.+ and iOS 6.+ if launched in Safari browser on iPad. Facebook login page is opened in a separate tab (in contrast, there are no separate tabs in UIWebView
)
Question: How can I get UIWebView
to open Facebook login page in response to the OAuth request on iOS 5+ and iOS 6+?
More technical details:
We log different NSURLRequest
fields from within
-(BOOL)webView(UIWebView*)webView shouldStartLoadWithRequest(NSURLREquest*)request navigationType:…
And we notice some difference in logs for “correct” and “incorrect” behaviors. Here how execution flows look for me:
Firstly, I press “FB Web App” launch button to initiate OAuth, then some cases go
iOS 4.3, “correct”
request to www.facebook.com/dialog/oauth?...
request to fbwebapp.com
request to m.facebook.com/login.php?....
--here facebook login appears
iOS 5.0, “incorrect1”
request to www.facebook.com/dialog/oauth?...
request to fbwebapp.com
request to m.facebook.com/login.php?...
Then it may be
--a lot of m.facebook.com/login.php?...with next… in parameters
followed by sqlite error
--right now I see “Sorry, something went wrong” page from facebook (it’s a first time at all I encounter it)
iOS 6.0 “incorrect2”
request to www.facebook.com/dialog/oauth?...
request to fbwebapp.com
-(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error is invoked with error code -999
You can see that behavior definitely depends on iOS version. But common case is that error happens on the step of obtaining m.facebook.com/login.php.. URL. But that’s all that we can detect.
We’re banging our heads against that wall for the whole day looking for solutions. Hopelessly.
Can you help us get the Facebook Login page opened in the UIWebView
in response to OAuth?
回答1:
just use : this code
if (![FBSDKAccessToken currentAccessToken])
{
FBSDKLoginManager *manager = [[FBSDKLoginManager alloc]init];
manager.loginBehavior = FBSDKLoginBehaviorWeb;
[manager logInWithReadPermissions:@[@"public_profile", @"email", @"user_friends"] handler:
^(FBSDKLoginManagerLoginResult *result, NSError *error) {
NSLog(@"result.token: %@",result.token.tokenString);
NSLog(@"%@",result.token.userID);
NSLog(@"%hhd",result.isCancelled);
}];
}
// here manager.loginBehavior = FBSDKLoginBehaviorWeb;
is all you need to open facebook in UIWebview
回答2:
Did it!
It kinda of a hack, but the js facebook sdk login on UiWebView at iOS 6 finally works.
How it could be done? It is a pure JS + Facebook JS SDK + UIWebView Delegate handling functions solution.
JS - First step)
a login button (to connect with facebook) calls this function example, that will trigger Face JS login/oauth dialogs:
function loginWithFacebookClick(){
FB.login(function(response){
//normal browsers callback
});
}
JS - Second step)
add a authResponseChange listener every time user loads the webpage ( after FB.init() ) to catch user's connected status:
FB.Event.subscribe('auth.authResponse.Change', function(response){
//UIWebView login 'callback' handler
var auth = response.authResponse;
if(auth.status == 'connected'){
//user is connected with facebook! just log him at your webapp
}
});
AND with app's UIWebView delegate functions you can handler facebook oauth responses
Objective C - Third step)
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = [[request URL] absoluteString];
//when user status == connected (has a access_token at facebook oauth response)
if([url hasPrefix:@"https://m.facebook.com/dialog/oauth"] && [url rangeOfString:@"access_token="].location != NSNotFound)
{
[self backToLastPage];
return NO;
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *url = [[webView.request URL] absoluteString];
if([url hasPrefix:@"https://m.facebook.com/dialog/oauth"])
{
NSString *bodyHTML = [webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"];
//Facebook oauth response dead end: is a blank body and a head with a script that does
//nothing. But if you got back to your last page, the handler of authResponseChange
//will catch a connected status if user did his login and auth app
if([bodyHTML isEqualToString:@""])
{
[self backToLastPage];
}
}
}
So, when 'redirect' user to the last loaded page, the second step is going to handler user action at facebook login dialogs.
If I got too fast with this answer, please ask me!
Hope it helps.
回答3:
In case anyone is googling, here's what worked for me:
-(BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)inType {
if ([request.URL.absoluteString containsString:@"m.facebook.com"]) {
if ([request.URL.absoluteString rangeOfString:@"back"].location == 0) {
[self.popUp removeFromSuperview];
self.popUp = nil;
return NO;
}
if (self.popUp) {
return YES;
}
UIWebView *wv = [self popUpWebView];
[wv loadRequest:request];
return NO;
}
return YES;
}
- (UIWebView *) popUpWebView {
toolbar height
UIWebView *webView = [[UIWebView alloc]
initWithFrame:CGRectMake(0, 0, (float)self.view.bounds.size.width,
(float)self.view.bounds.size.height)];
webView.scalesPageToFit = YES;
webView.delegate = self;
// Add to windows array and make active window
self.popUp = webView;
[self.view addSubview:webView];
return webView;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if (self.popUp) {
NSError *error = nil;
NSString *jsFromFile = @"window.close=function(){window.location.assign('back://' + window.location);};";
__unused NSString *jsOverrides = [webView
stringByEvaluatingJavaScriptFromString:jsFromFile];
JSContext *openerContext = [self.webView
valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSContext *popupContext = [webView
valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
popupContext[@"window"][@"opener"] = openerContext[@"window"];
}
//this is the secret sauce
if (webView == self.popUp
&& [webView.request.URL.absoluteString containsString:@"m.facebook.com"]
&& [[webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"] isEqualToString:@""]) {
[webView stringByEvaluatingJavaScriptFromString:@"eval(document.getElementsByTagName('script')[0].text)"];
}
}
I snagged a bunch of this implementation from here.
Depending on your web implementation, there will likely be one extra step. The Facebook script actually executes a window.close()
then a window.open()
then a window.close()
. For me this was causing problems because on the web side, after this login is complete, my window (i.e. for the webView that I want the user to log in to) was getting a window.close()
call, coming from the Facebook SDK. I'm assuming this is because the Facebook SDK expects that window.open()
call to open a new window that it will close.
Since we didn't override the functionality of window.open()
, calling window.open()
won't do anything, and the Facebook SDK will attempt to close your window. This could cause all kind of problems, but for me since I'm using Parse, window.localStorage
was set to null
so I was getting all kinds of errors.
If something like this is happening for you, you have two options to fix it:
- If you have control of the web code, and your down for a small hack, throw this in
window.close=function(){}
- If you don't have control of the web code, you can either add an override to
window.close
for the main webView like we did for the popUp webView, or override the window.open
function to open another popUp (which is described in more detail here)
回答4:
Use the FBDialog class to prompt the user to login. This uses a webview inside of the app, therefore on successful login the user will be logged in inside of any UIWebView:
NSString *kRedirectURL = @"fbconnect://success";
NSString *kSDK = @"ios" ;
NSString *kLogin = @"oauth";
NSString *kDialogBaseURL = @"https://m.facebook.com/dialog/";
NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
AE_FACEBOOK_APPID, @"client_id",
@"user_agent", @"type",
kRedirectURL, @"redirect_uri",
@"touch", @"display",
kSDK, @"sdk",
nil];
NSString *loginDialogURL = [kDialogBaseURL stringByAppendingString:kLogin];
FBLoginDialog* loginDialog = [[FBLoginDialog alloc] initWithURL:loginDialogURL
loginParams:params
delegate:self];
[loginDialog show];
Then make your class adhere to the FBDialogDelegate protocol, and add this function to your class:
-(void)fbDialogLogin: (NSString *)token expirationDate:(NSDate *)expirationDate{
// Store the token and expiration date into the FB SDK
self.facebook.accessToken = token;
self.facebook.expirationDate = expirationDate;
// Then persist these so the SDK picks them up on next load
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.facebook.accessToken forKey:ACCESS_TOKEN_KEY];
[defaults setObject:self.facebook.expirationDate forKey:EXPIRATION_DATE_KEY];
[defaults synchronize];
}
HTH!
回答5:
How to facebook login in UIWebView.
Objective-c
Use taylorstine's answer.
He saved my day. Thank you taylorstine
But I'm using Swift 3. so I just converted code below from taylorstine's answer.
Swift 3.
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let _ = request.url?.absoluteString.range(of: "m.facebook.com" ) {
if let _ = request.url?.absoluteString.range(of: "back"){
self.popUp?.removeFromSuperview()
self.popUp = nil
return false
}
if let _ = self.popUp {
return true
}
let wv = popUpWebView()
wv.loadRequest(request)
return false
}
return true
}
func popUpWebView() -> UIWebView {
let webView = UIWebView(frame: self.view.frame)
webView.delegate = self
self.popUp = webView
self.view.addSubview(webView)
return webView
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if let _ = self.popUp {
let jsFromFile = "window.close=function(){window.location.assign('back://' + window.location);};"
let _ = webView.stringByEvaluatingJavaScript(from: jsFromFile)
let openerContext = self.webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext
let popupContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext
popupContext.setObject("opener", forKeyedSubscript: "window" as (NSCopying & NSObjectProtocol)!)
popupContext.setObject(openerContext.objectForKeyedSubscript("window"), forKeyedSubscript: "opener" as (NSCopying & NSObjectProtocol)!)
}
if webView == self.popUp
&& (webView.request?.url?.absoluteString.range(of:"m.facebook.com") != nil)
&& webView.stringByEvaluatingJavaScript(from: "document.body.innerHTML") == "" {
webView.stringByEvaluatingJavaScript(from: "eval(document.getElementsByTagName('script')[0].text)")
}
}