Outdated Post

iOS 13 now has built in support for document scanning and OCR!
Check out my new post, "Scanning documents with Vision and VisionKit on iOS 13", to learn more.
Animated video showing someone using WeScan to scan a book cover.

Do you need to allow your users to scan documents or other papers? WeScan by WeTransfer is a great, fast iOS framework that allows you to easily add a document scanner to your app, with automatic document detection, PDF support, and a whole lot more.

WeScan is free and available under the MIT License, so it's the perfect choice if you don't want to waste time figuring out rectangle detection and perspective correction.

If you'd prefer to jump straight to a demo app, feel free to click here to go to the Conclusion. There, you'll find an Xcode Project containing all of the examples from this tutorial.

Note: While I've contributed code and major features to WeScan, I didn't receive anything in return for this blog post - I'm just sharing a nice framework :)

Installation

If you don't have an existing Xcode project to use, go ahead and create a new project with the "Single View App" template, which works well for our needs.

Now that you have your existing or new project, it's time to install WeScan, so it can be used in the project.

The best way to do this is via CocoaPods - if you don't already have CocoaPods installed, you should click the link above to read the official instructions and install it. Alternatively, if you'd prefer to install WeScan another way, feel free to skip this section and go on to Setup.

With CocoaPods installed, go ahead and create a file named Podfile in the root of your project directory, containing the following Ruby code (don't worry - we'll get on to the Swift soon!):

platform :ios, '12.1'
use_frameworks!

target 'Your_Target_Name' do
  pod 'WeScan', '>= 0.9'
end

This configuration file tells CocoaPods the 'pod' (WeScan) we want to install and which target to add it to (for example, your existing app). You can change the iOS version to the minimum iOS version your project supports, which as of the time of this writing is iOS 12.1 by default.

Now that you've setup the configuration file, go to Terminal and navigate to your project folder (cd /full/path/to/your/project/folder), then run pod install, which installs the pods.

CocoaPods has automatically created a new 'workspace' for you to use, which embeds WeScan into your project. Go ahead and close the Xcode project and open the .xcworkspace file, which you should use from now on.

Setup

With WeScan now automatically embedded into your app, there's just a quick change we need to make. To protect users, iOS will show a permission prompt, which you've probably seen countless times, before allowing apps to access the camera.

In the prompt, it shows a description, provided by the app, of what the app needs the permission for, so the user can choose whether or not to allow. If you don't provide this description, your app will crash when it attempts to access a protected resource such as the camera.

To fix this, just add a key named NSCameraUsageDescription to your Info.plist, and, for the value, put whatever is appropriate for your app. For example, "This app uses the Camera to allow you to scan documents".

Implementing WeScan

WeScan is now ready to use, and your app will no longer crash due to the missing description!

WeScan was made to be easy to implement and use with just a few lines of code. Go to your view controller and add the following code:

Note: It's recommended to add this to a button handler or similar, that way it'll be shown after a user taps a button rather than instantly!

let scannerViewController = ImageScannerController()
scannerViewController.imageScannerDelegate = self
present(scannerViewController, animated: true)

The code you just added creates a new ImageScannerController (which is WeScan's custom navigation controller) and sets its delegate to the current view controller before presenting it.

However, this code won't work yet, because your view controller doesn't conform to the ImageScannerControllerDelegate, which is required to use WeScan (if you don't implement it, the view controller will have no way of giving you the image or failing).

Conforming to the delegate

To implement it, you need to first add the delegate to your view controller, which may look something like this, depending on your view controller's type and name:

class ViewController: UIViewController, ImageScannerControllerDelegate {

Then, just add the methods from the delegate which will allow you to handle when it fails or succeeds. There are 3 methods you need to implement, which we'll go through in detail.

func imageScannerController(_ scanner: ImageScannerController, didFailWithError error: Error) {
    // The image scanner failed with an error.
    // For now, we'll print it, but you should handle it correctly in your app.
    print(error)

    // You are responsible for dismissing the controller.
    scanner.dismiss(animated: true)
}

The first method is called when the controller failed due to an error of some kind. This is typically something like the camera failing or the user denying permission to use the camera. In addition to the scanner, so you can dismiss the controller, it will also give you an error, which you should handle and, possibly, display to the user! Again: you are responsible for dismissing the controller!

func imageScannerController(_ scanner: ImageScannerController, didFinishScanningWithResults results: ImageScannerResults) {
    // The image scanner finished scanning.
    // The results contain the original, cropped, and enhanced images as well as other properties.

    // You are responsible for dismissing the controller.
    scanner.dismiss(animated: true)
}

The second method is the most important, and is called most of the time, when the scan succeeds. This time, it'll give you a results object in addition to the scanner, containing the cropped image as well as the enhanced image (if available) and other properties.

func imageScannerControllerDidCancel(_ scanner: ImageScannerController) {
    // The user clicked the Cancel button on the image scanner controller.

    // You are responsible for dismissing the controller.
    scanner.dismiss(animated: true)
}

Finally, the last method is called when the user taps the Cancel button. As this is a user-initiated action, you should not display an error message to the user.

As a reminder, in all 3 cases, you need to dismiss the controller!

Conclusion

You now have a working implementation of WeScan, and users can now scan documents within your app! If you find an issue or have a feature request for WeScan, feel free to head on over to its Github repo and create an issue.

WeScan's repo already includes an example project, but I've also created a demo app which lets you see how I implemented the code from this tutorial:

Download Materials

I hope you enjoyed this tutorial on scanning documents! If you have any questions or feedback, feel free to send them or email me: [email protected]. Thanks for reading 🥳