PulltoRefresh + await FadeTo+TranslateTo animation combination Crash on Xamarin.forms - xamarin

I'm making app with using Xamarin.forms PCL.
I'm having really hard time to solve this issue.
I've spent several days but couldn't solve.
I'm using this nuget
PullToRefresh
https://github.com/jamesmontemagno/Xamarin.Forms-PullToRefreshLayout
xabre ble plugin
https://github.com/xabre/xamarin-bluetooth-le
When I use this at the same time AND Add some animation on the page.
It gives this runtime exception when I try to refresh. (It happens very rare like 1 time on 10 times)
The Crash happens ONLY iOS.
It's very rare. You can try to refresh more than 10 times. You will see.
It happens only actual iOS Device.
Foundation.MonoTouchException: Objective-C exception thrown. Name:
NSGenericException Reason: *** Collection <__NSSetM: 0x14feb8b0> was
mutated while being enumerated.
I know well what this is and when this happen.
It happens when I try to access deleted item on list or different thread.
So I made very simple source code to look this issue simply.
I don't have any list or array on my code.
Well, it happens again.
https://github.com/myallb/test_pulltorefresh
This is my sample source code for reproducing this issue. If you can help me, please look this code.
The Crash happens ONLY iOS.
It's very rare. You can try to refresh more than 10 times. You will see.
It happens only actual iOS Device.
Thanks so much.
Full source code
using Xamarin.Forms;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE;
using Plugin.BLE.Abstractions.EventArgs;
using System;
using System.Diagnostics;
using Refractored.XamForms.PullToRefresh;
using System.Threading.Tasks;
namespace test
{
public partial class testPage : ContentPage
{
public static IAdapter Adapter { set; get; }
public PullToRefreshLayout RefreshView = null;
AbsoluteLayout layout;
public testPage()
{
InitializeComponent();
Adapter = CrossBluetoothLE.Current.Adapter;
if (Adapter != null)
{
Adapter.DeviceAdvertised += OnEvent_DeviceAdvertised;
Adapter.DeviceConnected += OnEvent_DeviceConnected;
Adapter.DeviceConnectionLost += OnEvent_DeviceConnectionLost;
Adapter.DeviceDisconnected += OnEvent_DeviceDisconnected;
Adapter.DeviceDiscovered += OnEvent_DeviceDiscovered;
Device.StartTimer(TimeSpan.FromSeconds(5), Timer_ScanDevice);
}
else {
}
layout = new AbsoluteLayout()
{
BackgroundColor = Color.Purple,
};
ScrollView scrollview = new ScrollView()
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Content = layout
};
RefreshView = new PullToRefreshLayout
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Content = scrollview,
RefreshColor = Color.Red,
RefreshCommand = new Command(RefreshStart)
};
RefreshView.IsPullToRefreshEnabled = true;
Content = RefreshView;
Device.StartTimer(new TimeSpan(0, 0, 1), ani);
}
bool ani()
{
Label z = new Label()
{
Text = "Z",
TextColor = Color.White,
FontAttributes = FontAttributes.Bold,
FontSize = new Random().Next(22, 35)
};
AbsoluteLayout.SetLayoutBounds(z, new Rectangle(0.67 + new Random().Next(0, 10) / 100.0, 0.13 + new Random().Next(0, 10) / 100.0, 40, 40));
AbsoluteLayout.SetLayoutFlags(z, AbsoluteLayoutFlags.PositionProportional);
layout.Children.Add(z);
Device.BeginInvokeOnMainThread(async () =>
{
Task t1 = z.FadeTo(0, 3500);
Task t2 = z.TranslateTo(0, -70, 3500, Easing.SinInOut);
await Task.WhenAll(t1, t2);
layout.Children.Remove(z);
});
return true;
}
void RefreshStart()
{
Debug.WriteLine("RefreshStart");
if (RefreshView != null)
RefreshView.IsRefreshing = true;
Device.BeginInvokeOnMainThread(async () =>
{
await Task.Delay(20);
Debug.WriteLine("RefreshEnd");
RefreshView.IsRefreshing = false;
});
}
bool Timer_ScanDevice()
{
Adapter.StartScanningForDevicesAsync();
return true;
}
void OnEvent_DeviceAdvertised(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceAdvertised");
}
void OnEvent_DeviceDiscovered(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceDiscovered");
}
void OnEvent_DeviceConnected(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceConnected");
}
void OnDeviceProcessError(IDevice device, string message)
{
Debug.WriteLine("OnDeviceProcessError");
}
void OnEvent_DeviceConnectionLost(object sender, DeviceErrorEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceConnectionLost");
}
void OnEvent_DeviceDisconnected(object sender, DeviceEventArgs a)
{
Debug.WriteLine("OnEvent_DeviceDisconnected");
}
}
}

