Custom Render not being used on Bindable property update - xamarin.forms

Please consider the following issue.
In my Xamarin.Forms app I have a custom render for UWP that allows for a button to have two lines, and be centralised.
The buttons in questions are items in a Listview that are bound to objects. When they are initially generated, they display correctly with both lines of text in the center of the button, however if I update the text, it updates, but seems to bypass the custom renders "be in the center" code.
Please see the below code snippets and images to explain the situation further.
Custom Render
[assembly: ExportRenderer(typeof(TwoLinedButton), typeof(TwoLinedButtonUWP))]
namespace aphiresawesomeproject.UWP
{
public class TwoLinedButtonUWP : ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement.Text != null)
{
var textBlock = new Windows.UI.Xaml.Controls.TextBlock
{
Text = e.NewElement.Text,
TextAlignment = Windows.UI.Xaml.TextAlignment.Center,
TextWrapping = TextWrapping.WrapWholeWords
};
Control.Content = textBlock;
}
}
}
}
XAML
<ListView x:Name="AphiresListView" CachingStrategy="RecycleElement" ItemsSource="{Binding ListViewItems}" Margin="0,20,0,0" RowHeight="130" SeparatorVisibility="None" VerticalOptions="FillAndExpand" Grid.Column="1" Grid.Row ="3" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<local:TwoLinedButton Command="{Binding ClickedCommand}" Margin="5,10,5,10" HorizontalOptions ="FillAndExpand" BackgroundColor="{Binding color_hex}" Grid.Column="1" TextColor="{StaticResource LightTextColor}" FontSize="Medium" Text="{Binding problem_title}"></local:TwoLinedButton>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Update in Viewmodel
foreach (AphiresObject ViewItem in ListViewItems)
{
ViewItem.problem_title = ViewItem.problem_title.Replace("Line 2", "Updated Line 2");
}
Before
After

I think all you need to do is override OnElementPropertyChanged in your renderer and set the textBlock properties again when your text property changes.
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == TwoLinedButton.TextProperty.PropertyName)
{
//Set text block properties
}
}

You may also need to tell the view to re-render itself.
iOS: this.SetNeedsDisplay();
Android: this.Invalidate();

Related

Xamarin Forms CollectionView with SwipeView on items - tapping ImageButton selects cell

I have an ImageButton in every cell in my CollectionView. When I tap on the ImageButton I expect it to capture the touch event and handle it, however it also passes the touch event up to the cell and selects that cell in the CollectionView.
Tapping the call changes the SelectedItem and opens the detail page for that contact. Tapping the ImageButton starts a call, but immediately switches to the detail page.
Here is a screenshot of the page:
The CollectionView is defined as:
<CollectionView
x:Name="contactsList"
ItemsSource="{Binding Contacts}"
SelectionMode="Single"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
ItemSizingStrategy="MeasureAllItems"
IsGrouped="True"
EmptyView="No Contacts">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.GroupHeaderTemplate>
...
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate>
<SwipeView
x:DataType="models:Contact">
...
<StackLayout
BackgroundColor="{StaticResource BackgroundColor}">
<Grid
Padding="0,15,0,10"
ColumnDefinitions="80,*,80"
RowDefinitions="*,*"
BackgroundColor="{StaticResource BackgroundColor}">
<Ellipse
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Fill="{Binding Colour, Converter={StaticResource intToBrushColor}}"
.../>
<Label
Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="2"
Text="{Binding Initials}"
.../>
<Label
Grid.Column="1"
Grid.Row="0"
Text="{Binding FullName}"
.../>
<StackLayout
Grid.Column="1"
Grid.Row="1"
Orientation="Horizontal">
<Image
HeightRequest="15"
Source="{Binding WasOutgoing, Converter={StaticResource callDirectionToIcon}}"/>
<Label
Grid.Column="1"
Grid.Row="1"
Text="{Binding TimeStamp}"
.../>
</StackLayout>
<ImageButton
Grid.Column="2"
Grid.Row="0"
Grid.RowSpan="2"
Margin="0,0,15,0"
Padding="10"
BackgroundColor="Transparent"
Source="{StaticResource IconCalls}"
Command="{Binding BindingContext.CallCommand, Source={x:Reference contactsPage}}"
CommandParameter="{Binding .}"/>
</Grid>
<BoxView
Style="{StaticResource Seperator}"/>
</StackLayout>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
How do I make the ImageButton keep the touch event and stop the cell from being selected when the ImageButton is tapped?
Here are a few dirty workarounds I considered but these are not ideal:
Split the cell into two Grids and have two TapGestureRecognizers.
Track if the ImageButton was tapped and ignore the next selection change.
These are not ideal, will cost more and break MVVM pattern. The root cause of this issue is the ImageButton not keeping the touch event or marking it as handled.
Does anyone know a cleaner solution to this problem?
I've narrowed your problem down to use of SwipeView, in ItemTemplate. This seems to force the item to be selected.
Without it, works as intended.
I infer that SwipeView alters touch events, to force row selection, in order to perform its action.
See WORKAROUND below, for a hack fix.
xaml:
<ContentPage.Content>
<StackLayout>
<CollectionView
x:Name="contactsList"
ItemsSource="{Binding Contacts}"
SelectionMode="Single"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
ItemSizingStrategy="MeasureAllItems" >
<CollectionView.ItemTemplate>
<DataTemplate>
<!--<SwipeView>-->
<StackLayout>
<Grid
Padding="0,15,0,10"
ColumnDefinitions="*,80">
<Label Grid.Column="0" Text="abcdef" />
<Button
Grid.Column="1"
Padding="4"
Text="Press Me"
Clicked="Button_Clicked"
/>
</Grid>
</StackLayout>
<!--</SwipeView>-->
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage.Content>
xaml.cs:
public partial class CollectionViewWithCellButtonPage : ContentPage
{
private Model selectedContact;
public CollectionViewWithCellButtonPage()
{
InitializeComponent();
BindingContext = this;
}
private void Button_Clicked(object sender, EventArgs e)
{
}
public ObservableCollection<Model> Contacts { get; set; } = new ObservableCollection<Model> {
new Model(),
new Model(),
new Model(),
};
public Model SelectedContact {
get => selectedContact;
set => selectedContact = value;
}
}
With breakpoints on SelectedContact setter, and on Button_Clicked, a click on button does not affect SelectedContact. Click elsewhere on row does. This is the desired behavior.
Then uncomment <SwipeView> and </SwipeView>.
Now, SelectedContact setter is called. BEFORE Button_Clicked.
Because the call is BEFORE, I don't see any easy fix.
Fixing this "right" probably requires custom renderer (per platform) for SwipeView.
WORKAROUND
Got it to work. But this is a hack.
Delay action taken when SelectContact. This gives us time to find out if Button was pushed. (Step 2 will show _suppressSelection getting set.)
private Model _selectedContact;
private bool _suppressSelection;
public Model SelectedContact
{
get => _selectedContact;
set
{
Device.BeginInvokeOnMainThread(async () =>
{
await DelayedSetSelectedContact(value);
});
}
}
private async Task DelayedSetSelectedContact(Model value)
{
await Task.Delay(100);
if (_suppressSelection)
{
// Button was pressed. DO NOTHING - DON'T select the item.
// Clear state for next time.
_suppressSelection = false;
}
else
{
_selectedContact = value;
// ... Do your other work here ...
}
}
Button click sets _suppressSelection. Make sure _suppressSelection can't get "stuck on".
private System.Timers.Timer _buttonTimer;
protected override void OnAppearing()
{
base.OnAppearing();
// Make sure _suppressSelection can't get "stuck on".
_buttonTimer = new System.Timers.Timer { Interval = 500, AutoReset = false };
_buttonTimer.Elapsed += Timer_Elapsed;
}
private void Button_Clicked(object sender, EventArgs e)
{
// FIRST LINE in method - do this as early as possible.
_suppressSelection = true;
//... your main logic here ...
// Make sure _suppressSelection can't get "stuck on".
_buttonTimer.Start();
}
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// "if" line can be commented out. I just have it so breakpoint on following line is only hit if
// timer is needed to do its job. Some sequences of item selection and button presses do hit that breakpoint.
if (_suppressSelection)
_suppressSelection = false;
}
Clean up when leave page.
protected override void OnDisappearing()
{
base.OnDisappearing();
// Stop timer. Release reference.
if (_buttonTimer != null)
{
_buttonTimer.Stop();
_buttonTimer = null;
}
// Clean up state, in case navigate back to page.
_suppressSelection = false;
}
Full code in CollectionViewWithCellButtonPage in ToolmakerSteve - repo XFormsSOAnswers.

An emptyView for loading data and another for when there is no data available

I have a case of using a CarouselView that is displayed based on certain data brought from an API, the point is that I need to see a certain view or at least text while the API data is being downloaded and another one in case That there is no data.
I tried to get to this using RefreshView and EmptyView but I cannot achieve the required behavior, I can make an EmptyView appear immediately the data begins to load since at that moment the ItemSource is null, then when the data reaches the app the Carousel appears , which seems to me quite ugly, the ideal would be to show some view that next to the RefreshView indicator shows that the data is loading and then in case of not bringing any data show a view that of the feedback that API data did not return .
I hope I have made myself understood and I hope someone can give me an idea on how to achieve this behavior.
MyViewModel:
public MyViewModel()
{
IsRefreshing = true;
Things = new ObservableCollection<Things>();
var t = Task.Run(async () =>
{
await LoadThings();
});
Task.WhenAll(t);
IsRefreshing = false;
}
private async Task LoadThings()
{
Things = new List<Thing>(await App.WebApiManager.GetThingsAsync(Id));
}
My IsRefreshing property is linked to the IsRefreshing property in the RefreshView that encompasses my CarouselView
I think you could use two empty view and switch between them when the refreshing status changes, and here is the code:
add two content view in in XAML and set default empty view to LoadingData:
<ContentPage.Resources>
<ContentView x:Key="LoadingData">
<StackLayout>
<Label Text="Loading data..."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
<ContentView x:Key="NoDataLoaded">
<StackLayout>
<Label Text="No items to display."
Margin="10,25,10,10"
FontAttributes="Bold"
FontSize="18"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentView>
</ContentPage.Resources>
<StackLayout Margin="20">
<RefreshView IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}">
<CarouselView x:Name="carouselView"
EmptyView="{StaticResource LoadingData}">
... ...
and in code, show different empty view accordingly:
public partial class HorizontalPullToRefreshPage : ContentPage
{
AnimalsViewModel viewModel;
public HorizontalPullToRefreshPage()
{
InitializeComponent();
viewModel = new AnimalsViewModel();
this.BindingContext = viewModel;
viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals("IsRefreshing"))
{
if (viewModel.IsRefreshing && viewModel.Animals.Count==0)
{
carouselView.EmptyView = Resources["LoadingData"];
}
else if (!viewModel.IsRefreshing && viewModel.Animals.Count == 0)
{
carouselView.EmptyView = Resources["NoDataLoaded"];
}
}
}
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
carouselView.ItemsSource = viewModel.Animals;
}
}
then, every time the property IsRefreshing changed, you got a chance to switch the empty view.
Hope it helps.

Custom content view with Label-Entry duplicates Xamarin forms

I have custom content view with a Label as title and another Label as detail and an edit Icon ; when the icon is clicked detail label is converted to Entry to make changes and the changes are carried over to binding.
I have bound multiple of these custom views to different properties of same object and trying to edit each one and move to next one, the problem is it seems to duplicate the individual views
I have also put x:Name but still it duplicates same value to the views above it ..
Just the edit of Lastname
Now if I move to 3rd view and edit it , it replicates new value to all previously edited values. - for lastname in this case which is weird considering its not same view used in the page and on debug it hits the method only once.
Custom content view:
<StackLayout Orientation="Horizontal"
VerticalOptions="Start"
Padding="25,10,25,10">
<StackLayout x:Name="stackLayoutDetail"
HorizontalOptions="FillAndExpand">
<Label x:Name="title"
Text="{Binding Title}" />
<Label x:Name="detail"
Text="{Binding Detail}"
FontSize="Large"
FontAttributes="Bold" />
</StackLayout>
<Image x:Name="editIcon"
Source="edit_icon.png"
WidthRequest="25"
HeightRequest="25"
IsVisible="{Binding EditIconVisible}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="EditIcon_Clicked" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
Code behind:
private static Entry newEntry = new Entry();
public static readonly BindableProperty DetailProperty = BindableProperty.Create(propertyName: nameof(Detail),
returnType: typeof(string),
declaringType: typeof(LabelledEntrywithIcon),
defaultValue: default(string));
public string Detail
{
get
{
return (string)GetValue(DetailProperty);
}
set => SetValue(DetailProperty, value);
}
private void EditIcon_Clicked(object sender, System.EventArgs e)
{
detailLabel = (Label)stackLayoutDetail.Children[1];
stackLayoutDetail.Children.RemoveAt(1);
newEntry.Text = Detail;
stackLayoutDetail.Children.Add(newEntry);
editIcon.IsVisible = false;
newEntry.Completed += NewEntry_Completed;
}
private void NewEntry_Completed(object sender, System.EventArgs e)
{
try
{
var _newText = newEntry.Text;
detailLabel.Text = _newText;
stackLayoutDetail.Children.RemoveAt(1);
stackLayoutDetail.Children.Add(detailLabel);
Detail = _newText;
editIcon.IsVisible = true;
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Page
<local:LabelledEntrywithIcon x:Name="firstName"
Title="First Name"
Detail="{Binding Fella.FirstName}" />
<local:LabelledEntrywithIcon x:Name="lastname"
Title="Last Name"
Detail="{Binding Fella.LastName}" />
<local:LabelledEntrywithIcon x:Name="gender"
Title="Gender"
Detail="{Binding Fella.Gender}" />
Code behind:
ViewModel=new MainViewModel();
BindingContext = ViewModel;
Complete code to test is at Github repo : https://github.com/pmahend1/CustomViewDuplicationIssue
Strange but I changed a line of code and it works as expected now.
On the class variables changed private static Entry newEntry= new Entry(); to
private static Entry newEntry;
in EditIcon_Clicked method instead of newEntry.Text = Detail; used
newEntry = new Entry { Text = Detail };
I am not sure why it was taking same reference even though its new Entry for each LabelledEntrywithIcon
Instead of creating a new entry and finding and removing the label and adding the new entry after, you could simplify your problem by:
<StackLayout Orientation="Horizontal"
VerticalOptions="Start"
Padding="25,10,25,10">
<StackLayout x:Name="stackLayoutDetail"
HorizontalOptions="FillAndExpand">
<Label x:Name="title"
Text="{Binding Title}" />
<Label x:Name="detail"
Text="{Binding Detail}"
IsVisible="{Binding ShowLabel}"
FontSize="Large"
FontAttributes="Bold" />
<Entry ... IsVisible="{Binding ShowEntry}" ... />
</StackLayout>
<Image x:Name="editIcon"
Source="edit_icon.png"
WidthRequest="25"
HeightRequest="25"
IsVisible="{Binding ShowLabel}">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="EditIcon_Clicked" />
</Image.GestureRecognizers>
</Image>
</StackLayout>
Note that I intentionally wrote ... inside the entry element as Placeholder for all customizations you might want do there (font size, etc...).
Now you add two BindablyProperties (type bool) ShowEntry and ShowLabel, where ShowLabel defaults to true and ShowEntry defaults to false.
Now all you have to do is to adapt your EditIcon_Clicked Event:
private void EditIcon_Clicked(object sender, System.EventArgs e)
{
ShowLabel = false;
ShowEntry = true;
newEntry.Text = Detail;
newEntry.Completed += NewEntry_Completed;
}
And adapt NewEntry_Completed to
private void NewEntry_Completed(object sender, System.EventArgs e)
{
try
{
var _newText = newEntry.Text;
detailLabel.Text = _newText;
ShowLabel = true;
ShowEntry = false;
Detail = _newText;
}
catch (System.Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
Basically this does the same as your solution, however you save yourself from having to push UI Items around in your codebehind and especially the bugs and errors coming with it.

How to change font family of ListView items

I have a ListView and I'm using the standard "native" template and groups.
How do I set the font for the text in the group header and the item?
Here is an example for setting dynamic LayoutOptions in ListHeader, like wise you can pre-set these attributes for individual label and customize your header. now as what you want, you can set your own font family or colour or font size, or may be customize your header cell, It is just an ItemTemplate
<ListView x:Name="ListView" IsGroupingEnabled="true">
<ListView.GroupHeaderTemplate>
<DataTemplate>
<ViewCell>
<Label
HorizontalOptions="{Binding Optn}"
Text="{Binding Heading}"
VerticalOptions="{Binding Optn}" />
</ViewCell>
</DataTemplate>
</ListView.GroupHeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding DisplayName}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Happy Coding!
I've fixed the text and detail labels using this...
public class MyTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell = base.GetCell(item, reusableCell, tv);
if (cell.TextLabel.Font.FamilyName != CommonStyles.RegularFontName)
{
cell.TextLabel.Font = UIFont.FromName(
CommonStyles.RegularFontName, cell.TextLabel.Font.PointSize);
cell.DetailTextLabel.Font = UIFont.FromName(
CommonStyles.RegularFontName, cell.DetailTextLabel.Font.PointSize);
}
return cell;
}
}
For the section headers, this works initially...
UILabel.AppearanceWhenContainedIn(typeof(UITableView)).Font = ...
When the section header has been scrolled off screen and back on again, it reverts to the standard though.
But this works...
public class CustomTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.Source = new CustomTableViewModelRenderer(e.NewElement);
}
}
}
public class CustomTableViewModelRenderer : UnEvenTableViewModelRenderer
{
public CustomTableViewModelRenderer(TableView model) : base(model)
{
}
public override void WillDisplayHeaderView(UITableView tableView, UIView headerView, nint section)
{
if (headerView is UITableViewHeaderFooterView view)
{
view.TextLabel.Font = UIFont.FromName(CommonStyles.RegularFontName, 18);
}
}
}
That's a lot of code to change some fonts.

