Skip to content

BluePic is a sample photo sharing application for iOS that shows you how to connect your mobile application with IBM Bluemix services.

License

Notifications You must be signed in to change notification settings

IBM-MIL/BluePic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

**This version of BluePic is deprecated. Please follow this link for the latest version of BluePic**

Drawing


## BluePic [![Build Status](https://travis-ci.org/IBM-MIL/BluePic.svg?branch=master)](https://travis-ci.org/IBM-MIL/BluePic)

BluePic is a sample application for iOS that shows you how to connect your mobile application with IBM Bluemix services. It is a photo sharing app that allows you to take photos, upload them and share them with the BluePic community. The BluePic community will be made up of all the users that run an instance of your created app.


## Table of Contents * [About IBM Bluemix](#about-ibm-bluemix) * [Requirements](#requirements) * [Project Structure](#project-structure) * [Getting Started](#getting-started) * [Create Bluemix Account](#1-create-bluemix-account) * [BluePic Account Requirements](#2-bluepic-account-requirements) * [Create Bluemix Application and Services](#3-create-bluemix-application-and-services) * [Connect BluePic to your Bluemix Account](#4-connect-bluepic-to-your-bluemix-account) * [Create an application instance on Facebook](#5-create-an-application-instance-on-facebook) * [Pre-populate Feed with Stock Photos (Optional)](#6-pre-populate-feed-with-stock-photos-optional) * [Using BluePic](#using-bluepic) * [Facebook Login](#facebook-login) * [View Feed](#view-feed) * [Post a Photo](#post-a-photo) * [View Profile](#view-profile) * [Architecture/Bluemix Services Implementation](#architecturebluemix-services-implementation) * [Mobile Client Access Facebook Authentication](#1-mobile-client-access-facebook-authentication) * [Cloudant Sync (CDTDatastore)](#2-cloudant-sync-cdtdatastore) * [Object Storage](#3-object-storage) * [Architecture Forethought](#architecture-forethought) * [Troubleshooting](#troubleshooting) * [Deploy to Bluemix Failure](#deploy-to-bluemix-failure) * [License](#license)
## About IBM Bluemix

Bluemix™ is the latest cloud offering from IBM®. It enables organizations and developers to quickly and easily create, deploy, and manage applications on the cloud. Bluemix is an implementation of IBM's Open Cloud Architecture based on Cloud Foundry, an open source Platform as a Service (PaaS). Bluemix delivers enterprise-level services that can easily integrate with your cloud applications without you needing to know how to install or configure them.

In Bluemix you should be aware that often the term “Application” is used to refer to a server component and its Bluemix services needed. It is this server component that gets deployed to Bluemix. It is not the mobile application that gets deployed, this will become clear as you go through the Getting Started guide.


## Requirements Currently, BluePic supports Xcode 7.1.1, iOS 9+, and Swift 2. Designed for iPhone, compatible with iPad.
## Project Structure * `/BluePic-iOS` directory for the iOS client. * `/BluePic-iOS/BluePic/Configuration` directory for configuring Bluemix services keys. * `/NodeStarterCode` directory for the server artifact that is deployed to Bluemix. * `/img` directory for images for this README.
## Getting Started

1. Create Bluemix Account

Create an IBM Bluemix account here and log in. If you already have an account, log in and continue to step 2.

2. BluePic Account Requirements

A free trial of Bluemix comes with 2 GB of memory and allows the use of up to 10 services. If this is your first time using Bluemix, continue to step 3. Otherwise, you might need to delete some resources. BluePic requires 512 MB of memory and 4 services. If your account does not have enough resources available, delete unused instances to free up resources. You can check your usage by looking at the Dashboard tab of Bluemix:

Drawing

Figure 1: Bluemix dashboard, account usage for memory and services highlighted.

3. Create Bluemix Application and Services

Click the "Deploy to Bluemix" button below. It will create the BluePic Bluemix application in your account and initialize the required services.

Deploy to Bluemix

If desired, update the app name, region, organization or space of the application (default parameters work). Click Deploy:

Drawing

Figure 2: Parameters to deploy a Bluemix application.

Upon success you should see:

Drawing

Figure 3: Bluemix application successfully deployed.

Note: If deploying to Bluemix fails, it will have created a faulty application on your account as well as a DevOps services (formerly known as JazzHub) project, these must be deleted manually before trying again. Steps on how to do this here.

Next, go to your dashboard by clicking the "Dashboard" tab on the top of the page:

Drawing

Figure 4: Getting back to dashboard after successful deployment.

On your dashboard the application should then become accessible, click on the Application box to open that Application Overview:

Drawing

Figure 5: Bluemix dashboard.

Application Overview:

Drawing

Figure 6: Application Overview.

4. Connect BluePic to your Bluemix Account

The app has to be configured with certain credentials from each of the three Bluemix services. Clone the BluePic repo and open BluePic.xcworkspace (NOT BluePic**.xcodeproj**) to get started. The file keys.plist located in the Configuration directory of the BluePic Xcode project must be updated accordingly.

Drawing

Figure 7. keys.plist located in the BluePic-iOS/BluePic/Configuration directory.

Cloudant NoSQL DB

  • cdt_username: This username will be used to identify your created databases. Click your newly created application in the Dashboard to open the Application Overview (see Figure 5 above). On the Application Overview, open the Cloudant NoSQL Instantiating Credentials by clicking on the "Show Credentials" tab of the service box:

Drawing

Figure 8. Credentials of a Cloudant NoSQL DB service.

Copy the “username” credential and paste it in the cdt_username field of the keys.plist file.

  • cdt_db_name: This will be the name of the main Cloudant database the iOS application will use to store information. We first must go to the Cloudant Dashboard, click on the CloudantNoSQL DB icon from the Application Overview (see Figure 5 above). You will land in this page:

Drawing

Figure 9. Cloudant service landing page.

Click "LAUNCH" to open the Cloudant Dashboard:

Drawing

Figure 10. Cloudant Dashboard.

Click on "Create Database", enter a name, and click "Create":

Drawing

Figure 11. Creating a new Database.

Note: The name must start with a letter and can only contain lowercase letters (a-z), digits (0-9) and the following characters _, $, (, ), +, -, and /.

Put the name of the newly created database into the cdt_db_name field of the keys.plist file.

  • cdt_key and cdt_pass: You must generate an API Key and Password for the mobile application to access the remote database. On the database page, click on the Permissions tab:

Drawing

Figure 12. Permissions button on database main page.

On the Permissions page click "Generate API key" button:

Drawing

Figure 13. Generate an API key for the database.

It will create a Key and Password:

Drawing

Figure 14. Generated Key and Password.

Store these values into the cdt_key and cdt_pass fields of the keys.plist file respectively. Also, ensure that the created API Key has Writer and Replicator permissions by checking these boxes:

Drawing

Figure 15. Ensure the generated API Key has the correct permissions.

  • cdt_tests_db_name: The application has test cases that run on a separate database, we're storing the name of this test database here. Go through the exact same steps as done for cdt_db_name except with a different database name. Put this name into cdt_tests_db_name field of keys.plist file. Once created, click on the "Permissions" tab of the new database. DO NOT generate another API key for the tests database. The previously generated API Key should be listed, again ensure it has Writer and Replicator permissions:

Drawing

Figure 16. Permissions page of the database to run test cases.

Mobile Client Access

  • backend_route: Listed on the top of the Application Overview page, next to the "Routes:" label:

Drawing

Figure 17. Routes label on Application Overview page.

Copy and paste this value into the backend_route field of keys.plist file. NOTE: Make sure to have "http://" at the front of the pasted value. For example: http://BluePic.bluemix.net.

  • GUID: From the Application Overview (see Figure 5 above) open the Mobile Client Access Instantiating Credentials by clicking on the "Show Credentials" tab of the service box:

Copy the "clientId" credential and paste into the GUID field of keys.plist file.

Drawing

Figure 18. Credentials of a Mobile Client Access service.

Object Storage

Download and install the Cloud Foundry CLI, make sure to install the Mac OS X 64 bit Installer, the latest release:

Drawing

Figure 19. Cloud Foundry CLI installer.

Run the following commands on the terminal:

cf api https://api.ng.bluemix.net

cf login -u <email_address> -o <email_address> -s dev

Pick a name for the service key and use it in the following commands:

cf create-service-key 'Object Storage-rz' <unique_name_for_this_key>

In the following command, use the same name as the one created above:

cf service-key 'Object Storage-rz' <unique_name_for_this_key>

It will return several values:

Drawing

Figure 20. Cloud Foundry CLI command.

  • obj_stg_password: Copy the "password" from CF CLI command into this field (Do not copy the quotation marks for this, nor for the keys below).
  • obj_stg_user_id: Copy the "userId" from CF CLI command into this field.
  • obj_stg_project_id: Copy the "projectId" from from CF CLI command into this field.
  • obj_stg_public_url: Copy the "projectId" from CF CLI command and append it to "https://dal.objectstorage.open.softlayer.com/v1/AUTH_" like so:

https://dal.objectstorage.open.softlayer.com/v1/AUTH_<project_id>

Paste the resulting string into this field.

5. Create an application instance on Facebook

In order to have the app authenticate with Facebook, you must create an application instance on Facebook's website and connect it to your Bluemix app's Mobile Client Access.

  1. To create an application instance on Facebook's website, first go to Facebook's Quick Start for iOS page. Type BluePic as the name of your new Facebook app and click the Create New Facebook App ID button. Choose any Category for the application, and click the Create App ID button.

  2. On the screen that follows, in the Configure your info.plist section under step 2, copy that information into your info.plist file. You can find the info.plist file in Configuration folder of the Xcode project. If you have trouble finding the CFBundleURLType key, note that Xcode changes the CFBundleURLType key to URL types when the key is entered. Your info.plist file should now look like this:

Drawing

Figure 21. Info.plist file.

  1. Next, scroll to the bottom of the quick start page where it says Supply us with your Bundle Identifier and enter the app's bundle identifier. To find the bundle identifier in the Xcode project you can do the following:
    • Make sure the project navigator folder icon is selected in the top left of Xcode. Select the BluePic project at the top of the file structure and then select the BluePic target. Under the identity section, you should see a text field for the bundle identifier that is empty or contains a dummy string like com.bundle.id.BluePic. You can keep the bundle id as is, or make the bundle identifier anything you want, com.BluePic for example.
  2. Once you entered the bundle ID on the Facebook quick start page, click next. That's it for the Facebook quick start setup!
  3. Next go back to your Bluemix dashboard, under services click BluePic-AdvancedMobileAccess. On the page that shows click the Set Up Authentication button and then click Facebook. Enter your Facebook app ID you gathered from step 2 and press the next arrow. No further setup is required at this point.

At this point, you should be able to build and run your instance of the BluePic application on Xcode!

6. Pre-populate Feed with Stock Photos (Optional)

Once BluePic is configured, you should be able to upload photos and see them appear on the feed and profile. However, initially your feed will be empty. If you would like to pre-populate your feed with 3 images, simply do the following:

  1. Open BluePic.xcworkspace to get started. With the BluePic Xcode project open, show the Test Navigator by clicking the 4th icon from the right of the Navigator (toolbar frame on the left side)

Drawing

Figure 22. PopulateFeedWithPhotos test case.

  1. Run the test called PopulateFeedWithPhotos which should be grayed out (disabled by default when tests are run) by right clicking it and clicking Test "PopulateFeedWithPhotos".

  2. The test should complete successfully. Launch the BluePic iOS app again, and you should see 3 images added by user "Mobile Innovation Lab" on the feed.


## Using BluePic

Facebook Login

BluePic was designed so that anyone can quickly launch the app and view photos posted without needing to log in. However, to view the profile or post photos, the user can easily login with his/her Facebook account. This is only used for a unique user id, the user's full name, as well as to display the user's profile photo.

Drawing

Figure 23. Welcome page.

View Feed

The feed (first tab) shows all the latest photos posted to the BluePic community (regardless if logged in or not).

Drawing

Figure 24. Main feed view.

Post a Photo

Posting to the BluePic community is easy. Tap the middle tab in the tab bar and choose to either Choose a photo from the Camera Roll or Take a photo using the device's camera. You can then give the photo a caption before posting.

Drawing

Figure 25. Posting a photo.

View Profile

By tapping the third tab, you can view your profile. This shows your Facebook profile photo, lists how many photos you've posted, and shows all the photos you've posted to BluePic.

Drawing

Figure 26. Profile feed.


