How to Merge Images into PDF on iOS using Swift

Sometimes, we may need to merge multiple images into a single PDF file. For example, we scan the front side and the back side of an ID card and want to store them in a single PDF file.

In this article, we are going to talk about how to build an iOS app using Swift to merge images into a PDF file with the help of Dynamsoft Document Normalizer.

New Project

Open Xcode and create a new app project.

Add Dependencies

Next, add Dynamsoft Document Normalizer and its dependencies via cocoapods.

  1. Init a pod project.

    pod init
  2. Add the following to Podfile.

    target 'PDFCreator' do
      pod 'DynamsoftCaptureVisionRouter','2.2.30'
      pod 'DynamsoftCore','3.2.30'
      pod 'DynamsoftDocumentNormalizer','2.2.11'
      pod 'DynamsoftImageProcessing','2.2.30'
      pod 'DynamsoftLicense','3.2.20'
      pod 'DynamsoftUtility','1.2.20'
  3. Install the pods.

    pod install

UI Design

Open Main.storyboard and add the following controls:


It contains a button to select images from the gallery, a switch to enable auto cropping of the document in the image and a picker view to select which color mode to use for the images in the PDF.

The picker view is configured with the following code:

@IBOutlet weak var colorModeUIPickerView: UIPickerView!
func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 1
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return 3

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int,
                    forComponent component: Int) -> String? {
    if row == 0 {
        return "Black & White"
    }else if row == 1 {
        return "Grayscale"
        return "Color"

override func viewDidLoad() {
    // Do any additional setup after loading the view.
    colorModeUIPickerView.dataSource = self
    colorModeUIPickerView.delegate = self

Here, we use PHPickerViewController to pick multiple images.

@IBAction func selectImagesUIButton_clicked(_ sender: Any) {

    var configuration = PHPickerConfiguration(photoLibrary: .shared())
    //0 - unlimited 1 - default
    configuration.selectionLimit = 0
    configuration.filter = .images
    let pickerViewController = PHPickerViewController(configuration: configuration)
    pickerViewController.delegate = self
    present(pickerViewController, animated: true)

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    picker.dismiss(animated: true)

We can convert the results into an array of UIImage for further use.

DispatchQueue.main.async {
    var images:[UIImage] = []
    var processed = 0
    let size = results.count
    for item in results {
        if (item.itemProvider.canLoadObject(ofClass: UIImage.self)) {
            item.itemProvider.loadObject(ofClass: UIImage.self) { image , error  in
                if let error{
                if let selectedImage = image as? UIImage{
                    processed = processed + 1

Use Dynamsoft Document Normalizer to Merge Images into PDF

  1. Initialize the license for Dynamsoft Document Normalizer in AppDelegate.swift . You can apply for a license here.

    import DynamsoftLicense
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let oneDayTrial = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="
        LicenseManager.initLicense(oneDayTrial, verificationDelegate: self)
        return true
    func onLicenseVerified(_ isSuccess: Bool, error: (any Error)?) {
        if isSuccess == false {
            print("license invalid")
            print("license valid")
  2. Create an instance of Capture Vision Router to call Dynamsoft Document Normalizer.

    import DynamsoftCore
    import DynamsoftCaptureVisionRouter
    let cvr:CaptureVisionRouter = CaptureVisionRouter()
  3. Process images and save them to a PDF file. It uses Capture Vision Router to process the images, uses Image Manager to save them to a PDF file and then share it so that we can save the PDF file to Files.

    If enableAutoCroppingUISwitch is on, it will detect the document boundaries and get the cropped image. Otherwise, process the whole image.

    It also updates the color mode. Converting an image to black and white can clean the background and save the file’s size while converting to grayscale has a balance of details and size.

    import DynamsoftDocumentNormalizer
    import DynamsoftUtility
    func mergeImagesIntoPDF(images:[UIImage]) {
        var enableAutoCropping = false
        var selectedColorModeIndex = 0
        DispatchQueue.main.sync {
            statusLabel.text = "Processing..."
            if enableAutoCroppingUISwitch.isOn {
                enableAutoCropping = true
            selectedColorModeIndex = colorModeUIPickerView.selectedRow(inComponent: 0)
        let templateName:String;
        if enableAutoCropping {
            templateName = PresetTemplate.detectAndNormalizeDocument.rawValue
            templateName = PresetTemplate.normalizeDocument.rawValue
        var settings = try? cvr.getSimplifiedSettings(templateName)
        if selectedColorModeIndex == 0 {
            settings?.documentSettings?.colourMode = ImageColourMode.binary
        }else if selectedColorModeIndex == 1 {
            settings?.documentSettings?.colourMode = ImageColourMode.grayscale
            settings?.documentSettings?.colourMode = ImageColourMode.colour
        if enableAutoCropping == false {
            settings?.roi = Quadrilateral(pointArray: [CGPoint(x:0,y:0),CGPoint(x:100,y:0),CGPoint(x:100,y:100),CGPoint(x:0,y:100)])
            settings?.roiMeasuredInPercentage = true
        try? cvr.updateSettings(templateName, settings: settings!)
        let imageManager = ImageManager()
        let url = FileManager.default.temporaryDirectory
        for image in images {
            let capturedResult:CapturedResult = cvr.captureFromImage(image, templateName: templateName)
            let items = capturedResult.items ?? []
            for item in items {
                if item.type == CapturedResultItemType.normalizedImage {
                    let image:NormalizedImageResultItem = item as! NormalizedImageResultItem
                    try? imageManager.saveToFile(image.imageData!, path: url.path, overWrite: true)
        DispatchQueue.main.async {
            self.statusLabel.text = ""
            let objectsToShare = [url]
            let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
            self.present(activityVC, animated: true, completion: nil)

Screenshot of converted files:


Source Code

You can check out the source code to have a try: