Scan Process
The scanning process happens entirely through the TireTreadScanView
and should be performed in Landscape mode.
Implementing the ScannerViewControllerHolder
Interface
In order to set up the scan view and retrieve the scan view controller, your UIViewController subclass should implement the ScannerViewControllerHolder
interface.
This interface gives your view controller two properties:
-
scannerViewController
: After the setup callTireTreadScanViewKt.TireTreadScanView
(see below) is successfully made, this property will hold the instance of theTireTreadScannerViewController
, which handles the scanning process. -
dismissViewController
: This is a function property that will be called by the SDK when a request has been made to dismiss the scanner. Assign it a block within the lifetime of yourScannerViewControllerHolder
that handles this scenario in the appropriate manner.
Here is an example of how to implement the ScannerViewControllerHolder
interface:
class YourViewController: UIViewController, ScannerViewControllerHolder {
// Implement the properties defined by the ScannerViewControllerHolder interface
var scannerViewController: UIViewController?
var dismissViewController: (() -> Void)?
// Rest of your class implementation...
}
Remember to replace YourViewController
with the actual name of your view controller.
Make sure that YourViewController runs in Landscape mode.
|
Setting up the Scan View Controller
Once the SDK has been initialized and the necessary permissions have been granted, you can set up the scan view controller.
private func setupTireTreadScanView() {
let config = TireTreadScanViewConfig.Builder()
// .withMeasurementSystem(measurementSystem: MeasurementSystem.metric)
// .disableDefaultHaptic()
// .disableDefaultUi()
.build()
TireTreadScanViewKt.TireTreadScanView(context: self, config: config) { error in
// Handle initialization error
}
}
private func addScanViewControllerAsChild() {
guard let scannerViewController = scannerViewController else {
// Handle error
return
}
addChild(scannerViewController)
view.addSubview(scannerViewController.view)
scannerViewController.didMove(toParent: self)
}
Use a
|
If the callback to TireTreadScanViewKt.TireTreadScanView(context, config)
does not produce an error, then the TireTreadScanView
becomes accessible to your application as the main view object of scannerViewController
.
Default UI
The Tire Tread SDK can provide a pre-made user interface with elements to guide the user through the scan process.
The default UI is enabled by default and requires you to add the following vector graphics into the main app bundle inside the compose-resources
directory:
-
Abort scan button
-
ic_abort_scan.xml
-
-
Tap to start scanning tooltip
-
ic_tap_to_scan.xml
-
You can get them from our Github example repository, or provide your own vector graphics.
If you use the default UI you need to provide these files in your main app bundle, otherwise the application will throw a ResourceNotFoundException .
|
Audio Feedback
The Tire Tread SDK can provide audio feedback to guide users through the scan process.
To make use of these audio feedbacks, your application needs to provide (and thus can customize) the audio files inside its Resources
folder.
The audio feedbacks (with their respective files names) on iOS are played on:
-
Focus Point Found
-
tiretread_focuspoint_found.wav
-
-
Scan Started
-
tiretread_sound_start.wav
-
-
Scan Stopped
-
tiretread_sound_stop.wav
-
-
Phone too close to the Tire
-
tiretread_sound_beep_too_close.wav
-
-
Phone too distant from the Tire
-
tiretread_sound_beep_too_far.wav
-
The SDK only supports these file names, and the .wav
extension.
An example implementation, and the example audio files, can be found in our GitHub Example implementation.
To disable the audio feedback, remove these audio files from the applications' Resources or rename them. |
Start Scanning
In the previous example, we are not disabling the default UI (disableDefaultUi
) in the TireTreadScanView. This means that the ScanView will draw and control all the required components for the scan process.
When using the default UI, your application only needs to implement behavior in a few callbacks from the SDK: onScanAbort
, onUploadCompleted
, onUploadFailed
, and onUploadAborted
. Jump to the Handling the SDK’s events section if you are using the default UI.
When not using the default UI, your application can define the UX of the entire scan process.
For that, start by adding a button alongside of the TireTreadScanView. This button will be responsible for starting and stopping the scan process. Use the functions TireTreadScanner.companion.instance.startScanning()
and TireTreadScanner.companion.instance.stopScanning()
for that.
The TireTreadScanner.companion.instance
also provides the captureDistanceStatus
property, with which your application can check if the user is positioning the device at the correct distance from the tire. This property can be used to prevent scans from initiating at an incorrect distance, e.g.:
if (TireTreadScanner.companion.instance.captureDistanceStatus == DistanceStatus.ok)
{
TireTreadScanner.companion.instance.startScanning()
}
else {
// Notify user to move the phone to the correct position before starting
print("Move the phone to the correct position before starting")
}
The scanning process can be stopped at any time. If not manually stopped, the scan process is automatically stopped after 10 seconds. |
Besides the "Start/Stop" button, when not using the default UI, add also an "Abort" button (or e.g., an 'x' icon), through which the users can abort the Scan Process without waiting for the results. This button should call the TireTreadScanner.companion.instance.abortScanning()
function.
Handling the SDK’s events
The TireTreadScanViewCallback
is the interface through which the TireTreadScanView
communicates back to your application. With these callbacks, you can react on changes and implement your own workflow around the scan process. The callbacks available are:
-
onFocusFound
: Invoked when the scanner has located a focus point, which is a requirement in order for the scanning to begin -
onScanStart
: Invoked when the scanning process begins -
onScanStop
: Invoked when the scanning process ends -
onScanAbort
: Invoked when the scanning process is aborted-
Should be implemented also when using the default UI
-
-
onImageUploaded
: Invoked after each frame is uploaded -
onUploadCompleted
: Invoked once all the frames were successfully uploaded for processing-
Should be implemented also when using the default UI
-
-
onUploadAborted
: Invoked when the upload process is aborted.-
Should be implemented also when using the default UI
-
-
onUploadFailed
: Invoked if any issue has occurred during upload-
Should be implemented also when using the default UI
-
-
onDistanceChanged
: Invoked whenever the distance between the device and the tire changes.-
This information serves as a guide to assist your app’s users in scanning correctly.
-
Once your TireTreadScanView
object is available, add your callback implementation to it. In this example, we will use the same UIViewController
as the callback implementation.
Implement the TireTreadScanViewCallback
interface, and override the desired methods, e.g.:
// Implement the TireTreadScanViewCallback interface
extension YourViewController: TireTreadScanViewCallback {
// These callbacks should always be implemented
func onScanAbort(uuid: String?) {
print("scan aborted for uuid: \(uuid ?? "")")
// Leave the scan activity
self.navigationController?.popViewController(animated: true)
}
func onUploadCompleted(uuid: String?) {
if let safeUuid = uuid {
// redirect to the result loading screen
self.displayLoading(uuid: safeUuid)
} else {
self.displayError()
}
}
func onUploadFailed(uuid: String?, exception: KotlinException) {
self.displayError()
}
// When not using the 'default UI', you will need to implement (at least) the following functions to control the scan process
func startScanning() {
TireTreadScanner.companion.startScanning()
}
func stopScanning() {
TireTreadScanner.companion.stopScanning()
}
func abortScan() {
TireTreadScanner.companion.abortScanning()
finish()
}
/// When not using the "default UI", use this callback to provide guidance to the users
/// Called when the distance has changed.
///
/// - Parameters:
/// - uuid: The UUID associated with the distance change.
/// - previousStatus: The previous distance status.
/// - newStatus: The new distance status.
/// - previousDistance: The previous distance value.
/// - newDistance: The new distance value.
///
/// Note: The distance values are provided in millimeters if the metric system is selected (`UserDefaultsManager.shared.imperialSystem = false`), and in inches if the imperial system is selected (`UserDefaultsManager.shared.imperialSystem = true`).
func onDistanceChanged(uuid: String?, previousStatus: DistanceStatus, newStatus: DistanceStatus, previousDistance: Float, newDistance: Float) {
if Int(newDistance) != Int(previousDistance) {
let distanceInCentimeters = UserDefaultsManager.shared.imperialSystem ? (newDistance * 2.54) : (newDistance / 10.0)
DispatchQueue.main.async { [weak self] in
self?.updateUI(status: newStatus, distance: Int(UserDefaultsManager.shared.imperialSystem ? newDistance : distanceInCentimeters))
}
}
}
}
You can check out our GitHub example repository for a full implementation of the ScanViewController. |
Check out the next section to learn how to handle the measurement results after the scan process is finished.