Finding integrated cloud services into an iOS application some time ago was something quite rare. There were not many services offering APIs to do that, and moreover just a small percentage out of the total users had a constant Internet connection (3G/4G connections, or constant access to Wifi networks). Nowadays things have dramatically changed, as technology has improved significantly and all the previous problems have been overcome. So, implementing cloud-related features in applications is now a common task, and users expect to see such features more and more to existing and new applications.
Not so long time ago I had written a tutorial about the CloudKit, the cloud solution provided by Apple. This time, I am going to present you another API that it can be used for dealing with files in the cloud, and this is not other than the Dropbox API. I really doubt if there’s even one computer or mobile device user that hasn’t heard of the Dropbox, a well-known platform allowing to keep files on the cloud. If you are not a Dropbox user, then it’s a good chance to give it a try now, as it consists of a good option for having files online, share them with other people and do even more tasks. Dropbox can be used directly in the web by using a browser, by installing a desktop application to the computer, or as a mobile app.
So, through the upcoming parts of this tutorial we’ll see how the Dropbox can be integrated into an iOS app, and how files can be uploaded and downloaded simply by making use of a framework provided by the Dropbox to developers. As you understand, having a Dropbox account is a prerequisite so you can continue in this tutorial, and if you don’t have one, it’s time to create it. It would also be good to be familiarised with it, but as it’s easy to use it, it’s not required.
As you’ll see next, before you’ll be able to write even the first line of code regarding Dropbox, certain preliminary steps are required to be done. I won’t dive into a lot of details now, as we’ll see them right next. In short, two things are required: To download the Dropbox SDK from the Dropbox website (actually from a special Dropbox website dedicated to developers), and to create an app record to a web platform so you get some unique keys that will enable the mobile app to get connected to Dropbox.
There are two options when creating that app record to the Dropbox platform. You can either select to allow access to the whole Dropbox folder, or to create a special folder that will be used by your application only. That folder will be the root for the app. In this tutorial we’ll choose the second option, but generally what you will select depends on what your app is supposed to do.
Further than that, you’ll see that using the Dropbox API is simpler than you imagine. What we’ll do here is what you need to know in the most of cases, as we’ll cover many important stuff; from getting connected to the Dropbox account, to uploading and downloading files.
And with that, let’s move to the next part so we see what we’re about to do in this tutorial, and then to go straight into the details of the Dropbox API.
Demo App Overview
As always, in this part we’ll get to know the demo app that we will work with in this tutorial. So, let me start by saying that what we’ll do here is separated in two top-level steps:
In the first one, we’ll perform some web-side tasks by visiting the Dropbox’s online platform for developers. There we are going to create a new record for the app that we’ll make here (you’ll see more details about that later), and we’ll obtain some required keys so we can get connected to our Dropbox account through the app.
In the second step, we’re going to develop the demo app. Here’s a list with all the features that we will add:
- Link to the Dropbox account and unlinking from it (sign in and sign out)
- File upload
- Dropbox folder contents display
- File download
- Visual representation of the upload and download progress
Regarding the file upload, we’ll keep things simple and we’ll just send some predefined files. For the sake of the simplicity, we won’t make any feature for creating or picking files. You’ll get more details about that in the respective part of the tutorial.
For saving time, we won’t build the demo app from scratch. You can download a starter project to work on, and after all, we have a lot of things to see. Once you download the project take a tour so you get familiarised with it. Regarding the interface part, you’ll notice that the main area of the view is occupied by a tableview. In it, we’ll display the contents of the Dropbox folder once we fetch them. At the top you will find a toolbar containing three bar button items: One to get connected to and disconnected from the Dropbox account, one to initiate the content fetching manually, and one to show an action sheet and select a file to upload. At the bottom there’s a progress view, where we’ll depict the progress of either the download or the upload process. Initially, this progress view is hidden, and it will become visible only when it’s necessary. In code, you’ll see that a few things have already been added. Also, you’ll find the standard tableview related delegate and datasource methods defined and containing the minimum required code so Xcode doesn’t show any errors. We’ll add the proper code later.
The next screenshot illustrates a file upload process:
So, now you’ve taken a first taste of the app, let’s get started. If you’ve never worked with the Dropbox API in the past, then you are definitely going to love the following experience.
Creating a New App Record
When working with a third-party API, it’s almost always necessary to create a new app record (or app entry) to an online platform (often called web console) provided by the API creators. By doing that you obtain some keys that are unique for the app you’re making, and their purpose is to let you get connected to the provided services in a secure manner and then use those services.
As you guess, in this part we’re about to do exactly this; we will create a new app record to a special online platform of the Dropbox, we’ll specify some details about our demo app, and then we’ll get the keys we need so we get connected to our Dropbox account in the upcoming steps. The first thing we have to do, is to visit the Developer Home, a place dedicated to all kind of developers that want to deal with Dropbox. If this is the first time you arrive here, then feel free to navigate yourself around and get to know what Dropbox has to offer. Of course, don’t forget to sign in before you continue.
Let’s begin by clicking to the App Console link. Here is the place where you can find all of your existing Dropbox apps, and also the place where you can create a new one. Let’s do so by clicking to the Create app button.
In the first step of the new app creation, you’re given with two options. We’re interested in the Dropbox API app option, so make sure that this is the one you select.
The next step is really important. This is the point where we have to specify whether our new application will have access to the whole Dropbox folder, or it will just have its own folder for writing and reading files. Even though you’re free to make your choice, I’d suggest to select the first option saying “Yes – My app only needs access to the files it creates”.
After you’ve done your selection, you have to give a name to the app you’re creating. Let’s name this one TutApp (you may need to choose another name), so type it in the given textfield and click to the Create app button.
At this point, a new app record has been created for you by the Dropbox, and you’re guided to a new page where you can find all of its details. As you understand, it’s out of my scope to give you a tour guide here, so you’re free to explore what the app details are about. Regarding our purpose, we care only about the App key and App secret values right now, as we will need them in our iOS app in a while (if the App secret isn’t visible, just click to the Show link to display it).
The first step we had to do is now over. Don’t logout from this webpage yet, as we’ll need the app keys in a while (so you have easy access to them). Next, let’s prepare our project so we’re able finally to get connected and use the Dropbox API services.
Prerequisite Steps
Let’s turn page now, and let’s focus to the starter project that you’ve downloaded. Obviously, before we are able to perform any Dropbox-related tasks, we must get the respective SDK and add it to our project. So, visit this page, and click to the Download iOS SDK link. During the writing of this tutorial, the SDK was in the 1.3.13 version.
In this part, we have to do three specific steps:
- To add the downloaded Dropbox framework and a couple of other necessary frameworks to the project.
- To create a new URL scheme, so the user can return to our app once he/she has logged in to his/her Dropbox account.
- To create a bridging file so we can import the Dropbox libraries to the code. This is necessary because the framework is written in Objective-C and we’re developing in Swift.
Let’s see each step in details.
Step 1 – Add frameworks
First of all, we have to add the downloaded framework to our project. The easiest way to do that is to locate the DropboxSDK.framework inside the downloaded folder using the Finder, and then drag and drop it to the Project Navigator of Xcode. Alternatively, in Xcode go to the File > Add files to ‘DBDemo’… menu, and search for the DropboxSDK.framework file. In any case, make sure to check the Copy items if needed option, so the framework to be really copied to our project.
Next, in the Project Navigator click to the project target, and then to the Build Phases tab. Click to the plus icon in the Link Binary With Libraries section and add the following two frameworks:
- QuartzCore.framework
- Security.framework
The above screenshot shows what you should eventually see.
Step 2 – Create a URL Scheme
When we will first-run the app, one of the first things we have to do is to get connected to our Dropbox account by providing our credentials. Thankfully, the whole authorisation process is automated and we don’t need to do anything manually. If the original Dropbox app is installed to the device, it will be launched so we can login to the Dropbox account. If not, a view controller from the Dropbox SDK is presented inside the app so we can login. However, in the first case we must also “tell” the Dropbox app that our own app is the one that should be called after the authorisation is over, that’s why we have to create a new URL scheme, and as you’ll see, it’s really easy to do so.
In Xcode, click to the Info tab and then expand the URL Types section. Click to the plus icon to create a new one, and then in the URL Schemes textfield type: db-YOUR_APP_KEY (replace the “YOUR_APP_KEY” with the real key you created in the previous step).
Note that the “db-” prefix is necessary and that the Dropbox API expects to find it. If you change it you won’t be able to pass the authorisation stage.
Step 3 – Create a bridging file
As I said before, the Dropbox framework is written in Objective-C language, and there’s not a direct way to import the SDK in the Swift code. What we have to do, is to create a bridging header file where we’ll import the SDK, and then it will be available in a project-wide scope. We will create this bridging file by adding first an Objective-C file (an .m file) to the project, so we force Xcode to create it for us.
In Xcode go to the File > New > File… menu. In the iOS section, click to the Source category, and from the provided templates select the Objective-C file as shown next:
Proceed and name the file temp.
Continue and get finished with that file creation. Upon finishing, Xcode will display the following message asking if you want to create a bridging header file:
Click to the Yes button, and a file named DBDemo-Bridging-Header.h will be added to the Project Navigator. This is what we want. Now, you can go and delete the temp.m file, as we don’t need it.
Next, click to the DBDemo-Bridging-Header.h file and add the following line:
1 | #import <DropboxSDK/DropboxSDK.h> |
Now we are ready to write the needed code so we get logged in to the Dropbox account and start uploading and fetching files.
Connecting to Dropbox
Focusing on the code from now on, the first task we have to perform is to establish a session between the app and the Dropbox. Every time the app is launched, the session will check if the user has logged in to his account or not, and respectively it will provide the proper features. In this part, we’ll use the app key and app secret values we created earlier.
Begin by opening the AppDelegate.swift file. In the application(application:didFinishLaunchingWithOptions:) method we’ll initialise a session as shown next, so the Dropbox SDK to be able to handle all the tasks regarding it. Add the following code to that method:
1 2 3 4 5 6 7 8 9 10 11 | func application (application : UIApplication, didFinishLaunchingWithOptions launchOptions : [ NSObject : AnyObject ]? ) -> Bool { // Override point for customization after application launch. let appKey = "YOUR_KEY" // Set your own app key value here. let appSecret = "YOUR_SECRET" // Set your own app secret value here. let dropboxSession = DBSession (appKey : appKey, appSecret : appSecret, root : kDBRootAppFolder ) DBSession.setSharedSession (dropboxSession ) return true } |
From the Dropbox App Console, copy and paste your app key and app secret values to the appKey and appSecret respectively. As you see in the above snippet, the session is initialised with three arguments: The app key and secret, and a value indicating whether the app should access its own folder only, or the whole Dropbox folder. The value you’ll set here must match to the option you set to the App Console earlier. There are two possible constants you can use:
- kDBRootAppFolder: Use it to access the app’s own folder only.
- kDBRootDropbox: Use it to access the Dropbox folder.
Note that if you connect to your Dropbox account once, the above session will use that connection and won’t ask you to get authorised again, unless you explicitly log out (we’ll see that later).
Let’s keep going, and before we leave the AppDelegate.swift file let’s add one more useful part. The application delegate is the place where any custom URL schemes must be handled, and don’t forget that we’ve created such one previously. So, let’s do that by implementing the following method:
1 2 3 4 5 6 7 8 9 10 | func application (application : UIApplication, openURL url : NSURL, sourceApplication : String?, annotation : AnyObject? ) -> Bool { if DBSession.sharedSession ( ).handleOpenURL (url ) { if DBSession.sharedSession ( ).isLinked ( ) { return true } } return false } |
The above will allow the authentication flow to be completed properly. No matter how the user will sign in, he is going to be able to return back to the app now without any problems at all. In a while we’ll come back to this method to add one more feature, but for now we’re good to go.
Now that we have created a session and made sure that the authentication flow will run smoothly, let’s perform the actual connection to a user Dropbox account. In the app interface there’s a bar button item to the toolbar at the top named Connect. By tapping it, we want the user to get connected (or linked as it’s called in the Dropbox API) by following the predefined authorisation process. Additionally, if the user is already connected we want to sign him out by “breaking” the link that has been established between our app and the Dropbox.
In the ViewController.swift file there’s an IBAction method named connectToDropbox(_:). Right now it doesn’t contain any code at all, so let’s add some:
1 2 3 4 5 6 7 8 9 | @IBAction func connectToDropbox (sender : AnyObject ) { if !DBSession.sharedSession ( ).isLinked ( ) { DBSession.sharedSession ( ).linkFromController (self ) } else { DBSession.sharedSession ( ).unlinkAll ( ) bbiConnect.title = "Connect" } } |
The first important thing here is that the authorisation process begins by calling the linkFromController(_:) method of the sharedSession class. When that part of code is executed (in other words when the Connect button gets tapped and the user is not already logged in), either the original Dropbox app will be launched if it’s installed to the device, or a predefined view controller will be shown to allow user to provide his credentials.
The sign out process is achieved by calling the unlinkAll() method respectively.
The above is quite easy and straightforward, but as you see we change the button’s title only when the user logs out, and not when he logs in. It would be easy to add one more command and change the title from “Connect” to “Disconnect” in the first case, but the above method is not the proper place to do so and I’ll explain why.
By default the app doesn’t really know if the user is connected or not to his Dropbox account when it’s launched. It actually expects from the session we created earlier to decide about that. If the user is connected indeed, and to be more precise, once a session is established, then we can be sure that there’s a real connection so we change the button’s title to “Disconnect”. If we would make that button’s title changing in the above method, the title would change and remain like that even if for some reason the user was unable to get connected. So, let’s act in a smarter way and let’s use the session.
Every time that a user logs in, or more precisely a link is set, the application(application:url:sourceApplication:annotation:) (the one that handles the URL scheme) is called. In there, we can post a custom notification that we’ll observe for in the ViewController class, and make that way known to our app that a new connection exists. In the handler method of that notification then, we can change the button’s title.
Let’s see things in the proper order. Initially, let’s update the application(application:url:sourceApplication:annotation:) method in the AppDelegate.swift file as shown next:
1 2 3 4 5 6 7 8 9 10 11 | func application (application : UIApplication, openURL url : NSURL, sourceApplication : String?, annotation : AnyObject? ) -> Bool { if DBSession.sharedSession ( ).handleOpenURL (url ) { if DBSession.sharedSession ( ).isLinked ( ) { NSNotificationCenter.defaultCenter ( ).postNotificationName ( "didLinkToDropboxAccountNotification", object : nil ) return true } } return false } |
Even though I provide you again the whole method, there’s actually only one new line there; the one we post the didLinkToDropboxAccountNotification notification. This notification will be posted every time a new connection is established by a Dropbox session.
Back to the ViewController.swift now, let’s observe for the above notification. In the viewDidLoad method add the next line:
1 2 3 4 5 6 | override func viewDidLoad ( ) { ... NSNotificationCenter.defaultCenter ( ).addObserver (self, selector : "handleDidLinkNotification:", name : "didLinkToDropboxAccountNotification", object : nil ) } |
Let’s implement the handleDidLinkNotification(_:) custom method:
1 2 3 |
With all the above additions, the button’s title will change from “Connect” to “Disconnect” once a new connection is made, and it won’t change if the authorisation fails for some reason. However, we’re still no good, because if the app is launched and an already established connection exists from a previous authorisation process, the button’s title will still show “Connect”. Let’s solve that too by modifying a bit more the viewDidLoad method:
1 2 3 4 5 6 7 | override func viewDidLoad ( ) { ... if DBSession.sharedSession ( ).isLinked ( ) { bbiConnect.title = "Disconnect" } } |
At this point, our app can successfully connect and disconnect to a Dropbox account. Note that later we’ll make it capable of fetching and displaying the contents of the designated folder once a link is made, but for now we’re just fine.
So, go ahead and give it a try either in the Simulator, or in a real device. Use the connect button and provide your Dropbox credentials. Once you successfully sign in, a new folder will be created in your Dropbox account having the name of the app you specified earlier (actually, two folders will be created: one named “Apps” that contains all applications’ subfolders, and one named “TutApp” for the current app).
Uploading Files
Being able to connect and disconnect from Dropbox is nice, but it’s also totally pointless if we don’t really interact with it either by uploading, or by fetching files. So, here we’ll add a new feature to our app that will make it possible to upload files to the Dropbox. In the starter project you’ve downloaded there are already two test files for that reason; a text file named testtext.txt and an image named nature.jpg. Let’s keep things simple, so let’s use those two files to test the uploading feature. Of course, you’re encouraged to extend the app’s functionality and make it capable of uploading files from other sources, such as the documents directory.
For both getting and sending files it’s necessary to initialise and use an object of another class that is part of the Dropbox SDK, called DBRestClient. This class is responsible for carrying out all the work of uploading and downloading content behind the scenes. Besides that, we’ll also use a relevant protocol, named DBRestClientDelegate. It provides many delegate methods, but we’ll use just a few of them. We need to use this protocol and its delegate methods, because it’s the only way to know when an upload process is over, to keep track of the uploading process, to be informed about when content has been downloaded, to know what errors might have occurred, and so on. The DBRestClient class works asynchronously, and uses the DBRestClientDelegate to let us know that one or more tasks on the background have finished.
Having said all the above, let’s declare our first property. In the ViewController.swift file, go to the top of the class and add the next line:
1 | var dbRestClient : DBRestClient ! |
The dbRestClient property is the object of the DBRestClient class that we’ll use throughout the rest of the project for performing upload and fetch tasks. After the declaration of a property we usually move to its initialisation, and most of the times that happens in the viewDidLoad method in the tutorial demo applications. However, this time we won’t do that. We’ll create a custom method to initialise the above property. That’s because we need the dbRestClient object to be initialised in two cases: Either when a new connection is made, or when the app is launched and a link to the Dropbox already exists.
We’ll see all what I just said, but first, let’s create a new custom method where we’ll initialise the new property:
1 2 3 4 | func initDropboxRestClient ( ) { dbRestClient = DBRestClient (session : DBSession.sharedSession ( ) ) dbRestClient.delegate = self } |
As you see, the only thing required to be provided is the session that we establish once the app is launched. Note that we make the ViewController class the delegate of the dbRestClient object, even though we haven’t adopted the DBRestClientDelegate yet. Don’t worry and don’t mind about the error Xcode is showing; we’ll fix everything in just a while.
Let’s call the above method now. At first, this should be done once a new connection is made, so the place to do that is in the handleDidLinkNotification(_:) custom method:
1 2 3 4 | func handleDidLinkNotification (notification : NSNotification ) { initDropboxRestClient ( ) bbiConnect.title = "Disconnect" } |
In case an existing link is found when the app is launched, the above method must be called once again. In the viewDidLoad method we’ll do a small addition:
1 2 3 4 5 6 7 8 | override func viewDidLoad ( ) { ... if DBSession.sharedSession ( ).isLinked ( ) { bbiConnect.title = "Disconnect" initDropboxRestClient ( ) } } |
At this point and before we proceed, we should take care to make the dbRestClient property nil in case we get disconnected from Dropbox, as there’s no need to keep it initialised. So, in the connectToDropbox(_:) IBAction method, and in the else case specifically, let’s add the next command:
1 2 3 4 5 6 7 8 | @IBAction func connectToDropbox (sender : AnyObject ) { ... else { ... dbRestClient = nil } } |
As I said, we have two test files to upload, and a nice and easy approach, suitable for the purpose of the tutorial, would be to present an action sheet that will let us choose the file we want to upload to Dropbox. We’ll make the action sheet appear by tapping on the action bar button item existing to the right side of the top toolbar.
In the ViewController.swift file you’ll find a defined IBAction method named performAction(_:). This method has already been connected to the action bar button item, so it’s waiting for us to add some code. Initially, let’s write the standard code that will present the action sheet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | @IBAction func performAction (sender : AnyObject ) { if !DBSession.sharedSession ( ).isLinked ( ) { println ( "You're not connected to Dropbox" ) return } let actionSheet = UIAlertController (title : "Upload file", message : "Select file to upload", preferredStyle : UIAlertControllerStyle.ActionSheet ) let uploadTextFileAction = UIAlertAction (title : "Upload text file", style : UIAlertActionStyle.Default ) { (action ) -> Void in } let uploadImageFileAction = UIAlertAction (title : "Upload image", style : UIAlertActionStyle.Default ) { (action ) -> Void in } let cancelAction = UIAlertAction (title : "Cancel", style : UIAlertActionStyle.Cancel ) { (action ) -> Void in } actionSheet.addAction (uploadTextFileAction ) actionSheet.addAction (uploadImageFileAction ) actionSheet.addAction (cancelAction ) presentViewController (actionSheet, animated : true, completion : nil ) } |
Notice that at the beginning of the method we check if there’s a link to the Dropbox. If there’s not, then we just display a relevant message to the console and we return from the method. The rest of the code is simple and easy, and there’s no reason to make any further discussion about it.
Now, let’s focus to the upload of the text file (the uploadTextFileAction action). Let me first show you the code, and then we’ll say a few more things about it:
1 2 3 4 5 6 7 8 | let uploadTextFileAction = UIAlertAction (title : "Upload text file", style : UIAlertActionStyle.Default ) { (action ) -> Void in let uploadFilename = "testtext.txt" let sourcePath = NSBundle.mainBundle ( ).pathForResource ( "testtext", ofType : "txt" ) let destinationPath = "/" self.dbRestClient.uploadFile (uploadFilename, toPath : destinationPath, withParentRev : nil, fromPath : sourcePath ) } |
The uploadFile(…) method of the DBRestClient class is the one we use here for first time, and it expects the following arguments:
- The name of the file that will be uploaded.
- The destination path. In our case it’s the root of the app’s own folder (TutApp folder).
- The revision of the file. We won’t deal with that here, and for new files it’s not needed (just pass nil).
- The source path of the file that will be uploaded.
All the arguments we provide to that method are specified right before we call it, and there’s nothing particularly difficult there. Note that this method will run asynchronously (in the background), and the delegate methods that we’ll implement in a while will let us know when the process has finished, or whether an error has occurred.
Let’s add the missing code now to the uploadImageFileAction action. Actually, we won’t do something different than what we did right before, so let’s see the code straight away:
1 2 3 4 5 6 7 8 | let uploadImageFileAction = UIAlertAction (title : "Upload image", style : UIAlertActionStyle.Default ) { (action ) -> Void in let uploadFilename = "nature.jpg" let sourcePath = NSBundle.mainBundle ( ).pathForResource ( "nature", ofType : "jpg" ) let destinationPath = "/" self.dbRestClient.uploadFile (uploadFilename, toPath : destinationPath, withParentRev : nil, fromPath : sourcePath ) } |
Time for the delegate methods now. First of all, we must adopt the DBRestClientDelegate protocol, so go to the header of the class do it:
1 | class ViewController : UIViewController, UITableViewDelegate, UITableViewDataSource, DBRestClientDelegate |
We’re going to implement two delegate methods at this point: One that will let us know when the upload has finished, and one that will be called in case an error occurs during the upload process. Beginning with the first one, the only thing we’ll do for now is to display a message to the console and the path of the file to the Dropbox folder:
1 2 3 4 | func restClient (client : DBRestClient !, uploadedFile destPath : String !, from srcPath : String !, metadata : DBMetadata ! ) { println ( "The file has been uploaded." ) println (metadata.path ) } |
The error delegate method:
1 2 3 4 | func restClient (client : DBRestClient !, uploadFileFailedWithError error : NSError ! ) { println ( "File upload failed." ) println (error.description ) } |
Similarly as before, we just display a message and the error description to the console.
Go and test what we’ve done so far. This time, use the action bar button item to select the file you want to upload (you can do that repeatedly), and send it to the Dropbox folder. Then, open that folder either in a browser or in the Finder (if you’ve installed the Dropbox app to your Mac), and verify that the file is there.
Displaying The Uploading Progress
One of the many delegate methods that the DBRestClientDelegate protocol provides allows to keep track of the upload progress. This is useful in cases you want to notify the user about the percentage of the file upload that has been completed, or to show the progress graphically based on the value you get back from that delegate method.
In this demo application we’ll make use of that method, and we’ll display the progress of the uploading using a progress view. This progress view already exists in the interface, and by default it’s not visible.
The first thing we’ll do is to create a new custom method. In this one we’ll perform two simple tasks:
- We’ll set the value of the progress view to 0, so it properly displays the progress.
- We’ll make the progress view visible.
Here we go:
1 2 3 4 | func showProgressBar ( ) { progressBar.progress = 0.0 progressBar.hidden = false } |
Next, we must determine when this method should be called. Obviously, the progress view must become visible when a file upload begins, so let’s modify both the uploadTextFileAction and uploadImageFileAction actions of the action sheet (in the performAction(_:) IBAction method) as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | let uploadTextFileAction = UIAlertAction (title : "Upload text file", style : UIAlertActionStyle.Default ) { (action ) -> Void in ... self.showProgressBar ( ) self.dbRestClient.uploadFile (uploadFilename, toPath : destinationPath, withParentRev : nil, fromPath : sourcePath ) } let uploadImageFileAction = UIAlertAction (title : "Upload image", style : UIAlertActionStyle.Default ) { (action ) -> Void in ... self.showProgressBar ( ) self.dbRestClient.uploadFile (uploadFilename, toPath : destinationPath, withParentRev : nil, fromPath : sourcePath ) } |
Notice that the progress bar must become visible right before the upload starts.
Now, let’s implement the new delegate method, which as you’ll see right next, is really simple:
1 2 3 | func restClient (client : DBRestClient !, uploadProgress progress : CGFloat, forFile destPath : String !, from srcPath : String ! ) { progressBar.progress = Float (progress ) } |
Note that the progress view expects a Float value, therefore the above cast is necessary.
So far so good, as the progress bar will become visible and will display the progress while a file is being uploaded. Don’t forget however that when the uploading process is over, the progress bar must become hidden again. We’ll do that in the two other delegate methods that we have already implemented, as those are the places that indicate that the upload has finished.
1 2 3 4 5 6 7 8 9 | func restClient (client : DBRestClient !, uploadedFile destPath : String !, from srcPath : String !, metadata : DBMetadata ! ) { ... progressBar.hidden = true } func restClient (client : DBRestClient !, uploadFileFailedWithError error : NSError ! ) { ... progressBar.hidden = true } |
Now you can go and test the app once again. This time you’ll see the upload progress in the progress view.
Fetching Content
As the title of this part says, here we are going to do the exact opposite thing from what we’ve already done; we’ll get the contents of the app’s folder in the Dropbox, and we’ll display them to the tableview existing to the app (and we haven’t used yet). Fetching the files from the Dropbox folder doesn’t mean that they will be downloaded. Actually, what we get is a list of files that we’ll display to the user.
Similarly to what we did before, here we are also going to use the dbRestClient object for getting the files from Dropbox. Doing so it’s easy, as it takes just one line of code, the following:
1 | dbRestClient.loadMetadata ( "/" ) |
The parameter of the loadMetadata method is the path of the folder we want to get the file list from. In this case, we ask from Dropbox to return all the contents of the app’s folder (the “/” means the root folder where the app has access to). If there was a subfolder named “test” and we would want its contents only, then we would pass the “/test/” value as the parameter of the above method.
Back to our app again, initially we want to load the existing contents of the app’s folder in two cases: When we connect to Dropbox, and when the app gets launched and there’s a connection already. If you recall, in both of those cases we call the initDropboxRestClient() custom method, so obviously this is the best place to load the file list. Here it is:
1 2 3 4 5 | func initDropboxRestClient ( ) { ... dbRestClient.loadMetadata ( "/" ) } |
Also, we want to refresh the file list in the app after an upload process has successfully finished. So, let’s update the following delegate method too:
1 2 3 4 5 | func restClient (client : DBRestClient !, uploadedFile destPath : String !, from srcPath : String !, metadata : DBMetadata ! ) { ... dbRestClient.loadMetadata ( "/" ) } |
Besides the above two cases, it would be also nice if we were able to get the Dropbox contents on demand. In the app’s interface there’s a refresh bar button item we haven’t used yet, but now it’s time to do so. By tapping it we’ll make our app ask for the Dropbox contents as we have already done in the above two cases.
In the ViewController.swift file there’s the reloadFiles( IBAction method that is already connected to the refresh bar button item. In there we’ll make a call to the loadMetadata( method of the DBRestClient class once again, so we fetch the Dropbox files whenever we wish so. Here’s that IBAction method:
1 2 3 | @IBAction func reloadFiles (sender : AnyObject ) { dbRestClient.loadMetadata ( "/" ) } |
The above is really convenient when new files have been added to the app’s folder in the Dropbox out of the app, and we want to get them here as well.
Now that we’ve covered all the possible cases for fetching the Dropbox contents, let’s see how we’ll handle the results that the DBRestClient returns back to the app. As you can imagine, we’ll implement another delegate method of the DBRestClientDelegate protocol to do that. This method is the next one:
1 2 3 | func restClient (client : DBRestClient !, loadedMetadata metadata : DBMetadata ! ) { } |
What we are interested in is the second parameter, the metadata object. This one contains various information about the contents of the directory we access (once again, in our case the app’s folder in the Dropbox), and also contains the list of the files we want to display to the tableview. I prompt you to the official Dropbox API documentation for more information about it; here we’ll be limited to what we need so we get the content list only.
So, our goal here is to hold this metadata object to a property and to have access to it even out of the scope of the above method, and then to display the contents to the tableview. Let’s see everything step by step.
First, let’s declare the following property to the top of the class:
1 | var dropboxMetadata : DBMetadata ! |
Now, let’ go to the above delegate method to add the missing code:
1 2 3 4 | func restClient (client : DBRestClient !, loadedMetadata metadata : DBMetadata ! ) { dropboxMetadata = metadata; tblFiles.reloadData ( ) } |
As you see, our work here couldn’t be any simpler than that. Our next task is to implement the tableview methods properly, but before we do that, let’s also cover the error case:
1 2 3 | func restClient (client : DBRestClient !, loadMetadataFailedWithError error : NSError ! ) { println (error.description ) } |
Now, let’s deal with the tableview. Initially, let’s see the easy stuff:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | func numberOfSectionsInTableView (tableView : UITableView ) -> Int { return 1 } func tableView (tableView : UITableView, numberOfRowsInSection section : Int ) -> Int { if let metadata = dropboxMetadata { return metadata.contents.count } return 0 } func tableView (tableView : UITableView, heightForRowAtIndexPath indexPath : NSIndexPath ) -> CGFloat { return 60.0 } |
Notice how we handle the number of rows in the tableview. If the dropboxMetadata object isn’t nil, we get the number of the contents using the contents property. The same property we’ll use right next, where we’ll display the name of each content:
1 2 3 4 5 6 7 8 | func tableView (tableView : UITableView, cellForRowAtIndexPath indexPath : NSIndexPath ) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier ( "idCellFile", forIndexPath : indexPath ) as ! UITableViewCell let currentFile : DBMetadata = dropboxMetadata.contents [indexPath.row ] as ! DBMetadata cell.textLabel?.text = currentFile.filename return cell } |
The above method performs the actual display in the tableview. Initially we hold each file to the currentFile variable, and then using the filename property we get its name and we set it to the cell.
Now we’re ready to test the app once again. Once you launch it, give it a few seconds to get the contents from the Dropbox. Then try to upload a file to make it reload the contents, or add a file to the app’s folder manually in the Dropbox and then use the refresh bar button item.
Downloading Files
Getting the file list from Dropbox is awesome, but it makes no good if we’re unable to use any of them. So in this part we’re going to add one more feature to our demo app, and we’ll make it capable to download files.
More specifically, we’ll initiate a file download process right after we tap to the respective cell. Obviously, we’ll set the documents directory of the app as the destination directory for the downloaded file. Furthermore, and similarly to what we did in the file upload, we’ll keep track of the download progress by using once again the progress view we have in the bottom side of the view.
So, let’s get started by letting me give you a new tableview delegate method implementation, and then we’ll discuss about it:
1 2 3 4 5 6 7 8 9 | func tableView (tableView : UITableView, didSelectRowAtIndexPath indexPath : NSIndexPath ) { let selectedFile : DBMetadata = dropboxMetadata.contents [indexPath.row ] as ! DBMetadata let documentsDirectoryPath = NSSearchPathForDirectoriesInDomains (.DocumentDirectory, .UserDomainMask, true ) [ 0 ] as ! NSString showProgressBar ( ) dbRestClient.loadFile (selectedFile.path, intoPath : documentsDirectoryPath as String ) } |
Let’s take things from the beginning. At first, we keep the file that has been tapped to a local variable so it’s easier to handle it. Next, we specify the path to the documents directory of the app, and then we call the showProgressBar() custom method to make the progress view visible (and also to set its value to 0).
The new thing here is the last line, where we make use of the loadFile(…) method of the DBRestClient class. This one, will initiate the actual download process in the background, and as you expect, it will call the proper delegate methods either upon a successful download, or if an error occurs.
All the above is what we need to start the downloading. Now, we must implement three new delegate methods of the DBRestClientDelegate protocol, so we handle the newly downloaded file, any error that might occurs, and also to display the download progress to the progress view.
Let’s get started with the first one. As this is just a demo app, we won’t do much. Actually, we’ll display to the console just one message telling that the file has been downloaded, and showing the file name and the file type. Here we go:
1 2 3 4 | func restClient (client : DBRestClient !, loadedFile destPath : String !, contentType : String !, metadata : DBMetadata ! ) { println ( "The file \(metadata.filename) was downloaded. Content type: \(contentType)" ) progressBar.hidden = true } |
Apparently, in a real application you should manage each piece of information you receive in this delegate method in a more proper way, and according to your app’s requirements. As you see, once the download is finished we hide the progress view. It’s not necessary to stay visible any more.
Let’s see the error case now:
1 2 3 4 | func restClient (client : DBRestClient !, loadFileFailedWithError error : NSError ! ) { println (error.description ) progressBar.hidden = true } |
As usually, we just display the error description, and again we make the progress view hidden.
Lastly, let’s update our progress view with the real progress of the download:
1 2 3 | func restClient (client : DBRestClient !, loadProgress progress : CGFloat, forFile destPath : String ! ) { progressBar.progress = Float (progress ) } |
That’s all. Go ahead and give the app one last try (or many tries). Tap to a file and watch the progress view indicating the download progress. Note that here we don’t handle the case of multiple file downloads, so don’t expect the progress view to behave “normally” if you tap to a file while another one is being downloaded. You can verify that the files have been downloaded if you simply open the documents directory either in the Simulator or in your device (by using Xcode).
Summary
So, as it seems working with the Dropbox API isn’t as hard as it may sounds eventually. What we’ve seen through the parts of this tutorial is what you’ll need or want to implement in the most cases if you decide to integrate the Dropbox API in your applications. For additional information, assistance or resources, don’t forget that the official website, the Dropbox Developer Home, is your best shot. Don’t hesitate to explore the API and dig even deeper, and of course feel free to change the demo app so you can test more things. It’s time to leave you here, hoping that you will make the best out of the tools and the services you are given by the Dropbox API!
For reference, you can download the Xcode project here.
Không có nhận xét nào:
Đăng nhận xét