## Architecture/Bluemix Services Implementation The following architecture is utilized for BluePic. For authentication, Mobile Client Access with Facebook Authentication is implemented. For profile and photo metadata, the Cloudant SDK is integrated. Finally, for photo storage and hosting, Object Storage is utilized.

Drawing

Figure 27. BluePic Architecture Diagram.

1. Mobile Client Access Facebook Authentication

Bluemix Mobile Client Access Facebook Authentication is used for logging into BluePic.

The FacebookDataManager under the BluePic-iOS/BluePic/DataManagers directory handles most of the code responsible for Facebook authentication. To start using Bluemix Facebook Authentication, it must first be configured on app launch, and we do this in the didFinishLaunchingWithOptions() method of AppDelegate.swift by calling the method below.

func initializeBackendForFacebookAuth() {
    //Initialize backend
    let key = Utils.getKeyFromPlist("keys", key: "backend_route")
    let guid = Utils.getKeyFromPlist("keys", key: "GUID")
    IMFClient.sharedInstance().initializeWithBackendRoute(key, backendGUID: guid);
    
    //Initialize FB
    IMFFacebookAuthenticationHandler.sharedInstance().registerWithDefaultDelegate()
    
    }

Also in the App Delegate, two other methods must be overridden to activate Facebook Authentication, as shown below:

func applicationDidBecomeActive(application: UIApplication) {
    FBAppEvents.activateApp()
    }
      
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?,annotation: AnyObject) -> Bool {
        return FBAppCall.handleOpenURL(url, sourceApplication:sourceApplication)
    }

Now that the Facebook and Bluemix frameworks are configured, you can actually try authenticating to receive a unique identifier for a user. The FacebookDataManager deals with authenticating and keeping track of user's credentials in a SharedInstance (singleton). The method below starts the process of showing a native Facebook login to the user when he/she presses the SIGN IN WITH FACEBOOK button on the LoginViewController.

/**
     Method to auth user using Facebook SDK
     
     - parameter callback: Success or Failure
     */
    func authenticateUser(callback : ((networkRequest : NetworkRequest) -> ())){
        if (self.checkIMFClient() && self.checkAuthenticationConfig()) {
            self.getAuthToken(callback) //this will in turn present FB login
        }
        else{
            callback(networkRequest: NetworkRequest.Failure)
        }
        
    }

The self.checkAuthenticationConfig() method call in the code above will try to present the native iOS 9 Safari Facebook Login Modal. The code above either continues with requesting a Facebook token if the login credentials were correct from the user, or throws an error if not correct or the user cancels.

After the user finishes inputting their credentials, the unique user id is received and saved in the getAuthToken() method of the FacebookDataManager. There, an IMFAuthorizationManager requests authorization by calling the obtainAuthorizationHeaderWithCompletionHandler() method, resulting in a success or failure.

The successful closure of getAuthToken() is shown below, where the user display name and unique id are saved to the sharedInstance property of the FacebookDataManager, as well as saved to NSUserDefaults to keep track of log-in status in future app launches.

if let userID = identity["id"] as? NSString {
                        if let userName = identity["displayName"] as? NSString {
                        
                            //save username and id to shared instance of this class
                            self.fbUniqueUserID = userID as String
                            self.fbUserDisplayName = userName as String
                        
                            //set user logged in
                            self.isLoggedIn = true
                            
                            //save user id and name for future app launches
                            NSUserDefaults.standardUserDefaults().setObject(userID, forKey: "user_id")
                            NSUserDefaults.standardUserDefaults().setObject(userName, forKey: "user_name")
                            NSUserDefaults.standardUserDefaults().synchronize()
                            
                            print("Got facebook auth token for user \(userName) with id \(userID)")
                            
                            callback(networkRequest: NetworkRequest.Success)
                        }
                    }

### 2. Cloudant Sync (CDTDatastore) Cloudant Sync [(CDTDatastore)](https://github.com/cloudant/CDTDatastore) enables you to create a single local database for every user. The app simply replicates and syncs a copy of the remote database in Cloudant with its local copy on their phone or tablet. If there’s no network connection, the app runs off the local database on the device. In BluePic we have two types of documents: profile and picture. Note that we only store the metadata for pictures, the actual image is stored in the Object Storage Bluemix service. The `CloudantSyncDataManager` class was created to handle communication between iOS and Cloudant Sync.

Creating a local datastore:

    /**
     * Creates a local datastore with the specific name stored in dbName instance variable.
     */
    func createLocalDatastore() throws {
        let fileManager = NSFileManager.defaultManager()
        let documentsDir = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last!
        let storeURL = documentsDir.URLByAppendingPathComponent("cloudant-sync-datastore")
        let path = storeURL.path
        self.manager = try CDTDatastoreManager(directory: path)
        self.datastore = try manager.datastoreNamed(dbName)
    }

Function to create a Profile document:

    /**
     * Creates a profile document.
     *
     * @param id Unique ID the created document to have.
     * @param name Profile name for the created document.
     */
    func createProfileDoc(id:String, name:String) throws -> Void {
        // Create a document
        let rev = CDTDocumentRevision(docId: id)
        rev.body = ["profile_name":name, "Type":"profile"]
        // Save the document to the datastore
        try datastore.createDocumentFromRevision(rev)
        print("Created profile doc with id: \(id)")
    }

Synching with a remote database is done by performing two main operations: push and pull. Below is an example of how we perform push.

    /**
      * This method will create a new Replicator object and push any new docs/updates on the local datastore to the remote database.
      * This is a asynchronous call and will run on a separate replication thread.
      */
    func pushToRemoteDatabase() throws {
        //Initialize replicator
        try createPushReplicator()
        //Start the replicator
        try self.pushReplicator.start()
    }
    
    /**
     * Creates a new Push Replicator and stores it in pushReplicator instance variable.
     */
    func createPushReplicator() throws {
        //Initialize replicators
        let replicatorFactory = CDTReplicatorFactory(datastoreManager: manager)
        let remoteDatabaseURL = generateURL()
        // Push Replicate from the local to remote database
        let pushReplication = CDTPushReplication(source: datastore, target: remoteDatabaseURL)
        self.pushReplicator =  try replicatorFactory.oneWay(pushReplication)
        self.pushReplicator.delegate = pushDelegate;
    }
    
    /**
     * Creates the URL of the remote database from instance variables.
     */
    private func generateURL() -> NSURL {
        let stringURL = "https://\(apiKey):\(apiPassword)@\(username).cloudant.com/\(dbName)"
        return NSURL(string: stringURL)!
    }

You can view the Cloudant database (including profile and picture documents) by navigating to your Cloudant NoSQL DB service instance on the Bluemix Dashboard. To do this, navigate to your Bluemix Dashboard by clicking Dashboard on the top of your Bluemix home page (#1 in the image below). Then, click the Cloudant NoSQL DB service to view the record of images uploaded to each container (#2 in the image below)

Drawing

Figure 28. Cloudant NoSQL service.


### 3. Object Storage [Object Storage](https://console.ng.bluemix.net/catalog/services/object-storage/) is used in BluePic for hosting images.

ObjectStorageDataManager and ObjectStorageClient were created based on this link for communicating between iOS and Object Storage.

Before uploading photos, it is necessary to authenticate with Object Storage by calling ObjectStorageDataManager.SharedInstance.objectStorageClient.authenticate() which returns either a success or failure, shown below in the FacebookDataManager.

ObjectStorageDataManager.SharedInstance.objectStorageClient.authenticate({() in
                    print("success authenticating with object storage!")
                    self.showLoginIfUserNotAuthenticated()
                }, onFailure: {(error) in
                    print("error authenticating with object storage: \(error)")
                    DataManagerCalbackCoordinator.SharedInstance.sendNotification(DataManagerNotification.ObjectStorageAuthError)
            })

Next, you must create a container on Object Storage for uploading photos to. The method below in the LoginViewModel creates a container for later uploading photos to.

/**
     Method to attempt creating an object storage container and call callback upon completion (success or failure)
     
     - parameter userID: user id to be used for container creation
     */
    func createObjectStorageContainer(userID: String!) {
        print("Creating object storage container...")
        ObjectStorageDataManager.SharedInstance.objectStorageClient.createContainer(userID, onSuccess: {(name) in
            print("Successfully created object storage container with name \(name)") //success closure
            self.fbAuthCallback(true)
            }, onFailure: {(error) in //failure closure
                print("Facebook auth successful, but error creating Object Storage container: \(error)")
                self.fbAuthCallback(false)
        })
        
    }

Finally, you can upload an image to Object Storage by utilizing code similar to the method below in the CameraDataManager

    /**
     Method called to upload the image to object storage
     */
    func uploadImageToObjectStorage() {
        print("uploading photo to object storage...")
        //push to object storage
        ObjectStorageDataManager.SharedInstance.objectStorageClient.uploadImage(FacebookDataManager.SharedInstance.fbUniqueUserID!, imageName: self.lastPhotoTakenName, image: self.lastPhotoTaken,
            onSuccess: { (imageURL: String) in
                print("upload to object storage succeeded.")
                print("imageURL: \(imageURL)")
            }, onFailure: { (error) in
                print("upload to object storage failed!")
                print("error: \(error)")
                DataManagerCalbackCoordinator.SharedInstance.sendNotification(DataManagerNotification.ObjectStorageUploadError)
        })
    }

You can view the Object Storage database (including all photos uploaded) by navigating to your Object Storage service instance on the Bluemix Dashboard. To do this, navigate to your Bluemix Dashboard by clicking Dashboard on the top of your Bluemix home page (#1 in the image below). Then, click the Object Storage service to view the record of images uploaded to each container (#2 in the image below)

Drawing

Figure 29. Object Storage service.


## Architecture Forethought

For BluePic, we used a simple architecture where there is no middle tier component between the mobile app and the storage components (e.g. Cloudant) on the server. To roll out BluePic to a production environment, a few architectural changes should be made.

Cloudant Sync requires a complete replica of the database on each mobile client. This may not be feasible for apps with large databases. Under such scenarios, instead of leveraging Cloudant Sync, the REST API provided by Cloudant could be used to perform CRUD and query operations against the remote Cloudant instance.  Though replicating subsets of records can be done today with Cloudant Sync, doing so with large databases where only a small subset of records should be replicated can introduce performance problems.

Using Cloudant Sync without an additional middle tier component between the mobile app and the database requires the mobile code to know the username and password for accessing the Cloudant database. This will lead to security breaches if someone gets their hands on those credentials. Hence, security could be a reason for having all database operations go first through a middleware component (e.g. Liberty, Node.js) to verify that only authenticated and authorized users of the app can perform such operations. In this architecture, the credentials to access the database are only known by the middleware component.

Troubleshooting

Deploy to Bluemix Failure

If the Deploy to Bluemix button failed, you would see a page similar to this:

Drawing

Figure 30. A failed deployment to Bluemix.

These are some of the most common reasons for a failed deployment:

  • Your account does not have enough resources. BluePic requires requires 512 Mb of memory and 4 services to deploy successfully.
  • You have already deployed BluePic to your account and have tried to deploy it again.
  • Bluemix servers are down.

Even though the deployment failed, Bluemix still have created an application, initializes some services and creates a DevOps project. You must delete these manually before attempting to deploy again. Begin by first going back to the dashboard, click the "Dashboard" tab on top of the page:

Drawing

Figure 31. Click on the "Dashboard" tab to go back to the main page.

Once in the dashboard, find the failed application under the "Applications" section and click on the gray gear located on the top right corner of the application block to open the menu:

Drawing

Figure 32. Opening the menu of a Bluemix application.

With the menu now open, click the "Delete App" option:

Drawing

Figure 33. Menu open, delete option highlighted of a Bluemix application.

You will get at "Are you sure..." message, make sure all the services under the Services tab are checked and click DELETE, this will delete the application and all of the created services:

Drawing

Figure 34. Deleting a Bluemix application and its bound services.

Now, you must delete the Bluemix DevOps project that was created. First, go to the IBM Bluemix DevOps Services main site here. The main page looks like this:

Drawing

Figure 35. IBM Bluemix DecOps Services main page.

Locate the created project on the main page and click the gray gear icon on the top right corner of the project block.

Drawing

Figure 36. Opening the menu of a DevOps Services Project.

With the project page open, click on the Delete tab on the left side of the page:

Drawing

Figure 37. Deleting a DevOps Services project.

On the next page, type out DELETE in the empty field and click CONFIRM:

Drawing

Figure 38. Confirm the deletion of selected project.


## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE](LICENSE).

About

BluePic is a sample photo sharing application for iOS that shows you how to connect your mobile application with IBM Bluemix services.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published