How to upload single and multiple images in iOS Swift, PHP and MySQL

When developing iOS applications, one of the most common features in almost all apps is to upload images. It can be in the form of a profile picture, or payment receipt or anything. Swift provides a built-in method to allow developers to add functionality where users can select an image from their photo library. However, to upload multiple images, there is no built-in method in Swift at the time of writing this. But we have external libraries that allow users to pick multiple images. When it comes to saving the image in the database, there are again multiple choices. You can either convert the image in an encoded base64 string or you can send an image entirely as multipart. The second choice is not very often used because it has some drawbacks, the major one of them is that the image with very high quality and large size are unable to transfer. So we will choose the first method i.e. to convert an image into an encoded base64 string.

1. Single image upload

In this method, no external library has been used. All the functions required to select single image from gallery are already provided by Swift. We just need to implement 2 delegates UIImagePickerControllerDelegate and UINavigationControllerDelegate to our view controller that will be responsible to open the screen to gallery and also to return the selected image. We have created a simple button which when clicked will open the UIImagePickerController in current view controller. imagePickerController this is a delegate function present in UIImagePickerControllerDelegate that will return the selected image from photo library. We are displaying that image in our UIImageView for confirmation, and also to send to the server. Because we can convert an image to base64 string using its UIImageView object.

Then we are creating a button which when clicked will convert the image to base64 string and send the request to the server. For sending an HTTP request, you also have many choices, for example, you can use the Alamofire library. But to keep things simple, we are using Swift’s built-in NSURLConnection class. Before sending the request we are also displaying a loading dialog and when the response is received from the server, we are simply displaying an alert that the image has been uploaded.

Swift iOS

import UIKit

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    private var image: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        let btnSelectImage: UIButton = UIButton(frame: CGRect(x: 30, y: 100, width: view.frame.size.width - 60, height: 30))
        btnSelectImage.setTitle("Select image", for: .normal)
        btnSelectImage.setTitleColor(UIColor.systemBlue, for: .normal)
        btnSelectImage.contentHorizontalAlignment = .center
        btnSelectImage.addTarget(self, action: #selector(selectImage), for: .touchUpInside)
        view.addSubview(btnSelectImage)
        
        image = UIImageView(frame: CGRect(x: 30, y: btnSelectImage.frame.origin.y + btnSelectImage.frame.size.height + 30, width: 300, height: 300))
        image.clipsToBounds = true
        image.contentMode = .scaleAspectFill
        view.addSubview(image)
        
        let btnUpload: UIButton = UIButton(frame: CGRect(x: 30, y: image.frame.origin.y + image.frame.size.height + 30, width: view.frame.size.width - 60, height: 40))
        btnUpload.setTitle("Upload to server", for: .normal)
        btnUpload.backgroundColor = UIColor.systemGreen
        btnUpload.setTitleColor(UIColor.white, for: .normal)
        btnUpload.addTarget(self, action: #selector(uploadToServer), for: .touchUpInside)
        btnUpload.layer.cornerRadius = 5
        view.addSubview(btnUpload)
    }
    
    @objc private func uploadToServer(sender: UITapGestureRecognizer) {
        let imageData: Data = image.image!.pngData()!
        let imageStr: String = imageData.base64EncodedString()

        let alert = UIAlertController(title: "Loading", message: "Please wait...", preferredStyle: .alert)
        present(alert, animated: true, completion: nil)

        let urlString: String = "imageStr=" + imageStr
        
        var request: URLRequest = URLRequest(url: URL(string: "http://172.20.10.2:8888/tutorials/single-multiple-image-upload/index.php")!)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = urlString.data(using: .utf8)

        NSURLConnection.sendAsynchronousRequest(request, queue: .main, completionHandler: { (request, data, error) in

            guard let data = data else {
                return
            }

            let responseString: String = String(data: data, encoding: .utf8)!
            print("my_log = " + responseString)

            alert.dismiss(animated: true, completion: {

                let messageAlert = UIAlertController(title: "Success", message: responseString, preferredStyle: .alert)
                messageAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
                    //
                }))

                self.present(messageAlert, animated: true, completion: nil)
            })
        })
    }
    
    @objc private func selectImage(sender: UITapGestureRecognizer) {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = self as! UIImagePickerControllerDelegate & UINavigationControllerDelegate
        imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
        imagePicker.allowsEditing = false
        present(imagePicker, animated: true, completion: nil)
    }
    
    @objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        let chosenImage = info[UIImagePickerController.InfoKey.originalImage.rawValue] as! UIImage
        image.image = chosenImage
        dismiss(animated: true, completion: nil)
    }

}

PHP & MySQL

Make sure to change the server path with yours. If you are working on localhost, you can get your IP address by command prompt or terminal and running the command: ifconfig. In our PHP file, we are first receiving base64 string in a variable and then creating a path where the image will be stored. We need to create a folder named “images” in our server’s root folder. Then we need to decode that base64 string into actual image file, so remove the base64 data part from a string, replace all empty spaces with + sign, and then decoding the base64 string, and finally saving the file in the images folder. We also need to save that file’s reference in our database, so we are simply making a connection with MySQL database and running an INSERT query to save the path of the image. Then we are sending the response as a string back to the iOS app. Make sure to change the database credentials as well as per your server.

<?php

$picture = $_POST["imageStr"];
$folder_path = "images/" . time() . ".png";

$data = str_replace('data:image/png;base64,', '', $picture);
$data = str_replace(' ', '+', $data);
$data = base64_decode($data);
file_put_contents($folder_path, $data);

$conn = mysqli_connect("localhost:8889", "root", "root", "tutorials");

$sql = "INSERT INTO images (path) VALUES ('$folder_path')";
mysqli_query($conn, $sql);

echo "Image has been uploaded";

?>

2. Multiple image upload

As discussed earlier, we need to use an external library to implement multiple image features in our iOS app. So we are going to use a library called BSImagePicker. It will be installed in our project using Cocoa pods, first, you need to initialize it. Open command prompt or terminal in your project’s root folder and run the following command:

pod init

This will create a Podfile (without any extension) in your project’s root folder. Open that file and include the library like the following, your project name could be different of course:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'MultipleImageUpload' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MultipleImageUpload
  pod "BSImagePicker", "~> 3.1"

end

Now run the command

pod update

to install the library. Once installed, close the project if already opened and now run the project using XCode but with the file ending with “.xcworkspace” NOT “.xcodeproj”. Now accessing the library requires user permission so open your “info.plist” file as source code and paste the following lines at the end of dict tag:

<key>NSPhotoLibraryUsageDescription</key>
<string>Why you want to access photo library</string>

Now open your view controller file and first import 2 files named BSImagePicker and Photos. When user select multiple images we can also show them in a collection view for a better user experience, for that purpose we need to implement 2 delegates, UICollectionViewDelegate and UICollectionViewDataSource. Creating a global variable for collection view so it can be accessible throughout the view controller class, colleection view has multiple cells and each cell has its own identifer and a class. For cell, we have created a class named ImageCell and it has only an image view because that is all we needed here.

Swift iOS

import UIKit