Xaamrin Forms BoxView Width Too Long when using to Underline

I am using a BoxView to accomplish underlining in my app. I have a couple of labels that are very short - Text such as Yes or No etc. Here is the XAML for one of the labels with the BoxView for underlining:
<StackLayout Orientation="Vertical" Grid.Row="5" Grid.Column="1" Margin="0,4,0,4" HorizontalOptions="Start" BackgroundColor="Purple" MinimumWidthRequest="1">
<Label x:Name="txtUseMetric" TextColor="Blue" FontSize="Small" Text="{Binding UseMetricText}" BackgroundColor="Yellow">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Value_Tapped" CommandParameter="usemetric" />
</Label.GestureRecognizers>
</Label>
<BoxView BackgroundColor="Green" HeightRequest="1" MinimumWidthRequest="1" />
</StackLayout>
My problem is that the width of the BoxView is always extending past my text I have tried overriding the MinWidthRequest in my App.Xaml file as seen below:
<Style TargetType="BoxView">
<Setter Property="MinimumWidthRequest" Value="3" />
</Style>
But this has not effect. I have included screen shots for you to see.
FYI - The yellow is the width of the Label. You don't see any purple (the background color of the StackLayout) because the StackLayout and Label are the same width. The second screen shot shows what the screen looks like if I remove the BoxView - i.e. the Label and StackLayout are sized correctly.
Any suggestions on how to fix this?
Screen shot with BoxView Too Long making label and StackLayout too long
Screen shot with BoxView removed and Label and Stack Layout sizing correctly
Please note the default HorizontalOptions and that Label derives from View:
Default value is LayoutOptions.Fill unless otherwise documented.
Add HorizontalOptions="Start" on the "Use Metric" Label:
<Label x:Name="txtUseMetric" TextColor="Blue" FontSize="Small"
Text="{Binding UseMetricText}" BackgroundColor="Yellow"
HorizontalOptions="Start">
<BoxView BackgroundColor="Green" HeightRequest="1"
WidthRequest="{Binding Path=Width, Source={x:Reference txtUseMetric}"
HorizontalOptions="Start"/>
One option is to replace the label/box underline with a custom renderer that adds an underline capability to the label.
Here is how to do it:
User Control
public class CustomLabel : Label
{
public static readonly BindableProperty IsUnderlinedProperty =
BindableProperty.Create(nameof(IsUnderlined), typeof(bool), typeof(CustomLabel), false);
public bool IsUnderlined
{
get { return (bool)GetValue(IsUnderlinedProperty); }
set
{
SetValue(IsUnderlinedProperty, value);
}
}
}
Android renderer
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Incident.Droid.CustomRenderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
var control = Control;
UpdateUi(view, control);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomLabel)Element;
if (e.PropertyName == CustomLabel.IsUnderlinedProperty.PropertyName)
{
Control.PaintFlags = view.IsUnderlined ? Control.PaintFlags | PaintFlags.UnderlineText : Control.PaintFlags &= ~PaintFlags.UnderlineText;
}
}
static void UpdateUi(CustomLabel view, TextView control)
{
if (view.FontSize > 0)
{
control.TextSize = (float)view.FontSize;
}
if (view.IsUnderlined)
{
control.PaintFlags = control.PaintFlags | PaintFlags.UnderlineText;
}
}
}
}
iOS Renderer
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Incident.iOS.CustomRenderers
{
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
var view = (CustomLabel)Element;
UpdateUi(view, Control);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomLabel)Element;
if (e.PropertyName == CustomLabel.IsUnderlinedProperty.PropertyName)
{
UpdateUi(view, Control);
}
}
private static void UpdateUi(CustomLabel view, UILabel control)
{
var attrString = new NSMutableAttributedString(control.Text);
if (view != null && view.IsUnderlined)
{
attrString.AddAttribute(UIStringAttributeKey.UnderlineStyle,
NSNumber.FromInt32((int)NSUnderlineStyle.Single),
new NSRange(0, attrString.Length));
}
control.AttributedText = attrString;
}
}
}

Resources