One benefit of iOS is that it supports file sharing. Developers can create apps that can share quotes, documents, images and information with members of your group within salesforce.com. So, this tutorial shows how you can use the files API in the Salesforce Mobile SDK 3.1 to accomplish this.

The Salesforce Mobile SDK provides you with a great framework for starting your iOS app, which includes auto-creation of a connected app. The tutorial walks you through the installation of the Salesforce Mobile SDK, app creation, accessing salesforce.com data, and handling files using methods in Salesforce REST API.

  • The steps we’ll go through will be to
  • Create a Developer Edition Account and log in.
  • Install the Mobile SDK 3.1
  • Create a Native iOS Project
  • Modify the iOS App
  • Add a thumbnail of the file being shared
  • Implement the file sharing methods
  • Test your App

When logging into your environment, you have the option to modify shared settings as well as upload PDFs and images. To have it accessible on a native iOS app is even more critical for sharing quotes, documents, and other information immediately on your phone. When you have completed this tutorial, you will have a native iOS app that shares files with members of a group.

Step 1: Sign up for a Free Developer Edition (DE) account and sign in

  1. Go to https://developer.salesforce.com/signup and sign up for your Developer Edition (DE) account. For the purposes of this example, I recommend signing up for a Developer Edition even if you already have an account. This ensures you get a clean environment with the latest features enabled.
  2. Navigate to https://login.salesforce.com to log into your developer account.

Step 2: Install the Salesforce Mobile SDK 3.1 using npm

There are two ways to install the Salesforce Mobile SDK. In this tutorial, we are going to use npm, otherwise known as the package manager for Node.js.

  1. In order to use npm, go to this site to download Node.js.
  2. Run the download installer provided.
  3. Verify npm was installed correctly:
    • Launch a terminal window.
    • Type npm at the prompt.
    • You should see Usage information related to npm similar to what is listed below; if not, please run through the installation steps of Node.js.
  4. Install the forceios package by running the following command in a terminal window: sudo npm install -g forceios

Native1-1024x724.png


This command will allow you to run "forceios install" from any directory to create a new iOS project. The forceios package will be installed in /usr/local/lib/node_modules, and links binary modules in /usr/local/bin.

Step 3: Create a Native iOS Project utilizing the Salesforce Mobile SDK 3.1

Set up a working directory:

  1. Navigate to the directory where you will be creating your project.
  2. Create your iOS project. At the terminal window, enter the following: forceios create
    This utility will prompt you for the following information:
    Native3-1024x226.png
    • The application type is native.
    • The application name is FileShareTutorialApp.
    • For package name (company identifier), enter com.testapp.
    • For organization name, enter TestApp, Inc.
    • For output directory, the default is the current directory.
    • For the connected app ID, retrieve this from your Force.com account or press Enter to use the default value.
    • For the Callback URI, retrieve this from your Force.com account or press Enter to use the default value.
  3. After successfully creating the iOS project, you will receive a notification at the command line:
    Native4.png
  4. Open your Project. Cd to the FileSharingTutorial directory and open the *.xcodeproj file. This will launch XCode with the newly generated project you just created. The following is a screenshot of your project files in XCode. Note that the frameworks and Salesforce SDKs have already been integrated into your project.
    Native5.png
  5. Run the Project in an iOS simulator using the play button at the top of XCode. The simulator will launch and you will see the salesforce login screen:
    Login-native6-492x1024.png
  6. Login to your Force.com account. An OAuth authorization screen will appear to allow the application to access user data.
    Authorization-native6-492x1024.png
  7. Select “Allow.” You will then be brought to the home screen, which will include minimal functionality displaying your first and last name.

Users-native8-483x1024.jpg


Congratulations! You have built your first salesforce.com connected app.

Step 4: Modifying the iOS App

Overview of the iOS App

A majority of changes that will take place during the course of this tutorial will be done in the RootViewController.

