Reader SDK Documents

IFU008(03) Reader SDK iOS Integration Guide


Novarum DX Ltd
Instructions for Use / Guides

Document ID: IFU008 
Revision: 03 
Released: Aug 5, 2026 

1. Overview

The Novarum Reader SDK for iOS enables iOS applications to capture lateral flow test images using the device camera, analyse captured frames, perform PMF fitting, report strip status, and return structured results through completion, abort, and frame-captured callbacks.

This guide describes the supported customer integration path for the current v4.x Swift Package Manager based SDK. It is intended to remain mostly version agnostic; version-specific fixes, verification evidence, and detailed release differences are maintained in the versioned release notes.

Swift Package Manager is the supported customer integration path. Add the novarumdx.ndx-imaging-swift package and link the Imaging product rather than manually embedding XCFrameworks.

The SDK supports UIKit and SwiftUI applications running on iOS 14.2 or later.

2. Project setup

2.1. Minimum requirements

Item

Minimum

iOS version

14.2

Swift tools version

5.9

Xcode

Xcode version compatible with Swift 5.9 and iOS 14.2 deployment targets

Swift package product

Imaging

2.2. Registry authentication

The SDK is distributed as a private Swift package in NovarumDX AWS CodeArtifact. Authenticate SwiftPM before adding or resolving the package.

Bash
aws codeartifact login \
  --tool swift \
  --domain novarumdx \
  --namespace novarumdx \
  --region eu-west-2 \
  --domain-owner 945969778369 \
  --repository novarum.client.swift \
  --profile <your-aws-profile>

For customer-facing integrations, use novarum.client.swift when the release has been promoted for client access. Internal development and pre-promotion validation may use novarum.swift. If your credentials were issued for a different repository, confirm the expected repository with Novarum support.

The login command configures the novarumdx SwiftPM registry namespace and stores a temporary CodeArtifact token. Re-run the command when the token expires or when Xcode/SwiftPM starts returning authentication errors.

2.3. Add the SDK dependency

In Xcode, add a package dependency using the Swift package identity:

Swift
.package(
  id: "novarumdx.ndx-imaging-swift",
  from: "4.1.1"
)

Then link your app target to the Imaging product:

Swift
.product(
  name: "Imaging",
  package: "novarumdx.ndx-imaging-swift"
)

When integrated through SwiftPM, the Imaging product resolves its native dependency on novarumdx.ndx-imaging-core and ndx_imaging_ios transitively. Customer apps should not add an OpenCV2 pod or a separate native core package unless Novarum provides a special integration path.

2.4. Platform configuration

The SDK requires camera access for scanning. Add NSCameraUsageDescription to the app target Info.plist:

XML
<key>NSCameraUsageDescription</key>
<string>We will use this for scans</string>

The reader will request camera permission if it has not already been granted. For best user experience, request and explain camera permission before presenting the reader view.

3. Using the reader view

The reader view displays the live camera feed, applies the analyser configuration, tracks PMF fitting progress, reports strip status, and returns result data to the host application.

3.1. Loading the analyser configuration

Load the analyser configuration JSON as Data. The current public PMFLoader API exposes data-based decoding methods:

Swift
let configURL = Bundle.main.url(forResource: "reader-config", withExtension: "json")!
let configData = try Data(contentsOf: configURL)
let analyserConfiguration = try PMFLoader.loadAnalyserConfiguration(from: configData)

Additional current loader methods are:

Swift
let testConfiguration = try PMFLoader.loadTestConfiguration(from: data)
let testConfigurations = try PMFLoader.loadTestConfigurations(from: data)

Do not use older examples such as PMFLoader.loadAnalyserConfiguration(json: ...) or PMFLoader.loadAnalyserConfiguration(jsonString: ...). Those methods are not present in the current public source.

3.2. SwiftUI integration

Use ReaderViewWrapper to embed the reader in SwiftUI. The current initialiser requires an AnalyserConfiguration, optional PreviewMode, three callbacks, and an abort binding.

Swift
import SwiftUI
import Imaging

struct ReaderScreen: View {
  let analyserConfiguration: AnalyserConfiguration

  @State private var progress: Float = 0
  @State private var isAborted = false
  @State private var lastLux: Float = -1
  @State private var lastOrientation: [Double] = []
  @State private var lastStripStatuses: [StripStatus] = []
  @State private var analysisModel: AnalysisModel?

  var body: some View {
    ReaderViewWrapper(
      analyserConfiguration: analyserConfiguration,
      previewMode: .fill,
      onFrameCaptured: { callback in
        progress = callback.progress
        lastLux = callback.lux
        lastOrientation = callback.orientation
        lastStripStatuses = callback.stripStatuses
      },
      onComplete: { model in
        analysisModel = model
      },
      onAbort: { partialModel in
        analysisModel = partialModel
      },
      isAborted: $isAborted
    )
  }
}

3.3. UIKit integration

UIKit apps can use ReaderViewController directly and implement ReaderViewControllerDelegate.

Swift
import UIKit
import Imaging

final class ScanHostViewController: UIViewController, ReaderViewControllerDelegate {
  private let analyserConfiguration: AnalyserConfiguration
  private var readerViewController: ReaderViewController?

  init(analyserConfiguration: AnalyserConfiguration) {
    self.analyserConfiguration = analyserConfiguration
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    let reader = ReaderViewController(
      readerParams: analyserConfiguration,
      previewMode: .fill
    )
    reader.delegate = self

    addChild(reader)
    view.addSubview(reader.view)
    reader.view.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      reader.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      reader.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      reader.view.topAnchor.constraint(equalTo: view.topAnchor),
      reader.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
    reader.didMove(toParent: self)

    readerViewController = reader
  }

  func didCaptureFrame(frameCaptured: FrameCapturedCallback) {
    // Update progress UI, lux/orientation indicators, or strip-status UI.
  }

  func didComplete(analysisModel: AnalysisModel) {
    // Handle completed results.
  }

  func didAbort(analysisModel: AnalysisModel) {
    // Handle partial results after abort.
  }

  func abortScan() {
    readerViewController?.abort()
  }
}

3.4. Parameter overview

Parameter

Type

Required

Description

analyserConfiguration

AnalyserConfiguration

Yes

Configuration decoded from the analyser JSON. Contains testConfiguration, pointModel, classifier, and optional pathData.

previewMode

PreviewMode

No

Controls camera preview scaling. .fill is the default and may crop; .fit preserves the full camera preview inside the available space.

onFrameCaptured

(FrameCapturedCallback) -> Void

Yes

Called on processed frames with progress, PMF result, strip statuses, orientation, and lux.

onComplete

(AnalysisModel) -> Void

Yes

Called when analysis completes successfully.

onAbort

(AnalysisModel) -> Void

Yes

Called when the scan is aborted. The model contains partial frame data collected up to that point.

isAborted

Binding<Bool>

SwiftUI only

Set to true from SwiftUI to call ReaderViewController.abort(). Current source does not reset this binding to false; manage that state in your app if the view can be reused.

3.5. Callback data structures

3.5.1. ReaderViewControllerDelegate

Swift
public protocol ReaderViewControllerDelegate: AnyObject {
  func didCaptureFrame(frameCaptured: FrameCapturedCallback)
  func didComplete(analysisModel: AnalysisModel)
  func didAbort(analysisModel: AnalysisModel)
}

3.5.2. FrameCapturedCallback

FrameCapturedCallback provides frame-by-frame progress and capture state during scanning.

Property

Type

Description

progress

Float

Scan progress for the current analysis session.

resultPMF

PMFResult

PMF fitting result for the current frame.

stripStatuses

[StripStatus]

Per-strip status values for the current frame.

orientation

[Double]

Device orientation values captured with the frame.

lux

Float

Ambient light value captured with the frame.

3.5.3. PMFResult

Case

Raw value

Meaning

.noFit

NoFit

The PMF model did not fit the current frame.

.marginalFit

MarginalFit

The PMF model fitted marginally; the frame is reported but may not contribute as a stored profile.

.fit

Fit

The PMF model fitted the current frame.

3.5.4. StripStatus

Case

Raw value

Error?

.storedAndMaxFrames

StoredAndMaxFrames

No

.storedButNotFull

StoredButNotFull

No

.baselineBadAndLevelsBad

BaselineBadAndLevelsBad

Yes

.baselineOkAndLevelsBad

BaselineOkAndLevelsBad

Yes

.baselineBadAndLevelsOk

BaselineBadAndLevelsOk

Yes

.undefined

Undefined

No

3.5.5. AnalysisModel

Swift
public struct AnalysisModel: Encodable {
  public let testConfig: TestConfiguration
  public let testStrips: [TestStrip]
  public let pmfStory: [FrameData]
}

AnalysisModel is returned by both completion and abort callbacks. On abort, testStrips may be empty and pmfStory contains the frame history collected before abort.

3.5.6. FrameData

Swift
public struct FrameData: Encodable {
  public let timestamp: Date
  public let pmfStatus: PMFResult
  public var lux: Float
  public var orientation: [Double]
  public var homography: [Double]
  public var stripBaselines: [[Double]?]
  public var stripProfiles: [[Double]?]
  public var stripStatuses: [StripStatus]
  public var stripImages: [UIImage?]
  public var image: UIImage?
}

When encoded, FrameData serialises timestamp, PMF status, homography, orientation, lux, strip baselines, strip profiles, and strip statuses. Images are intentionally not encoded.

Type

Kind

Description

ReaderViewControllerDelegate

protocol

Receives frame, completion, and abort events from UIKit integrations.

PreviewMode

enum

Controls live camera preview scaling using .fill or .fit.

TestConfiguration

struct

Configuration parameters used by the analyser and returned in AnalysisModel.

TestStrip

struct

Contains analysed strip data, including control and test line information.

UIImage

class

iOS image object used for frame and strip images. Images are not serialised when FrameData is encoded.

3.7. Behaviour notes

  • Preview mode.fill uses aspect fill and may crop the camera feed; .fit uses aspect fit.

  • AbortReaderViewController.abort() stops scanning and returns partial frame data through the abort callback.

  • Screen keep-awake — The SDK disables the idle timer during scanning and restores the previous idle-timer state when the reader disappears or deinitialises.

  • Backgrounding/foregrounding — When the app backgrounds, the reader pauses the camera session and resumes when the app returns to the foreground if the reader was running.

  • PMFInitialHScaleFactor — The SDK passes PMFInitialHScaleFactor from the test configuration to the native engine as initHscale. This can affect wireframe fitting and result behaviour.

  • Layout updatesReaderViewController updates the preview layer and overlay layout in viewDidLayoutSubviews, so it respects container resizing.

3.8. SVG overlay visualisation

The SDK supports an optional SVG-based overlay rendering mode for higher-quality graphics and scalable test result visuals.

  • Default visualisation mode: dotMatrix.

  • To enable SVG overlays, request an updated analyser configuration file from Novarum support.

  • No code changes are required. The overlay mode is controlled via configuration.

4. Version compatibility summary

Component

Current value

Notes

Novarum iOS Reader SDK

4.1.1

Use package identity novarumdx.ndx-imaging-swift and product Imaging.

Minimum iOS version

14.2

Verified from current Package.swift.

Swift tools

5.9

Verified from current Package.swift.

Native core

novarumdx.ndx-imaging-core 1.1.1

Resolved transitively by SwiftPM.

5. Support & troubleshooting

Symptom

Cause

Resolution

SwiftPM cannot resolve novarumdx.ndx-imaging-swift

Not authenticated to the CodeArtifact Swift registry, wrong repository, or missing package access.

Run aws codeartifact login --tool swift ... again using the repository you were granted access to, then reset Xcode package caches and resolve packages.

Resolution previously worked but now returns 401/403

CodeArtifact token expired.

Re-run the CodeArtifact login command. Tokens are temporary and must be refreshed periodically.

Package resolves internally but not for a customer account

The release may not have been promoted to novarum.client.swift, or the customer is using the internal novarum.swift repository.

Confirm the release version exists in the client registry and that the AWS principal has read access.

Camera does not start

Camera permission is missing or denied.

Add NSCameraUsageDescription, request permission before showing the reader, and handle denied permission in the app UI.

Abort callback repeats when rebuilding the SwiftUI view

The app leaves the isAborted binding set to true.

Reset your app state after handling abort if the reader can be shown again.

For access credentials, configuration files, or integration assistance, contact your Novarum support representative or technical account manager. The support team uses an intelligent triage agent to route requests efficiently — provide your SDK version, platform, Xcode version, and a clear description of the issue for fastest resolution.

For support requests and issue tracking, use the SDK Service Desk.

6. Document History

Revision Summary Date
01 Initial document revision Nov 13, 2025
02 Revision for version 4.0.1 iOS SDK layer using c++ core 1.0.0 Jul 16, 2026
03 Revison for SDKIOS v4.1.1 Aug 5, 2026