xamarin xzing barcode scanner re-scan - xamarin

I'm using the zxing barcode scanner in xamarin android forms and I can get it to scan one barcode with no issues, but I want to be able to discard the scan they have taken and have the ability to take a another scan.
I'm also using MVVM. Here is my xaml...
<Grid VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<forms:ZXingScannerView x:Name="zxingView"
IsTorchOn="{Binding TorchON}"
IsScanning="{Binding IsScanning}"
IsAnalyzing="{Binding IsAnalyzing}"
Result="{Binding Result, Mode=TwoWay}"
ScanResultCommand="{Binding ScanCommand}"
/>
<forms:ZXingDefaultOverlay
x:Name="scannerOverlay"
BottomText="Place the red line over the barcode you'd like to scan." />
<Button Grid.Row="1" Text="Toggle Flash" Command="{Binding FlashToggleCommand}"></Button>
</Grid>
And this is my page model
private string barcode = string.Empty;
public string Barcode
{
get { return barcode; }
set { barcode = value; }
}
private bool _isAnalyzing = true;
public bool IsAnalyzing
{
get { return _isAnalyzing; }
set
{
if (!Equals(_isAnalyzing, value))
{
_isAnalyzing = value;
OnPropertyChanged("IsAnalyzing");
}
}
}
private bool _isScanning = true;
private bool _torchON = false;
private DynamicContainerPageModel _hhtScreen;
private readonly IDeviceManager _deviceManager;
public ScanningViewPageModel(IDeviceManager deviceManager)
{
_deviceManager = deviceManager;
}
public override void Init(object initData)
{
base.Init(initData);
_hhtScreen = initData as DynamicContainerPageModel;
}
public bool IsScanning
{
get { return _isScanning; }
set
{
if (!Equals(_isScanning, value))
{
_isScanning = value;
OnPropertyChanged("IsScanning");
}
}
}
public bool TorchON
{
set
{
if (_torchON != value)
{
_torchON = value;
OnPropertyChanged("TorchON");
}
}
get { return _torchON; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public Command ScanCommand
{
get
{
return new Command(() =>
{
IsAnalyzing = false;
IsScanning = false;
Device.BeginInvokeOnMainThread(async () =>
{
Barcode = Result.Text;
var response = await CoreMethods.DisplayAlert("Barcode found", "Found: " + Result.Text, "Keep",
"Scan Again");
if (response)
{
//Save the value into the model
_deviceManager.BeginInvokeOnMainThread(() =>
{
_hhtScreen.SelectedControl.Text = barcode;
});
//close page
await this.CoreMethods.PopPageModel(false);
}
else
{
Result = null;
IsAnalyzing = true;
IsScanning = true;
}
});
IsAnalyzing = true;
IsScanning = true;
});
}
}
public Command FlashToggleCommand
{
get { return new Command(async () => { TorchON = !TorchON; }); }
}
public Result Result { get; set; }
When I press scan again on my pop up, I find it a bit hit and miss whether the scanning camera activates again or not, majority of the time it just freezes. Am I doing something wrong? Is there a better way to get the control to rescan?

I've hit a very similar problem in the past. What I had to end up doing was defining the Scanner view in the code behind, then conditionally add/remove from the view as needed.
This is what mine ended up looking like:
The XAML:
<!--
The barcode scanner grid. The actual barcode scanner is
created and added to the grid in the code behind class.
-->
<Grid x:Name="ScannerViewGrid"
Grid.Row="3"
HorizontalOptions="FillAndExpand"
IsVisible="{Binding IsBarcodeScannerRunning}"
VerticalOptions="FillAndExpand" />
The code behind:
private ZXingDefaultOverlay scannerOverlay;
private ZXingScannerView scannerView;
private void CreateNewScannerView()
{
var vm = BindingContext.DataContext as SearchViewModel;
ScannerViewGrid.Children.Clear();
scannerOverlay = null;
scannerView = null;
scannerOverlay = new ZXingDefaultOverlay();
scannerOverlay.ShowFlashButton = false;
scannerView = new ZXingScannerView();
scannerView.SetBinding(ZXingScannerView.ResultProperty, nameof(vm.BarcodeScanResult), BindingMode.OneWayToSource);
scannerView.SetBinding(ZXingScannerView.ScanResultCommandProperty, nameof(vm.BarcodeScanResultCommand));
scannerView.IsScanning = true;
ScannerViewGrid.Children.Add(scannerView);
ScannerViewGrid.Children.Add(scannerOverlay);
}
private void RemoveScannerView()
{
ScannerViewGrid.Children.Clear();
scannerView.IsScanning = false;
scannerView.IsAnalyzing = false;
scannerView.RemoveBinding(ZXingScannerView.ResultProperty);
scannerView.RemoveBinding(ZXingScannerView.ScanResultCommandProperty);
scannerView = null;
scannerOverlay = null;
}

How I solve this:
Xaml
<coreControls:ContainerLayout.Content>
<Grid>
<ContentView
x:Name="contentViewCamera"/>
<coreControls:SvgImage
SvgSource="img_qr_background"
VerticalOptions="FillAndExpand"
Aspect="AspectFill"/>
<Grid
x:Name="mainLayout"
RowDefinitions="48,*,*,*">
<contentViews:HeaderView Title="Canjear cupon"
TitleColor="{AppThemeBinding Light={StaticResource LightWhiteColor}, Dark={StaticResource DarkWhiteColor}}"
RightIconSvg="ic_flash_w"
Margin="6,0" />
<material:MaterialEntry
Grid.Row="3"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="240"
HeightRequest="42"
BackgroundColor="#70000000"
BorderColor="#70000000"
Placeholder="Ingresa el codigo"/>
</Grid>
</Grid>
</coreControls:ContainerLayout.Content>
code behind
public partial class RedeemCouponPage
{
private ZXingScannerView _scannerView;
public RedeemCouponPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
//mainLayout.Padding = PageHelper.GetPageSafeArea(new Thickness(0, 20), this);
}
protected override void OnViewModelSet()
{
base.OnViewModelSet();
var options = new MobileBarcodeScanningOptions
{
AutoRotate = false,
TryHarder = true,
TryInverted = true,
DelayBetweenAnalyzingFrames = 5,
DelayBetweenContinuousScans = 5,
PossibleFormats = new List<BarcodeFormat>() { BarcodeFormat.QR_CODE }
};
ViewModel.InitializeCameraCommand = new MvxCommand(() =>
{
_scannerView = new ZXingScannerView()
{
Options = options,
ScanResultCommand = ViewModel.ScanResultCommand
};
_scannerView.SetBinding(ZXingScannerView.IsScanningProperty, nameof(ViewModel.IsBusy));
_scannerView.SetBinding(ZXingScannerView.IsAnalyzingProperty, nameof(ViewModel.IsBusy));
_scannerView.SetBinding(ZXingScannerView.IsTorchOnProperty, nameof(ViewModel.IsFlashActive));
_scannerView.SetBinding(ZXingScannerView.ResultProperty, nameof(ViewModel.Result), BindingMode.TwoWay);
contentViewCamera.Content = _scannerView;
});
}
}
viewModel
public class RedeemCouponViewModel: BaseViewModel
{
private readonly ICouponService CouponService = Mvx.IoCProvider.Resolve<ICouponService>();
public IMvxCommand InitializeCameraCommand { get; set; }
private ZXing.Result _result;
public ZXing.Result Result
{
get => _result;
set
{
SetProperty(ref _result, value);
BarCodeText = value.Text;
}
}
private bool _isFlashActive;
public bool IsFlashActive
{
get => _isFlashActive;
set => SetProperty(ref _isFlashActive, value);
}
private string _barCodeText;
public string BarCodeText
{
get => _barCodeText;
set => SetProperty(ref _barCodeText, value);
}
private Coupon _coupon;
public Coupon Coupon
{
get => _coupon;
set => SetProperty(ref _coupon, value);
}
public override void ViewAppeared()
{
base.ViewAppeared();
if (DeviceInfo.DeviceType == DeviceType.Physical)
InitializeCameraCommand.Execute();
IsBusy = true;
}
public IMvxAsyncCommand ScanResultCommand => new MvxAsyncCommand(async () =>
{
if (Result == null)
{
IsBusy = true;
return;
}
else
IsBusy = false;
Coupon = await CouponService.Get(BarCodeText);
PopupIsVisible = true;
}, () => IsBusy);
public IMvxCommand ConfirmRedeemCommand => new MvxCommand(()=>
{
DisplaySuccessView("Canjeado!","El cupon ha sido canjeado con exito.");
if (DeviceInfo.DeviceType == DeviceType.Physical)
InitializeCameraCommand.Execute();
PopupIsVisible = false;
IsBusy = true;
});
public IMvxCommand BackToScanerCommand => new MvxCommand(() =>
{
PopupIsVisible = false;
if (DeviceInfo.DeviceType == DeviceType.Physical)
InitializeCameraCommand.Execute();
IsBusy = true;
});
}

Related

Saving Picker Value null exception cannot convert string to model

I am having an issue with saving of my picker value. I am rewriting old code and i am struggling with saving a picker value. Once I select the picker i have the value but it doesn't go through the getter. I have similar method on other picker it works there perfectly co I dont really understand what is wrong. The only thing is that if you notice the older piece there is the Model property.Position when filling the value. Please see my notes in the code. I have Inotify in the model as advised and in the vm
public class SettingsViewModel : ObservableObject
{
private List<CustomTextSize> _textSize = new List<CustomTextSize>();
private CustomTextSize _selectedTextSize;
public CustomTextSize SelectedTextSize
{
get => _selectedTextSize; // doesnt go through here
set
{
if (_selectedTextSize != value && _selectedTextSize != null)
{
this.IsBusy = true;
SetProperty(ref _selectedTextSize, value);
NotificationService.ShowToast("Probíhá ukládání, prosím čekejte");
UserSettings newSetting = new UserSettings()
{
DateSaved = DateTime.Now,
OptionId = (int)SettingOption.TextFontSize,
Value = value.Position.ToString()
};
new Thread(new ThreadStart(async () =>
{
await LangUpDataSaverLoader.SaveUserSettings(newSetting);
LangUpDataSaverLoader.LoadUserSettingsAndFillAppValues();
})).Start();
this.IsBusy = false;
}
}
}
public List<CustomTextSize> TextSize
{
get => _textSize;
set => SetProperty(ref _textSize, value);
}
_textSize = FillTextSizeOptions();
var customSize = _textSize.FirstOrDefault(m => m.Value == LangUpUserCustomSettings.CustomFontSize); // here should be position? But then I am getting that I cannot conevrt int to Model.
if (customSize != null)
{
_selectedTextSize = customSize;
}
}
Model
public class CustomTextSize : ObservableObject {
public int Position { get; set; }
public int Value { get; set; }
public string Text { get; set; }
}
Xaml
<Picker x:Name="textSize" WidthRequest="300"
VerticalOptions="CenterAndExpand" FontSize="20"
Title="{ grial:Translate A_PickerSizeOfText }"
ItemsSource ="{Binding TextSize}" ItemDisplayBinding="{Binding Text}"
SelectedItem ="{Binding SelectedTextSize, Mode=TwoWay}"
grial:PickerProperties.BorderStyle="Default"
BackgroundColor="Transparent"
TextColor= "{ DynamicResource ListViewItemTextColor }" >
</Picker>
Old piece of code that I am rewrting
Spinner textSizeSpinner = new Spinner(MainActivity.Instance);
ArrayAdapter textSizeAdapter = new ArrayAdapter(MainActivity.Instance, Android.Resource.Layout.SimpleSpinnerDropDownItem);
textSizeAdapter.AddAll(FillTextSizeOptions().Select(m => m.Text).ToList());
textSizeSpinner.Adapter = textSizeAdapter;
textSizeSpinner.SetBackgroundColor(Color.FromHex("#3680b2").ToAndroid());
fontSizeStepper.Children.Add(textSizeSpinner);
initialTextSizeSpinnerPosition = FillTextSizeOptions().FirstOrDefault(m => m.Value == LangUpUserCustomSettings.CustomFontSize).Position; //here is the .Position
textSizeSpinner.SetSelection(initialTextSizeSpinnerPosition);
protected async void TextSizeSelected(object sender, AdapterView.ItemSelectedEventArgs e)
{
if (e.Position != initialTextSizeSpinnerPosition)
{
dialog.SetProgressStyle(ProgressDialogStyle.Spinner);
dialog.SetCancelable(false);
dialog.SetMessage("Probíhá ukládání, prosím čekejte");
dialog.Show();
Spinner spinner = (Spinner)sender;
Model.UserSettings newSetting = new Model.UserSettings()
{
DateSaved = DateTime.Now,
OptionId = (int)SettingOption.TextFontSize,
Value = FillTextSizeOptions().First(m => m.Position == e.Position).Value.ToString(),
};
await LangUpDataSaverLoader.SaveUserSettings(newSetting);
initialTextSizeSpinnerPosition = e.Position;
LangUpDataSaverLoader.LoadUserSettingsAndFillAppValues();
dialog.Dismiss();
}
}

Highlight URL using label span - xamarin.forms

I am creating a chat application in xamarin.forms.What I am trying to achieve is whenever user typed message contains a URL, that should be highlighted and provide click to it.For this feature I found Span in Label text.When user click on send button of chat , I will check for URL and make it as another span.I got this idea from Lucas Zhang - MSFT form this question here.
The problem is I am trying to do the spanning in view model and the individual chat bubble is in another view cell which will call as ItemTemplate in my chat listview. Anyway the spanning is not working as I intended ie; it doesn't highlight .
My view Model.
public Queue<Message> DelayedMessages { get; set; } = new Queue<Message>();
public ObservableCollection<Message> Messages { get; set; } = new ObservableCollection<Message>();
public string TextToSend { get; set; }
public ChatPageViewModel()
{
OnSendCommand = new Command(() =>
{
if (!string.IsNullOrEmpty(TextToSend))
{
var urlStr = TextToSend;
int startIndex = 0, endIndex = 0;
if (urlStr.Contains("www."))
{
startIndex = urlStr.IndexOf("www.");
}
if (urlStr.Contains(".com"))
{
endIndex = urlStr.IndexOf(".com") + 3;
}
if (startIndex != 0 || endIndex != 0)
{
var formattedString = new FormattedString();
Span span1 = new Span() { Text = urlStr.Substring(0, startIndex), TextColor = Color.Black };
formattedString.Spans.Add(span1);
Span span2 = new Span() { Text = urlStr.Substring(startIndex, endIndex - startIndex + 1), TextColor = Color.LightBlue };
span2.GestureRecognizers.Add(new TapGestureRecognizer()
{
NumberOfTapsRequired = 1,
Command = new Command(() => {
})
});
formattedString.Spans.Add(span2);
Span span3 = new Span() { Text = urlStr.Substring(endIndex, urlStr.Length - 1 - endIndex), TextColor = Color.Black };
formattedString.Spans.Add(span3);
var message = new Message
{
Text = formattedString.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
else
{
var message = new Message
{
Text = urlStr.ToString(),
IsIncoming = false,
MessageDateTime = DateTime.Now
};
Messages.Add(message);
TextToSend = string.Empty;
}
}
});
}
Single chat Bubble XAML
<Label x:Name="OutgoingMessage" TextColor="White" FormattedText="{Binding Text}" HorizontalOptions="End" >
</Label>
My Chat page XAML
<Grid RowSpacing="0" Margin="0,20,0,0"
ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="1" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages,Mode=OneWay}"
Margin="0"
SelectionMode="None"
FlowDirection="RightToLeft"
HasUnevenRows="True" x:Name="ChatList"
VerticalOptions="FillAndExpand"
SeparatorColor="Transparent"
>
</ListView>
<BoxView HorizontalOptions="FillAndExpand"
HeightRequest="1"
BackgroundColor="#F2F3F5"
Grid.Row="1"/>
<partials:ChatInputBarView Grid.Row="2"
Margin="0,0,0,0"
x:Name="chatInput"/>
</Grid>
ChatPage.xaml.cs
public partial class ChatPage : ContentPage
{
ChatPageViewModel vm;
public ChatPage()
{
InitializeComponent();
this.BindingContext = vm= new ChatPageViewModel();
}
}
Messages class
public class Message : ObservableObject
{
string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}
Any Help is appreciated.
EDIT:
This question was actually continuation of question. Previously I used AwesomeHyperLinkLabel fromlink. The problem was I cant manage the click event of that label.Thats why I moved with label span.Thanks to Leo Zhu - MSFT For the render changes.
For Android:
[assembly: ExportRenderer(typeof(AwesomeHyperLinkLabel), typeof(AwesomeHyperLinkLabelRenderer))]
namespace App18.Droid
{
public class AwesomeHyperLinkLabelRenderer : LabelRenderer
{
public AwesomeHyperLinkLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (AwesomeHyperLinkLabel)Element;
if (view == null) return;
TextView textView = new TextView(Forms.Context);
textView.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
textView.SetTextColor(view.TextColor.ToAndroid());
// Setting the auto link mask to capture all types of link-able data
textView.AutoLinkMask = MatchOptions.All;
// Make sure to set text after setting the mask
textView.Text = view.Text;
AddHyperlinksManually(textView);
//textView.SetTextSize(ComplexUnitType.Dip, (float)view.FontSize);
// overriding Xamarin Forms Label and replace with our native control
SetNativeControl(textView);
}
public static void AddHyperlinksManually(TextView _tv)
{
SpannableStringBuilder currentSpan = new SpannableStringBuilder(_tv.Text);
Linkify.AddLinks(currentSpan, MatchOptions.WebUrls);
var objects = currentSpan.GetSpans(0, currentSpan.Length(), Java.Lang.Class.FromType(typeof(URLSpan)));
var urlSpans = new URLSpan[objects.Length];
for (var i = 0; i < urlSpans.Length; i++)
{
urlSpans[i] = objects[i] as URLSpan;
}
foreach (URLSpan _url in urlSpans)
{
int iStart = currentSpan.GetSpanStart(_url);
int iEnd = currentSpan.GetSpanEnd(_url);
currentSpan.RemoveSpan(_url);
currentSpan.SetSpan(new CustomURLSpan(_url.URL), iStart, iEnd, SpanTypes.InclusiveInclusive);
_tv.SetText(currentSpan, TextView.BufferType.Normal);
_tv.MovementMethod = LinkMovementMethod.Instance;
}
}
public class CustomURLSpan : ClickableSpan
{
string mTargetURL;
public CustomURLSpan(string _url) {
mTargetURL =_url;
}
public override void OnClick(Android.Views.View widget)
{
//here you could handle the click event,and you could use MessagingCenter to send mTargetURL to your Page.
Console.WriteLine("Click");
}
}
}
The mistake was with my model.Changed string to FormattedString and also changed in the viewmodel
public class Message : ObservableObject
{
FormattedString text;
public FormattedString Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
DateTime messageDateTime;
public DateTime MessageDateTime
{
get { return messageDateTime; }
set { SetProperty(ref messageDateTime, value); }
}
public string MessageTimeDisplay => MessageDateTime.Humanize();
bool isIncoming;
public bool IsIncoming
{
get { return isIncoming; }
set { SetProperty(ref isIncoming, value); }
}
}

Xamarin Forms Webview - Scroll event

I need to know when the user has ended the scroll on a webview displaying a agreement, to display an "Accept" only when the user has read this.
Here is my webview (xaml) :
<WebView x:Name="webView" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Content}" />
</WebView.Source>
How can I do that (.cs side) ?..
What is the best practice ?
Xamarin Forms - Targets : iOS & Android
Thanks for your help ;)
EDIT (iOs/Android working solution) :
Here is what I implemented to listen scroll inside the webview (JS event):
Custom WebView renderer :
namespace xxx.Views.Common.Controls
{
public class WebViewer : WebView
{
public static BindableProperty EvaluateJavascriptProperty =
BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(WebViewer),
null, BindingMode.OneWayToSource);
public static BindableProperty RefreshProperty =
BindableProperty.Create(nameof(Refresh), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty GoBackProperty =
BindableProperty.Create(nameof(GoBack), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty CanGoBackFunctionProperty =
BindableProperty.Create(nameof(CanGoBackFunction), typeof(Func<bool>), typeof(WebViewer), null,
BindingMode.OneWayToSource);
public static BindableProperty GoBackNavigationProperty =
BindableProperty.Create(nameof(GoBackNavigation), typeof(Action), typeof(WebViewer), null,
BindingMode.OneWay);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>) this.GetValue(EvaluateJavascriptProperty); }
set { this.SetValue(EvaluateJavascriptProperty, value); }
}
public Action Refresh
{
get { return (Action) this.GetValue(RefreshProperty); }
set { this.SetValue(RefreshProperty, value); }
}
public Func<bool> CanGoBackFunction
{
get { return (Func<bool>) this.GetValue(CanGoBackFunctionProperty); }
set { this.SetValue(CanGoBackFunctionProperty, value); }
}
public Action GoBackNavigation
{
get { return (Action) this.GetValue(GoBackNavigationProperty); }
set { this.SetValue(GoBackNavigationProperty, value); }
}
}
}
Android Renderer :
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace xxx.Droid.Renderers
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
var oldWebView = e.OldElement as WebViewer;
if (oldWebView != null)
{
oldWebView.EvaluateJavascript = null;
}
var newWebView = e.NewElement as WebViewer;
if (newWebView != null)
{
newWebView.EvaluateJavascript = async js =>
{
ManualResetEvent reset = new ManualResetEvent(false);
var response = "";
Device.BeginInvokeOnMainThread(() =>
{
System.Diagnostics.Debug.WriteLine("Javascript Send: " + js);
this.Control?.EvaluateJavascript(js, new JavascriptCallback(r =>
{
response = r;
reset.Set();
}));
});
await Task.Run(() => { reset.WaitOne(); });
if (response == "null")
{
response = string.Empty;
}
return response;
};
}
if (this.Control != null && e.NewElement != null)
{
this.SetupControl();
}
}
/// <summary>
/// Sets up various settings for the Android WebView
/// </summary>
private void SetupControl()
{
// Ensure common functionality is enabled
this.Control.Settings.DomStorageEnabled = true;
this.Control.Settings.JavaScriptEnabled = true;
// Must remove minimum font size otherwise SAP PDF's go massive
this.Control.Settings.MinimumFontSize = 0;
// Because Android 4.4 and below doesn't respect ViewPort in HTML
if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
{
this.Control.Settings.UseWideViewPort = true;
}
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
private readonly Action<string> _callback;
public JavascriptCallback(Action<string> callback)
{
this._callback = callback;
}
public void OnReceiveValue(Java.Lang.Object value)
{
System.Diagnostics.Debug.WriteLine("Javascript Return: " + Convert.ToString(value));
this._callback?.Invoke(Convert.ToString(value));
}
}
public class WebViewChromeClient : WebChromeClient
{
readonly Action<IValueCallback, Java.Lang.String, Java.Lang.String> callback;
public WebViewChromeClient(Action<IValueCallback, Java.Lang.String, Java.Lang.String> callback)
{
this.callback = callback;
}
//For Android 4.1+
[Java.Interop.Export]
public void openFileChooser(IValueCallback uploadMsg, Java.Lang.String acceptType, Java.Lang.String capture)
{
this.callback(uploadMsg, acceptType, capture);
}
// For Android 5.0+
public override bool OnShowFileChooser(WebView webView, IValueCallback filePathCallback,
FileChooserParams fileChooserParams)
{
return base.OnShowFileChooser(webView, filePathCallback, fileChooserParams);
}
}
}
iOS Renderer :
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace xxx.iOS
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
//if (this.NativeView != null && e.NewElement != null)
//{
// this.InitializeCommands((WebViewer) e.NewElement);
//}
var webView = e.NewElement as WebViewer;
if (webView != null)
{
webView.EvaluateJavascript = js => { return Task.FromResult(this.EvaluateJavascript(js)); };
}
}
private void InitializeCommands(WebViewer element)
{
var ctl = (UIWebView) this.NativeView;
ctl.ScalesPageToFit = true;
}
}
}
xaml Display :
<pages:xxxContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:xxx.Views"
xmlns:controls="clr-namespace:xxx.Views.Common.Controls;assembly=xxx.View"
x:Class="xxx.Views.AboutPage">
<pages:xxxInfoContentPage.PageView>
<StackLayout HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand">
<controls:WebViewer x:Name="webView" EvaluateJavascript="{Binding EvaluateJavascript}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding Content}" />
</WebView.Source>
</controls:WebViewer>
</StackLayout>
</pages:DriversInfoContentPage.PageView>
</pages:DriversInfoContentPage>
JS inside webview (redirection when the bottom is reached) :
<script type="text/javascript">
function init() {
window.onscroll = function(ev) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
window.location.hash = "bottom";
location.reload();
}
};
}
</script>
<body onload="init()">
<!-- Scrolled content-->
</body>
Catching & Canceling Navigating Event xaml.xs side :
public AboutPage()
{
this.webView.Navigating += this.NavigatingEvent;
}
private void NavigatingEvent(object sender, WebNavigatingEventArgs e)
{
if (e.Url.Contains("bottom") || e.Url.Contains("about:blank"))
{
e.Cancel = true;
// ACTION WHEN BOTTOM IS REACHED HERE
}
}
I would recommend making your C# access the javascript.
See my tutorial:
https://www.youtube.com/watch?v=_0a7NzkNl-Q