import BSImagePicker
import Photos

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
    
    private var collectionView: UICollectionView!
    private let identifier: String = "identifier"
    private var selectedImages: [UIImage] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        let btnSelectImage: UIButton = UIButton(frame: CGRect(x: 30, y: 100, width: view.frame.size.width - 60, height: 30))
        btnSelectImage.setTitle("Select images", for: .normal)
        btnSelectImage.setTitleColor(UIColor.systemBlue, for: .normal)
        btnSelectImage.contentHorizontalAlignment = .center
        btnSelectImage.addTarget(self, action: #selector(selectImages), for: .touchUpInside)
        view.addSubview(btnSelectImage)
        
        let flowLayout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
        flowLayout.minimumLineSpacing = 10
        flowLayout.scrollDirection = .horizontal
        flowLayout.minimumInteritemSpacing = 10
        flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
        flowLayout.itemSize = CGSize(width: 300, height: 300)

        collectionView = UICollectionView(frame: CGRect(x: 0, y: btnSelectImage.frame.origin.y + btnSelectImage.frame.size.height + 30, width: view.frame.size.width, height: 300), collectionViewLayout: flowLayout)
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.backgroundColor = UIColor.clear.withAlphaComponent(0)
        collectionView.alwaysBounceHorizontal = true
        collectionView.register(ImageCell.self, forCellWithReuseIdentifier: identifier)
        view.addSubview(collectionView)
        
        let btnUpload: UIButton = UIButton(frame: CGRect(x: 30, y: collectionView.frame.origin.y + collectionView.frame.size.height + 30, width: view.frame.size.width - 60, height: 40))
        btnUpload.setTitle("Upload to server", for: .normal)
        btnUpload.backgroundColor = UIColor.systemGreen
        btnUpload.setTitleColor(UIColor.white, for: .normal)
        btnUpload.addTarget(self, action: #selector(uploadToServer), for: .touchUpInside)
        btnUpload.layer.cornerRadius = 5
        view.addSubview(btnUpload)
    }
    
    @objc private func uploadToServer(sender: UITapGestureRecognizer) {
        let alert = UIAlertController(title: "Loading", message: "Please wait...", preferredStyle: .alert)
        present(alert, animated: true, completion: nil)

        var imageStr: [String] = []
        for a in 0..<self.selectedImages.count {
            let imageData: Data = self.selectedImages[a].jpegData(compressionQuality: 0.1)!
            imageStr.append(imageData.base64EncodedString())
        }

        guard let data = try? JSONSerialization.data(withJSONObject: imageStr, options: []) else {
            return
        }

        let jsonImageString: String = String(data: data, encoding: String.Encoding.utf8) ?? ""
        let urlString: String = "imageStr=" + jsonImageString

        var request: URLRequest = URLRequest(url: URL(string: "http://172.20.10.2:8888/tutorials/single-multiple-image-upload/multiple.php")!)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        request.httpBody = urlString.data(using: .utf8)

        NSURLConnection.sendAsynchronousRequest(request, queue: .main, completionHandler: { (request, data, error) in

            guard let data = data else {
                return
            }

            let responseString: String = String(data: data, encoding: .utf8)!
            print("my_log = " + responseString)

            alert.dismiss(animated: true, completion: {
                let messageAlert = UIAlertController(title: "Success", message: responseString, preferredStyle: .alert)
                messageAlert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
                    //
                }))
                self.present(messageAlert, animated: true, completion: nil)
            })
        })
    }
    
    @objc private func selectImages(sender: UITapGestureRecognizer) {
        let imagePicker = ImagePickerController()
        presentImagePicker(imagePicker, select: { (asset) in
        }, deselect: { (asset) in
            
        }, cancel: { (assets) in
            
        }, finish: { (assets) in
            
            self.selectedImages = []
            let options: PHImageRequestOptions = PHImageRequestOptions()
            options.deliveryMode = .highQualityFormat

            for asset in assets {
                PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: options) { (image, info) in
                    self.selectedImages.append(image!)
                    self.collectionView.reloadData()
                }
            }
        })
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return selectedImages.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let data: UIImage = selectedImages[indexPath.item]
        let cell: ImageCell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! ImageCell
        cell.image.image = data
        return cell
    }


}

class ImageCell: UICollectionViewCell {
    var image: UIImageView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupViews()
    }
    
    private func setupViews() {
        image = UIImageView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
        image.clipsToBounds = true
        image.contentMode = .scaleAspectFill
        addSubview(image)
    }
}

PHP & MySQL

As we are sending multiple images in base64 string so we have to loop through all and save them one-by-one in the server as well as in MySQL database. The code for this is pretty much same as the previous file.

<?php

$conn = mysqli_connect("localhost:8889", "root", "root", "tutorials");
$imageStr = json_decode($_POST["imageStr"]);

for ($a = 0; $a < count($imageStr); $a++)
{
	$folder_path = "images/" . $a . "-" . time() . ".png";

	$picture = $imageStr[$a];
	$data = str_replace('data:image/png;base64,', '', $picture);
	$data = str_replace(' ', '+', $data);
	$data = base64_decode($data);

	file_put_contents($folder_path, $data);

	$sql = "INSERT INTO images (path) VALUES ('$folder_path')";
	mysqli_query($conn, $sql);
}

echo "Image has been uploaded";

?>

Leave a Reply

Please disable your adblocker or whitelist this site!