Related

Receiving event Button in a Grid event from outside

I have created a custom control.
It consists of a Grid in which a Button is placed:
using Xamarin.Forms;
namespace MyApp
{
public class clsButton : ContentView
{
private Grid _grid;
private Button _button;
public clsButton()
{
_grid = new Grid
{
Margin = new Thickness(0),
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
_grid.BindingContext = this;
_button = new Button()
{
};
_button.Clicked += async (sender, e) =>
{
//I tried different things here, but none gave me the right results. I need to "bubble" this click to the outside
return;
};
_grid.Children.Add(_button, 0, 0);
this.Content = _grid;
}
}
}
I create some of these custom controls in a ContentPage like this:
_MyButton = new clsImageButton()
{
};
var nTapGestureRecognizer = new TapGestureRecognizer();
nTapGestureRecognizer.Tapped += OnButtonClicked;
_MyButton.GestureRecognizers.Add(nTapGestureRecognizer);
And this is the void in the same ContentPage:
async void OnButtonClicked(object sender,EventArgs e)
{
//I don't managed to get here
}
This doesn't work.
"OnButtonClicked" is never called.
I think I have to raise an event from within the custom control.
I tried some things, but none of them were successful.
How would I do this correctly?
in your clsButton, declare a public event
public EventHandler ButtonClicked { get; set; }
then raise the event when your button is clicked
_button.Clicked += async (sender, e) =>
{
if (ButtonClicked != null) ButtonClicked(this,e);
};
finally, where ever you are using clsButton, you can subscribe to the event (the gesture recognizer is not needed)
var btn = new clsButton();
btn.ButtonClicked += async (sender, e) => {
// respond here
}

xamarin forms dynamically adding custom font labels to scrollview is extremely slow

So I have a horizontal scrollview that I'm trying to dynamically populate when the user takes a certain action. The items I am throwing into the view each contain 4 labels that are using custom fonts. When I try to add about 10 of these items it lags for about 1.5 seconds on android and 1 second on IOS. If I take the custom font out then its about 1 second on each platform. If I take out 3 of the labels and only display one then its almost instantaneous. Is there any known reason for the lag? And is there any way around it so I can still use a custom font without a huge lag?
Here's a quick sample I made that pretty much does what I'm doing in my app. However, my app has more stuff so the lag isn't quite as bad here but it is still very noticeable
public class App : Application
{
public int count;
public ScrollView scroll, scroll2, scroll3;
public App ()
{
count = 1;
scroll = new ScrollView {
VerticalOptions = LayoutOptions.Center,
Orientation = ScrollOrientation.Horizontal
};
scroll2 = new ScrollView {
VerticalOptions = LayoutOptions.Center,
Orientation = ScrollOrientation.Horizontal
};
Button button = new Button(){
Text = "click",
};
button.Clicked += (sender, e) => AddStuff();
Button button2 = new Button(){
Text = "click",
};
button2.Clicked += (sender, e) => AddStuff2();
MainPage = new ContentPage {
BackgroundColor = Color.White,
Content = new StackLayout{
Children={
button,
scroll,
button2,
scroll2
}
}
};
}
//this one is instantaneous
public void AddStuff()
{
StackLayout stack = new StackLayout () {
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
HeightRequest = 200,
};
for (int i = 0; i < 11; i++)
stack.Children.Add (
new StackLayout(){
Children = {
new Label (){TextColor = Color.Blue, Text = "Size: ", WidthRequest = 100 },
}
}
);
scroll.Content = stack;
count++;
}
//this one takes forever
public void AddStuff2()
{
StackLayout stack = new StackLayout () {
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
HeightRequest = 200,
};
for (int i = 0; i < 11; i++)
stack.Children.Add (
new StackLayout(){
Children = {
new Label (){TextColor = Color.Blue, Text = "Size: ", WidthRequest = 100 },
new Label (){TextColor = Color.Blue, Text ="" + count*i, WidthRequest = 100 },
new Label (){TextColor = Color.Blue, Text = "Size: ", WidthRequest = 100 },
new Label (){TextColor = Color.Blue, Text ="" + count*i, WidthRequest = 100 }
}
}
);
scroll2.Content = stack;
count++;
}
}
and the custom font label for droid
[assembly: ExportRenderer (typeof (Label), typeof (CustomFontLabel_Droid))]
namespace df.Droid
{
public class CustomFontLabel_Droid:LabelRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.Label> e) {
base.OnElementChanged (e);
var label = (TextView)Control;
Typeface font = Typeface.CreateFromAsset (Forms.Context.Assets, "SourceSansPro-Semibold.otf");
label.Typeface = font;
}
}
}
Just incase anyone else is having a similar problem, if you make a static typeface property in the android MainActivity instead of calling createFromAsset inside the Label.OnElementChanged function every time then it gets rid of the extra lag on android.
CustomFontLabel_Droid.cs
[assembly: ExportRenderer (typeof (Label), typeof (CustomFontLabel_Droid))]
namespace df.Droid
{
public class CustomFontLabel_Droid:LabelRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.Label> e) {
base.OnElementChanged (e);
var label = (TextView)Control;
// this guy slows things down-> Typeface font = Typeface.CreateFromAsset (Forms.Context.Assets, "SourceSansPro-Semibold.otf");
label.Typeface = MainActivity.semiBoldFont;
}
}
}
MainActivity.cs
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
public static Typeface semiBoldFont = null;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
global::Xamarin.Forms.Forms.Init (this, bundle);
LoadApplication (new App ());
semiBoldFont = Typeface.CreateFromAsset (Forms.Context.Assets, "SourceSansPro-Semibold.otf");
}
}