Is there a way to bring up the native iOS and Android busy indicators with Xamarin Forms apps?

I have a Forms app that takes a few seconds to populate data when I click on a viewCell.
Is there a way that I can show a circular busy indicator during this time through custom renderers or something like that?
You can implement the same by using ActivityIndicator control.
If you are expecting have busy-indicators on multiple pages, then would recommend to implement this using the ControlTemplate (it also allows you to define overlays if needed).
Page Template
<Application.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="DefaultTemplate">
<Grid>
<!-- page content -->
<ContentPresenter />
<!-- overlay -->
<BoxView BackgroundColor="Black" Opacity="0.5"
IsVisible="{TemplateBinding BindingContext.IsBusy}"/>
<!-- busy indicator with text -->
<Frame HorizontalOptions="Center" VerticalOptions="Center"
IsVisible="{TemplateBinding BindingContext.IsBusy}">
<StackLayout>
<ActivityIndicator IsRunning="{TemplateBinding BindingContext.IsBusy}" />
<Label Text="{TemplateBinding BindingContext.BusyText}" />
</StackLayout>
</Frame>
</Grid>
</ControlTemplate>
</ResourceDictionary>
</Application.Resources>
Sample usage:
XAML - assign template to page
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
ControlTemplate="{StaticResource DefaultTemplate}"
.. >
....
</ContentPage>
View Model
public class BaseViewModel : ObservableObject
{
bool _isBusy;
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
string _busyText = "loading..";
public string BusyText
{
get => _busyText;
set => SetProperty(ref _busyText, value);
}
}
public class TestViewModel : BaseViewModel
{
public ICommand OnTapCommand {
get => new Command(async (obj) =>
{
IsBusy = true;
//do heavy lifting here
await Task.Delay(2000);
IsBusy = false;
});
}
...
You can use Acr.UserDialogs, it's a cross-platform package with busy indicators, dialogs, toasts, etc.
In your case, you need to use Loading.
using (Acr.UserDialogs.UserDialogs.Instance.Loading("your message here"))
{
//your long task here
}
For example...
I accomplished this by creating an activity indicator control that can be used in my entire app. I even made it so that you can change the activity indicator text to show any text that you want such as 'Logging in', 'loading', 'uploading', etc. See my post below. Let me know if you have any questions.
Is it possible to have one Activity indicator for entire app?
you can use a DependencyService to Show and Hide a loading indicator.
You will have to download AndHUD for android and BTProgressHUD for iOS NuGet packages.
DependencyService interface
using MyApp.Helpers;
namespace MyApp
{
interface IHudService
{
void ShowHud(string ProgressText = StaticData.Loading);
void HideHud();
void SetText(string Text);
void SetProgress(double Progress, string ProgressText = "");
}
}
Android Code
using AndroidHUD;
using Android.Views;
using Xamarin.Forms;
using MyApp.Droid;
using MyApp.DependencyServices;
using MyApp.Helpers;
[assembly: Dependency(typeof(DroidHudService))]
namespace MyApp.Droid
{
public class DroidHudService : IHudService
{
#region IHudManager implementation
bool isHudVisible;
public void ShowHud(string ProgressText = StaticData.Loading)
{
Device.BeginInvokeOnMainThread(() =>
{
AndHUD.Shared.Show(Forms.Context, ProgressText, maskType: MaskType.Black);
isHudVisible = true;
});
}
public void HideHud()
{
Device.BeginInvokeOnMainThread(() =>
{
AndHUD.Shared.Dismiss(Forms.Context);
isHudVisible = false;
});
}
public void SetProgress(double Progress, string ProgressText = "")
{
if (!isHudVisible)
return;
Device.BeginInvokeOnMainThread(() =>
{
int progress = (int)(Progress * 100);
AndHUD.Shared.Show(Forms.Context, ProgressText + progress + "%", progress, MaskType.Black);
});
}
public void SetText(string Text)
{
if (!isHudVisible)
return;
Device.BeginInvokeOnMainThread(() =>
{
AndHUD.Shared.Show(Forms.Context, Text, maskType: MaskType.Black);
});
}
Android.Views.View CustomLoadingView(string ProgressText)
{
Android.Views.View loadingView = new Android.Views.View(Forms.Context);
return loadingView;
}
#endregion
}
}
iOS Code
using System;
using BigTed;
using CoreAnimation;
using CoreGraphics;
using MyApp.DependencyServices;
using MyApp.Helpers;
using MyApp.iOS;
using Foundation;
using UIKit;
using Xamarin.Forms;
[assembly: Dependency(typeof(IosHudService))]
namespace MyApp.iOS
{
public class IosHudService : IHudService
{
UIView _load;
bool isHudVisible;
#region IHudManager implementation
public void ShowHud(string ProgressText = StaticData.Loading)
{
isHudVisible = true;
SetText(ProgressText);
}
public void HideHud()
{
Device.BeginInvokeOnMainThread(() =>
{
BTProgressHUD.Dismiss();
if (_load != null)
_load.Hidden = true;
isHudVisible = false;
});
}
public void SetProgress(double Progress, string ProgressText = "")
{
int progress = (int)(Progress * 100);
string text = ProgressText + progress + "%";
SetText(text);
}
public void SetText(string text)
{
if (!isHudVisible)
return;
Device.BeginInvokeOnMainThread(() =>
{
BTProgressHUD.Show(status: text, maskType: ProgressHUD.MaskType.Black);
try
{
lblTitle.Text = text;
UIView[] subView = ProgressHUD.Shared.Subviews;
for (int i = 0; i < subView.Length; i++)
{
subView[i].Hidden = true;
}
_load.Hidden = false;
ProgressHUD.Shared.BringSubviewToFront(_load);
}
catch (Exception ex)
{
Console.WriteLine("IosHudService.cs - SetText() " + ex.Message);
}
});
}
UILabel lblTitle;
UIView CustomLoadingView(string ProgressText)
{
UIView loadingView = new UIView();
loadingView.Frame = new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height);
UIImageView imgBg = new UIImageView();
imgBg.Image = UIImage.FromFile("load_bg.png");
imgBg.Frame = new CGRect((loadingView.Frame.Width / 2) - 65, (loadingView.Frame.Height / 2) - 70, 130, 140);
loadingView.Add(imgBg);
UIImageView someImageView = new UIImageView();
someImageView.Frame = new CGRect((loadingView.Frame.Width / 2) - 40, (loadingView.Frame.Height / 2) - 50, 75, 75);
someImageView.AnimationImages = new UIImage[]
{
UIImage.FromBundle("spinner.png"),
};
someImageView.AnimationRepeatCount = nint.MaxValue; // Repeat forever.
someImageView.AnimationDuration = 1.0; // Every 1s.
someImageView.StartAnimating();
CABasicAnimation rotationAnimation = new CABasicAnimation();
rotationAnimation.KeyPath = "transform.rotation.z";
rotationAnimation.To = new NSNumber(Math.PI * 2);
rotationAnimation.Duration = 1;
rotationAnimation.Cumulative = true;
rotationAnimation.RepeatCount = float.PositiveInfinity;
someImageView.Layer.AddAnimation(rotationAnimation, "rotationAnimation");
loadingView.Add(someImageView);
lblTitle = new UILabel();
lblTitle.Text = ProgressText;
lblTitle.Frame = new CGRect(imgBg.Frame.X, someImageView.Frame.Y + someImageView.Frame.Height + 15, 130, 20);
lblTitle.TextAlignment = UITextAlignment.Center;
lblTitle.TextColor = UIColor.White;
lblTitle.AdjustsFontSizeToFitWidth = true;
loadingView.Add(lblTitle);
return loadingView;
}
#endregion
}
}
Show/Hide via DependencyService Method
public static void ShowLoadingIndicator(string progressText = "Loading...")
{
Device.BeginInvokeOnMainThread(() =>
{
DependencyService.Get<IHudService>().ShowHud(progressText);
});
}
public static void HideLoadingIndicator()
{
Device.BeginInvokeOnMainThread(() =>
{
DependencyService.Get<IHudService>().HideHud();
});
}
I have manage my code by Creating Disposable class and use it in ViewModels like this:
public class Busy : IDisposable
{
readonly object _sync = new object();
readonly BaseViewModel _viewModel;
readonly bool _showProgressView;
public Busy(BaseViewModel viewModel, bool showProgressView, string displayMessage = null)
{
try
{
_viewModel = viewModel;
lock (_sync)
{
_viewModel.IsBusy = true;
_showProgressView = showProgressView;
if (_showProgressView)
{
if (string.IsNullOrEmpty(displayMessage))
{
displayMessage = "Loading...";
}
DependencyService.Get<IHudService>().ShowHud(displayMessage);
}
}
}
catch(Exception ex)
{
ex.Track();
}
}
public void Dispose()
{
try
{
lock (_sync)
{
_viewModel.IsBusy = false;
if (_showProgressView)
{
DependencyService.Get<IHudService>().HideHud();
}
}
}
catch(Exception ex)
{
ex.Track();
}
}
}
Show the loader indicater via using instance of Busy class Like this:
using (new Busy(this, true))
{
//Your api or waiting stuff
}

