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.
Comments
Anonymous
March 18, 2015
An even easier approach (at least for some projects) is to build a Web app/service and encapsulate it in a native app. That means the Web app is available to other mobile platforms as well, but for typically iOS and Android the user can install an app the normal way. Of course not an option for most types of games etc, but might be quite sufficient for front-ends to corporate services.Anonymous
August 07, 2015
Hi according with your experience is possible connect an ios/swift app with sharepoint/office365 (with adfs) i've make (with other colleagues) in windows phone application that read a sharepoint 2013 list (on office 365) but with adfs authentication (not azure AD) we have realized an authentication based on saml and cookies without a web form for make the signin (just two textbox) is possible to make the same thing with ios/swift or is necessary to connect to the azureAD? tnk