Developing Native iOS Apps using the Office 365 SDK for iOS

Several months ago I authored a post on Developing iOS Apps with Azure and Office 365 APIs. In that post, I leveraged the Azure AD Authentication Library (ADAL) for iOS to authenticate and get resource-specific access tokens for Office 365 data retrieval. At the time of the post, performing REST queries and parsing results was the only mechanism available for communicating with Office 365 services. Since then, Microsoft has released an Office 365 SDK for iOS that makes it much easier to work with Office 365 services from a native iOS app. In this post, I’ll show you how to reference and use this new SDK in an Xcode project using the Swift programming language. I used Swift in my first post and will continue to use it here because of the lack of documentation that exists in the community.

[View:https://www.youtube.com/watch?v=jy7LUPojBTw]

Ensure Client Created

Almost all of the Office 365 API samples in Visual Studio use an “Ensure Client Created” method to abstract away authentication context, access tokens, and discovery from normal CRUD operations (EnsureSharePointClientCreated for SharePoint services and EnsureOutlookClientCreated for Exchange services). You can find this pattern being used on MVC, Universal App, and Cordova samples in GitHub and does a nice job of cleaning up CRUD methods. As such, I decided to pattern my Swift code in a similar way. Below you can see my EnsureMSSharePointClientCreated function that establishes authentication context based on authority, calls into the Discovery Service to find the “MyFiles” resource, and initializes the MSSharePointClient object.

EnsureMSSharePointClientCreated

// //  MyFilesController.swift //  SDKTesterOffice365 // //  Created by Richard diZerega on 11/19/14. //  Copyright (c) 2014 Richard diZerega. All rights reserved. // import Foundation typealias SPClientCreatedResponse = (MSSharePointClient?, NSString?) -> Void typealias ServiceResponse = (Array<SPItem>?, NSError?) -> Void typealias FileResponse = (NSData?, NSError?) -> Void class MyFilesController {     init() {    }        var DISC_RESOURCE:NSString = "https://api.office.com/discovery/"    var DISC_SERVICE_ENDPOINT:NSString = "https://api.office.com/discovery/v1.0/me/"        func EnsureMSSharePointClientCreated(onCompletion:SPClientCreatedResponse) -> Void {            var er:ADAuthenticationError? = nil            //setup the authentication context for the authority        var authContext:ADAuthenticationContext = ADAuthenticationContext(authority: authority, error: &er)                //get access token for calling discovery service        authContext.acquireTokenWithResource(DISC_RESOURCE, clientId: clientID, redirectUri: redirectURI, completionBlock: {             (discATResult: ADAuthenticationResult!) in                        //validate token exists in response            if (discATResult.accessToken == nil) {                onCompletion(nil, "Error getting Discovery Service Access Token")            }            else {                //setup resolver for injection for discovery                               var discResolver:MSDefaultDependencyResolver = MSDefaultDependencyResolver()                var discCred:MSOAuthCredentials = MSOAuthCredentials()                discCred.addToken(discATResult.accessToken)                var discCredImpl:MSCredentialsImpl = MSCredentialsImpl()                discCredImpl.setCredentials(discCred)                 discResolver.setCredentialsFactory(discCredImpl)                                //create the discovery client instance                 var client:MSDiscoveryClient = MSDiscoveryClient(url: self.DISC_SERVICE_ENDPOINT, dependencyResolver: discResolver)                                //get the services for the user                var task:NSURLSessionTask = client.getservices().read({(discoveryItems: [AnyObject]!, error: NSError!) -> Void in                                        //check for error and process items                    if (error == nil) {                        dispatch_async(dispatch_get_main_queue(), {                            //cast the discoveryItems as an array of MSDiscoveryServiceInfo                            var discList = (discoveryItems as Array<MSDiscoveryServiceInfo>)                                                        //loop through and find the MyFiles resource                            var myFilesResource:MSDiscoveryServiceInfo?                            for discItem in discList {                                if (discItem.capability == "MyFiles") {                                    myFilesResource = discItem                                    break                                }                                                  }                                  //make sure we found the MyFiles resource                            if (myFilesResource != nil) {                                var resource:MSDiscoveryServiceInfo = myFilesResource!                                                                //get a MyFiles access token                                authContext.acquireTokenWithResource(resource.serviceResourceId, clientId: clientID, redirectUri: redirectURI, completionBlock: {                                     (shptATResult: ADAuthenticationResult!) in                                                                        //validate token exists in response                                    if (shptATResult.accessToken == nil &&                                         shptATResult.tokenCacheStoreItem == nil &&                                         shptATResult.tokenCacheStoreItem.accessToken == nil) {                                        onCompletion(nil, "Error getting SharePoint Access Token")                                    }                                    else {                                        //get the access token from the result (could be cached)                                        var accessToken:NSString? = shptATResult.accessToken                                        if (accessToken == nil) {                                            accessToken = shptATResult.tokenCacheStoreItem.accessToken                                         }                                                                                //setup resolver for injection                                        var shptResolver:MSDefaultDependencyResolver = MSDefaultDependencyResolver()                                        var spCred:MSOAuthCredentials = MSOAuthCredentials()                                        spCred.addToken(accessToken)                                        var spCredImpl:MSCredentialsImpl = MSCredentialsImpl()                                        spCredImpl.setCredentials(spCred)                                         shptResolver.setCredentialsFactory(spCredImpl)                                                                                //build SharePointClient                                         var client:MSSharePointClient = MSSharePointClient(url: resource.serviceEndpointUri, dependencyResolver: shptResolver)                                                                                //return the SharePointClient in callback                                        onCompletion(client, nil)                                    }                                })                            }                            else {                                onCompletion(nil, "Unable to find MyFiles resource")                            }                        })                    }                    else {                        onCompletion(nil, "Error calling Discovery Service")                    }                })                                task.resume()            }        })    }     

 

Performing CRUD Operations

All CRUD operations are wrapped in the EnsureMSSharePointClientCreated function, whose callback returns an initialized MSSharePointClient object that can be used to query SharePoint (Outlook is similar). If you have worked with other Office 365 SDKs, you should find the syntax for performing CRUD operations very similar. Below are functions for getting folder items and file contents.

Performing Basic CRUD Operations with Office 365 SDK for iOS

func GetFiles(id:NSString, onCompletion:ServiceResponse) -> Void {       EnsureMSSharePointClientCreated() { (client:MSSharePointClient?, error:NSString?) in         //check for null client        if (client != nil) {            var spClient:MSSharePointClient = client!                         //determine if we load root or a subfolder            if (id == "") {                //get the files using SDK                var task:NSURLSessionTask = spClient.getfiles().read({ (items: [AnyObject]!, error: NSError!) -> Void in                    if (error == nil) {                         dispatch_async(dispatch_get_main_queue(), {                            var list = (items as Array<MSSharePointItem>)                             var spItems:Array<SPItem> = self.ConvertToSPItemArray(list)                             onCompletion(spItems, nil)                        })                     }                     else {                         println("Error: \(error)")                    }                 })                task.resume()            }             else {                //get the files using SDK                var task:NSURLSessionTask = spClient.getfiles().getById(id).asFolder().getchildren().read({ (items: Array<AnyObject>!, error: NSError!) -> Void in                     if (error == nil) {                         dispatch_async(dispatch_get_main_queue(), {                             var list = (items as Array<MSSharePointItem>)                            var spItems:Array<SPItem> = self.ConvertToSPItemArray(list)                            onCompletion(spItems, nil)                        })                    }                    else {                        println("Error: \(error)")                    }                 })                 task.resume()            }         }         else {             println("Error: \(error)")        }     }}     func GetFiles(onCompletion:ServiceResponse) -> Void {     GetFiles("", onCompletion) }     func GetFileContent(id: NSString, onCompletion:FileResponse) {    //ensure client created    EnsureMSSharePointClientCreated() { (client:MSSharePointClient?, error:NSString?) in         //check for null client        if (client != nil) {            var spClient:MSSharePointClient = client!             //get the file content using SDK             spClient.getfiles().getById(id).asFile().getContent({ (data: NSData!, er: NSError!) -> Void in                onCompletion(data, nil)            }).resume()         }        else {            println("Error: \(error)")        }     }   }

 

Conclusions

The Office 365 SDK for iOS certainly makes it easy to perform basic operations against Office 365 services. At the very least, I’m not parsing JSON/XML or trying to figure out the exact REST or header syntax. That said, I would encourage you to learn both patterns (pure REST and SDK) as both can be helpful in developing powerful applications that run on iOS. Feel free to download the Xcode project outlined in this post HERE.