Swift
Dimensioning (3D Box Measurement)
VisionSDK ships an optional dimensioning module that measures a real-world box's length, width, height, and volume using the device's LiDAR sensor. Use it to verify package dimensions at a shipping or receiving counter, spot-check cartons before palletization, or capture dimensional data for billing, manifests, or catalog entry without a tape measure.
The module is opt-in: it is not in the default Core install. It lives in the source distribution at packagexlabs/vision-sdk-ios.
First shipped in: VisionSDK iOS v2.2.2 (May 2026).
Changed in v2.7.0. The
VSDK-prefixed wrapper types were removed and the module now re-exports the underlying engine's API directly -DimensioningView,DimensioningSession,DimensioningConfiguration,DimensioningMeasurement. Code written against 2.6.x will not compile. See Migrating from 2.6.x at the end of this page. The release also adds overlay callbacks, live tracking updates, multi-box capture, and per-capture telemetry, and drops the encrypted-model setup entirely.
R&D status: the dimensioning module is in active development. The accuracy envelope, supported shapes, and device requirements may evolve in future SDK releases. Design your integration with that in mind: surface dimensions to your users as approximate, allow re-captures, and don't auto-act on a single capture without confirmation.
5-Minute Quickstart
If you just want to wire it up, the minimum viable integration is four steps:
- Add
pod 'VisionSDK/Dimensioning'(or the matching SPM product) to your project. - Set your Podfile platform to
'17.0'and add the two Info.plist keys below. - Gate your entry-point on
Dimensioning.deviceCapabilities().lidar. - Drop a
DimensioningViewinto your SwiftUI hierarchy and read the measurement out of itsonCaptureclosure.
The rest of this page is the same flow with much more context: hardware support, configuration, capture conditions and accuracy, error handling, lifecycle, and troubleshooting.
What ships with the SDK
All assets the dimensioning module needs at runtime are bundled with the SDK. You do not need to download anything at first launch and the .offline mode works without a network connection. The .online mode (covered below) is a separate cloud-augmented path that you opt into per session.
Linking the dimensioning module adds a meaningful amount to your app's install size; if size is a concern, gate the install behind a device-capability check at build time, or ship dimensioning in a separate App Clip / extension that only LiDAR-capable users download.
Hardware and OS Requirements
Always check Dimensioning.deviceCapabilities() at runtime before showing any dimensioning entry-point in your UI; do not assume LiDAR from the iOS version or device model.
Installation
The dimensioning module is not included in the binary SPM distribution at packagexlabs/vision-sdk. Install via the source distribution instead.
CocoaPods
In your Podfile:
Then:
The Dimensioning subspec depends on Core, so listing both is redundant but explicit. The subspec declares its own ios.deployment_target = '17.0' and pulls in ARKit and RealityKit as framework dependencies.
Swift Package Manager
Point at vision-sdk-ios directly (not at vision-sdk) and link both products:
If you're using an Xcode .xcodeproj instead of Package.swift, add the package URL via File > Add Package Dependencies, then in your target's Frameworks, Libraries, and Embedded Content section add both VisionSDK and VisionSDKDimensioning.
Verifying the install
A clean build that succeeds plus a successful import VisionSDKDimensioning is enough confirmation.
Info.plist
Add the following keys to the Info.plist of any target that links the dimensioning module:
Replace the strings with copy that's appropriate for your app; users see this string verbatim in the iOS permission prompt. The Core SDK already requires the camera key for scanning, so you only need to add the LiDAR key when you add dimensioning.
The first time your app shows a DimensioningView, iOS prompts the user for camera permission. If the user denies, subsequent sessions fail with DimensioningError.arSessionFailed(reason:); there is no second-chance prompt without a Settings round-trip.
App-Launch Setup
There is no required setup call. As of v2.7.0 the CoreML models ship unencrypted inside the framework, so there is no decryption-key fetch, no first-launch network round-trip, and no Apple Developer Team requirement - the module works offline from first launch for any signing team.
What remains is a capability check before you show any dimensioning UI:
Warming the models (optional)
prefetchModels() was removed along with the wrapper; the engine exposes no prefetch entry point of its own. The first capture still pays a one-time CoreML compile cost, so if first-capture latency matters you can warm the bundled models yourself at launch:
Fire-and-forget; it's idempotent and skipping it is non-fatal.
Capability check
Dimensioning.deviceCapabilities() returns a DimensioningCapabilities value with three booleans:
All three are false on simulators. The most important one to check is lidar - gate the entry-point on it.
Dimensioning.version returns the bundled engine build (a YYYY.MM.DD string), which is worth logging in diagnostics.
SwiftUI Integration
DimensioningView is the drop-in camera. It owns its own AR session and reports results through closures.
Use it from a parent:
UIKit Integration
There is no UIKit view as of v2.7.0 - VSDKDimensioningView and its delegate protocol were removed, and the engine ships a SwiftUI view only. Host it in a UIHostingController:
Objective-C is no longer supported for dimensioning. The removed wrapper was what provided the
@objcsurface. The engine's API is Swift-only (structs, enums with associated values,asyncmethods), so it cannot be bridged. Objective-C callers need a small Swift shim of their own.
Live guidance - onMeasurementUpdate
DimensioningView streams the tracking state and the in-progress dimensions of every tracked box, which is what you need for a "hold steady… ready" HUD:
DimensioningTrack.measurement is nil until the pipeline has a reading for that box.
Custom overlays - onOverlayUpdate
Set overlayMode = .callback to suppress the built-in graphics entirely and receive every overlay primitive each frame. All geometry arrives in view-space points, so it maps 1:1 onto the view:
Drawing them with a Canvas is straightforward - the 8 box vertices are ordered base face 0–3, top face 4–7, with corner k under corner k+4:
This fires every frame - keep the handler cheap.
Full-control path: DimensioningSession
DimensioningSession is a @MainActor ObservableObject exposing @Published lifecycle state, plus imperative capture control. It runs headless - it publishes state but renders nothing, so use DimensioningView when you need the preview.
Phase lifecycle
session.phase walks through:
Bind directly to it in SwiftUI for a live status indicator - drive a "Capture" button that only enables once phase == .detected and disables while .capturing.
session.tracks gives live per-box overlay data (isStable, normalizedScreenRect) for drawing on top of the AR view, and session.trackingState is the coarser searching → groundFound → boxDetected → stable progression.
Telemetry
Pass a DimensioningTelemetrySink to receive per-capture diagnostics. Events are delivered only when enableTelemetry is true.
.measurementAborted is the only signal for a capture that gave up - worth surfacing as a retry hint. The SDK has no analytics backend of its own and makes no telemetry network calls; events go only to your sink.
The session holds
telemetryweakly. Retain your sink for the lifetime of the session or you will silently stop receiving events.
Configuration
All knobs live on DimensioningConfiguration:
On-device vs cloud segmentation
segmentationBackend replaced the old mode enum in v2.7.0, and now carries its credentials directly instead of reading them from VSDKConstants:
A cloud-side segmentation step can improve accuracy on harder surfaces, at the cost of a per-capture HTTP request. measurement.usedCloudSAM is true on results where the cloud path ran.
There is no longer a VSDKConstants.apiKey fallback - the credential resolver that read it was part of the removed wrapper. If you want the old behaviour, read the constants yourself:
Default to .localOnly. Reach for a cloud backend only when you specifically need the extra accuracy.
Capture Conditions and Accuracy
The dimensioning pipeline reaches its typical accuracy only inside an operational envelope. Design your in-app guidance around these limits and re-prompt the user if a capture falls outside them.
Accuracy envelope
- Typical variance: roughly ±3-5 cm per dimension, per capture, on the same box. This comes from LiDAR depth noise, pose drift, and lighting. Treat the result as approximate; surface it to your users as such.
- Minimum box size: ~10 cm on the smallest side. Smaller objects are below the SDK's current tuning floor.
- Shape: cuboids only. The pipeline is designed for rectangular boxes. Tubes, polybags, envelopes, soft bags that sag, and other non-cuboidal shapes are not supported and will fail to capture or return unreliable values.
Geometry and framing
- The whole box must be in view, with the top face visible. Length and width are derived primarily from the top face.
- The box must rest on a flat horizontal surface (table, floor, conveyor) - the SDK uses that surface as the height reference.
- One box at a time. Multiple boxes in frame, or stacked boxes, can confuse the segmentation and may include neighbor edges in the measurement.
Distance and motion
- Optimal capture distance: 40-90 cm from the box. Closer than ~30 cm or farther than ~1.5-2 m degrades accuracy meaningfully.
- Hold steady for 1-2 seconds before and during capture. The pipeline averages multiple frames; motion within the capture window translates directly into error.
- Tilt slightly downward so the camera sees the top face plus at least one side face. Pure top-down or pure side-on shots give the pipeline less geometric information.
Surface and lighting
- Box surfaces: prefer flat, opaque, matte cardboard. Highly reflective wrap, transparent film, and very dark matte surfaces reduce LiDAR signal quality.
- Lighting: ambient indoor light or diffuse warehouse lighting is ideal. Very dim environments hurt detection. Direct sunlight on glossy boxes can introduce false depth readings.
- Background: cluttered backgrounds (boxes stacked behind the target, shelving directly behind) can cause the SDK to include background edges. A contrasting empty surface behind the box helps.
Recommended UX guidance
If you build a customer-facing dimensioning flow, the following patterns hold up well:
- Show a live framing hint that asks the user to move closer if the box occupies less than ~30% of the screen, or farther if it occupies more than ~80%.
- Require a stable detection before enabling the capture button (use
phase == .detectedandtrack.isStablefrom the session path). - Offer a one-tap "Re-capture" - because of the ±3-5 cm variance, averaging two or three captures noticeably tightens the result.
- Confirm with the user before persisting a measurement to a downstream system (billing, manifest, catalog).
Result Type
DimensioningMeasurement is a Sendable, Hashable, Identifiable struct (it was an @objc NSObject class before v2.7.0, and its dimensions were NSMeasurement):
Reading values
Note the API change:
Measurementuses.valueand.converted(to:), where the oldNSMeasurementused.doubleValueand.converting(to:).
The captured frame
trackId, image / imageData, imagePixelSize and boxVertices2D are new in v2.7.0. boxVertices2D holds the 8 projected box corners in the captured image's pixel space - indices 0–3 the base face, 4–7 the top face, corner k under corner k+4 - so you can draw the measured box over the photo:
All three are empty/zero when the engine kept no frame.
Confidence
measurement.confidence is a Float in [0, 1]. Use it to gate auto-save: a typical threshold is 0.85 for unattended capture. Below that, prompt the user to retry from a better angle.
Live tracks
session.tracks (and update.tracks from onMeasurementUpdate) is an array of DimensioningTrack:
Use normalizedScreenRect to draw an overlay (multiply by your view's bounds). isStable flips to true once the box has been tracked long enough for the engine to attempt a capture. track.id matches measurement.trackId, so you can correlate a capture back to the box it came from.
Errors
DimensioningError is a plain Swift enum thrown by DimensioningSession. Catch it with catch let err as DimensioningError.
Two cases from 2.6.x are gone: .missingCredentials (credentials are now passed at the call site, so there is nothing to resolve and fail) and .notConfigured (there is no separate configure() step).
DimensioningViewreports no errors. OnlyDimensioningSessionthrows. The view has no error callback, so if you need to react to in-session failures you must either use the session directly or infer them from telemetry's.measurementAborted.
Example: handling the full error set
Important: Capture Session Conflict
DimensioningView and DimensioningSession own an ARSession internally. ARKit and AVCaptureSession cannot share the camera, so:
- Before showing the dimensioning view, stop any existing
CodeScannerView(call its scanning teardown), then present the dimensioning UI. - Before returning to the barcode/OCR scanner, tear the dimensioning UI down. From the session path call
await session.shutdown()- it resolves once ARKit has actually released the camera, which makes it a reliable handoff point. From the view path, remove the view (or its hosting controller) from the hierarchy;DimensioningViewowns its session privately and exposes no handle, so there is nothing to await.
If both are alive simultaneously, one of them will fail to start with arSessionFailed. There is no automatic handoff - your navigation code must serialize them.
A common pattern in a single-screen flow with both:
Troubleshooting
Build succeeds but the first session fails on launch
Confirm pod install or SPM resolution completed cleanly and that VisionSDKDimensioning is linked in your target's Frameworks, Libraries, and Embedded Content. If you still see issues, do a clean build (Cmd-Shift-K) and reset the Xcode package cache.
Camera opens, AR session starts, but no boxes are detected
This is most often a cold-start race: the SDK has not finished warming up. Either:
- Warm the bundled models at app launch (see "Warming the models" above), or
- Wait a few seconds in the view before expecting detections, or
- Filter Xcode console logs for
[VSDK-DIM]- the SDK prints diagnostic progress messages.
If you've warmed up and still see no detection, check the capture conditions: framing, distance, surface, and lighting. Highly reflective wrap, transparent film, very dark matte surfaces, or boxes smaller than ~10 cm are below the pipeline's tuning floor.
"Scanning Environment..." or initialization phase doesn't progress
ARKit world-tracking is still establishing. Pan the device slowly side to side and downward to give it more visual texture to anchor on. Ensure the scene has enough light. If phase stays at .initializing for more than a few seconds, the user is likely pointing the camera at a textureless surface (a blank wall) - re-prompt them to point at the floor / their workspace.
Dimensions look wildly wrong (e.g. 1 cm height)
The SDK locked onto the box's own top face as the floor plane. This usually happens when the user starts the session already aiming at the box without giving ARKit a chance to see the floor first. Workaround: re-capture after panning the camera over the supporting surface (floor / table) for a second, then back to the box.
Capture / measure path stays disabled
The pipeline has not reached a stable detection. Wait 1-2 seconds with the box centered, the whole box in frame, and the device steady. If this persists, the capture conditions are out of envelope - see "Capture Conditions and Accuracy" above.
lidarUnavailable on what should be a LiDAR device
Devices in some lighting conditions return spurious false from ARKit's capability probe. Confirm by running Apple's "Object Capture" or "Measure" apps. If those work but the SDK says no LiDAR, file an issue with the device model, iOS version, and the SDK version.
noGroundPlane
The dimensioning pipeline expects a flat floor plane before locking onto a box. Pan the camera at the floor for ~1 second before pointing at the box. This is also a hint to surface to your user via UI ("Point at the floor...").
Measurements jitter / change a lot
Typical capture variance is roughly ±3-5 cm per dimension; if you're seeing more, three things help:
- Keep the device steady for a full 1-2 seconds before and during capture. The pipeline averages frames - any motion in the capture window translates directly into error.
- Ensure all four top corners of the box are in frame.
- Avoid extreme lighting (direct sunlight on glossy tape; near-dark warehouses).
- Move the device into the 40-90 cm sweet spot. Very close (<30 cm) or very far (>1.5 m) captures degrade meaningfully.
- Confirm the box is on a flat horizontal surface and there are no neighboring boxes within ~10 cm of its edges.
Online mode is slow or unreliable
.online makes a per-capture HTTP request to PackageX cloud. On poor connections, prefer .offline and pick .online only for a manual "high-accuracy retry" button.
Both barcode scanning and dimensioning fail to start
Almost always the capture-session conflict (see "Capture Session Conflict" above). Tear down one before starting the other.
FAQ
Do I need a separate API key for dimensioning?
No. Only a cloud segmentation backend needs credentials, and as of v2.7.0 you pass them directly to .cloud(url:apiKey:sdkID:). .localOnly - the default - needs no key and no network.
Will the dimensioning feature work on iPad?
Yes, on LiDAR-equipped iPads (iPad Pro 2020 / 4th gen and newer). The same deviceCapabilities().lidar check applies.
Can I use ARKit elsewhere in my app while dimensioning is open?
Not for the same camera. Two ARSessions cannot share the rear camera. Tear down one before starting the other.
Can the SDK measure cylindrical or irregular shapes? The dimensioning module is designed for rectangular boxes (cuboids). Tubes, polybags, envelopes, and soft bags that sag are not supported and will either fail to capture or return unreliable values.
What's the smallest box the SDK can measure? The pipeline is currently tuned for parcels larger than about 10 cm on the smallest side. Smaller items are below the tuning floor and produce unreliable measurements.
How accurate is a single measurement? Plan around roughly ±3-5 cm of variance per dimension. The variance is centered around the true value, so averaging two or three captures of the same box tightens the result. See "Capture Conditions and Accuracy" above for the full operational envelope.
What units does the volume property return? volume is always a Measurement<UnitVolume> in .cubicMeters, derived from length * width * height. Convert as needed (measurement.volume.converted(to: .cubicCentimeters)).
Do the CoreML models still need a decryption key or a specific signing team? No. Before v2.7.0 the models were encrypted and scoped to PackageX's Apple Developer Team, which meant third-party builds silently degraded to LiDAR-only measurement. They now ship unencrypted inside the framework and work offline from first launch for any team.
Can I still use the module from Objective-C?
No. The @objc surface lived in the removed wrapper. The engine's API is Swift-only, so Objective-C callers need their own Swift shim.
Migrating from 2.6.x
import VisionSDKDimensioning now re-exports the engine's API directly. Type-for-type:
Behavioural changes worth checking:
- Measurement values.
.doubleValue→.value,.converting(to:)→.converted(to:). - Credentials. No
VSDKConstants.apiKeyfallback; pass them at the call site (VSDKDimensioningCredentials.currentstill exists if you want the old behaviour). - Telemetry. Events route to a
DimensioningTelemetrySinkyou supply, not throughVSDKAnalyticsManager. - Objective-C. No longer supported.
- Simulator.
start()no longer throws.lidarUnavailable; it returns and then produces nothing. Gate ondeviceCapabilities().lidar.