ActivityIndicator in Xamarin.Forms

Currently I am working on a project in which I have to navigate from one page (page1) to another (page2). As page2 is taking a long time to load so I was thinking to use ActivityIndicator, but it is not effective at all.
Please give me complete code to resolve my problem.
Page(1)
//code for tapping a label and to navigate to page2
void onpage1tapped(Object sender,EventArgs args)
{ label1.Opacity=0.5;
Device.StartTimer(TimeSpan.FromMilliseconds(100),()=>{label1.Opacity=1;return false; })
This.Navigate.PushAsync(new Page2());
}
Page(2)
//constructor for page2
public page2
{ InitializeComponent();
ActivityIndicator indicator=new ActivityIndicator{HorizontalOptions=LayoutOptions.CenterAndExpand,Color=Color.Black,IsVisible=false};
StackLayout stk=new StackLayout();
stk.Children.Add(indicator);
//Enabling activityindicator
indicator.IsRunning=true;
indicator.IsVisible=true;
//Below long time taking task
Assembly assembly = GetType().GetTypeInfo().Assembly;
string resource = String.Format("ks.texts.BST.txt");
//string resource = "advjava.texts.insertion.txt";
using (Stream s = assembly.GetManifestResourceStream(resource))
{
using (StreamReader sr = new StreamReader(s))
{
string line;
while (null != (line = sr.ReadLine()))
{
Label l = new Label { Text = line, TextColor = Color.FromRgb(110, 139, 69), FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)) };
stk.Children.Add(l);
}
}
}
//Disabling Activity indicator as the long task is complete
indicator.IsVisible=false;
indicator.IsRunning=false;
}
Use Acr.XamForms.UserDialogs Package. Have a look at it.. Hope it helps
Here is a documentation on how to implement Acr.XamForms.UserDialogs..
Hope it helps
This is only a sample code, But it's logic is right and pretty good works for me. :)
var indicator = new ActivityIndicator() {
HorizontalOptions = LayoutOptions.CenterAndExpand
};
indicator.SetBinding(ActivityIndicator.IsVisibleProperty, "IsBusy", BindingMode.OneWay);
indicator.SetBinding(ActivityIndicator.IsRunningProperty, "IsBusy", BindingMode.OneWay);
var button = new Button() {
HorizontalOptions = LayoutOptions.Fill,
Text = "BUTTON"
};
button.Clicked += (sender, e) => IsBusy = !IsBusy;
var root = new StackLayout() {
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Children = {
button,
indicator
}
};
Content = root;
You can refer this Link, which give a clear note on Github by ChaseFlorell.
Working Sample : Github
The Issue is with how your code is being read. It'll finish, then execute, but if you utilize the Task.Run, you'll be able to run on separate threads and keep that activity indicator up and running to show that your page is loading and your machine is busy.
void onpage1tapped(Object sender,EventArgs args) {
activityIndicator.IsVisible= true;
Task.Run( () => {
label1.Opacity=0.5;
Device.StartTimer(TimeSpan.FromMilliseconds(100),()= {
label1.Opacity=1;returnfalse;
})
This.Navigate.PushAsync(new Page2());
});
}