In our AppDelegate, the Connected App settings have been set on SalesforceSDKManager in our init method. Please note that these are basic minimum requirements for identifying the Connected App for OAuth. Also, code blocks have been added for post login and logout callbacks. After creating a Force.com connected application, the RemoteAccessConsumerKey and the OAuthRedirectURI are populated in the AppDelegate.

After the user logs in, the control is passed to the RootViewController. Overall, the flow consists of displaying the InitialViewController prior to connecting, logging into salesforce.com, and displaying the response in the RootViewController.

In our RootViewController, there is no associated xib file. It inherits from UITableViewController and uses the SFRestDelegate. In our viewDidLoad method, we are sending a request to the server to retrieve the name from the user with the Salesforce RestAPI delegate methods already populated. This interface is critical for acceptance of REST responses. There are four methods that address each response type. Add to these methods for additional customization upon a successful request, failed request, cancelled request, or timeout request. The default functionality will print a message out to the console.

- (void)request:(SFRestRequest *)request didLoadResponse:(id)jsonResponse
- (void)request:(SFRestRequest*)request didFailLoadWithError:(NSError*)error {
- (void)requestDidCancelLoad:(SFRestRequest *)request
- (void)requestDidTimeout:(SFRestRequest *)request

The delegate methods for the UITableViewController have defaulted to returning one section, as well as the number of data rows returned from the initial request. In each of the table view cells, the default image provided will display as well as the data which consists of the user’s name. The accessory disclosure indicator has also been included in the cell.


Modifying the UI

In order to include groups and share and upload files capabilities, we are going to create simple buttons on the top to demonstrate this functionality. Note that the RootViewController, where a majority of the implementation will take place, does not have an associated xib file.

Let’s modify the viewDidLoad method to add the following bar button items. Here, we are adding Navigation bar button items to the TableView of the RootViewController. We will implement the button handlers in the following steps.

- (void) viewDidLoad {
    [super viewDidLoad];
    self.thumbnailCache = [NSMutableDictionary dictionary];

    self.ownedFilesButton = [[UIBarButtonItem alloc] initWithTitle:@"Owned" style:UIBarButtonItemStylePlain target:self action:@selector(showOwnedFiles)];
    self.groupsFilesButton = [[UIBarButtonItem alloc] initWithTitle:@"Groups" style:UIBarButtonItemStylePlain target:self action:@selector(showGroupsFiles)];
    self.sharedFilesButton = [[UIBarButtonItem alloc] initWithTitle:@"Shared" style:UIBarButtonItemStylePlain target:self action:@selector(showSharedFiles)];
    self.uploadFilesButton = [[UIBarButtonItem alloc] initWithTitle:@"Upload & Share" style:UIBarButtonItemStylePlain target:self action:@selector(uploadFiles)];

    self.navigationItem.rightBarButtonItems = @[self.ownedFilesButton, self.groupsFilesButton, self.sharedFilesButton, self.uploadFilesButton];
}

Include the following declarations in the RootViewController header file:

#import <SalesforceSDKCore/SFAuthenticationManager.h>
#import <SalesforceNativeSDK/SFRestAPI+Blocks.h>
#import <SalesforceNativeSDK/SFRestAPI+Files.h>
#import <SalesforceNativeSDK/SFRestRequest.h>

typedef void (^ThumbnailLoadedBlock) (UIImage *thumbnailImage);

@property (nonatomic, strong) UIBarButtonItem* ownedFilesButton;
@property (nonatomic, strong) UIBarButtonItem* sharedFilesButton;
@property (nonatomic, strong) UIBarButtonItem* groupsFilesButton;
@property (nonatomic, strong) UIBarButtonItem* uploadFilesButton;

@property (nonatomic, strong) NSMutableDictionary* thumbnailCache;

- (void) downloadThumbnail:(NSString*)fileId completeBlock:(ThumbnailLoadedBlock)completeBlock;
- (void) showOwnedFiles;
- (void) showGroupsFiles;
- (void) showSharedFiles;
- (void) uploadFiles;

Step 5: Getting Thumbnail of File to Share

When we display shared files in our TableView, we want to display the thumbnails. In your viewDidLoad method within RootViewController, we created a dictionary object– thumbnailCache.

self.thumbnailCache = [NSMutableDictionary dictionary];

Our dictionary object will be used in getting and downloading our thumbnails for the file to be shared. Therefore, if the image is already cached, return the image. If it is not cached, then resize it and cache it.

Add the following two methods to our RootViewController file.

- (void) getThumbnail:(NSString*) fileId completeBlock:(ThumbnailLoadedBlock)completeBlock {
    // cache hit
    if (self.thumbnailCache[fileId]) {
        completeBlock(self.thumbnailCache[fileId]);
    }
    // cache miss
    else {
        [self downloadThumbnail:fileId completeBlock:^(UIImage *image) {
            // size it
            UIGraphicsBeginImageContext(CGSizeMake(120,90));
            [image drawInRect:CGRectMake(0, 0, image.size.width, 90)];
            UIImage *thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            // cache it
            self.thumbnailCache[fileId] = thumbnailImage;
            // done
            completeBlock(thumbnailImage);
        }];
    }
}

- (void) downloadThumbnail:(NSString*)fileId completeBlock:(ThumbnailLoadedBlock)completeBlock {
    SFRestRequest *imageRequest = [[SFRestAPI sharedInstance] requestForFileRendition:fileId version:nil renditionType:@"THUMB120BY90" page:0];
    [[SFRestAPI sharedInstance] sendRESTRequest:imageRequest failBlock:nil completeBlock:^(NSData *responseData) {
        NSLog(@"downloadThumbnail:%@ completed", fileId);
        UIImage *image = [UIImage imageWithData:responseData];
        dispatch_async(dispatch_get_main_queue(), ^{
            completeBlock(image);
        });
    }];
}

Step 6: Implement File Sharing Methods

For file sharing methods in this tutorial, we will demonstrate how to share an image. For future enhancements, we will incorporate other file types supported by the SDK which include PDF and other document types.

File Sharing Methods

Add the following methods to your RootViewController file:

  • In the showOwnedFiles method, we are sending a request to the server to retrieve the list of files owned by the user currently logged in.
- (void) showOwnedFiles {
    NSLog(@"Show Owned Files Pressed");
    SFRestRequest *request = [[SFRestAPI sharedInstance] requestForOwnedFilesList:nil page:0];
    [[SFRestAPI sharedInstance] send:request delegate:self];
}
  • In the showGroupsFiles method, the request we are sending to the server will provide a list of files accessible to groups.
- (void) showGroupsFiles {
    NSLog(@"Show Group Files Pressed");
    SFRestRequest *request = [[SFRestAPI sharedInstance] requestForFilesInUsersGroups:nil page:0];
    [[SFRestAPI sharedInstance] send:request delegate:self];
}
  • In the showSharedFiles method, we need to display what files are shared by others for that user, or shared as a group.
- (void) showSharedFiles {
    NSLog(@"Show Shared Files Pressed");
    SFRestRequest *request = [[SFRestAPI sharedInstance] requestForFilesSharedWithUser:nil page:0];
    [[SFRestAPI sharedInstance] send:request delegate:self];
}
  • In the shareFiles method, we will utilize the requestForAddFileShare method to share the file to the group. For the shareType, we can use V for viewing or C for collaboration. In this example we will use V and share the file with the current user's organization.
- (void) shareFiles:(NSString*)fileId {
    NSLog(@"Share Files Pressed");
    NSString *entId = [SFUserAccountManager sharedInstance].currentUser.idData.orgId;
    SFRestRequest *request = 
    [[SFRestAPI sharedInstance] requestForAddFileShare:fileId
                                              entityId:entId
                                             shareType:@"V"];
    [[SFRestAPI sharedInstance] send:request delegate:nil];
}

Upload Image and Share

Let’s start with implementing functionality to upload an image to salesforce.com and share with others:

In order to implement uploading capabilities, we will implement the UIImagePickerController class to handle access to the photo library. In order to use this, we need to conform to its delegate.

  • Include the UIImagePickerControllerDelegate and UINavigationControllerDelegate Protocol in your header file:
@interface RootViewController : UITableViewController <SFRestDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate>
  • Add this uploadFile method and set the image picker source type to UIImagePickerControllerSourceTypePhotoLibrary. (If you want to capture an image, use the following source type: UIImagePickerControllerSourceTypeCamera).
- (void) uploadFiles {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.allowsEditing = YES;
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    [self presentViewController:picker animated:YES completion:NULL];
}
  • Add the following delegate methods. In the didFinishPickingMediaWithInfo, we need to convert the image into NSData and also include the compression quality. We then call the requestForUploadFile method, which will generate a request that can upload a local file to the server.
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    UIImage *chosenImage = info[UIImagePickerControllerEditedImage];
    self.imageView.image = chosenImage;
    NSData *imageData= UIImageJPEGRepresentation(chosenImage,0.0);

    SFRestRequest *request =
    [[SFRestAPI sharedInstance] requestForUploadFile:imageData
                                                name:@"TestP.png"
                                         description:@"Share Img"
                                            mimeType:@"image/png"];
    [[SFRestAPI sharedInstance] send:request delegate:self];
    [picker dismissViewControllerAnimated:YES completion:NULL];
}

