Highlight URL using label span - xamarin.forms - xamarin

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); }
}
}

Related

xamarin xzing barcode scanner re-scan

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;
});
}

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();
}
}

Xamarin Forms: Flowlistview item tapping issue

I am implementing a game in my xamarin forms application, a name match game. It has two lists: one for showing the images and the other one for showing the names. The player taps the image from the top list and then taps the name from the bottom list(or name first then image). If they match the player gets points and tapped image and name will be removed from lists.
I am using flowlistview for showing the image list and name list because I need to show multiple items in a row. When tapping the image and name I have done the matching and remove the image and name if they matched. But I need to highlight the selected image or selected name when tapping and disable selection for other items. I have done highlighting feature using this thread, but it is not working perfectly. Sometimes multiple images are highlighting and sometimes when selecting an image on the top name on the below list is automatically highlighting.
I have created a sample project and uploaded it here. Please help me to complete this game. We have this game on our website, https://www.catholicbrain.com/edu-namematch/39524/1/the-two-great-commandments , please have a look at it for the working of the game. I will give you login details by DM.
Edit 1
#LucasZhang-MSFT I am accepting that, but the current question is different. It has 2 different flowlistviews. On the top an image list and on the bottom a name list. This is a simple game for kids, the player taps the image from the top list and then taps the name from the bottom list(or name first then image). If they match the player gets points and tapped image and name will be removed from lists. When not match I reset the items background colors like below:
foreach (var item1 in ImageItems)
{
item.BGColor = Color.White;
}
foreach (var item2 in NameItems)
{
item.BGColor = Color.White;
}
OnPropertyChanged("NameMatchImagItems");
OnPropertyChanged("NameMatchNameItems");
After this point, multiple images are highlighting and sometimes when selecting an image on the top name on the below list is automatically highlighting.
If you have time, can you please download the sample and have a look? I tried my best, but no luck.
Cause:
You set the ItemsSource of two flowlistview with the same source _allItems !!
Solution:
in xaml
<ContentPage.Content>
<StackLayout Orientation="Vertical">
<!--imageflowlistview-->
<flv:FlowListView
x:Name="NameMatchImageList"
FlowItemTappedCommand="{Binding ImageItemTappedCommand}"
FlowItemsSource="{Binding ImageItems}"
FlowColumnCount="2"
FlowLastTappedItem="{Binding LastImageTappedItem}"
HasUnevenRows="True">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout BackgroundColor="{Binding BGColor}" Orientation="Vertical">
<Frame
Padding="5"
Margin="5"
HasShadow="False"
BorderColor="#a4e6f9"
CornerRadius="15">
<ffimageloading:CachedImage
Source="{Binding imageUrl, Converter={StaticResource urlJoinConverter}}"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HeightRequest="100"
Aspect="AspectFill"/>
</Frame>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
<!--NamesFlowlistview-->
<flv:FlowListView
x:Name="NameMatchNameList"
FlowItemTappedCommand="{Binding NameItemTappedCommand}"
FlowItemsSource="{Binding NameItems}"
FlowColumnCount="2"
FlowLastTappedItem="{Binding LastNameTappedItem}"
HasUnevenRows="True">
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<StackLayout Orientation="Vertical">
<Label
TextColor="Black"
FontSize="Large"
BackgroundColor="{Binding BGColor}"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
Text="{Binding name}"/>
</StackLayout>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</ContentPage.Content>
in code behind
namespace FlowListView_Tap
{
class NameMatchViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<NameMatchList> imageItems;
public ObservableCollection<NameMatchList> ImageItems { get
{
return imageItems;
}
set {
if(value!=null)
{
imageItems = value;
OnPropertyChanged("ImageItems");
}
}
}
public ObservableCollection<NameMatchList> nameItems;
public ObservableCollection<NameMatchList> NameItems
{
get
{
return nameItems;
}
set
{
if (value != null)
{
nameItems = value;
OnPropertyChanged("NameItems");
}
}
}
public bool isImageSelected = false;
public bool isNameSelected = false;
public ICommand NameItemTappedCommand { get; set; }
public ICommand ImageItemTappedCommand { get; set; }
private NameMatchList lastImageTappedItem;
public NameMatchList LastImageTappedItem
{
get
{
return lastImageTappedItem;
}
set
{
if(value!=null)
{
lastImageTappedItem = value;
OnPropertyChanged("LastImageTappedItem");
}
}
}
private NameMatchList lastNameTappedItem;
public NameMatchList LastNameTappedItem
{
get
{
return lastNameTappedItem;
}
set
{
if (value != null)
{
lastNameTappedItem = value;
OnPropertyChanged("LastNameTappedItem");
}
}
}
public NameMatchViewModel()
{
ImageItemTappedCommand = new Command((obj) => {
try
{
//reset the bg color
foreach (var item in ImageItems)
{
item.BGColor = Color.White;
}
NameMatchList imageList = obj as NameMatchList;
int index = ImageItems.IndexOf(imageList);
imageList.BGColor = Color.Red;
///ImageItems.RemoveAt(index);
//ImageItems.Insert(index, imageList);
//Storing name and imageurl to local db
Application.Current.Properties["NameMatchImageList_Image"] = imageList.imageUrl;
Application.Current.Properties["NameMatchImageList_Name"] = imageList.name;
Application.Current.Properties["ImageItem"] = imageList;
isImageSelected = true;
if (isImageSelected && isNameSelected)
{
//If both image and name selected by player startes checking the matching
StartNameMatchCheck(imageList);
}
}
catch (Exception imagetapEx)
{
Debug.WriteLine("imagetapEx:>>" + imagetapEx);
}
});
NameItemTappedCommand = new Command((obj) => {
try
{
//reset the bg color
foreach (var item in NameItems)
{
item.BGColor = Color.White;
}
NameMatchList nameList = obj as NameMatchList;
int index = NameItems.IndexOf(nameList);
nameList.BGColor = Color.Red;
//NameItems.RemoveAt(index);
//NameItems.Insert(index, nameList);
//Storing name and imageurl to local db
Application.Current.Properties["NameMatchNameList_Image"] = nameList.imageUrl;
Application.Current.Properties["NameMatchNameList_Name"] = nameList.name;
Application.Current.Properties["NameItem"] = nameList;
isNameSelected = true;
if (isImageSelected && isNameSelected)
{
//If both image and name selected by player startes checking the matching
StartNameMatchCheck(nameList);
}
}
catch (Exception nametapEx)
{
Debug.WriteLine("nametapEx:>>" + nametapEx);
}
});
}
public async void StartNameMatchCheck(NameMatchList item)
{
isImageSelected = false;
isNameSelected = false;
//Fetching data from local db
string NameMatchImageListImage = Application.Current.Properties["NameMatchImageList_Image"].ToString();
string NameMatchImageListName = Application.Current.Properties["NameMatchImageList_Name"].ToString();
string NameMatchNameListImage = Application.Current.Properties["NameMatchNameList_Image"].ToString();
string NameMatchNameListName = Application.Current.Properties["NameMatchNameList_Name"].ToString();
//Match check
if ((NameMatchImageListImage == NameMatchNameListImage) && (NameMatchImageListName == NameMatchNameListName))
{
await Application.Current.MainPage.DisplayAlert("Alert", "Success", "Ok");
//Removing the items from list if they match
ImageItems.Remove(LastImageTappedItem);
NameItems.Remove(LastNameTappedItem);
LastImageTappedItem = null;
LastNameTappedItem = null;
}
else
{
await Application.Current.MainPage.DisplayAlert("Alert", "Failed", "Ok");
//resetting the colors
LastImageTappedItem.BGColor = Color.White;
LastNameTappedItem.BGColor = Color.White;
}
}
public async void CallNameMatch()
{
try
{
//HttpClient client = new HttpClient();
//var nameMatchResponse = await client.GetAsync("");
//if (nameMatchResponse.IsSuccessStatusCode)
//{
// var Response = await nameMatchResponse.Content.ReadAsStringAsync();
// var imageResponse = JsonConvert.DeserializeObject<Games>(Response.ToString());
// var namematch = JsonConvert.DeserializeObject<Games>(Response.ToString());
ImageItems = new ObservableCollection<NameMatchList>();
ImageItems.Add(new NameMatchList() { name = "Comfort the Sorrowing", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/06/971/head/Comfort the Sorrowing.png" });
ImageItems.Add(new NameMatchList() { name = "Giving Food To The Hungry", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/23/784/head/Giving Food To The Hungry.png" });
ImageItems.Add(new NameMatchList() { name = "Pray for the Living and The Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/39/707/head/Pray for the Living and The Dead.png" });
ImageItems.Add(new NameMatchList() { name = "To bury the Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/54/828/head/To bury the Dead.png" });
//shuffling image list
//Random r1 = new Random();
//int randomIndex1 = 0;
//while (ImageItems.Count > 0)
//{
// randomIndex1 = r1.Next(0, ImageItems.Count);
// ImageItems[randomIndex1].BGColor = Color.White;
// ImageItems.Add(ImageItems[randomIndex1]);
// ImageItems.RemoveAt(randomIndex1);
//}
//NameMatchImagItems = new ObservableCollection<NameMatchList>(ImageItems);
NameItems = new ObservableCollection<NameMatchList>();
NameItems.Add(new NameMatchList() { name = "To bury the Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/54/828/head/To bury the Dead.png" });
NameItems.Add(new NameMatchList() { name = "Pray for the Living and The Dead", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/39/707/head/Pray for the Living and The Dead.png" });
NameItems.Add(new NameMatchList() { name = "Comfort the Sorrowing", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/06/971/head/Comfort the Sorrowing.png" });
NameItems.Add(new NameMatchList() { name = "Giving Food To The Hungry", imageUrl = "/cbrain-app/files/doc-lib/2018/02/22/11/46/23/784/head/Giving Food To The Hungry.png" });
//shuffling name list
//Random r2 = new Random();
//int randomIndex2 = 0;
//while (NameItems.Count > 0)
//{
// randomIndex2 = r2.Next(0, NameItems.Count);
// NameItems[randomIndex2].BGColor = Color.White;
// NameItems.Add(NameItems[randomIndex2]);
// NameItems.RemoveAt(randomIndex2);
//}
// NameMatchNameItems = new ObservableCollection<NameMatchList>(NameItems);
//}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("NMException:>" + ex);
}
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void ShowAlert(string message)
{
Device.BeginInvokeOnMainThread(async () => {
await Application.Current.MainPage.DisplayAlert("Alert", message, "Ok");
});
}
}
}

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