Expanding the size of ListView cell on tap

I'm trying to implement a solution to increase the size of a ListView Cell when tapped using Xamarin Forms (and custom renderers if required).
I'm still pretty new to C#, and the idea of data binding is still a little unclear to me, however, it seems like that is the way to go to solve this problem (perhaps something along the lines of binding the Height / HeightRequest properties of the cell?).
My attempts thus far have been unsuccessful.
If anyone could give me a push in the right direction it would be much appreciated.
Thank you!
ViewCell does not expose Height as a BindableProperty in Xamarin.Forms 1.4.2x
However if you create your own BindableProperty in your Model you can achieve changing the height still as shown below:-
Model:-
public class MenuItem2 : BindableObject
{
public static readonly BindableProperty TextProperty = BindableProperty.Create<MenuItem2, string>(p => p.Text, default(string));
public static readonly BindableProperty CellHeightProperty = BindableProperty.Create<MenuItem2, int>(p => p.CellHeight, default(int));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public int CellHeight
{
get { return (int)GetValue(CellHeightProperty); }
set { SetValue(CellHeightProperty, value); }
}
}
XAML:-
<StackLayout>
<Button x:Name="cmdButton1" Text="Change Cell Heights" Clicked="cmdButton1_Clicked"/>
<ListView x:Name="lstItems" />
</StackLayout>
XAML Code-Behind:-
lstItems.HasUnevenRows = true;
lstItems.ItemTemplate = new DataTemplate(typeof(Classes.MenuCell2));
//
lstItems.ItemsSource = new List<MenuItem2>
{
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
new MenuItem2(),
};
If you don't set .HasUnevenRows you will not be able to change the cell height.
void cmdButton1_Clicked(object sender, EventArgs e)
{
Random objRandom = new Random();
//
var objItems = lstItems.ItemsSource;
//
foreach (MenuItem2 objMenuItem in objItems)
{
int intNewCellHeight = objRandom.Next(80, 160);
objMenuItem.CellHeight = intNewCellHeight;
objMenuItem.Text = "Cell Height = " + intNewCellHeight.ToString();
}
}
Custom ViewCell:-
public class MenuCell2 : ViewCell
{
public MenuCell2()
{
Label objLabel = new Label
{
YAlign = TextAlignment.Center,
TextColor = Color.Yellow,
};
objLabel.SetBinding(Label.TextProperty, new Binding("Text"));
StackLayout objLayout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.StartAndExpand,
Children = { objLabel }
};
Frame objFrame_Inner = new Frame
{
Padding = new Thickness(15, 15, 15, 15),
HeightRequest = 36,
OutlineColor = Color.Accent,
BackgroundColor = Color.Blue,
Content = objLayout,
};
Frame objFrame_Outer = new Frame
{
Padding = new Thickness(0, 0, 0, 10),
Content = objFrame_Inner
};
View = objFrame_Outer;
this.BindingContextChanged += MenuCell2_BindingContextChanged;
}
void MenuCell2_BindingContextChanged(object sender, EventArgs e)
{
MenuItem2 objMenuItem = (MenuItem2)this.BindingContext;
objMenuItem.PropertyChanged += objMenuItem_PropertyChanged;
}
void objMenuItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "CellHeight":
this.Height = (this.BindingContext as MenuItem2).CellHeight;
(this.View as Frame).ForceLayout();
break;
}
}
Remember to call ForceLayout on the root element of the ViewCell's View property, so it can redraw correctly.
This will give you a result something similar to the following (tested only on WindowsPhone at present):-
In order to do it on a ViewCell being tapped, on the XAML Page add:-
lstItems.ItemTapped += lstItems_ItemTapped;
and then change the model for the item to something like this:-
void lstItems_ItemTapped(object sender, ItemTappedEventArgs e)
{
(e.Item as MenuItem2).CellHeight = 200;
}
Xamarin now has an official example of doing this right within xaml and xaml code behind:
Overview:
https://developer.xamarin.com/samples/xamarin-forms/UserInterface/ListView/DynamicUnevenListCells/
Code:
https://github.com/xamarin/xamarin-forms-samples/tree/master/UserInterface/ListView/DynamicUnevenListCells