If the user cancels selection, include the following method to dismiss the Photo Library view.

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    [picker dismissViewControllerAnimated:YES completion:NULL];
}

Update the response handler in RootViewController

We now need to update the didLoadResponse method of RootViewController to handle the API response from Files API. We also need to initiate file sharing if user uploaded a new file.

- (void)request:(SFRestRequest *)request didLoadResponse:(id)jsonResponse {
    // If the request method is POST, then the request is a upload file request. Let's share the file now.
    if (request.method == SFRestMethodPOST) {
        [self shareFiles:[jsonResponse objectForKey:@"id"]];
    } else { // Otherwise refresh the table view with the new file records returned by the API
        NSArray *records = [jsonResponse objectForKey:@"files"];
        NSLog(@"request:didLoadResponse: #records: %d", records.count);
        self.dataRows = records;
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
        });
    }
}


Modify UITableView in RootViewController

Next, we will modify our tableView. We want to change the cell contents to include the thumbnail as well as the name of the file and owner name. In our celLForRowAtIndexPath method, replace the contents of the method with the following code:

static NSString *CellIdentifier = @"CellIdentifier";

// Dequeue or create a cell of the appropriate type.
UITableViewCell *cell = [tableView_ dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];

}

// Configure the cell to show the data.
NSDictionary *obj = [dataRows objectAtIndex:indexPath.row];
NSString *fileId = obj[@"id"];
NSInteger tag = [fileId hash];

cell.textLabel.text =  obj[@"title"];
cell.detailTextLabel.text = obj[@"owner"][@"name"];
cell.tag = tag;
[self getThumbnail:fileId completeBlock:^(UIImage* thumbnailImage) {
    // Cell are recycled - we don't want to set the image if the cell is showing a different file
    if (cell.tag == tag) {
        cell.imageView.image = thumbnailImage;
        [cell setNeedsLayout];
    }
}];

return cell;

Step 7: Test your App

  1. Access your Force.com account. Upload a new file and create a group called “TestGroup.” Create dummy data for testing purposes and modify the data to include sharing the file within the group.
  2. Run your File Sharing iOS App in the emulator.
  3. Login to your account.
  4. Upon successful login, select “Groups.” You will see the group you created from the site.
  5. Select “Owned.” You will see the file uploaded from the website.
  6. Select “Upload & Share.” You will be prompted to upload an image from your camera roll.

Resources

For a comprehensive set of resources, check out:

About the Author

Jeanine Swatton develops mobile apps and web applications. She teaches iOS development and Ruby on Rails at a few universities and also works as a Software Product Manager focusing on education apps for the K-12 market. She is a member of Women in Technology International, the Society of Women Engineers, as well as Computer Users Educator