Using Amazon S3 To Store, List and Remove Images from your iOS App

Using Amazon S3 To Store, List and Remove Images from your iOS App

Amazon Simple Storage Service (S3) provides secure, durable, highly scalable object storage in the cloud. Using the AWS Mobile SDK for iOS, you can directly access Amazon S3 from your mobile app.

With AWS object storage solutions like Amazon Simple Storage Service (Amazon S3) and Amazon Glacier, you manage your storage in one place with an easy-to-use application interface. You can use policies to optimize storage costs, tiering between different storage classes automatically. AWS makes storage easier to use to perform analysis, gain insights, and make better decisions faster.

Amazon S3 Benefits

Amazon S3 provides the most powerful object storage platform available in the cloud today.

1

Unmatched durability, reliability, & scalability.

2

Most comprehensive security & compliance capabilities.

3

Query in place.

4

Flexible management.

5

Most supported platform with the largest ecosystem.

6

Easy, flexible data transfer.

Getting Started with AWS S3

Amazon Simple Storage Service (Amazon S3) is storage for the Internet. You can use Amazon S3 to store and retrieve any amount of data at any time, from anywhere on the web. You can accomplish these tasks using the AWS Management Console, which is a simple and intuitive web interface.  You can get the whole documentation from here – AWS s3

Before proceeding further please follow the documentation to create the s3 account and as well the bucket. Every object in Amazon S3 is stored in a bucket. Before you can store data in Amazon S3, you must create a bucket.

Let’s get started with integrating AWS SDK to iOS app and build a sample to:

1

Store images.

2

List images.

3

Remove images.

The AWS Mobile Android and iOS SDKs help you build high quality mobile apps quickly and easily. They provide easy access to a range of AWS services, including Amazon Cognito, AWS Lambda, Amazon S3, Amazon Kinesis, Amazon DynamoDB, Amazon Pinpoint and many more.

Please check set-up here how to configure the iOS app and do necessary configurations.  We need to configure “Add User DataStorage” to the iOS app we will be creating in the AWS mobile hub.

User Data Storage

Enable your app to store and retrieve user files from cloud storage with the permissions model that suits your purpose. Mobile Hub User Data Storage deploys and configures cloud storage buckets using Amazon Simple Storage Service (Amazon S3).

Follow the steps listed here to configure the data storage feature to the app. So now Update your app with the latest copy of the cloud configuration file that has to be downloaded after the user data storage has been configured.

Till here we are able to configure the required set-up for AWS SDK and as well setting-up the app in AWS mobile hub and using the cloud configuration file.

Let’s start working on the application to save, list and delete the images in the s3 bucket we had created in the above process.

Requirements

  • Xcode 7 and later
  • iOS 8 and later

1. Open Xcode and select a single view application and name it as you like. For the sample i use as s3sample.

2. There are three ways to import the AWS Mobile SDK for iOS into your project:

You should use one of these three ways to import the AWS Mobile SDK but not multiple. Importing the SDK in multiple ways loads duplicate copies of the SDK into the project and causes compiler errors.

3. Let’s use Carthage to set-up the SDK.

4. Install the latest version of Carthage, navigate to your project folder and create the Cartfile and add the following to your Cartfile that is created in your project:

github “aws/aws-sdk-ios”

5. Then run the following command:

$ carthage update  

6. Now open the project in Xcode, select your Target. Under General tab, find Embedded Binaries and then click ‘+’ button.

7. Click the Add Other… button, navigate to the AWS<#ServiceName#>.framework files that are listed under Carthage -> build -> iOS and select them. Do not check the Destination: Copy items if needed checkbox when prompted.

8. Under the Build Phases tab in your Target, click the + button on the top left and then select New Run Script Phase. Then setup the build phase as follows. Make sure this phase is below the Embed Frameworks phase.

Shell /bin/sh      
    bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/AWSCore.framework/strip-frameworks.sh"      
  
    Show environment variables in build log: Checked    
    Run script only when installing: Not checked      
    Input Files: Empty  Output Files: Empty

9. Running the following command in your project directory with Carthage will automatically pickup the new changes if any in SDK.

$ carthage update  

10. Add the following imports to the classes that perform user data storage operations:

import AWSCore  
import AWSS3

11. Add the following code to your AppDelegate to establish a run-time connection with AWS Mobile.

import AWSMobileClient  
  
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {  
        // Override point for customization after application launch.  
          
        return AWSMobileClient.sharedInstance().interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)  
  
       // return true  
    }

Let’s see how to upload an image file from device gallery to Amazon  S3 bucket

In XCode,  open the storyboard and in viewcontroller interface, add a button to open the device gallery, an imageview to show the selected image and as well another button to upload that image and set the respective actions and outlets for the buttons and imageview.

Write the following code.

To open the device gallery

@IBAction func selectImageFromGallery(_ sender: UIButton) {  
          
        let imagePicker = UIImagePickerController()  
        imagePicker.delegate = self  
        imagePicker.allowsEditing = false  
        imagePicker.sourceType = .photoLibrary  
        present(imagePicker, animated: true, completion: nil)  
    }

To set the selected image to imageview using the imagepicker delegate method. Extend the image picker and navigation controller delegate to get the image picker delegate method assigned to the viewcontorller.

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {  
        if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {  
            selectedImageView.contentMode = .scaleAspectFit  
            selectedImageView.image = pickedImage  
        }  
          
        dismiss(animated: true, completion: nil)  
    }

And To upload the image to S3 bucket using the AWS Sdk methods.

For upload button Action

@IBAction func uploadImageToS3(_ sender: UIButton) {  
          
        uploadData(image: selectedImageView.image!)  
    }  
}  

func getCurrentMillis()->Int64 {  
        return Int64(Date().timeIntervalSince1970 * 1000)  
    }  

func uploadData(image: UIImage) {  
      //  let img = UIImage(named: "bob.jpg")  
        let data:Data = UIImagePNGRepresentation(image)!  
          
        let expression = AWSS3TransferUtilityUploadExpression()  
        expression.progressBlock = {(task, progress) in  
            DispatchQueue.main.async(execute: {  
                // Do something e.g. Update a progress bar.  
                  
                print("upload in process \(progress)")  
                  
            })  
        }  
          
        var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?  
        completionHandler = { (task, error) -> Void in  
            DispatchQueue.main.async(execute: {  
                // Do something e.g. Alert a user for transfer completion.  
                // On failed uploads, `error` contains the error object.  
                  
                print("upload completed1 \(task.bucket)")  
                print("upload completed2 \(String(describing: task.response))")  
                print("upload completed3 \(task.key)")  
                  
                  
                  
            })  
        }  
          
        let transferUtility = AWSS3TransferUtility.default()  
let currentTime = getCurrentMillis()  
        var imageKey = String(format: "%ld", currentTime)  
        imageKey = imageKey + ".jpg"  

          
        transferUtility.uploadData(data,  
                                   bucket: "BUCKET NAME",  
                                   key: imageKey,  
                                   contentType: "image/jpeg",  
                                   expression: expression,  
                                   completionHandler: completionHandler).continueWith {  
                                    (task) -> AnyObject! in  
                                    if let error = task.error {  
                                        print("Error: \(error.localizedDescription)")  
                                    }  
                                      
                                    if let _ = task.result {  
                                        // Do something with uploadTask.  
                                          
                                        print("something upload completed \(String(describing: task.result.debugDescription))")  
                                          
                                    }  
                                    return nil;  
        }  
    }

In the above method, near “BUCKET NAME” specify the bucket name you had created in AWS and as well let the key be your image name.  The image name should be unique. Else if we use the same image name, it will get replaced in s3 bucket.

So, it’s better we use the current time in milliseconds to be unique. The KEY plays the important role as we can identify the images only with that KEY.

At this point you can run the application and see app showing the interface. Select the image and upload that to S3. Once you get success log, you can go to the S3 in AWS console and check the image there.

Notes:

1. If your image is not appeared in the S3 bucket, then it might be due to some S3 policies issues. You can check the details about policies here. Policies are nothing but the permissions that we have to give to the bucket. So for now let’s go with the default policy which will give the read and write access to the bucket.

{  
    "Version": "2012-10-17",  
    "Statement": [  
        {  
            "Sid": "AddPerm",  
            "Effect": "Allow",  
            "Principal": "*",  
            "Action": "s3:GetObject",  
            "Resource": "arn:aws:s3:::YOUR BUCKET NAME/*"  
        }  
    ]  
}

2. And one important thing you should remember is about the region of S3 bucket. We need to have the S3 bucket in the same region of your account or the region that cognito pool id is generated. (you can check this in the AWS SDK configuration steps that are listed above and in the “awsconfiguration.json” that is generated while setting up the app in AWS mobile hub). It should be mostly “USEAST-1” which is (US-EAST (N.Virginia)).

Once the above configuration is done correctly, re-run the app and upload an image. And now you can see the image in S3 bucket. Incase if you are still not able to see the image please repeat the above steps again and see if anything is wrong. Mainly don’t forget about the S3 bucket policies and region.

Listing the images from S3

Add another Swift class to the Xcode project and set-up the UITableview in that class in storyboard or via programmatically to list the images that are stored in the S3 bucket. Lets name it as “ImagesListView.Swift” for now.

Add a bar button in the ViewController class where we had set-up image upload and set the segue for that barbutton to show this list.

In “ImagesListView.Swift”, lets add the following code to get the images list. Define an array of any object to store the images list and load in tableview and add the following method to get the list of objects from S3 bucket.

To get this method working, we have to update our info.plist file with the following options.

AWS --> CredentialsProvider --> CognitoIdentity --> Default --> PoolId (put your Cognito IdentityPoolID here)  
AWS --> CredentialsProvider --> CognitoIdentity --> Default --> Region (for example: USEast1)  
AWS --> S3TransferUtility --> Default --> Region (for example: USEast1)  
  
<key>AWS</key>  
    <dict>  
        <key>CredentialsProvider</key>  
        <dict>  
            <key>CognitoIdentity</key>  
            <dict>  
                <key>Default</key>  
                <dict>  
                    <key>PoolId</key>  
                    <string>POOL ID FROM AWSCONFIGURATION.json file </string>  
                    <key>Region</key>  
                    <string>REGION</string>  
                </dict>  
            </dict>  
        </dict>  
        <key>S3TransferManager</key>  
        <dict>  
            <key>Default</key>  
            <dict>  
                <key>Region</key>  
                <string> REGION </string>  
            </dict>  
        </dict>  
        <key>S3</key>  
        <dict>  
            <key>Default</key>  
            <dict>  
                <key>Region</key>  
                <string> REGION </string>  
            </dict>  
        </dict>  
    </dict>

Add the following code.

import AWSS3   
  
func listS3Objects(){  
        let credentialProvider = AWSCognitoCredentialsProvider(regionType: <BUCKET REGION>, identityPoolId: <POOL ID listed in aws configuration.json file>)  
        let configuration = AWSServiceConfiguration(region: <REGION>, credentialsProvider: credentialProvider)  
        AWSServiceManager.default().defaultServiceConfiguration = configuration  
          
        AWSS3.register(with: configuration!, forKey: "defaultKey")  
        let s3 = AWSS3.s3(forKey: "defaultKey")  
          
        let listRequest: AWSS3ListObjectsRequest = AWSS3ListObjectsRequest()  
        listRequest.bucket = <BUCKET NAME>  
          
        s3.listObjects(listRequest).continueWith { (task) -> AnyObject? in  
            print("Object result = \(task.result)")  
              
            print("Object contents = \(task.result?.contents)")  
            for object in (task.result?.contents)! {  
                  
                print("Object key = \(object.key!)")  
                self.imagesArray.append(object)  
                //self.tableView.reloadData()  
            }  
              
            DispatchQueue.main.async {  
                self.tableView.reloadData()  
            }  
            //self.tableView.reloadData()  
            return nil  
              
        }  
          
    }

Add the following code in respective tableview delegate & datasource methods

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {  
        return 100  
    }  
      
override func numberOfSections(in tableView: UITableView) -> Int {  
        return 1  
    }  
      
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {  
        return self.imagesArray.count  
          
    }  
      
      
      
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {  
        let cell = tableView.dequeueReusableCell(withIdentifier: "imgcell", for: indexPath)  
        let s3ImageObj = self.imagesArray[indexPath.row]  
          
        let finalUrl = self.s3Url + s3ImageObj.key . // here the S3Url should be hardcoded. The url will be available in the S3 bucket.  
        print("the final url is \(finalUrl)")  
          
        let imageV = cell.viewWithTag(1000) as! UIImageView  
          
        imageV.loadImageUsingCache(withUrl: finalUrl)  
        return cell  
    }

As here we need to load the image from remote url, we need to use Async image loading. Add the following extension at the bottom of the class.

let imageCache = NSCache<NSString, AnyObject>()  
  
extension UIImageView {  
    func loadImageUsingCache(withUrl urlString : String) {  
        let url = URL(string: urlString)  
        //self.image = nil  
          
        // check cached image  
        if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage {  
            self.image = cachedImage  
            return  
        }  
          
        // if not, download image from url  
        URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in  
            if error != nil {  
                print(error!)  
                return  
            }  
              
            DispatchQueue.main.async {  
                if let image = UIImage(data: data!) {  
                    self.image = image  
  
                    imageCache.setObject(image, forKey: urlString as NSString)  
                }  
            }  
              
        }).resume()  
    }  
}

Run the code to see the images that are uploaded to your s3 bucket.

Let’s see how to delete the images from s3 bucket.

In “ImagesListView.Swift”, lets add a bar button to enable the tableview editing and deleting the selected image from the list and as well from S3 bucket. Write the following code for bar button and respective action.

override func viewDidLoad() {  
        super.viewDidLoad()  
          
        let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(enableEdit))  
        self.navigationItem.rightBarButtonItem = editButton  
        self.title = "Images List"  
          
        listS3Objects()  
  
        tableView.dataSource = self  
        tableView.delegate = self  
      
          
    }  
      
    @objc func enableEdit(){  
          
        if(self.tableView.isEditing == true)  
        {  
            self.tableView.isEditing = false  
            self.navigationItem.rightBarButtonItem?.title = "Edit"  
        }  
        else  
        {  
            self.tableView.isEditing = true  
            self.navigationItem.rightBarButtonItem?.title = "Done"  
        }  
    }

Once the “Edit” is clicked, the tableview will be in edit mode with default delete buttons. You can select any image and delete it. So to get that image deleted from list and as well from s3 bucket write the following code.

Tableview Delegate method:

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {  
        if editingStyle == .delete {  
            // Delete the row from the data source  
            let s3ImageObj = self.imagesArray[indexPath.row]  
            self.deleteS3Object(imageKey: s3ImageObj.key)  
            self.imagesArray.remove(at: indexPath.row)  
            tableView.deleteRows(at: [indexPath], with: .fade)  
        } else if editingStyle == .insert {  
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view  
        }  
    }

And Delete image from bucket:

func deleteS3Object(imageKey : String){  
         
       let credentialProvider = AWSCognitoCredentialsProvider(regionType: <REGION>, identityPoolId: <POOL ID listed in aws configuration.json file>)  
       let configuration = AWSServiceConfiguration(region: <REGION>, credentialsProvider: credentialProvider)  
       AWSServiceManager.default().defaultServiceConfiguration = configuration  
         
       AWSS3.register(with: configuration!, forKey: "defaultKey")  
       let s3 = AWSS3.s3(forKey: "defaultKey")  
       let deleteObjectRequest = AWSS3DeleteObjectRequest()  
       deleteObjectRequest?.bucket = "<BUCKET NAME>" // bucket name  
       deleteObjectRequest?.key = imageKey // File name  
       s3.deleteObject(deleteObjectRequest!).continueWith { (task:AWSTask) -> AnyObject? in  
           if let error = task.error {  
               print("Error occurred: \(error)")  
               return nil  
           }  
           print("Bucket deleted successfully.")  
           return nil  
       }  
         
   }

You can run the app and if everything is done correctly as listed above, then you can upload the images, List the images and Remove the images to and from your AWS S3 bucket.

The final output:

Using Realm Mobile Database

KalyanKumar Parise
A lead iOS developer committed to collaborative problem solving, sophisticated design, and the creation of quality products for mobile devices powered by Apple’s iOS operating system. Interested to learn and share knowledge about different development platforms. Loves to code both as a hobby and profession. When not coding, will try to explore different subjects over internet and be a loving husband.