Ernie Thomason
Ernie Thomason

Reputation: 1709

iOS OneDrive SDK - needs update for deprecated UIWebView

OneDriveSDK for iOS hasn't been updated in a while and uses the deprecated UIWebView for sign in. Apple will stop accepting apps that use UIWebView. Because we don't know if/when Microsoft will update this SDK, I wanted to share the code changes I made which uses WKWebView instead.

Upvotes: 3

Views: 736

Answers (2)

Ernie Thomason
Ernie Thomason

Reputation: 1709

This solution is now outdated. Don't use.


My solution updates ODAuthenticationViewController.m

See code below.
I added comment "ern2" to places I made updates.

In ADAL target/pod, I removed the reference to 4 files that references UIWebView that I'm not using. (If you are using these, then my solution doesn't work.)

ADAuthenticationViewController.h
ADAuthenticationViewController.m
ADAuthenticationWebViewController.h
ADAuthenticationWebViewController.m

//  Copyright 2015 Microsoft Corporation
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//  
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//  
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//


#import "ODAuthenticationViewController.h"
#import "ODAuthHelper.h"
#import "ODAuthConstants.h"

#define kRequestTimeoutDefault  60

@interface ODAuthenticationViewController() <WKNavigationDelegate>  // Ernie Ern2 UIWebVie_wDelegate

@property WKWebView *webView;   // Ern2

@property NSURLRequest *initialRequest;
@property (strong, nonatomic) ODEndURLCompletion successCompletion;
@property (strong, nonatomic) NSURL *endURL;

@property (strong, nonatomic) NSTimer *timer;
@property (nonatomic) BOOL isComplete;

@end

@implementation ODAuthenticationViewController

- (instancetype)initWithStartURL:(NSURL *)startURL
                          endURL:(NSURL *)endURL
                         success:(ODEndURLCompletion)sucessCompletion
{
    self = [super init];
    if (self){
        _endURL = endURL;
        _initialRequest = [NSURLRequest requestWithURL:startURL];
        _successCompletion = sucessCompletion;
        _requestTimeout = kRequestTimeoutDefault;
        _isComplete = NO;
    }
    return self;
}

- (void)cancel
{
    if (!self.isComplete)
    {
        [self.timer invalidate];
        self.timer = nil;
        self.isComplete = YES;
        
        NSError *cancelError = [NSError errorWithDomain:OD_AUTH_ERROR_DOMAIN code:ODAuthCanceled userInfo:@{}];
        if (self.successCompletion){
            self.successCompletion(nil, cancelError);
        }
    }
}

- (void)loadInitialRequest
{
    [self.webView loadRequest:self.initialRequest];
}

- (void)redirectWithStartURL:(NSURL *)startURL
                      endURL:(NSURL *)endURL
                      success:(ODEndURLCompletion)successCompletion
{
    self.endURL = endURL;
    self.successCompletion = successCompletion;
    self.initialRequest = [NSURLRequest requestWithURL:startURL];
    self.isComplete = NO;
    [self.webView loadRequest:self.initialRequest];
}

- (void)loadView
{
    self.webView = [[WKWebView alloc] init];
    // Ern2 [self.webView setScalesPageToFit:YES];
    self.webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.webView.navigationDelegate = self; // Ern2
    self.view = self.webView;
    UIBarButtonItem *cancel = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                                                                target:self
                                                                                action:@selector(cancel)];
    self.navigationController.topViewController.navigationItem.leftBarButtonItem = cancel;
    
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self.webView loadRequest:self.initialRequest];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [self.webView stopLoading];
    self.webView.navigationDelegate = nil;
    [super viewWillDisappear:animated];
}

#pragma mark - UI_WebViewDelegate

// Ern2

- (void) webView: (WKWebView *) webView didStartProvisionalNavigation: (null_unspecified WKNavigation *) navigation {
    [self.timer invalidate];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:self.requestTimeout target:self selector:@selector(failWithTimeout) userInfo:nil repeats:NO];
}
/*
- (void)webViewDidStartLoad:(UIWebVie_w *)webView
{
    [self.timer invalidate];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:self.requestTimeout target:self selector:@selector(failWithTimeout) userInfo:nil repeats:NO];
} */

- (void) webView: (WKWebView *) webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
    [self.timer invalidate];
    self.timer = nil;
}
/*
- (void)webViewDidFinishLoad:(UIWebVie_w *)webView
{
    [self.timer invalidate];
    self.timer = nil;
} */

