Skip to the content

iOS SDK

The Swift package for iPhone and iPad apps, and what is in it today.

On this page

ReceVizCapture is the Swift package for iPhone apps: a scanner that finds the document in the live camera, tells the person what to fix, takes the picture itself when it is sharp, lit and still, straightens and cleans it up, uploads that one photograph and returns the extraction; and a client for the API.

Status#

In development: not yet compiled or published

The package has been written but has never been built, run on a simulator or used on a device. Expect changes before it is released, and do not ship it in an app yet. It is not published to a package registry.

What exists: the REST client (credentials, token renewal, polling, idempotency, typed errors), the models, the capture rules, the photo pipeline and the scanner screens. Not there yet: text read on the device, a review step before upload, localisation beyond English, iPad and landscape.

Install#

Swift Package Manager, from a local path while the package is unpublished: in Xcode, File, Add Package Dependencies, Add Local, and choose sdk/ios/ReceVizCapture, or in Package.swift:

swift
dependencies: [
    .package(path: "../AccountHouse/sdk/ios/ReceVizCapture"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "ReceVizCapture", package: "ReceVizCapture"),
    ]),
]

iOS 16.4 or later, Swift 5.9 (Xcode 15 or later), no third-party dependencies. The scanner opens the camera, so the app must say why in its Info.plist; without it, the scanner throws ReceVizError.configuration instead of opening the camera.

xml
<key>NSCameraUsageDescription</key>
<string>The camera is used to scan receipts and documents.</string>

“Choose a photo instead” uses the system photo picker, which needs no photo library permission.

Scan and extract#

swift
import ReceVizCapture

let client = try ReceVizClient(configuration: .init(credential: .tokenProvider { try await MyBackend.fetchReceVizToken() }))
let scanner = ReceVizScanner(client: client, documentType: "payment_receipt", mode: .verified)
let result = try await scanner.present(from: self)
print(result.extraction.data["amount"])

result is a ScanResult: the extraction, the jpegData that was uploaded (cropped, straightened, cleaned, with no metadata; the SDK keeps no copy) and whether the scanner took the picture itself (autoCaptured). present(from:) throws ReceVizError.cancelled when the person closes the scanner, and cancelling the calling task closes it. A delegate style is available too:

swift
scanner.delegate = self          // ReceVizScannerDelegate
scanner.start(from: self)        // didFinishWith / didFailWith / scannerDidCancel
autoCaptureBool

Take the picture at the best moment. On by default; the shutter always works.

enhanceBool

Clean up the photo before upload. On by default.

allowPhotoLibraryBool

Offer a photo from the library. On by default.

schemaVersionInt

A published version of the document type.

extractionOptions[String: JSONValue]

The request's options, e.g. ["date_order": "DMY"].

metadata[String: JSONValue]

Your own keys, returned on the extraction.

waitTimeoutTimeInterval

How long to wait for a queued extraction.

nounString?

What guidance calls the document. By default the scanner works it out from the document type (“Point at an invoice”), then uses the type's own capture.noun once the client has read it.

Credentials#

The SDK refuses a secret key wherever it appears: in the configuration, and in whatever a token provider returns, before anything is sent.

iOS SDK credentials
CredentialWhenHow
.tokenProvider { … }Recommended: your app has a backend.Your backend mints a client token (POST /v1/client-tokens). The SDK caches it, asks again shortly before it expires, and renews it and retries once on 401 token_expired.
.publishableKey("rv_pk_live_…")No backend of your own.Exchanged for a client token at POST /v1/client-sessions, sending the app's bundle identifier in X-ReceViz-Bundle-Id. Restrict the key to that bundle identifier in the console.
.clientToken("rv_ct_…")Tests, one-off scans.Used as it is; cannot be renewed.

The base URL defaults to the hosted API (ReceVizConfiguration.defaultBaseURL); pass baseURL: for another deployment. It must be https, except for localhost. See client tokens for your backend's half.

Use the client directly#

swift
let client = try ReceVizClient(configuration: .init(credential: .tokenProvider(fetchToken)))

// A photo or a PDF you already have.
let extraction = try await client.extract(
    image: jpegData,
    request: ExtractionRequest(
        documentType: "invoice",
        mode: .standard,
        options: ["tiling": "auto", "date_order": "DMY"],
        metadata: ["order_id": "A-17"],
        idempotencyKey: UUID().uuidString   // makes a retry safe
    )
)
let pdfResult = try await client.extract(pdf: pdfData, request: ExtractionRequest(documentType: "invoice", asynchronous: true))

// Everything else.
let me = try await client.me()                       // application, mode, scopes
let types = try await client.documentTypes()
let later = try await client.extraction(id: "rv_req_…")
let done = try await client.waitForExtraction(id: "rv_req_…", timeout: 120)
  • extract returns a finished extraction. When ReceViz queues one, the client polls every second at first, backing off to eight, and honours Retry-After; set waitForCompletion: false to get the queued extraction back at once.
  • A failed extraction is thrown as ReceVizError.processing; running out of wait time is .processing with the code wait_timeout, and the extraction carries on.
  • Timeouts: 90 seconds without progress for an extraction upload, 20 for anything else. Every request sends X-ReceViz-SDK: ios/0.1.0.

Errors are cases of ReceVizError: authentication, permission, invalidRequest, rateLimited, processing, server, network, cancelled and configuration, with the API's code and request id where there is one.

How the scanner decides#

The camera runs at 30 frames a second for the preview; about 8 frames a second are analysed, each reduced to a small grid of brightness and edge energy, with Apple Vision finding the document's outline on the device. The rules that decide are a port of the web scanner's engine:

  • It takes the picture once, at a peak: the frame is capturable (a document, lit, large enough, not cut off, not tilted, no glare, sharp), still for 300 ms, at 85% or more of the best frame of the last two seconds, never in the first 400 ms, with 1.2 s between attempts. After three failed automatic attempts it stops and asks for the shutter.
  • Guidance names one problem at a time and is announced to VoiceOver.
  • The photograph is cropped to the outline, straightened, cleaned up and kept at most 2,400 px on its long side, or 3,264 px for a long receipt, so ReceViz's Adaptive Document Tiling can read its small print.
  • The upload carries an Idempotency-Key, so trying again never creates a second extraction.

Privacy#

  • Camera frames never leave the device. No frame, grid or outline is stored or sent.
  • Only the final photograph is uploaded, once, over https. It is re-encoded from its pixels, so it carries no EXIF, location or device metadata.
  • Nothing is written to disk by the SDK.
  • The bundle identifier is sent only when a publishable key opens a session.
  • Quality feedback carries verdicts only; the type has nowhere to put a value or a line of text.