Skip to content

iOS: app is killed on Done when VisionKit returns a scan with 0 pages (0...scan.pageCount - 1 traps) #184

Description

@FrknSnr

Summary

On iOS the app is killed (EXC_BREAKPOINT) right after the user taps Done in the scanner, if VisionKit finishes with a scan that has zero pages. The page loop builds the range 0...(-1), which Swift traps on. It cannot be caught from JS, so scanDocument() never resolves or rejects: the process is simply gone.

Where

The line is in the ios-document-scanner submodule that this plugin ships as ios/DocScanner:

https://github.com/WebsiteBeaver/ios-document-scanner/blob/d32a8b73039e99e4d4f0f17a3ea05106e704315e/DocScanner.swift#L126

public func documentCameraViewController(
    _ controller: VNDocumentCameraViewController,
    didFinishWith scan: VNDocumentCameraScan
) {
    var results: [String] = []

    // loop through all scanned pages
    for pageNumber in 0...scan.pageCount - 1 {   // pageCount == 0  ->  0...(-1)  ->  trap

0...n is a ClosedRange, and Swift requires lowerBound <= upperBound ("Range requires lowerBound <= upperBound"). With pageCount == 0 the upper bound is -1, so this is a runtime trap, not a thrown error.

I filed it here rather than on the submodule repo because this is where the plugin's users are; the fix itself belongs in ios-document-scanner. The Capacitor plugin uses the same submodule, so it is presumably affected too.

Still present in 2.0.4 (the submodule is pinned to the same commit).

Crash stack (symbolicated, production)

-[UIBarButtonItem _triggerActionForEvent:fallbackSender:]
-[ICDocCamViewController overlayDoneButtonTapped:]
-[ICDocCamViewController saveAction:]
-[VNDocumentCameraViewController_InProcess documentCameraController:didFinishWithDocInfoCollection:imageCache:warnUser:]
DocScanner.documentCameraViewController(_:didFinishWith:)   DocScanner.swift:126
lowerBound                                                  <compiler-generated>

EXC_BREAKPOINT

How a scan ends up with zero pages

This surprised us, because the scanner UI does not offer Done without a captured page. In the events we looked at, the user had captured a page (and in one case tapped Keep on the review screen). The common factor is memory pressure: iOS low-memory warnings fire between 0.5 and 3 seconds before Done is tapped.

One session, for illustration:

Time Event
:50.4 shutter tapped
:50.8 low-memory warning ×2
:52.3 Keep tapped
:52.6 low-memory warning ×4
:55.3 Done tapped → crash

Most of our crashes are on iPads, and the largest single group is the 3 GB iPad (9th gen, iPad12,1), across several different users. It also happens on recent iPhones, on iOS 18 and iOS 26.

Our reading, which is an inference since VisionKit is closed source: the in-process scanner drops its cached page images when it receives a memory warning, the Done button stays on screen, and didFinishWith then delivers a scan with pageCount == 0. The imageCache: and warnUser: parameters on the private callback in the stack point the same way.

Because it depends on memory pressure at the exact moment Done is tapped, we do not have a deterministic repro. Regardless of the cause, pageCount == 0 is a value the delegate can receive, and the loop should not trap on it.

Suggested fix

Guard the empty case and use a half-open range:

guard scan.pageCount > 0 else {
    goBackToPreviousView(controller)
    self.errorHandler("No pages were returned by the document scanner")
    return
}

var results: [String] = []

for pageNumber in 0..<scan.pageCount {

0..<scan.pageCount alone already removes the trap (an empty half-open range is valid), but then the success handler fires with an empty scannedImages array. The explicit guard lets the app tell "nothing came back" apart from a normal result. Whether that should go through errorHandler or cancelHandler is your call; since the user did scan something, an error they can retry from seems more honest than a silent cancel.

We are running this as a local patch in production. Happy to open a PR against ios-document-scanner (plus the submodule bump here) if that is useful.

Environment

  • react-native-document-scanner-plugin 2.0.2 (line unchanged in 2.0.4)
  • React Native 0.86, New Architecture, Expo SDK 57
  • iOS 18.7 through 26.5, iPhone and iPad

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions