Search code examples
c#xamarinxamarin.formszxingzxing.net

How to flip Camera used by ZXing in Xamaring Forms


As part of the app i'm developing i want the ability to change between using the front or back camera, but from my searches and attempts i haven't been able to get it to work using the front camera.

The scanner view doing the scanning is the one from ZXing.Net.Mobile.Forms called ZXingScannerView, defined in my xaml like so, together with the button that should do the flipping of the camera.

<elements:AdvancedTabbedPage
...
xmlns:elements="clr-namespace:Wolf.Utility.Main.Xamarin.Elements;assembly=Wolf.Utility.Main"
xmlns:forms="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms">
...

<ContentPage>
   <ContentPage.ToolbarItems>
       <ToolbarItem Text="{x:Static resources:AppResources.CameraFlipText}" x:Name="CameraFlipButton" Clicked="CameraFlipButton_OnClicked"/>
   </ContentPage.ToolbarItems>
   <ContentPage.Content>
...
       <forms:ZXingScannerView x:Name="ScannerView" HeightRequest="200" IsAnalyzing="False" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" IsVisible="False" IsScanning="True"/>
...
   </ContentPage.Content>
</ContentPage>

The Button can be seen in the top right of the following image, while the Scanner View is only visible while scanning is On, which it is not on the image.

Image of the Page where Scannning is happening

Clicking the button should toggle between using the front and back camera, with the front one as the default. Clicking the button however, doesn't seem to do anything, other then write to my Log. Code for the Clicked event of the button can be seen below.

...
private void CameraFlipButton_OnClicked(object sender, EventArgs e)
        {
            Logging.Log(LogType.Information, "Flipping Camera...");

            Config.DefaultOptions.UseFrontCameraIfAvailable = !Config.DefaultOptions.UseFrontCameraIfAvailable;
            Config.CustomOptions.UseFrontCameraIfAvailable = !Config.CustomOptions.UseFrontCameraIfAvailable;

            if (!ScanningToggle.IsToggled) return;

            Logging.Log(LogType.Information, "Restarting Scanning...");
            ScanningToggle.IsToggled = false;
            ScanningToggle.IsToggled = true;
        }

The options mentioned in the above code is defined as so, in my Config class. Additional values in the one called CustomOptions are set in my Init method of my Config class, but those are irrelevant to this question.

public class Config
{
...
public static MobileBarcodeScanningOptions CustomOptions = new MobileBarcodeScanningOptions() { UseFrontCameraIfAvailable = true };
public static MobileBarcodeScanningOptions DefaultOptions = new MobileBarcodeScanningOptions() { UseFrontCameraIfAvailable = true };
...
}

The options that my scanner will use, is always picked between these two, depending on a few user inputs in the settings.

Attempting to get it to work i have also tried to...

  1. Invert the value UseFrontCameraIfAvailable, while scanning is running

  2. Invert the value UseFrontCameraIfAvailable on the options used to start the scan with and then restarting the scan - The code shown above.

  3. Change IsScanning of the ZXingScannerView from true to false, while restarting the scanning with changed options, but this just resulted in the camera freezing.

Found this one as i was about to submit the question. I'm going to attempt to follow that one tomorrow, but would still very much like input on mine.

Fell free to ask questions, or ask for additional code if i have left something out, that you think could help.


Solution

  • I managed to figure out how to successfully flip the camera.

    • To do so i first remove the ZXingScannerView from my stack that contains it.

    • Then i create a new instance of the ZXingScannerView, copying over all the settings from the old one (layout positioning, ZXingScannerView specific values and so on).

    • Then i re-add the ZXingScannerView to the stack, and from there any changes to the UseFrontCameraIfAvailable property took effect.

    The code it made to succeed, is as follows. First the generic method that copies over properties, then the method that recreates the ZXingScannerView, and lastly my method that enables the scanning.

    public class GenericFactory
        {
            // Assistance with Setter Accessibility: https://stackoverflow.com/questions/3762456/how-to-check-if-property-setter-is-public
            public static T CopyProperties<T>(T newObject, T oldObject, bool ignoreDefaults = true,
                bool skipSelectedProperties = true, params string[] skippedProperties) where T : class
            {
                var type = typeof(T);
                var properties = type.GetProperties();
    
                foreach (var property in properties)
                {
                    if (ignoreDefaults && property.GetValue(oldObject) == default)
                        continue;
                    if (skipSelectedProperties && skippedProperties.Contains(property.Name))
                        continue;
                    if (!property.CanWrite)
                        continue;
    
                    property.SetValue(newObject, property.GetValue(oldObject));
                }
    
                return newObject;
            }
        }
    
    private void RecreateScannerView()
            {
                if (Config.DebugMode) Logging.Log(LogType.Debug, $"{nam1eof(RecreateScannerView)} method called");
                ScannerStack.Children.Remove(ScannerView);
    
                if (Config.DebugMode)
                    Logging.Log(LogType.Debug,
                        $"Coping properties from existing {nameof(ZXingScannerView)} into a new {nameof(ZXingScannerView)}");
                ScannerView = GenericFactory.CopyProperties(new ZXingScannerView() {IsScanning = false}, ScannerView,
                    skippedProperties: new List<string>() {nameof(ScannerView.IsScanning)}.ToArray());
                ScannerView.OnScanResult += ScannerView_OnScanResult;
    
                ScannerStack.Children.Add(ScannerView);
            }
    
    private void EnableScan(MobileBarcodeScanningOptions imputedOptions = null)
            {
                if (Config.DebugMode) Logging.Log(LogType.Debug, $"{nameof(EnableScan)} Method is run in Thread named => {Thread.CurrentThread.Name}");
    
                var chosenOptions = imputedOptions ?? (Config.UseCustomOptions ? Config.CustomOptions : Config.DefaultOptions);
                if (Config.DebugMode)
                    Logging.Log(LogType.Information,
                        $"Chose this option for Scanning => {(imputedOptions != null ? nameof(imputedOptions) : (Config.UseCustomOptions ? nameof(Config.CustomOptions) : nameof(Config.DefaultOptions)))}");
                ScannerView.Options = chosenOptions;
    
                RecreateScannerView();
    
                Logging.Log(LogType.Information, $"Starting the Scanning...");
                ScannerView.IsScanning = true;
                ScannerView.IsAnalyzing = true;
                ScannerView.IsVisible = true;
    
                if (Config.DebugMode)
                    Logging.Log(LogType.Debug,
                        $"{nameof(EnableScan)} Called and Finished; ScannerView.IsAnalyzing => {ScannerView.IsAnalyzing}; ScannerView.IsVisible => {ScannerView.IsVisible}");
            }
    

    My method to flip the value of UseFrontCameraIfAvailable is the one shown in the question above.

    Hope to this ends up helping others who might stumble upon a likewise issue.