- (void) webView: (WKWebView *) webView decidePolicyForNavigationAction: (WKNavigationAction *) navigationAction decisionHandler: (void (^)(WKNavigationActionPolicy)) decisionHandler {
    
    //NSLog(@"[ept] %@   %@", [navigationAction.request.URL absoluteString], [self.endURL absoluteString]);
    if ([[[navigationAction.request.URL absoluteString] lowercaseString] hasPrefix:[[self.endURL absoluteString] lowercaseString]]){
        self.isComplete = YES;
        [self.timer invalidate];
        self.timer = nil;
        
        self.successCompletion(navigationAction.request.URL, nil);
        decisionHandler(WKNavigationActionPolicyCancel);
    }
    else decisionHandler(WKNavigationActionPolicyAllow);
}

/*
- (BOOL)webView:(UIWebVie_w *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebVie_wNavigationType)navigationType
{
    if ([[[request.URL absoluteString] lowercaseString] hasPrefix:[[self.endURL absoluteString] lowercaseString]]){
        self.isComplete = YES;
        [self.timer invalidate];
        self.timer = nil;
        
        self.successCompletion(request.URL, nil);
        return NO;
    }
    return YES;
} */

- (void) webView: (WKWebView *) webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(nonnull NSError *)error {
    
    [self.timer invalidate];
    self.timer = nil;
    
    if (NSURLErrorCancelled == error.code)
    {
        //This is a common error that webview generates and could be ignored.
        //See this thread for details: https://discussions.apple.com/thread/1727260
        return;
    }
    
    if([error.domain isEqual:@"WebKitErrorDomain"]){
        return;
    }
    
    // Ignore failures that are triggered after we have found the end URL
    if (self.isComplete)
    {
        //We expect to get an error here, as we intentionally fail to navigate to the final redirect URL.
        return;
    }
    
    if (self.successCompletion) {
        self.successCompletion(nil, error);
    }
}
/*
- (void)webView:(UIWebVie_w *)webView didFailLoadWithError:(NSError *)error
{
    [self.timer invalidate];
    self.timer = nil;
    
    if (NSURLErrorCancelled == error.code)
    {
        //This is a common error that webview generates and could be ignored.
        //See this thread for details: https://discussions.apple.com/thread/1727260
        return;
    }
    
    if([error.domain isEqual:@"WebKitErrorDomain"]){
        return;
    }
    
    // Ignore failures that are triggered after we have found the end URL
    if (self.isComplete)
    {
        //We expect to get an error here, as we intentionally fail to navigate to the final redirect URL.
        return;
    }
    
    if (self.successCompletion) {
        self.successCompletion(nil, error);
    }
}
*/

- (void)failWithTimeout
{
    [self webView: self.webView didFailNavigation: nil withError: [NSError errorWithDomain: NSURLErrorDomain code: NSURLErrorTimedOut userInfo:nil]];
    //[self webView:self.webView didFailLoadWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:nil]];
}

@end

Upvotes: 2

Ernie Thomason
Ernie Thomason

Reputation: 1709

The "accepted" answer is now outdated, as you shouldn't be using the old OneDrive pod anymore.

OneDrive API for iOS really made me frustrated at the lack of simple instructions and setup. The Microsoft main demo pages don't even link to iOS samples anymore (at least for what I found).

You need to use GRAPH api. The ObjC Graph pod is outdated it seems, and not sure if/when it will be updated.

MSAL is the authentication library used to authenticate to Microsoft/Graph. This is writen in ObjC. No problem. This IS current and maintained. Use this.

Problem with not having a Graph/OneDrive API pod from Microsoft (unless you want to use "old" one) means you need to figure out all the calls yourself, and do the low level GET/PUT/POST calls, and figure out the URLs. Somewhat troublesome.

Below are links I found useful:

Sample swift code that only uses MSAL cocoapod. I like this because clean; doesn't use old Graph API, which hasn't been updated since 2015 not version 1.0 yet. Downside is you have to figure out your own URL calls, but sample includes a demo call https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-ios

Another older tutorial. The archived tutorial pages (link below) has some setup details I found useful, to compliment above demo. Above demo might all you need, but I found this demo first and some of the instructions related to MSAL setup were helpful. Uses the "outdated" (?) ObjC Graph API cocoapod https://github.com/microsoftgraph/msgraph-training-ios-swift/tree/main/tutorial

OneDrive graph API info: https://learn.microsoft.com/en-us/onedrive/developer/rest-api/?view=odsp-graph-online

General graph info: https://learn.microsoft.com/en-us/graph/use-the-api

Query params: https://learn.microsoft.com/en-us/graph/query-parameters

Paging: https://learn.microsoft.com/en-us/graph/paging

Upvotes: 0

Related Questions