Placing a SearchBar in the top/navigation bar

In a Forms project, is it possible to place a SearchBar such that it appears in the top/navigation bar of the app? What I want to achieve is something along the lines of the Android Youtube app, just cross-platform:
To do this, you should write a renderer for your Page
There is my implementation for iOS (with custom 'searchField')
using CoreGraphics;
using Foundation;
using MyControls;
using MyRenderer;
using UIKit;
using Xamarin.Forms;
[assembly: ExportRenderer(typeof(MySearchContentPage), typeof(MySearchContentPageRenderer))]
namespace IOS.Renderer
{
using Xamarin.Forms.Platform.iOS;
public class MySearchContentPageRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
SetSearchToolbar();
}
public override void WillMoveToParentViewController(UIKit.UIViewController parent)
{
base.WillMoveToParentViewController(parent);
if (parent != null)
{
parent.NavigationItem.RightBarButtonItem = NavigationItem.RightBarButtonItem;
parent.NavigationItem.TitleView = NavigationItem.TitleView;
}
}
private void SetSearchToolbar()
{
var element = Element as MySearchContentPage;
if (element == null)
{
return;
}
var width = NavigationController.NavigationBar.Frame.Width;
var height = NavigationController.NavigationBar.Frame.Height;
var searchBar = new UIStackView(new CGRect(0, 0, width * 0.85, height));
searchBar.Alignment = UIStackViewAlignment.Center;
searchBar.Axis = UILayoutConstraintAxis.Horizontal;
searchBar.Spacing = 3;
var searchTextField = new MyUITextField();
searchTextField.BackgroundColor = UIColor.FromRGB(239, 239, 239);
NSAttributedString strAttr = new NSAttributedString("Search", foregroundColor: UIColor.FromRGB(146, 146, 146));
searchTextField.AttributedPlaceholder = strAttr;
searchTextField.SizeToFit();
// Delete button
UIButton textDeleteButton = new UIButton(new CGRect(0, 0, searchTextField.Frame.Size.Height + 5, searchTextField.Frame.Height));
textDeleteButton.Font = UIFont.FromName("FontAwesome", 18f);
textDeleteButton.BackgroundColor = UIColor.Clear;
textDeleteButton.SetTitleColor(UIColor.FromRGB(146, 146, 146), UIControlState.Normal);
textDeleteButton.SetTitle("\uf057", UIControlState.Normal);
textDeleteButton.TouchUpInside += (sender, e) =>
{
searchTextField.Text = string.Empty;
};
searchTextField.RightView = textDeleteButton;
searchTextField.RightViewMode = UITextFieldViewMode.Always;
// Border
searchTextField.BorderStyle = UITextBorderStyle.RoundedRect;
searchTextField.Layer.BorderColor = UIColor.FromRGB(239, 239, 239).CGColor;
searchTextField.Layer.BorderWidth = 1;
searchTextField.Layer.CornerRadius = 5;
searchTextField.EditingChanged += (sender, e) =>
{
element.SetValue(MySearchContentPage.SearchTextProperty, searchTextField.Text);
};
searchBar.AddArrangedSubview(searchTextField);
var searchbarButtonItem = new UIBarButtonItem(searchBar);
NavigationItem.SetRightBarButtonItem(searchbarButtonItem, true);
NavigationItem.TitleView = new UIView();
if (ParentViewController != null)
{
ParentViewController.NavigationItem.RightBarButtonItem = NavigationItem.RightBarButtonItem;
ParentViewController.NavigationItem.TitleView = NavigationItem.TitleView;
}
}
}
}
Also, there is some discussion:How to include view in NavigationBar of Xamarin Forms?
I hope, you understood the main idea.

Resources