We have a problem with the ZXing Barcodescanner for Xamarin.Forms.
The scanner works perfectly on Android, but on IOS I can't see the camera image (preview). The scanner does scan barcodes on IOS if I hold them in front of the camera but the camera preview is just a white background.
I tried playing around with the options but without luck.
We are using Prism.Forms for MVVM.
As I mentioned, my code works well on android.
Here are some details:
The permissions are properly set on both platforms.
The NuGets ZXing.Net.Mobile and ZXing.Net.Mobile.Forms are added too
all three projects (Android, IOS and portable)
We are using .NET Standard 2.0
Xamarin.Forms is version 3.4.0
ScannerView.xaml
<forms:ZXingScannerPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:forms="clr-namespace:ZXing.Net.Mobile.Forms;assembly=ZXing.Net.Mobile.Forms"
x:Class="App.Portable.View.ScannerView">
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<forms:ZXingScannerView x:Name="scanner" Grid.Column="0" Grid.Row="0" HorizontalOptions="EndAndExpand" VerticalOptions="FillAndExpand"
IsScanning="{Binding IsScanning}"
IsAnalyzing="{Binding IsAnalyzing}"
Result="{Binding Result, Mode=TwoWay}"
ScanResultCommand="{Binding CmdScanResult}"
Options="{Binding ScannerOptions}"
/>
<forms:ZXingDefaultOverlay Grid.Column="0" Grid.Row="0"
TopText="Some title"
ShowFlashButton="False"
BottomText="Some bottom text"
Opacity="0.9"/>
</Grid>
</ContentPage.Content>
ScannerViewModel.cs
public class ScannerViewModel : ViewModelBase
{
//Initializing variables
public ScannerViewModel()
{
var options = new MobileBarcodeScanningOptions();
options.TryHarder = true;
options.InitialDelayBeforeAnalyzingFrames = 300;
options.DelayBetweenContinuousScans = 100;
options.DelayBetweenAnalyzingFrames = 200;
options.AutoRotate = false;
ScanningOptions = options;
Title = "Barcode-Scanner";
CmdScanResult = new DelegateCommand(OnCmdScanResult);
IsScanning = true;
IsAnalyzing = true;
}
public MobileBarcodeScanningOptions ScanningOptions
{
get => _scanningOptions;
set => SetProperty(ref _scanningOptions, value);
}
public bool IsScanning
{
get => _isScanning;
set => SetProperty(ref _isScanning, value);
}
public bool IsAnalyzing
{
get => _isAnalyzing;
set => SetProperty(ref _isAnalyzing, value);
}
public Result Result
{
get => _result;
set => SetProperty(ref _result, value);
}
public DelegateCommand CmdScanResult { get; }
private void OnCmdScanResult()
{
IsAnalyzing = false;
IsScanning = false;
Device.BeginInvokeOnMainThread(
async () =>
{
IsAnalyzing = false;
var parameters = new NavigationParameters();
parameters.Add(CodeConstants.BARCODE, Result);
await NavigationService.GoBackAsync(parameters);
});
}
}
Does anyone see an issue at my code or has some suggestions on how to do it better or at least get it to work?
EDIT:
I uploaded a Testproject to my repo to reproduce the error. The Scanner works on iPhone but doesn't show the camera preview:
https://gitlab.com/mitti2000/zxingtest
Cause: You put the ZXingScannerView and ZXingDefaultOverlay in the same cell of grid .Then you set the HorizontalOptions of ZXingScannerView as EndAndExpand .
Solution:
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
Related
Disclaimer: I am a newbie to c# and Xamarin.Forms - sorry for missing anything obvious.
I am trying to create an app that takes user input in the form of a voice command (using Speech-To-Text) and outputs an audio announcement from the application (using Text-To-Speech).
The issue is that when you start recording audio for the Speech-To-Text service, the device's audio is set to recording mode (not sure what the technical term for this is called) and playback audio is set to a very low volume (as described in this SO question and here) and here.
I'm ideally looking for a way to revert this so that once the appropriate voice command is recognised (i.e. 'Secret command') via Speech-To-Text, the user can hear the secret phrase back at full/normal volume through Text-To-Speech in a Xamarin Forms application.
I tried to produce a working example by adapting the sample code for Azure Cognitive Speech Service. I cloned the code and adapted the Xaml and CS for the MainPage slightly, as shown below, to stop the speech recognition service once a certain voice command is triggered and then activate a phrase to be spoken via the Text-To-Speech service. My sample demonstrates the issue. If the user starts by selecting the Transcribe button and enters the appropriate voice command, they should hear back the secret phrase, but the playback volume is so low when testing on a physical IOS device I can barely hear it.
XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="CognitiveSpeechService.MyPage"
Title="Speech Services Transcription"
Padding="10,35,10,10">
<StackLayout>
<Frame BorderColor="DarkGray"
CornerRadius="10"
HeightRequest="300"
WidthRequest="280"
HorizontalOptions="Center"
VerticalOptions="Start"
BackgroundColor="LightGray">
<ScrollView x:Name="scroll">
<Label x:Name="transcribedText"
Margin="10,10,10,10" />
</ScrollView>
</Frame>
<ActivityIndicator x:Name="transcribingIndicator"
HorizontalOptions="Center"
VerticalOptions="Start"
WidthRequest="300"
IsRunning="False" />
<Button x:Name="transcribeButton"
WidthRequest="300"
HeightRequest="50"
Text="Transcribe"
TextColor="White"
CornerRadius="10"
BackgroundColor="Green"
BorderColor="DarkGray"
BorderWidth="1"
FontAttributes="Bold"
HorizontalOptions="Center"
VerticalOptions="Start"
Clicked="TranscribeClicked"/>
<Button x:Name="SpeakBtn"
WidthRequest="300"
HeightRequest="50"
Text="Speak"
TextColor="White"
CornerRadius="10"
BackgroundColor="Red"
BorderColor="DarkGray"
BorderWidth="1"
FontAttributes="Bold"
HorizontalOptions="Center"
VerticalOptions="Start"
Clicked="SpeakBtn_Clicked"/>
</StackLayout>
</ContentPage>
Code-behind
namespace CognitiveSpeechService
{
public partial class MyPage : ContentPage
{
AudioRecorderService recorder = new AudioRecorderService();
SpeechRecognizer recognizer;
IMicrophoneService micService;
bool isTranscribing = false;
public MyPage()
{
InitializeComponent();
micService = DependencyService.Resolve<IMicrophoneService>();
}
async void TranscribeClicked(object sender, EventArgs e)
{
bool isMicEnabled = await micService.GetPermissionAsync();
// EARLY OUT: make sure mic is accessible
if (!isMicEnabled)
{
UpdateTranscription("Please grant access to the microphone!");
return;
}
// initialize speech recognizer
if (recognizer == null)
{
var config = SpeechConfig.FromSubscription(Constants.CognitiveServicesApiKey, Constants.CognitiveServicesRegion);
recognizer = new SpeechRecognizer(config);
recognizer.Recognized += (obj, args) =>
{
UpdateTranscription(args.Result.Text);
};
}
// if already transcribing, stop speech recognizer
if (isTranscribing)
{
StopSpeechRecognition();
}
// if not transcribing, start speech recognizer
else
{
Device.BeginInvokeOnMainThread(() =>
{
InsertDateTimeRecord();
});
try
{
await recognizer.StartContinuousRecognitionAsync();
}
catch (Exception ex)
{
UpdateTranscription(ex.Message);
}
isTranscribing = true;
}
UpdateDisplayState();
}
// https://stackoverflow.com/questions/56514413/volume-has-dropped-significantly-in-text-to-speech-since-adding-speech-to-text
private async void StopSpeechRecognition()
{
if (recognizer != null)
{
try
{
await recognizer.StopContinuousRecognitionAsync();
Console.WriteLine($"IsRecording: {recorder.IsRecording}");
}
catch (Exception ex)
{
UpdateTranscription(ex.Message);
}
isTranscribing = false;
UpdateDisplayState();
}
}
void UpdateTranscription(string newText)
{
Device.BeginInvokeOnMainThread(() =>
{
if (!string.IsNullOrWhiteSpace(newText))
{
if (newText.ToLower().Contains("Secret command"))
{
Console.WriteLine("heart rate voice command detected");
// stop speech recognition
StopSpeechRecognition();
// do callout
string success = "this works!";
var settings = new SpeechOptions()
{
Volume = 1.0f,
};
TextToSpeech.SpeakAsync(success, settings);
// start speech recongition
} else
{
transcribedText.Text += $"{newText}\n";
}
}
});
}
void InsertDateTimeRecord()
{
var msg = $"=================\n{DateTime.Now.ToString()}\n=================";
UpdateTranscription(msg);
}
void UpdateDisplayState()
{
Device.BeginInvokeOnMainThread(() =>
{
if (isTranscribing)
{
transcribeButton.Text = "Stop";
transcribeButton.BackgroundColor = Color.Red;
transcribingIndicator.IsRunning = true;
}
else
{
transcribeButton.Text = "Transcribe";
transcribeButton.BackgroundColor = Color.Green;
transcribingIndicator.IsRunning = false;
}
});
}
async void SpeakBtn_Clicked(object sender, EventArgs e)
{
await TextToSpeech.SpeakAsync("Sample audio line. Blah blah blah. ");
}
}
}
Thanks for your help!
Found a working solution. Posting it below for whoever else it can help and future me.
I noticed this issue was only happening on IOS and not Android, it has to do with the category that AVAudioSession is set to when STT is enabled. As I best understand it, once STT is enabled, audio-ducking turns on for any non-STT-related audio.
You can resolve this issue by programmatically setting the right category using the AVAudioSession Xamarin.IOS API.
To get this working properly in a Xamarin.Forms project, you will need to use the Dependency Service to execute the Xamarin.IOS code in your shared project code.
I have set out the relevant bits of the code that worked for me below.
A full working example can be found in the solution branch of the Github repo mentioned in the comments above.
Mainpage (where STT and TTS services are happening)
public partial class MainPage : ContentPage
{
IAudioSessionService audioService;
public MainPage()
{
InitializeComponent();
micService = DependencyService.Resolve<IMicrophoneService>();
if (Device.RuntimePlatform == Device.iOS)
{
audioService = DependencyService.Resolve<IAudioSessionService>();
}
}
public void SpeechToText()
{
// wherever STT is required, call this first to set the right audio category
audioService?.ActivateAudioRecordingSession();
}
public void TextToSpeech()
{
// wherever TTS is required, let the OS know that you're playing audio so TTS interrupts instead of ducking.
audioService?.ActivateAudioPlaybackSession();
await TextToSpeech.SpeakAsync(TextForTextToSpeechAfterSpeechToText, settings);
// set audio session back to recording mode ready for STT
audioService?.ActivateAudioRecordingSession();
}
IAudioSessionService
// this interface should be in your shared project
namespace CognitiveSpeechService.Services
{
public interface IAudioSessionService
{
void ActivateAudioPlaybackSession();
void ActivateAudioRecordingSession();
}
}
project.Android/AndroidAudioSessionService
using System;
using CognitiveSpeechService.Services;
using Xamarin.Forms;
[assembly: Dependency(typeof(CognitiveSpeechService.Droid.Services.AndroidAudioSessionService))]
namespace CognitiveSpeechService.Droid.Services
{
public class AndroidAudioSessionService : IAudioSessionService
{
public void ActivateAudioPlaybackSession()
{
// do nothing as not required on Android
}
public void ActivateAudioRecordingSession()
{
// do nothing as not required on Android
}
}
}
Project.iOS/IOSAudioSessionService
using System;
using AVFoundation;
using CognitiveSpeechService.Services;
using Foundation;
using Xamarin.Forms;
[assembly: Dependency(typeof(CognitiveSpeechService.iOS.Services.IOSAudioSessionService))]
namespace CognitiveSpeechService.iOS.Services
{
public class IOSAudioSessionService : IAudioSessionService
{
public void ActivateAudioPlaybackSession()
{
var session = AVAudioSession.SharedInstance();
session.SetCategory(AVAudioSessionCategory.Playback, AVAudioSessionCategoryOptions.DuckOthers);
session.SetMode(AVAudioSession.ModeSpokenAudio, out NSError error);
session.SetActive(true);
}
public void ActivateAudioRecordingSession()
{
try
{
new System.Threading.Thread(new System.Threading.ThreadStart(() =>
{
var session = AVAudioSession.SharedInstance();
session.SetCategory(AVAudioSessionCategory.Record);
session.SetActive(true);
})).Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
First of all I am new to UWP and I have already searched almost everywhere (using Google and Stackoverflow) for the answer but couldn't find the answer for my problem.
So, Here is the problem:
I planned to create a pixel paint app that has tab function like Edge (utilizing title bar) for UWP using Visual Studio 2017 and Target Sdk: Creators Update.
I wanted to move the custom title bar I made to the content view when the app in fullscreen condition.
I wanted to move from here (windows title bar, this is just the button XAML code, I'm not including the tab bar XAML code because it's a commercial project):
<Grid x:Name="btnMenuPlace1" Grid.Column="0">
<Grid x:Name="btnMenuPlaceContent" Background="{StaticResource SystemControlHighlightListAccentMediumBrush}">
<Button x:Name="btnMenu" FontFamily="Segoe MDL2 Assets" Content=""
Width="50" Height="50" Background="Transparent" Click="btnMenu_Click"/>
</Grid>
</Grid>
To here (user view):
<Grid x:Name="btnMenuPlace2" Grid.Column="0">
</Grid>
Both parent of those Grid is an another Grid using Grid.ColumnDefinitions like this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
And here's my C# Code:
private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
{
var appView = ApplicationView.GetForCurrentView();
if (appView.IsFullScreenMode)
{
Utility.RemoveChild(btnMenuPlaceContent);
btnMenuPlace2.Children.Add(btnMenuPlaceContent);
Utility.RemoveChild(tabBarPlaceContent);
tabBarPlace2.Children.Add(tabBarPlaceContent);
}
else
{
Utility.RemoveChild(btnMenuPlaceContent);
btnMenuPlace1.Children.Add(btnMenuPlaceContent);
Utility.RemoveChild(tabBarPlaceContent);
tabBarPlace1.Children.Add(tabBarPlaceContent);
}
e.Handled = true;
}
And here is my Utility RemoveChild Code:
public static void RemoveChild(DependencyObject parent, UIElement child)
{
var parentAsPanel = VisualTreeHelper.GetParent(child);
if (parentAsPanel != null)
{
parentAsPanel.Children.Remove(child);
return;
}
var parentAsContentPresenter = parent as ContentPresenter;
if (parentAsContentPresenter != null)
{
if (parentAsContentPresenter.Content == child)
{
parentAsContentPresenter.Content = null;
}
return;
}
var parentAsContentControl = parent as ContentControl;
if (parentAsContentControl != null)
{
if (parentAsContentControl.Content == child)
{
parentAsContentControl.Content = null;
}
return;
}
}
This is my app looks like before entered the fullscreen mode:
So the problem is whenever the app entered the fullscreen mode, the child does move to the new parent, but the button is not there only the background color of the grid remaining and I can't click any of them (not a single click event work), take a look:
And when I switched back to not fullscreen view the proggressbar loading on the new tab not shown.
I don't know which one I did was wrong XAML or the C# code.
Any help would be appreciated.
P.S. I'm Indonesian, so maybe there is something wrong with my sentence, hopefully You are understand what I'm talking about.
There are somethings wrong with your code snippet. For example, RemoveChild method has two parameters but you only provide one when you invoking it. And without assign the parentAsPanel variable type, you cannot get the Children property.
Since the code is not completed, after code updated and add some other code needed I can run your code snippet correctly and cannot reproduce the issue above. Here is my completed testing code:
XAML
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<Grid x:Name="btnMenuPlace1" Grid.Column="0" Grid.Row="0">
<Grid x:Name="btnMenuPlaceContent" Background="{StaticResource SystemControlHighlightListAccentMediumBrush}">
<StackPanel Orientation="Horizontal">
<Button x:Name="btnMenu" FontFamily="Segoe MDL2 Assets" Content="" Width="50" Height="50" Background="Transparent" />
<!--<local:CustomTitleBar Width="200" Height="50"></local:CustomTitleBar>-->
</StackPanel>
</Grid>
</Grid>
<Grid x:Name="btnMenuPlace2" Grid.Column="1" Grid.Row="1"/>
<TextBlock Text="text" x:Name="txtresult"></TextBlock>
<Button x:Name="ToggleFullScreenModeButton" Margin="0,10,0,0" Click="ToggleFullScreenModeButton_Click">
<SymbolIcon x:Name="ToggleFullScreenModeSymbol" Symbol="FullScreen" />
</Button>
</StackPanel>
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
Window.Current.SetTitleBar(btnMenuPlace1);
Window.Current.SizeChanged += Current_SizeChanged;
}
private void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
var appView = ApplicationView.GetForCurrentView();
if (appView.IsFullScreenMode)
{
RemoveChild(btnMenuPlace1, btnMenuPlaceContent);
btnMenuPlace2.Children.Add(btnMenuPlaceContent);
}
else
{
RemoveChild(btnMenuPlace2, btnMenuPlaceContent);
btnMenuPlace1.Children.Add(btnMenuPlaceContent);
}
e.Handled = true;
}
public void RemoveChild(DependencyObject parent, UIElement child)
{
Grid parentAsPanel = VisualTreeHelper.GetParent(child) as Grid;
if (parentAsPanel != null)
{
parentAsPanel.Children.Remove(child);
return;
}
var parentAsContentPresenter = parent as ContentPresenter;
if (parentAsContentPresenter != null)
{
if (parentAsContentPresenter.Content == child)
{
parentAsContentPresenter.Content = null;
}
return;
}
var parentAsContentControl = parent as ContentControl;
if (parentAsContentControl != null)
{
if (parentAsContentControl.Content == child)
{
parentAsContentControl.Content = null;
}
return;
}
}
private void ToggleFullScreenModeButton_Click(object sender, RoutedEventArgs e)
{
var view = ApplicationView.GetForCurrentView();
if (view.IsFullScreenMode)
{
view.ExitFullScreenMode();
}
else
{
if (view.TryEnterFullScreenMode())
{
txtresult.Text = "full screen";
}
else
{
txtresult.Text = "no full screen";
}
}
}
}
My testing environment is OS build 15063. If you still have issues please provide the minimal reproduced project. You may just try to reproduce the issue on my testing demo. More details please reference the official sample.
Sorry it was My mistake, that above code I post is actually working (just some of the code wrongly copied, like for example the parameter on the utility code is not necessary).
The false is on it's parent, i forgot to add row definition on the second place (btnPlace2) parent.
Now it works and looks great now :)
Here is some picture of em:
On FullScreen Mode:
Thanks to anyone answering and voting this question up.
Best regards,
andr33ww
SOLVED
I solved this problem with the fact that I have to add a MapItemsControl.ItemTemplate. Without this it does not render anything more than the name of the control.
So just add this code:
<Maps:MapItemsControl x:Name="mainMapItems" ItemsSource="{Binding MapItems}">
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Background="Transparent">
<TextBlock Maps:MapControl.Location="{Binding Location}" Text="{Binding Title}" Maps:MapControl.NormalizedAnchorPoint="0.5,0.5" FontSize="20" Margin="5"/>
</StackPanel>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
It's not perfect because this will not give you an icon on the map, but rather just a text. But it can easily be solve with adding Image = "" in the Collection.
I'm trying to use MapControl in a Template10 layout.
The code I use is
MainPage.xaml
<Maps:MapControl x:Name="MapControl1"
ZoomInteractionMode="GestureAndControl"
TiltInteractionMode="GestureAndControl"
MapServiceToken="<ApiCodeHere>"
Loaded="{x:Bind ViewModel.Map_Loaded}"/>
MainPageViewModel.cs
MapControl _Map;
public MapControl Map { get { return _Map; } set { Set(ref _Map, value); RaisePropertyChanged(); } }
public async void Map_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
MapControl newMap = new MapControl();
Geoposition userLocation = await GetUserLocation();
BasicGeoposition GeoPos_UserLocation = new BasicGeoposition() { Latitude = userLocation.Coordinate.Point.Position.Latitude, Longitude = userLocation.Coordinate.Point.Position.Longitude };
BasicGeoposition GeoPos_NorthWest = new BasicGeoposition() { Latitude = userLocation.Coordinate.Point.Position.Latitude + 0.05, Longitude = userLocation.Coordinate.Point.Position.Latitude + 0.1 };
BasicGeoposition GeoPos_SouthEast = new BasicGeoposition() { Latitude = userLocation.Coordinate.Point.Position.Latitude - 0.05, Longitude = userLocation.Coordinate.Point.Position.Latitude - 0.1 };
GeoboundingBox mapBox = new GeoboundingBox(GeoPos_NorthWest, GeoPos_SouthEast);
// Add Point for User
MapIcon Icon_UserLocation = new MapIcon() { Location = new Geopoint(GeoPos_UserLocation) };
newMap.MapElements.Add(Icon_UserLocation);
newMap.Center = new Geopoint(mapBox.Center);
Map = newMap;
await Task.CompletedTask;
}
The Map_Loaded function is fired as exepcted. The thing that I have a trouble with is that if this was a normal project I would set the data directly to MapControl1.Center and MapControl1.MapElements.Add. But since this is a MVVM project this is not how it's done and I'm a bit confused on how to proceed.
I would like to do something like Views.MainPage.MapControl1.Center = new Geopoint(), but that clearly does not work.
Additional Update
As it turns out this was as easy as I thought. It was just a matter of thinking in the right order.
The MapControl has controls for Zooming and Center and such. So for MVVM code this works
Center="{Binding MapCenter,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Zoom="{Binding MapZoom,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
I have however had issues with setting up MapItems as described in the document I sources to.
To get items on the map you need to add MapItemsControl and it should work like such...
<Maps:MapItemsControl x:Name="mainMapItems" ItemsSource="{Binding MapItems,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></Maps:MapItemsControl>
But my code in MainPageViewModel.xaml does not work with this. The items does not update.
IList<MapElement> _MapItems;
public IList<MapElement> MapItems { get { return _MapItems; } set { Set(ref _MapItems, value); RaisePropertyChanged(); } }
IList<MapElement> MapItems = new List<MapElement>() {
new MapIcon() {
Location = new Geopoint(GeoPos_UserLocation),
Title = "You Are Here!"
}
};
Sources: Windows 10 SDK Bing Maps Control
Try using
ObservableCollection ObserverableCollection<MapElement> MapItems =
new ObservableCollection<MapElement>();
Since you expecting the item to be "updatable at runtime" or react to changes ObservableCollection behind the scenes implements INPC
I solved this problem with the fact that I have to add a MapItemsControl.ItemTemplate. Without this it does not render anything more than the name of the control.
So just add this code:
<Maps:MapItemsControl x:Name="mainMapItems" ItemsSource="{Binding MapItems}">
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Background="Transparent">
<TextBlock Maps:MapControl.Location="{Binding Location}" Text="{Binding Title}" Maps:MapControl.NormalizedAnchorPoint="0.5,0.5" FontSize="20" Margin="5"/>
</StackPanel>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
It's not perfect because this will not give you an icon on the map, but rather just a text. But it can easily be solve with adding Image = "" in the Collection.
I'm using an ObservableCollection<BarcodeInfo> as ItemsSource of a ListView to generate ViewCells. A Cell contains 2 Labels and a ZXingBarcodeImageView with bindings to my BarcodeInfo-class, everything works as expected.
Now I've to remove several cells from the ListView, but as soon as I try to do so, I get the following Exception from the ZXingBarcodeImageView
System.ArgumentException: Found empty contents
Here's my XAML
<ListView RowHeight="50">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<zxing:ZXingBarcodeImageView
BarcodeFormat="{Binding Format}"
BarcodeOptions="{Binding Options}"
BarcodeValue="{Binding Text}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
Margin="5"
Grid.Column="0"/>
<StackLayout Grid.Row="0" Grid.Column="1"
Spacing="0" VerticalOptions="CenterAndExpand">
<Label Text="{Binding Text}"
LineBreakMode="TailTruncation"
VerticalOptions="End"/>
<Label Text="{Binding Format}"
VerticalOptions="End"
LineBreakMode="TailTruncation"/>
</StackLayout>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the Class for ObservableCollection<BarcodeInfo> _barcodeCollection; in the of the ListView
public class BarcodeInfo
{
public string Text
{ get; set; }
public string Detail
{ get; set; }
public BarcodeFormat Format
{ get; set; }
public EncodingOptions Options
{ get; set; }
}
The Exceptions happens as soon as I try
_barcodeCollection.RemoveAt(i);
I've implemented the INotifyPropertyChanged and tried to set all properties to null which works without exception, but the ZXingBarcodeImageView is not clearing the barcode-image and the Exception is still thrown if I try to remove the Item from the Collection. I'm at a point where I've no more ideas.
I hope anybody can help me.
Update
Because the i seems to be confusing here's the loop I'm using it
for (int i = 0; i < _barcodeCollection.Count; i++)
{
var response =
await _serverUrl.PostUrlEncodedAsync(
new { barcode = _barcodeCollection[i].Text })
.ReceiveString();
if (string.Equals(response, "ok", StringComparison.OrdinalIgnoreCase))
{
percentage += progressSteps;
_barcodeCollection.RemoveAt(i); //EXCEPTION!!!
i--; // index must be checked twice else one element will be skipped
await UploadProgress.ProgressTo(percentage, 250, Easing.Linear);
}
}
Not a straightforward solution, but the easiest workaround I've found was to provide FallbackValue when binding BarcodeValue
<forms:ZXingBarcodeImageView BarcodeFormat="{Binding Format}"
BarcodeValue="{Binding Content, FallbackValue='1'}" />
I have managed to identify the cause of the problem, it is a side effect of the way that the ListView is refreshed when the elements are removed and the ListViewCachingStrategy is set to RetainElement (the default).
The custom renderer OnElementPropertyChanged is called when the items are removed from the collection, and the regenerate() method in the ZXing custom renderer crashes.
This can be solved by setting the ListViewCachingStrategy to RecycleElement, this will work with Android, however there is still a problem with iOS 10, which does not correctly support a listview with ListViewCachingStrategy set to RecycleElement, iOS 10 currentlly works only with ListViewCachingStrategy set to RetainElement, this can be accommodated if you create a custom control in your portable code eg:
public class MyZXingBarcodeImageView : ZXingBarcodeImageView
{
//THERE IS NO CODE REQUIRED HERE
}
And then create your own custom renderer based on the ZXing source, but replace the void regenerate() method with the following:
void regenerate()
{
if(formsView != null && !string.IsNullOrWhiteSpace(formsView.BarcodeValue) )
{
var writer = new ZXing.Mobile.BarcodeWriter();
if(formsView != null && formsView.BarcodeOptions != null)
writer.Options = formsView.BarcodeOptions;
if(formsView != null && formsView.BarcodeFormat != null)
writer.Format = formsView.BarcodeFormat;
var value = formsView != null ? formsView.BarcodeValue : string.Empty;
Device.BeginInvokeOnMainThread(() =>
{
var image = writer.Write(value);
imageView.Image = image;
});
}
}
The change is in the very first If statement, changing from testing for null string to string.IsNullOrWhiteSpace
You do NOT have to create a custom renderer for Android if you use ListViewCachingStrategy set to RecycleElement, the base renderer for ZXingBarcodeImageView will be used without error.
This is a problem that has been bugging me a lot. I still haven't got a solution for this.
I have this XAML code, the DrawingSurfaceBackgroundGrid is an Unity game, and the rest is collapsed Camera.
<DrawingSurfaceBackgroundGrid x:Name="DrawingSurfaceBackground" Loaded="DrawingSurfaceBackground_Loaded">
<!-- Augmented Reality -->
<Canvas VerticalAlignment="Center" x:Name="arCameraStack" Canvas.ZIndex="1" Width="732" Height="549" HorizontalAlignment="Left" Visibility="Collapsed" Tap="viewfinderCanvas_Tap">
<Canvas.Background>
<VideoBrush x:Name="viewfinderBrush" />
</Canvas.Background>
<Image x:Name="imgTarget" Source="/Assets/Icons/camera.target.png" VerticalAlignment="Center" HorizontalAlignment="Center" Canvas.Left="114" Canvas.Top="27"/>
</Canvas>
</DrawingSurfaceBackgroundGrid>
These are my Start/Stop camera functions:
public void StartCamera(bool hasTarget)
{
camera = new PhotoCamera(CameraType.Primary);
viewfinderBrush.SetSource(camera);
Dispatcher.BeginInvoke(() =>
{
if (hasTarget)
{
imgTarget.Visibility = Visibility.Visible;
}
else
{
imgTarget.Visibility = Visibility.Collapsed;
}
});
}
public void StopCamera()
{
if (camera != null)
{
camera.Dispose();
camera = null;
}
}
I have a screen system that changes the views according to what you want to see, sorta like changing pages.
This gets called in the page that shows the Camera part.
public void Show()
{
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.arCameraStack.Visibility = Visibility.Visible;
});
MainPage.Instance.StartCamera(false);
}
And this gets called when I want to hide the Unity part.
public void Hide()
{
UnityApp.SetNativeResolution(0, 0);
UnityApp.Obscure(false);
UnityApp.Deactivate();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.ApplicationBar.IsVisible = true;
});
}
Everything works when I have the solution build and run through Visual Studio, but once I start the app from the phone, not in Master/Debug mode from VS, the camera just hangs. I found out that when I toggle the visibility of anything in the page, the camera will update for a second, and then hang once again.
Well, I found out what was the problem.
My Show/Hide functions look like these now:
public void Hide()
{
UnityApp.Obscure(false);
UnityApp.Deactivate();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundContentProvider(null);
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundManipulationHandler(null);
MainPage.Instance.HideUnityBorder.Visibility = Visibility.Visible;
MainPage.Instance.ApplicationBar.IsVisible = true;
});
}
public void Show()
{
var content = Application.Current.Host.Content;
var width = (int)Math.Floor(content.ActualWidth * content.ScaleFactor / 100.0 + 0.5);
var height = (int)Math.Floor(content.ActualHeight * content.ScaleFactor / 100.0 + 0.5);
UnityApp.SetNativeResolution(width, height);
UnityApp.UnObscure();
MainPage.Instance.Dispatcher.BeginInvoke(() =>
{
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundContentProvider(UnityApp.GetBackgroundContentProvider());
MainPage.Instance.DrawingSurfaceBackground.SetBackgroundManipulationHandler(UnityApp.GetManipulationHandler());
MainPage.Instance.HideUnityBorder.Visibility = Visibility.Collapsed;
MainPage.Instance.ApplicationBar.IsVisible = false;
});
}
The main problem was in the DrawingSurfaceBackground.SetBackgroundContentProvider and DrawingSurfaceBackground.SetBackgroundManiuplationHandler functions.
Now switching between the Unity part and WP part is easy.