Open Inrego opened 4 years ago
I am having the same issue but using the MobileBarcodeScanner component for Xamarin.Ios. It works fine in Android and also works fine old older iPhones running earlier versions of iOS, but does not work on the iPhone X or iPhone 11 running iOS 13.3.1. There may be other phones/iOS versions that don't work, as well, but those are the two cases that we've been able to confirm. The scanner view displays just fine but never successfully scans in the barcode. We were on the 2.4.1 version of the component, but I tried upgrading to the newest 3.0.0-beta5 version and we see the same behavior in both. I've also tried adding lots of hints to the MobileBarcodeScanningOptions, tried to force native scanning, etc. and none of it seems to have had any effect. I've included some relevant sections of the code below:
private static readonly MobileBarcodeScanningOptions BarcodeScanningOptions = new MobileBarcodeScanningOptions()
{
PossibleFormats = new List<ZXing.BarcodeFormat> {
ZXing.BarcodeFormat.UPC_A,
ZXing.BarcodeFormat.UPC_E
},
AutoRotate = true,
AssumeGS1 = true,
CameraResolutionSelector = HandleCameraResolutionSelectorDelegate,
DisableAutofocus = false,
UseNativeScanning = true,
PureBarcode = true,
TryHarder = true,
TryInverted = true,
UseFrontCameraIfAvailable = false
};
var scanner = new MobileBarcodeScanner
{
UseCustomOverlay = false,
TopText = "Hold the camera up to the barcode about 6 inches away",
BottomText = "Please wait for the barcode to automatically scan"
};
//Start scanning
try
{
var result = await scanner.Scan(BarcodeScanningOptions, true);
await ViewModel.AddBarCodeItem(result?.Text);
}
catch (Exception)
{
var alertController = UIAlertController.Create("", "An error occurred while scanning. Please try again.", UIAlertControllerStyle.Alert);
PresentViewController(alertController, true, null);
NSTimer.CreateScheduledTimer(TimeSpan.FromSeconds(1), (timer) => alertController.DismissViewController(true, null));
}
Our viewmodel takes the result of the scan and attempts to look up a product based on the scanned UPC from a remote database using a Web API. If the scan returns a null or empty barcode or if the item can't be found in the product database, we display a message to the user to that effect. In any event, we never get this far in the code and just continue awaiting the Scan() method forever with no exception and no return. Any light you could shed would be greatly appreciated. Thanks in advance for taking a look.
I managed a kind of workaround, where I'm not using the zxing framework on iOS. I can post more details when I'm near a computer.
Ok, so basically I create an interface which has the properties I need for a barcode view. You can implement more if you need it, but this is a good starting point. Basically, I only add the ZXing package to my Android project. The shared code doesn't know about ZXing.
public interface IBarcodeScanner
{
public static readonly BindableProperty IsActiveProperty =
BindableProperty.Create("IsActive", typeof(bool), typeof(IBarcodeScanner), default(bool));
bool IsActive { get; set; }
public static readonly BindableProperty FlashOnProperty =
BindableProperty.Create("FlashOn", typeof(bool), typeof(BarcodeScannerView), default(bool));
bool FlashOn { get; set; }
public static readonly BindableProperty BarcodeFoundCommandProperty =
BindableProperty.Create("BarcodeFoundCommand", typeof(Command<string>), typeof(IBarcodeScanner), default(Command<string>));
public Command<string> BarcodeFoundCommand
{
get;
set;
}
}
I then implement this interface on each platform. Note, that each implementation also inherits an actual view.
Android implementation inherits ZXingScannerView as well as my barcode interface:
[assembly: Dependency(typeof(BarcodeSample.Droid.Barcode.BarcodeScanner))]
namespace BarcodeSample.Droid.Barcode
{
public class BarcodeScanner : ZXingScannerView, IBarcodeScanner
{
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(IsActive):
IsScanning = IsActive;
IsAnalyzing = IsActive;
break;
case nameof(Result):
BarcodeFoundCommand?.Execute(Result.Text);
break;
case nameof(FlashOn):
IsTorchOn = FlashOn;
break;
}
}
public bool IsActive
{
get => (bool)GetValue(IBarcodeScanner.IsActiveProperty);
set => SetValue(IBarcodeScanner.IsActiveProperty, value);
}
public Command<string> BarcodeFoundCommand
{
get => (Command<string>)GetValue(IBarcodeScanner.BarcodeFoundCommandProperty);
set => SetValue(IBarcodeScanner.BarcodeFoundCommandProperty, value);
}
public bool FlashOn
{
get => (bool)GetValue(IBarcodeScanner.FlashOnProperty);
set => SetValue(IBarcodeScanner.FlashOnProperty, value);
}
}
}
On iOS, the implementation uses the built-in OS scanner. The code is more or less completely copied from here: https://github.com/omiimo/Omi.Xamarin.Forms.BarcodeX/blob/master/Omi.Xamarin.Forms.BarcodeX.iOS/BarcodeScannerRenderer.cs I didn't just add a reference to this package, because it's kind of old, and not targeted for .NET Standard as far as I could see. I had some problems using the package, so I just took the renderer. View:
[assembly: Dependency(typeof(BarcodeSample.iOS.Barcode.BarcodeScanner))]
namespace BarcodeSample.iOS.Barcode
{
public class BarcodeScanner : View, IBarcodeScanner
{
public BarcodeScanner()
{
BarcodeType = BarcodeFormat.QrCode;
}
public enum BarcodeFormat
{
Unknown = 0,
Code128 = 1,
Code39 = 2,
Code93 = 4,
Codabar = 8,
DataMatrix = 16,
Ean13 = 32,
Ean8 = 64,
Itf = 128,
QrCode = 256,
UpcA = 512,
UpcE = 1024,
Pdf417 = 2048
}
public static readonly BindableProperty BarcodeProperty =
BindableProperty.Create(nameof(Barcode), typeof(string), typeof(BarcodeScanner), null, propertyChanged: OnBarcodeChanged);
/// <summary>
/// Event will be fired as soon as barcode is detected.
/// </summary>
public event EventHandler<string> BarcodeChanged;
public static readonly BindableProperty SlideToZoomProperty =
BindableProperty.Create(nameof(SlideToZoom), typeof(float), typeof(BarcodeScanner), 1f);
public static readonly BindableProperty IsFlashOnProperty =
BindableProperty.Create(nameof(FlashOn), typeof(bool), typeof(BarcodeScanner), false);
public static readonly BindableProperty MaxZoomProperty =
BindableProperty.Create(nameof(MaxZoom), typeof(float), typeof(BarcodeScanner), 1f);
public static readonly BindableProperty BarcodeTypeProperty =
BindableProperty.Create(nameof(BarcodeType), typeof(BarcodeFormat), typeof(BarcodeScanner), BarcodeFormat.DataMatrix);
public BarcodeFormat BarcodeType
{
get { return (BarcodeFormat)GetValue(BarcodeTypeProperty); }
set { SetValue(BarcodeTypeProperty, value); }
}
/// <summary>
/// Start Barcode scanning process
/// </summary>
public void StartScan()
{
IsActive = true;
}
/// <summary>
/// Stop barcode scanning process
/// </summary>
public void StopScan()
{
IsActive = false;
}
public bool IsActive
{
get { return (bool)GetValue(IBarcodeScanner.IsActiveProperty); }
set { SetValue(IBarcodeScanner.IsActiveProperty, value); }
}
/// <summary>
/// Turn camera flash on/off
/// </summary>
public bool FlashOn
{
get { return (bool)GetValue(IBarcodeScanner.FlashOnProperty); }
set
{
SetValue(IBarcodeScanner.FlashOnProperty, value);
}
}
/// <summary>
/// Zoom value
/// </summary>
public float SlideToZoom
{
get { return (float)GetValue(SlideToZoomProperty); }
set { SetValue(SlideToZoomProperty, value); }
}
/// <summary>
/// Max available zoom.
/// </summary>
public float MaxZoom
{
get { return (float)GetValue(MaxZoomProperty); }
set { SetValue(MaxZoomProperty, value); }
}
/// <summary>
/// Current barcode value
/// </summary>
public string Barcode
{
get { return (string)GetValue(BarcodeProperty); }
set { SetValue(BarcodeProperty, value); }
}
public Command<string> BarcodeFoundCommand
{
get => (Command<string>)GetValue(IBarcodeScanner.BarcodeFoundCommandProperty);
set => SetValue(IBarcodeScanner.BarcodeFoundCommandProperty, value);
}
private static void OnBarcodeChanged(BindableObject bindable, object oldValue, object newValue)
{
if (newValue == null)
{
return;
}
var bobj = (BarcodeScanner)bindable;
var barcode = newValue as string;
bobj.BarcodeChanged?.Invoke(bobj, barcode);
bobj.BarcodeFoundCommand?.Execute(barcode);
}
}
}
And the renderer:
[assembly: ExportRenderer(typeof(BarcodeScanner), typeof(BarcodeScannerRenderer))]
namespace BarcodeSample.iOS.Barcode
{
public class BarcodeScannerRenderer : ViewRenderer, IAVCaptureMetadataOutputObjectsDelegate
{
public BarcodeScannerRenderer()
{
}
private BarcodeScanner barcodeScanner;
private UIView view;
private AVCaptureVideoPreviewLayer captureVideoPreviewLayer;
private AVCaptureSession session;
private NSObject orientationObserverToken;
private AVCaptureDevice device;
private AVCaptureDeviceInput input;
private AVCaptureMetadataOutput output;
[Export("captureOutput:didOutputMetadataObjects:fromConnection:")]
public void DidOutputMetadataObjects(AVCaptureMetadataOutput captureOutput, AVMetadataObject[] metadataObjects, AVCaptureConnection connection)
{
foreach (var metadata in metadataObjects)
{
if (!barcodeScanner.IsActive)
return;
SystemSound.Vibrate.PlaySystemSound();
string resultstring = ((AVMetadataMachineReadableCodeObject)metadata).StringValue;
barcodeScanner.Barcode = resultstring;
barcodeScanner.IsActive = false;
barcodeScanner.Barcode = null;
return;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
return;
barcodeScanner = (BarcodeScanner)Element;
if (!InitScanner(barcodeScanner.BarcodeType))
return;
view = new UIView(CGRect.Empty)
{
BackgroundColor = UIColor.Gray
};
view.Layer.AddSublayer(captureVideoPreviewLayer);
session.StartRunning();
captureVideoPreviewLayer.Connection.VideoOrientation = GetDeviceOrientation();
orientationObserverToken = NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.OrientationDidChangeNotification, UpdateViewOnOrientationChanged);
SetNativeControl(view);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (input == null)
return;
barcodeScanner.MaxZoom = (float)input.Device.MaxAvailableVideoZoomFactor;
updateSize(e);
UpdateZoom(e);
UpdateFlash(e);
IsScannerActive(e);
}
private void IsScannerActive(PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(barcodeScanner.IsActive))
{
if (barcodeScanner.IsActive)
session.StartRunning();
else
session.StopRunning();
}
}
private void UpdateZoom(PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(barcodeScanner.SlideToZoom))
{
if (barcodeScanner.SlideToZoom < 1)
return;
input.Device.LockForConfiguration(out NSError err);
if (err != null)
return;
nfloat maxzoom = input.Device.MaxAvailableVideoZoomFactor;
input.Device.VideoZoomFactor = (float)NMath.Min(input.Device.ActiveFormat.VideoMaxZoomFactor, barcodeScanner.SlideToZoom);
input.Device.UnlockForConfiguration();
}
}
private void UpdateFlash(PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(barcodeScanner.FlashOn))
{
try
{
input.Device.LockForConfiguration(out NSError err);
if (err != null)
return;
if (input.Device.HasTorch)
{
if (barcodeScanner.FlashOn)
input.Device.TorchMode = AVCaptureTorchMode.On;
else
input.Device.TorchMode = AVCaptureTorchMode.Off;
}
else
{
barcodeScanner.FlashOn = false;
}
input.Device.UnlockForConfiguration();
}
catch { }
}
}
private void updateSize(PropertyChangedEventArgs e)
{
if (e.PropertyName == VisualElement.WidthProperty.PropertyName || e.PropertyName == VisualElement.HeightProperty.PropertyName)
captureVideoPreviewLayer.Frame = new CGRect(0, 0, Element.Width, Element.Height);
}
private bool InitScanner(BarcodeScanner.BarcodeFormat barcodeType)
{
device = AVCaptureDevice.GetDefaultDevice(AVMediaType.Video);
if (device == null)
return false;
input = AVCaptureDeviceInput.FromDevice(device);
if (input.Device.IsFocusModeSupported(AVCaptureFocusMode.ContinuousAutoFocus))
{
input.Device.LockForConfiguration(out NSError err);
input.Device.FocusMode = AVCaptureFocusMode.ContinuousAutoFocus;
input.Device.UnlockForConfiguration();
}
if (input == null)
return false;
output = new AVCaptureMetadataOutput();
output.SetDelegate(this, DispatchQueue.MainQueue);
session = new AVCaptureSession();
session.AddInput(input);
session.AddOutput(output);
output.MetadataObjectTypes = GetBarcodeFormat(barcodeType);
captureVideoPreviewLayer = AVCaptureVideoPreviewLayer.FromSession(session);
captureVideoPreviewLayer.Frame = CGRect.Empty;
captureVideoPreviewLayer.VideoGravity = AVLayerVideoGravity.ResizeAspectFill;
captureVideoPreviewLayer.Connection.VideoOrientation = GetDeviceOrientation();
return true;
}
private AVMetadataObjectType GetBarcodeFormat(BarcodeScanner.BarcodeFormat barcodeType)
{
switch (barcodeType)
{
case BarcodeScanner.BarcodeFormat.DataMatrix:
return AVMetadataObjectType.DataMatrixCode;
case BarcodeScanner.BarcodeFormat.QrCode:
return AVMetadataObjectType.QRCode;
case BarcodeScanner.BarcodeFormat.Pdf417:
return AVMetadataObjectType.PDF417Code;
case BarcodeScanner.BarcodeFormat.Code128:
return AVMetadataObjectType.Code128Code;
case BarcodeScanner.BarcodeFormat.Code39:
return AVMetadataObjectType.Code39Code;
case BarcodeScanner.BarcodeFormat.Code93:
return AVMetadataObjectType.Code93Code;
case BarcodeScanner.BarcodeFormat.Ean13:
return AVMetadataObjectType.EAN13Code;
case BarcodeScanner.BarcodeFormat.Ean8:
return AVMetadataObjectType.EAN8Code;
default:
return AVMetadataObjectType.DataMatrixCode;
}
}
private void UpdateViewOnOrientationChanged(NSNotification obj)
{
if (Element == null)
return;
var previewLayer = captureVideoPreviewLayer.Connection;
captureVideoPreviewLayer.Frame = new CGRect(0, 0, Element.Width, Element.Height);
if (previewLayer.SupportsVideoOrientation)
{
previewLayer.VideoOrientation = GetDeviceOrientation();
}
}
private AVCaptureVideoOrientation GetDeviceOrientation()
{
return (AVCaptureVideoOrientation)UIApplication.SharedApplication.StatusBarOrientation;
}
private void removeOrientationObserver()
{
NSNotificationCenter.DefaultCenter.RemoveObserver(orientationObserverToken);
}
}
}
And then a Scanner view in the shared project, which is just a ContentView
which sets its content to a view from platform implementation:
public class BarcodeScannerView : ContentView
{
private IBarcodeScanner _platformScanner;
public BarcodeScannerView()
{
_platformScanner = DependencyService.Get<IBarcodeScanner>();
var view = (View)_platformScanner ?? new Label { Text = "Missing barcode view for this platform." };
view.BindingContext = this;
view.SetBinding(IBarcodeScanner.IsActiveProperty, nameof(IsActive));
view.SetBinding(IBarcodeScanner.BarcodeFoundCommandProperty, nameof(BarcodeFoundCommand));
view.SetBinding(IBarcodeScanner.FlashOnProperty, nameof(FlashOn));
Content = view;
}
public static readonly BindableProperty IsActiveProperty =
BindableProperty.Create("IsActive", typeof(bool), typeof(BarcodeScannerView), default(bool));
public bool IsActive
{
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public static readonly BindableProperty BarcodeFoundCommandProperty =
BindableProperty.Create("BarcodeFoundCommand", typeof(Command<string>), typeof(BarcodeScannerView), default(Command<string>));
public Command<string> BarcodeFoundCommand
{
get => (Command<string>)GetValue(BarcodeFoundCommandProperty);
set => SetValue(BarcodeFoundCommandProperty, value);
}
public static readonly BindableProperty FlashOnProperty =
BindableProperty.Create("FlashOn", typeof(bool), typeof(BarcodeScannerView), default(bool));
public bool FlashOn
{
get => (bool)GetValue(FlashOnProperty);
set => SetValue(FlashOnProperty, value);
}
}
Now just use this BarcodeScannerView like any other view. The bindable properties should hopefully be self-explanatory :) Hope this helps.
Hi Inrego, Your workaround sound interesting! But for me it is not clear how do I start the Scanning process and how do I get the result?
Kind regards,
Martin
You use the BarcodeScannerView
as a view on any page. It will contain the viewfinder. Set the property IsActive
to true when you want to scan for barcodes, and set it back to false when finished scanning. The command BarcodeFoundCommand
will be executed when a barcode is found.
Personally, I've put it in a ContentPage
:
BarcodeScannerPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="ErfaWarehouse.Components.BarcodeScanner.BarcodeScannerPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:barcodeScanner="clr-namespace:ErfaWarehouse.Components.BarcodeScanner;assembly=ErfaWarehouse"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<ContentPage.Content>
<barcodeScanner:BarcodeScannerView x:Name="scannerView" />
</ContentPage.Content>
</ContentPage>
BarcodeScannerPage.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BarcodeScannerPage : ContentPage
{
public BarcodeScannerPage()
{
InitializeComponent();
}
public Command<string> BarcodeFoundCommand
{
get => scannerView.BarcodeFoundCommand;
set => scannerView.BarcodeFoundCommand = value;
}
protected override void OnAppearing()
{
base.OnAppearing();
scannerView.IsActive = true;
}
protected override void OnDisappearing()
{
scannerView.IsActive = false;
base.OnDisappearing();
}
}
I'm using it with an Entry which has a QR icon button thingy. When tapped, it opens my QR scanner, and puts the result into my entry. So here's the relevant code in my entry:
private Command<string> BarcodeFoundCommand => new Command<string>(barcode =>
{
if (barcode == null)
return;
Xamarin.Essentials.Vibration.Vibrate(TimeSpan.FromSeconds(0.5));
Device.BeginInvokeOnMainThread(() =>
{
Entry.Text = barcode;
Navigation.RemovePage(_scannerPage);
_scannerPage = null;
});
});
private BarcodeScannerPage _scannerPage;
private void QrIcon_OnTapped(object sender, EventArgs e)
{
_scannerPage = new BarcodeScannerPage {BarcodeFoundCommand = BarcodeFoundCommand};
Navigation.PushAsync(_scannerPage);
}
@Inrego Hi Inrego, thanks a lot, your workaround is much helpful
But no barcodes are ever recognized.
@Inrego what version were you using when you found this bug?
I have the same bug. I am using latest Stable and the latest Beta and works on Android but it never recognizes the QRCode on iOS. FYI, my iOS device also gets really hot if I leave it in scan mode for 5 minutes so it is really pounding the processor.
I have also tried Xamarin Forms 5.x BETA but that did not work either.
Same problem here. I wonder if there is another solution other than custom renderer on iOS?
I seem to have the same issue. Barcodes do scan with iPhoneX and iPhone11 but after trying out different distances and angles. So users have to struggle a lot to get a scan, compared to older iPhones and Android.
so this bug is not impacting QR codes right?
We are not using QR codes in our app. Only Barcodes - only 1 format - BarcodeFormat.CODE_128. We are using 2.4.1 stable though. Going to try with the latest beta.
Can you please suggest a way for us to troubleshoot it @knocte ?
I'm not a maintainer, just helping triage bugs.
Thanks a lot @knocte. I will update the ticket if I find out more details.
It does impact QR codes, yes.
To the people who are experiencing issues: give my solution a try, it's working perfectly.
This issue is still Open and we are facing this on iPhone 13 but not on other iPhones.
Barcodes are not getting scanned in any ways. We have tried all possible solution from most and all platforms.
We are on latest stable version of ZXing.
Has this issue been resolved or a working resolution or workaround for this has been made available anywhere.
We are on very high priority and tight timeline to resolve this issue so expect a solution that works for all the above who are facing this.
@utkarshsaviant you could try ZXing.Net.Maui
I had to switch to this https://github.com/JimmyPun610/BarcodeScanner.Mobile which might be a better option with a tight timeline.
The viewfinder shows just fine. But no barcodes are ever recognized. The same code works fine on Android (Xamarin.Forms).