-
Notifications
You must be signed in to change notification settings - Fork 3
Camera
DIPS.Mobile.UI provides ways for you to use the camera of the phone with different APIs. The APIs require a CameraPreview which visually changes when using it with the different APIs.
Add NSCameraUsageDescription to your Info.plist.
<key>NSCameraUsageDescription</key>
<string>This app uses the camera take pictures and scan barcodes, which is used for ..... </string>Make sure your description is clear and obvious as to what you are using it for. We've experienced that Apple denies uploading to App Store if this is not.
Add android.permission.CAMERA permission to your android manifest.
<uses-permission android:name="android.permission.CAMERA" />To start off, add a CameraPreview to your page and give it a x:Name. This lets us refer it in the code behind.
<dui:ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
...>
<dui:CameraPreview x:Name="CameraPreview"/>
</dui:ContentPage>DIPS.Mobile.UI provides a camera API for people to scan barcodes. The implementations are optimised and is inspired by ML Kit Barcode scanning for Android and AVCamBarcode from Apple for iOS.
NOTE: On iOS, devices with ultra-wide cameras can use the macro-capable lens for close-range barcode focus while starting the preview in normal wide framing. On those devices, the zoom controls can show
0.5xfor the ultra-wide lens.
To get started, create an BarcodeScanner object in your code behind, and Start() it when it makes sense for people.
public MyPage()
{
InitializeComponent();
m_scanner = new BarcodeScanner();
}In many cases, starting it when page appear makes sense:
protected override void OnAppearing()
{
try
{
await m_scanner.Start(new BarcodeScannerStartOptions
{
Preview = CameraPreview,
OnCameraFailed = CameraFailed,
OnBarcodeAcceptedAsync = result =>
{
var theBarCode = result.Barcode.RawValue;
return Task.CompletedTask;
}
});
}
catch (Exception e)
{
//Todo: catch exception
}
base.OnAppearing();
}Notice that
CameraPreviewis the code behind reference from the preview guide.
Use ValidateBarcodeAsync when the scanner should ask your app whether a detected barcode belongs in the current workflow before playing the success animation. If validation returns invalid, the scanner plays the failure animation, does not increment the progress counter, and then resumes scanning. Repeated scans of the same barcode are processed like any other scan; reject already-scanned values in ValidateBarcodeAsync when your workflow needs that behavior.
private readonly BarcodeScanner m_scanner = new();
await m_scanner.Start(new BarcodeScannerStartOptions
{
Preview = CameraPreview,
OnCameraFailed = CameraFailed,
Strategy = new ScanRectangleBarcodeScanStrategy
{
WidthFraction = 0.8f,
HeightFraction = 0.25f
},
Completion = new BarcodeScanCompletionOptions
{
RequiredCount = 5,
InitialCount = alreadyScannedCount,
OnCompletedAsync = OnScanCountCompletedAsync
},
OnBarcodeAcceptedAsync = OnBarcodeAcceptedAsync,
ValidateBarcodeAsync = ValidateBarcodeAsync,
OnBarcodeRejectedAsync = OnBarcodeRejectedAsync
});
private Task OnBarcodeAcceptedAsync(BarcodeScanResult result)
{
var acceptedBarcode = result.Barcode.RawValue;
if (result.TryGetValidationState<BarcodeMatch>(out var match))
{
ViewModel.UseMatch(match);
}
return Task.CompletedTask;
}
private async Task<BarcodeScanValidationResult> ValidateBarcodeAsync(string barcode)
{
var match = await ViewModel.FindBarcodeMatchAsync(barcode);
return match is not null
? BarcodeScanValidationResult.Valid(match)
: BarcodeScanValidationResult.Invalid("Barcode does not belong to this step.", "not-in-step");
}
private Task OnBarcodeRejectedAsync(BarcodeScanResult result, BarcodeScanValidationResult validationResult)
{
return Task.CompletedTask;
}
private Task OnScanCountCompletedAsync()
{
return Task.CompletedTask;
}When Completion.RequiredCount is greater than zero, the scanner displays a simple counter at the top of the bottom toolbar, such as 3 av 5 skannet. Set Completion.InitialCount when the session starts with already accepted barcodes; after that, the scanner owns the active count and increments it after each accepted scan. Rejected scans do not change the counter. When the active count reaches Completion.RequiredCount, scanner analysis stops and Completion.OnCompletedAsync is invoked.
If ValidateBarcodeAsync throws, the scanner treats the barcode as invalid, plays the failure animation, logs the exception, and resumes scanning.
When you use ScanRectangleBarcodeScanStrategy, rejected validation results can show a short message above the scan rectangle while the failure animation plays. Return BarcodeScanValidationResult.Invalid(errorMessage, reasonCode) from ValidateBarcodeAsync to explain why the scan was rejected.
private Task<BarcodeScanValidationResult> ValidateBarcodeAsync(string barcode)
{
return barcode switch
{
_ when ViewModel.HasAlreadyScanned(barcode) => Task.FromResult(
BarcodeScanValidationResult.Invalid("The label has already been scanned.", "already-scanned")),
_ when !ViewModel.BelongsToCurrentPatient(barcode) => Task.FromResult(
BarcodeScanValidationResult.Invalid("The label does not belong to this patient.", "wrong-patient")),
_ when !ViewModel.IsLabel(barcode) => Task.FromResult(
BarcodeScanValidationResult.Invalid("The code you scanned is not a label.", "not-label")),
_ => Task.FromResult(BarcodeScanValidationResult.Valid())
};
}Use Hint when the scanner should automatically show the camera zoom tip if scanning takes longer than expected, for example “Hold the label farther away to focus.” The scanner attempts the automatic hint once per scanner session and does not show it after people have already used zoom. You can also provide callbacks to decide whether the hint can be shown and to record that it was shown.
await m_scanner.Start(new BarcodeScannerStartOptions
{
Preview = CameraPreview,
OnCameraFailed = CameraFailed,
Strategy = new ScanRectangleBarcodeScanStrategy(),
ValidateBarcodeAsync = ValidateBarcodeAsync,
Hint = new BarcodeScannerHintOptions
{
ShowAutomaticHint = true,
HintText = "Hold the label farther away to focus.",
Delay = TimeSpan.FromSeconds(15),
CanShowHintAsync = () => TipService.CanShow(BarcodeScanningZoomTip),
OnHintShownAsync = () =>
{
TipService.DidShow(BarcodeScanningZoomTip);
return Task.CompletedTask;
}
}
});Use SetTooltipView after Start() only when you need fully custom tooltip content above the scan rectangle.
Remember to release resources to make sure your page does not leak memory.
Use PauseScanning() when you only need to pause barcode processing temporarily, for example while a result bottom sheet is open. This keeps overlay views attached so the scan rectangle does not disappear behind the sheet. Call ResumeScanning() when the temporary UI is dismissed.
m_scanner.PauseScanning(resetOverlay: false);
m_scanner.ResumeScanning();In most cases it makes sense to do this when the page disappear. Other cases can be when people got the result.
protected override void OnDisappearing()
{
base.OnDisappearing();
m_scanner.StopAndDispose();
}DIPS.Mobile.UI provides a tailored view that you can use where it makes sense. This is helpful when you need to debug the values of the barcode live in the app when an error occurs.
<dui:BarcodeScanResultView BarcodeScanResult="{Binding BarcodeScanResult}" />This gives you an overview of the current barcode along with all the barcodes that was detected during scanning.The barcode you get is the "most detected" barcode.
Components
- Buttons
- Checkboxes
- Chip
- CollectionView
- Content control
- Context Menus
- Counters
- Divider
- Labels
- ListItem
- Pickers
- SaveView
- SortControl
- StepFlow
- Tag
- TextFields
- Toolbar
Feedback & State
Guides
- Displaying code inside a mobile app
- Layout Diagnostics
- Memory Leaks
- Performance
- Providing help in your app
Interaction & Accessibility
Layout & Navigation
Media
Styling & Resources