How do I solve Xamarin.Forms.Xaml.XamlParseException

I am new to Xamarin Forms and C# too. Kindly help me to get rid of the above issue.
I am getting
Xamarin.Forms.Xaml.XamlParseException
while I am trying to add the string format of my selected image source to CandidateDetails.cs.
The pages are as follow:
CandidateDetails.cs
public event PropertyChangedEventHandler PropertyChanged;
private string imageBase64;
public string ImageBase64
{
get { return imageBase64; }
set
{
imageBase64 = value;
OnPropertyChanged("ImageBase64");
CandImage = Xamarin.Forms.ImageSource.FromStream(
() => new MemoryStream(Convert.FromBase64String(imageBase64)));
}
}
private Xamarin.Forms.ImageSource _candImage;
public Xamarin.Forms.ImageSource CandImage
{
get { return _candImage; }
set
{
_candImage = value;
OnPropertyChanged("CandImage");
}
}
public string _candName;
public string CandName
{
get { return _candName; }
set
{
if (_candName == value)
return;
_candName = value;
OnPropertyChanged("CandName");
}
}
public string _candInst;
public string CandInst
{
get { return _candInst; }
set
{
if (_candInst == value)
return;
_candInst = value;
OnPropertyChanged("CandInst");
}
}
public string _candEmailId;
public string CandEmailId
{
get { return _candEmailId; }
set
{
if (_candEmailId == value)
return;
_candEmailId = value;
OnPropertyChanged("CandEmailId");
}
}
public string _candMob;
public string CandMob
{
get { return _candMob; }
set
{
if (_candMob == value)
return;
_candMob = value;
OnPropertyChanged("CandMob");
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
CandidateDetailsModalPage.xaml.cs
public partial class CandidateDetailsModalPage : ContentPage
{
Stream input;
string imageAsString;
public static ObservableCollection<CandidateDetails> _candidate = new ObservableCollection<CandidateDetails>();
async void OnDoneClicked(object sender, System.EventArgs e)
{
_candidate.Add(new CandidateDetails
{
CandName = (string)candNameEntry.Text,
CandEmailId = (string)candEmailId.Text,
CandMob = (string)candMobNumber.Text,
ImageBase64 = imageAsString
});
await Navigation.PopModalAsync();
}
public CandidateDetailsModalPage()
{
InitializeComponent();
pickPhoto.Clicked += async (sender, args) =>
{
if (!CrossMedia.Current.IsPickPhotoSupported)
{
await DisplayAlert("Photos Not Supported", ":( Permission not granted to photos.", "OK");
return;
}
var file = await CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium
});
if (file == null)
return;
input = file.GetStream();
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
imageAsString = Convert.ToBase64String(ms.ToArray());
}
//image.Source = ImageSource.FromStream(() => new MemoryStream(Convert.FromBase64String(imageAsString)));
image.Source = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
file.Dispose();
return stream;
});
};
}
}
CandidateDetailsModalPage.xaml
<StackLayout Orientation="Vertical" BackgroundColor="#3B4371" Padding="0">
<Button Text="Done" Clicked="OnDoneClicked" />
<ic:CircleImage x:Name="image"/>
<Button x:Name="pickPhoto" Text="Pick Photo"/>
<StackLayout VerticalOptions="FillAndExpand">
<Entry x:Name="candNameEntry" Placeholder="Candidate Name" />
<Entry x:Name="candInst" Placeholder="Candidate Institution / Party"/>
<Entry x:Name="candEmailId" Placeholder="Candidate Email Id" />
<Entry x:Name="candMobNumber" Placeholder="Candidate Mobile Number"/>
</StackLayout>
</StackLayout>
The following is the backend of the xaml page where I am displaying the data CandidateDisplayPage.xaml.cs
candidateListView.ItemsSource = CandidateDetailsModalPage._candidate;
CandidateDisplayPage
<ListView x:Name="candidateListView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<Image x:Name="candImage" ImageSource="{Binding Path=CandImage}"/>
<Label Text="{Binding CandName}" x:Name="candName"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You say in a comment (that should have been part of the original report):
This is the error that's causing the exception Cannot assign property "ImageSource": Property does not exists, or is not assignable, or mismatching type between value and property
Indeed, Image doesn't have an ImageSource property, but instead a Source property of type ImageSource.
public ImageSource Source { get; set; }
So your Xaml should look like
Image x:Name="candImage" Source="{Binding Path=CandImage}"/>

Resources