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 (2)

  1. MobilePundits says:

    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.

  2. Leonardo D&#39;Ottavi says:

    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

Skip to main content