Skip to content

iOS: honor frameWidth/frameHeight as the scan area, improve scan reliability - #469

Open
macCesar wants to merge 11 commits into
tidev:masterfrom
macCesar:feat/ios-scan-area
Open

iOS: honor frameWidth/frameHeight as the scan area, improve scan reliability#469
macCesar wants to merge 11 commits into
tidev:masterfrom
macCesar:feat/ios-scan-area

Conversation

@macCesar

Copy link
Copy Markdown

Depends on #468 — this branch is stacked on it, so the first 5 commits here belong to that PR. The commits specific to this PR start at feat(ios): honor frameWidth/frameHeight as the scan area.

Summary

Brings the iOS scanner to parity with Android and fixes long-standing accuracy issues in the scan-area mapping:

  • frameWidth/frameHeight supportcapture() now honors these options (in points) to size the scan area, as Android already does: both the drawn rectangle and the region ZXCapture decodes are limited to a centered rect of the requested size. A thin rect (e.g. frameWidth: 300, frameHeight: 50) isolates a single barcode on dense sheets, like a scanner gun.
  • Viewfinder parity — red "laser" line through the middle of the scan area and a dark mask outside it, matching the Android UI.
  • Aspect-fill-aware mapping — the previous scanRect math scaled the crop rect by layer/video size directly, ignoring the overflow hidden by AVLayerVideoGravityResizeAspectFill (and mixing X/Y scales after the portrait axis swap). On 16:9 phones the huge default rect masked the error; on 4:3 iPads the decoded region drifted far from the drawn rectangle.
  • Reliability — capture at 1080p instead of ZXCapture's 720p default (set before the session is created, which is the only moment ZXCapture honors it), restrict autofocus to the near range and enable smooth autofocus per Apple's barcode-scanning guidance. Small/distant barcodes now decode cleanly instead of producing corrupted reads.
  • Audible feedback — system beep per new decoded value, matching Android's BeepManager; deduplicated by value since keepOpen decodes the same barcode on every frame.
  • SafetycaptureResult no longer passes scanned text as an NSLog format string.

Module bumped to 7.1.0 with a CHANGELOG entry. Default behavior without frameWidth/frameHeight is unchanged.

Test plan

  • ti build -p ios --build-only green (Titanium SDK 13.3.0.GA, Xcode 26.6)
  • Verified on a real iPad: thin scan area decodes only the barcode under the laser line on a sheet with 15+ stacked barcodes, beep fires once per new value, dark mask renders, and continuous scan (keepOpen: true) collects multiple distinct codes reliably
  • Verified default (no frame options) still scans
  • CI green

macCesar added 11 commits July 14, 2026 17:35
…yst slices

Repackage the vendored ZXingObjC.xcframework with modern slices only:
ios-arm64 (device) and ios-arm64_x86_64-simulator. Drops armv7/i386
(32-bit) and the Mac Catalyst slice, none of which are supported by
current Titanium SDKs or Xcode toolchains.

Update the manifest accordingly: architectures arm64 x86_64, mac: false
(Catalyst slice no longer shipped), and bump module version to 6.0.1.
minsdk is unchanged.
…s for Xcode 26

Bump TITANIUM_SDK_VERSION to 13.3.0.GA and replace the recursive
Frameworks/** search path with per-SDK slice paths. Xcode 26's module
resolution picked the ios-arm64 (device) TitaniumKit slice while
compiling the x86_64 simulator arch, failing with 'unsupported Swift
architecture'. Pointing iphoneos/iphonesimulator builds at their
matching xcframework slices fixes the archive on both destinations.

Verified with: ti build -p ios --build-only (SDK 13.3.0.GA, Xcode 26.6)
Dropping the Mac Catalyst slice (shipped in the v6.0.0-iphone release)
is a breaking change for Catalyst consumers, so this is a major bump
rather than a patch. Matches the Android precedent (v7.0.0-android was
a major bump for its library update).
Bring the iOS scanner to parity with Android: capture() now reads
frameWidth/frameHeight (points) and sizes the overlay crop rect to a
centered rect of those dimensions. Since applyRectOfInterest already
maps the crop rect to ZXCapture.scanRect, both the drawn rectangle and
the decoded region honor the requested size — a thin rect isolates a
single barcode on dense sheets, like a scanner gun.

Also draw a red laser line through the middle of the scan area,
matching the Android viewfinder. Default behavior (no frame options)
is unchanged.

Bump module to 7.1.0.
…ensor

The previous scanRect math scaled the crop rect by layer/video size
directly, ignoring that AVLayerVideoGravityResizeAspectFill scales the
video until it fills the layer and hides the overflow (and it mixed the
X/Y scale factors after the portrait axis swap). On 16:9 screens the
error was small enough for the huge default rect to work, but on 4:3
screens (iPads) the decoded region drifted far from the drawn
rectangle — with a small custom frame nothing under it ever decoded.

Unproject the on-screen crop rect through the aspect-fill transform
(uniform scale + hidden overflow) before converting it to sensor
coordinates, and drop the now-unused _captureSizeTransform ivar.

Also darken the area outside the scan rect, matching the Android
viewfinder mask.
- Capture at 1080p instead of ZXCapture's 720p default, so small or
  distant barcodes have enough pixels to decode without misreads.
- Restrict autofocus to the near range and enable smooth autofocus
  (Apple's recommendation for barcode scanning) so the camera stops
  hunting to infinity at typical scanning distances.
- Play a system beep on each new decoded value, matching the Android
  client's BeepManager. Deduped by value: with keepOpen the same
  barcode decodes on every frame and would otherwise beep continuously.
- Fix captureResult's NSLog to use a proper format string instead of
  passing the scanned text as the format (crash/format-specifier risk).
…xists

ZXCapture only honors sessionPreset when it first creates its capture
session, which the camera setter triggers — before viewDidLoad. Setting
the preset there left the session at 720p while applyRectOfInterest
mapped the scan area against the 1080p geometry the property reported,
pushing the decode crop out of the real buffer and breaking scanning
entirely. Set the preset right after the ZXCapture instance is created
instead.
applyRectOfInterest only ran when the native rectangle was drawn, so
apps that hide it (showRectangle: false) to draw their own overlay
silently lost the frameWidth/frameHeight decode limit and scanned the
whole frame. Android limits the decode region independently of the
drawn viewfinder; do the same here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant