How to Use iOS Client Library for Azure Mobile Apps
Overview
This guide teaches you to perform common scenarios using the latest Azure Mobile Apps iOS SDK. If you are new to Azure Mobile Apps, first complete Azure Mobile Apps Quick Start to create a backend, create a table, and download a pre-built iOS Xcode project. In this guide, we focus on the client-side iOS SDK. To learn more about the server-side SDK for the backend, see the Server SDK HOWTOs.
Reference documentation
The reference documentation for the iOS client SDK is located here: Azure Mobile Apps iOS Client Reference.
Supported Platforms
The iOS SDK supports Objective-C projects, Swift 2.2 projects, and Swift 2.3 projects for iOS versions 8.0 or later.
The "server-flow" authentication uses a WebView for the presented UI. If the device is not able to present a WebView UI, then another method of authentication is required that is outside the scope of the product. This SDK is thus not suitable for Watch-type or similarly restricted devices.
Setup and Prerequisites
This guide assumes that you have created a backend with a table. This guide assumes that the table has the
same schema as the tables in those tutorials. This guide also assumes that in your code, you reference
MicrosoftAzureMobile.framework
and import MicrosoftAzureMobile/MicrosoftAzureMobile.h
.
How to: Create Client
To access an Azure Mobile Apps backend in your project, create an MSClient
. Replace AppUrl
with the app URL. You may leave gatewayURLString
and applicationKey
empty. If you set up a gateway for authentication, populate gatewayURLString
with the gateway URL.
Objective-C:
MSClient *client = [MSClient clientWithApplicationURLString:@"AppUrl"];
Swift:
let client = MSClient(applicationURLString: "AppUrl")
How to: Create Table Reference
To access or update data, create a reference to the backend table. Replace TodoItem
with the name of your table
Objective-C:
MSTable *table = [client tableWithName:@"TodoItem"];
Swift:
let table = client.tableWithName("TodoItem")
How to: Query Data
To create a database query, query the MSTable
object. The following query gets all the items in TodoItem
and logs the text of each item.
Objective-C:
[table readWithCompletion:^(MSQueryResult *result, NSError *error) {
if(error) { // error is nil if no error occurred
NSLog(@"ERROR %@", error);
} else {
for(NSDictionary *item in result.items) { // items is NSArray of records that match query
NSLog(@"Todo Item: %@", [item objectForKey:@"text"]);
}
}
}];
Swift:
table.readWithCompletion { (result, error) in
if let err = error {
print("ERROR ", err)
} else if let items = result?.items {
for item in items {
print("Todo Item: ", item["text"])
}
}
}
How to: Filter Returned Data
To filter results, there are many available options.
To filter using a predicate, use an NSPredicate
and readWithPredicate
. The following filters returned data to find only incomplete Todo items.
Objective-C:
// Create a predicate that finds items where complete is false
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"complete == NO"];
// Query the TodoItem table
[table readWithPredicate:predicate completion:^(MSQueryResult *result, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
for(NSDictionary *item in result.items) {
NSLog(@"Todo Item: %@", [item objectForKey:@"text"]);
}
}
}];
Swift:
// Create a predicate that finds items where complete is false
let predicate = NSPredicate(format: "complete == NO")
// Query the TodoItem table
table.readWithPredicate(predicate) { (result, error) in
if let err = error {
print("ERROR ", err)
} else if let items = result?.items {
for item in items {
print("Todo Item: ", item["text"])
}
}
}
How to: Use MSQuery
To perform a complex query (including sorting and paging), create an MSQuery
object, directly or by using a predicate:
Objective-C:
MSQuery *query = [table query];
MSQuery *query = [table queryWithPredicate: [NSPredicate predicateWithFormat:@"complete == NO"]];
Swift:
let query = table.query()
let query = table.queryWithPredicate(NSPredicate(format: "complete == NO"))
MSQuery
lets you control several query behaviors.
- Specify order of results
- Limit which fields to return
- Limit how many records to return
- Specify total count in response
- Specify custom query string parameters in request
- Apply additional functions
Execute an MSQuery
query by calling readWithCompletion
on the object.
How to: Sort Data with MSQuery
To sort results, let's look at an example. To sort by field 'text' ascending, then by 'complete' descending,
invoke MSQuery
like so:
Objective-C:
[query orderByAscending:@"text"];
[query orderByDescending:@"complete"];
[query readWithCompletion:^(MSQueryResult *result, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
for(NSDictionary *item in result.items) {
NSLog(@"Todo Item: %@", [item objectForKey:@"text"]);
}
}
}];
Swift:
query.orderByAscending("text")
query.orderByDescending("complete")
query.readWithCompletion { (result, error) in
if let err = error {
print("ERROR ", err)
} else if let items = result?.items {
for item in items {
print("Todo Item: ", item["text"])
}
}
}
How to: Limit Fields and Expand Query String Parameters with MSQuery
To limit fields to be returned in a query, specify the names of the fields in the selectFields property. This example returns only the text and completed fields:
Objective-C:
query.selectFields = @[@"text", @"complete"];
Swift:
query.selectFields = ["text", "complete"]
To include additional query string parameters in the server request (for example, because a custom server-side script uses them), populate query.parameters
like so:
Objective-C:
query.parameters = @{
@"myKey1" : @"value1",
@"myKey2" : @"value2",
};
Swift:
query.parameters = ["myKey1": "value1", "myKey2": "value2"]
How to: Configure Page Size
With Azure Mobile Apps, the page size controls the number of records that are pulled at a time from the backend tables. A call to pull
data would then batch up data, based on this page size, until there are no more records to pull.
It's possible to configure a page size using MSPullSettings as shown below. The default page size is 50, and the example below changes it to 3.
You could configure a different page size for performance reasons. If you have a large number of small data records, a high page size reduces the number of server round-trips.
This setting controls only the page size on the client side. If the client asks for a larger page size than the Mobile Apps backend supports, the page size is capped at the maximum the backend is configured to support.
This setting is also the number of data records, not the byte size.
If you increase the client page size, you should also increase the page size on the server. See "How to: Adjust the table paging size" for the steps to do this.
Objective-C:
MSPullSettings *pullSettings = [[MSPullSettings alloc] initWithPageSize:3];
[table pullWithQuery:query queryId:@nil settings:pullSettings
completion:^(NSError * _Nullable error) {
if(error) {
NSLog(@"ERROR %@", error);
}
}];
Swift:
let pullSettings = MSPullSettings(pageSize: 3)
table.pullWithQuery(query, queryId:nil, settings: pullSettings) { (error) in
if let err = error {
print("ERROR ", err)
}
}
How to: Insert Data
To insert a new table row, create a NSDictionary
and invoke table insert
. If Dynamic Schema is enabled,
the Azure App Service mobile backend automatically generates new columns based on the NSDictionary
.
If id
is not provided, the backend automatically generates a new unique ID. Provide your own id
to use
email addresses, usernames, or your own custom values as ID. Providing your own ID may ease joins and
business-oriented database logic.
The result
contains the new item that was inserted. Depending on your server logic, it may have additional
or modified data compared to what was passed to the server.
Objective-C:
NSDictionary *newItem = @{@"id": @"custom-id", @"text": @"my new item", @"complete" : @NO};
[table insert:newItem completion:^(NSDictionary *result, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
NSLog(@"Todo Item: %@", [result objectForKey:@"text"]);
}
}];
Swift:
let newItem = ["id": "custom-id", "text": "my new item", "complete": false]
table.insert(newItem) { (result, error) in
if let err = error {
print("ERROR ", err)
} else if let item = result {
print("Todo Item: ", item["text"])
}
}
How to: Modify Data
To update an existing row, modify an item and call update
:
Objective-C:
NSMutableDictionary *newItem = [oldItem mutableCopy]; // oldItem is NSDictionary
[newItem setValue:@"Updated text" forKey:@"text"];
[table update:newItem completion:^(NSDictionary *result, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
NSLog(@"Todo Item: %@", [result objectForKey:@"text"]);
}
}];
Swift:
if let newItem = oldItem.mutableCopy() as? NSMutableDictionary {
newItem["text"] = "Updated text"
table2.update(newItem as [NSObject: AnyObject], completion: { (result, error) -> Void in
if let err = error {
print("ERROR ", err)
} else if let item = result {
print("Todo Item: ", item["text"])
}
})
}
Alternatively, supply the row ID and the updated field:
Objective-C:
[table update:@{@"id":@"custom-id", @"text":"my EDITED item"} completion:^(NSDictionary *result, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
NSLog(@"Todo Item: %@", [result objectForKey:@"text"]);
}
}];
Swift:
table.update(["id": "custom-id", "text": "my EDITED item"]) { (result, error) in
if let err = error {
print("ERROR ", err)
} else if let item = result {
print("Todo Item: ", item["text"])
}
}
At minimum, the id
attribute must be set when making updates.
How to: Delete Data
To delete an item, invoke delete
with the item:
Objective-C:
[table delete:item completion:^(id itemId, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
NSLog(@"Todo Item ID: %@", itemId);
}
}];
Swift:
table.delete(newItem as [NSObject: AnyObject]) { (itemId, error) in
if let err = error {
print("ERROR ", err)
} else {
print("Todo Item ID: ", itemId)
}
}
Alternatively, delete by providing a row ID:
Objective-C:
[table deleteWithId:@"37BBF396-11F0-4B39-85C8-B319C729AF6D" completion:^(id itemId, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
NSLog(@"Todo Item ID: %@", itemId);
}
}];
Swift:
table.deleteWithId("37BBF396-11F0-4B39-85C8-B319C729AF6D") { (itemId, error) in
if let err = error {
print("ERROR ", err)
} else {
print("Todo Item ID: ", itemId)
}
}
At minimum, the id
attribute must be set when making deletes.
How to: Call Custom API
With a custom API, you can expose any backend functionality. It doesn't have to map to a table operation. Not only do you gain more control over messaging, you can even read/set headers and change the response body format.
To call a custom API, call MSClient.invokeAPI
. The request and response content are treated as JSON. To use
other media types, use the other overload of invokeAPI
. To make a GET
request instead of a POST
request, set parameter HTTPMethod
to "GET"
and parameter body
to nil
(since GET requests do not have
message bodies.) If your custom API supports other HTTP verbs, change HTTPMethod
appropriately.
Objective-C:
[self.client invokeAPI:@"sendEmail"
body:@{ @"contents": @"Hello world!" }
HTTPMethod:@"POST"
parameters:@{ @"to": @"bill@contoso.com", @"subject" : @"Hi!" }
headers:nil
completion: ^(NSData *result, NSHTTPURLResponse *response, NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
} else {
// Do something with result
}
}];
Swift:
client.invokeAPI("sendEmail",
body: [ "contents": "Hello World" ],
HTTPMethod: "POST",
parameters: [ "to": "bill@contoso.com", "subject" : "Hi!" ],
headers: nil)
{
(result, response, error) -> Void in
if let err = error {
print("ERROR ", err)
} else if let res = result {
// Do something with result
}
}
How to: Register push templates to send cross-platform notifications
To register templates, pass templates with your client.push registerDeviceToken method in your client app.
Objective-C:
[client.push registerDeviceToken:deviceToken template:iOSTemplate completion:^(NSError *error) {
if(error) {
NSLog(@"ERROR %@", error);
}
}];
Swift:
client.push?.registerDeviceToken(NSData(), template: iOSTemplate, completion: { (error) in
if let err = error {
print("ERROR ", err)
}
})
Your templates are of type NSDictionary and can contain multiple templates in the following format:
Objective-C:
NSDictionary *iOSTemplate = @{ @"templateName": @{ @"body": @{ @"aps": @{ @"alert": @"$(message)" } } } };
Swift:
let iOSTemplate = ["templateName": ["body": ["aps": ["alert": "$(message)"]]]]
All tags are stripped from the request for security. To add tags to installations or templates within installations, see Work with the .NET backend server SDK for Azure Mobile Apps. To send notifications using these registered templates, work with Notification Hubs APIs.
How to: Handle Errors
When you call an Azure App Service mobile backend, the completion block contains an NSError
parameter. When
an error occurs, this parameter is non-nil. In your code, you should check this parameter and handle the error
as needed, as demonstrated in the preceding code snippets.
The file <WindowsAzureMobileServices/MSError.h>
defines the constants MSErrorResponseKey
,
MSErrorRequestKey
, and MSErrorServerItemKey
. To get more data related to the error:
Objective-C:
NSDictionary *serverItem = [error.userInfo objectForKey:MSErrorServerItemKey];
Swift:
let serverItem = error.userInfo[MSErrorServerItemKey]
In addition, the file defines constants for each error code:
Objective-C:
if (error.code == MSErrorPreconditionFailed) {
Swift:
if (error.code == MSErrorPreconditionFailed) {
How to: Authenticate users with the Active Directory Authentication Library
You can use the Active Directory Authentication Library (ADAL) to sign users into your application using
Azure Active Directory. Client flow authentication using an identity provider SDK is preferable to using
the loginWithProvider:completion:
method. Client flow authentication provides a more native UX feel
and allows for additional customization.
Configure your mobile app backend for AAD sign-in by following the How to configure App Service for Active Directory login tutorial. Make sure to complete the optional step of registering a native client application. For iOS, we recommend that the redirect URI is of the form
<app-scheme>://<bundle-id>
. For more information, see the ADAL iOS quickstart.Install ADAL using Cocoapods. Edit your Podfile to include the following definition, replacing YOUR-PROJECT with the name of your Xcode project:
source 'https://github.com/CocoaPods/Specs.git' link_with ['YOUR-PROJECT'] xcodeproj 'YOUR-PROJECT'
and the Pod:
pod 'ADALiOS'
Using the Terminal, run
pod install
from the directory containing your project, and then open the generated Xcode workspace (not the project).Add the following code to your application, according to the language you are using. In each, make these replacements:
- Replace INSERT-AUTHORITY-HERE with the name of the tenant in which you provisioned your application. The format should be https://login.microsoftonline.com/contoso.onmicrosoft.com. This value can be copied from the Domain tab in your Azure Active Directory in the Azure portal.
- Replace INSERT-RESOURCE-ID-HERE with the client ID for your mobile app backend. You can obtain the client ID from the Advanced tab under Azure Active Directory Settings in the portal.
- Replace INSERT-CLIENT-ID-HERE with the client ID you copied from the native client application.
- Replace INSERT-REDIRECT-URI-HERE with your site's /.auth/login/done endpoint, using the HTTPS scheme. This value should be similar to https://contoso.azurewebsites.net/.auth/login/done.
Objective-C:
#import <ADALiOS/ADAuthenticationContext.h>
#import <ADALiOS/ADAuthenticationSettings.h>
// ...
- (void) authenticate:(UIViewController*) parent
completion:(void (^) (MSUser*, NSError*))completionBlock;
{
NSString *authority = @"INSERT-AUTHORITY-HERE";
NSString *resourceId = @"INSERT-RESOURCE-ID-HERE";
NSString *clientId = @"INSERT-CLIENT-ID-HERE";
NSURL *redirectUri = [[NSURL alloc]initWithString:@"INSERT-REDIRECT-URI-HERE"];
ADAuthenticationError *error;
ADAuthenticationContext *authContext = [ADAuthenticationContext authenticationContextWithAuthority:authority error:&error];
authContext.parentController = parent;
[ADAuthenticationSettings sharedInstance].enableFullScreen = YES;
[authContext acquireTokenWithResource:resourceId
clientId:clientId
redirectUri:redirectUri
completionBlock:^(ADAuthenticationResult *result) {
if (result.status != AD_SUCCEEDED)
{
completionBlock(nil, result.error);;
}
else
{
NSDictionary *payload = @{
@"access_token" : result.tokenCacheStoreItem.accessToken
};
[client loginWithProvider:@"aad" token:payload completion:completionBlock];
}
}];
}
Swift:
// add the following imports to your bridging header:
// #import <ADALiOS/ADAuthenticationContext.h>
// #import <ADALiOS/ADAuthenticationSettings.h>
func authenticate(parent: UIViewController, completion: (MSUser?, NSError?) -> Void) {
let authority = "INSERT-AUTHORITY-HERE"
let resourceId = "INSERT-RESOURCE-ID-HERE"
let clientId = "INSERT-CLIENT-ID-HERE"
let redirectUri = NSURL(string: "INSERT-REDIRECT-URI-HERE")
var error: AutoreleasingUnsafeMutablePointer<ADAuthenticationError?> = nil
let authContext = ADAuthenticationContext(authority: authority, error: error)
authContext.parentController = parent
ADAuthenticationSettings.sharedInstance().enableFullScreen = true
authContext.acquireTokenWithResource(resourceId, clientId: clientId, redirectUri: redirectUri) { (result) in
if result.status != AD_SUCCEEDED {
completion(nil, result.error)
}
else {
let payload: [String: String] = ["access_token": result.tokenCacheStoreItem.accessToken]
client.loginWithProvider("aad", token: payload, completion: completion)
}
}
}
How to: Authenticate users with the Facebook SDK for iOS
You can use the Facebook SDK for iOS to sign users into your application using Facebook. Using a client flow
authentication is preferable to using the loginWithProvider:completion:
method. The client flow authentication
provides a more native UX feel and allows for additional customization.
Configure your mobile app backend for Facebook sign-in by following the How to configure App Service for Facebook login tutorial.
Install the Facebook SDK for iOS by following the Facebook SDK for iOS - Getting Started documentation. Instead of creating an app, you can add the iOS platform to your existing registration.
Facebook's documentation includes some Objective-C code in the App Delegate. If you are using Swift, you can use the following translations for AppDelegate.swift:
// Add the following import to your bridging header: // #import <FBSDKCoreKit/FBSDKCoreKit.h> func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions) // Add any custom logic here. return true } func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool { let handled = FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation) // Add any custom logic here. return handled }
In addition to adding
FBSDKCoreKit.framework
to your project, also add a reference toFBSDKLoginKit.framework
in the same way.Add the following code to your application, according to the language you are using.
Objective-C:
#import <FBSDKLoginKit/FBSDKLoginKit.h> #import <FBSDKCoreKit/FBSDKAccessToken.h> // ... - (void) authenticate:(UIViewController*) parent completion:(void (^) (MSUser*, NSError*)) completionBlock; { FBSDKLoginManager *loginManager = [[FBSDKLoginManager alloc] init]; [loginManager logInWithReadPermissions: @[@"public_profile"] fromViewController:parent handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) { if (error) { completionBlock(nil, error); } else if (result.isCancelled) { completionBlock(nil, error); } else { NSDictionary *payload = @{ @"access_token":result.token.tokenString }; [client loginWithProvider:@"facebook" token:payload completion:completionBlock]; } }]; }
Swift:
// Add the following imports to your bridging header: // #import <FBSDKLoginKit/FBSDKLoginKit.h> // #import <FBSDKCoreKit/FBSDKAccessToken.h> func authenticate(parent: UIViewController, completion: (MSUser?, NSError?) -> Void) { let loginManager = FBSDKLoginManager() loginManager.logInWithReadPermissions(["public_profile"], fromViewController: parent) { (result, error) in if (error != nil) { completion(nil, error) } else if result.isCancelled { completion(nil, error) } else { let payload: [String: String] = ["access_token": result.token.tokenString] client.loginWithProvider("facebook", token: payload, completion: completion) } } }
How to: Authenticate users with Twitter Fabric for iOS
You can use Fabric for iOS to sign users into your application using Twitter. Client Flow authentication is
preferable to using the loginWithProvider:completion:
method, as it provides a more native UX feel and allows
for additional customization.
Configure your mobile app backend for Twitter sign-in by following the How to configure App Service for Twitter login tutorial.
Add Fabric to your project by following the Fabric for iOS - Getting Started documentation and setting up TwitterKit.
Note
By default, Fabric creates a Twitter application for you. You can avoid creating an application by registering the Consumer Key and Consumer Secret you created earlier using the following code snippets. Alternatively, you can replace the Consumer Key and Consumer Secret values that you provide to App Service with the values you see in the Fabric Dashboard. If you choose this option, be sure to set the callback URL to a placeholder value, such as
https://<yoursitename>.azurewebsites.net/.auth/login/twitter/callback
.If you choose to use the secrets you created earlier, add the following code to your App Delegate:
Objective-C:
#import <Fabric/Fabric.h> #import <TwitterKit/TwitterKit.h> // ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[Twitter sharedInstance] startWithConsumerKey:@"your_key" consumerSecret:@"your_secret"]; [Fabric with:@[[Twitter class]]]; // Add any custom logic here. return YES; }
Swift:
import Fabric import TwitterKit // ... func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { Twitter.sharedInstance().startWithConsumerKey("your_key", consumerSecret: "your_secret") Fabric.with([Twitter.self]) // Add any custom logic here. return true }
Add the following code to your application, according to the language you are using.
Objective-C:
#import <TwitterKit/TwitterKit.h> // ... - (void)authenticate:(UIViewController*)parent completion:(void (^) (MSUser*, NSError*))completionBlock { [[Twitter sharedInstance] logInWithCompletion:^(TWTRSession *session, NSError *error) { if (session) { NSDictionary *payload = @{ @"access_token":session.authToken, @"access_token_secret":session.authTokenSecret }; [client loginWithProvider:@"twitter" token:payload completion:completionBlock]; } else { completionBlock(nil, error); } }]; }
Swift:
import TwitterKit // ... func authenticate(parent: UIViewController, completion: (MSUser?, NSError?) -> Void) { let client = self.table!.client Twitter.sharedInstance().logInWithCompletion { session, error in if (session != nil) { let payload: [String: String] = ["access_token": session!.authToken, "access_token_secret": session!.authTokenSecret] client.loginWithProvider("twitter", token: payload, completion: completion) } else { completion(nil, error) } } }
How to: Authenticate users with the Google Sign-In SDK for iOS
You can use the Google Sign-In SDK for iOS to sign users into your application using a Google account. Google recently announced changes to their OAuth security policies. These policy changes will require the use of the Google SDK in the future.
Configure your mobile app backend for Google sign-in by following the How to configure App Service for Google login tutorial.
Install the Google SDK for iOS by following the Google Sign-In for iOS - Start integrating documentation. You may skip the "Authenticate with a Backend Server" section.
Add the following to your delegate's
signIn:didSignInForUser:withError:
method, according to the language you are using.Objective-C:
NSDictionary *payload = @{ @"id_token":user.authentication.idToken, @"authorization_code":user.serverAuthCode }; [client loginWithProvider:@"google" token:payload completion:^(MSUser *user, NSError *error) { // ... }];
Swift:
let payload: [String: String] = ["id_token": user.authentication.idToken, "authorization_code": user.serverAuthCode] client.loginWithProvider("google", token: payload) { (user, error) in // ... }
Make sure you also add the following to
application:didFinishLaunchingWithOptions:
in your app delegate, replacing "SERVER_CLIENT_ID" with the same ID that you used to configure App Service in step 1.Objective-C:
[GIDSignIn sharedInstance].serverClientID = @"SERVER_CLIENT_ID";
Swift:
GIDSignIn.sharedInstance().serverClientID = "SERVER_CLIENT_ID"
Add the following code to your application in a UIViewController that implements the
GIDSignInUIDelegate
protocol, according to the language you are using. You are signed out before being signed in again, and although you don't need to enter your credentials again, you see a consent dialog. Only call this method when the session token has expired.Objective-C:
#import <Google/SignIn.h> // ... - (void)authenticate { [GIDSignIn sharedInstance].uiDelegate = self; [[GIDSignIn sharedInstance] signOut]; [[GIDSignIn sharedInstance] signIn]; }
Swift:
// ... func authenticate() { GIDSignIn.sharedInstance().uiDelegate = self GIDSignIn.sharedInstance().signOut() GIDSignIn.sharedInstance().signIn() }