Custom ViewCellRenderer in iOS does not work - xamarin

I'm trying to use a custom ViewCellRenderer for iOS in Xamarin Forms, but the renderer never called. I'm using a custom ListViewRenderer too and that is working like a charm. Anyone have any idea about this?
Here is my TableViewRenderer
[assembly: ExportRenderer(typeof(ListView), typeof(CustomTableViewRenderer))]
namespace OdontoWayPaciente.Mobile.iOS.Renderers
{
public class CustomTableViewRenderer : ListViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement != null)
{
Control.TableFooterView = new UIView(CGRect.Empty);
if (e.NewElement.IsGroupingEnabled)
{
var groupedTableView = new UITableView(Control.Frame, UITableViewStyle.Grouped);
groupedTableView.Source = Control.Source;
SetNativeControl(groupedTableView);
}
}
}
}
}
Here is my ViewCellRenderer:
[assembly: ExportRenderer(typeof(ViewCell), typeof(CustomAllViewCellRendereriOS))]
namespace OdontoWayPaciente.Mobile.iOS.Renderers
{
public class CustomAllViewCellRendereriOS : ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tableView)
{
var cell = base.GetCell(item, reusableCell, tableView);
if (cell != null)
{
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
}
if (tableView.Style == UITableViewStyle.Grouped)
{
cell.TintColor = UIColor.Blue;
cell.BackgroundColor = UIColor.Blue;
}
return cell;
}
}
}
Here is my ViewCell XAML code:
<ViewCell Height="{StaticResource listItem_alturaLinhaUnica}"
StyleId="disclosure">
<ContentView>
<ContentView.BackgroundColor>
<OnPlatform x:TypeArguments="Color" iOS="White"/>
</ContentView.BackgroundColor>
<AbsoluteLayout VerticalOptions="Center" BackgroundColor="White">
<Label
Text="{Binding TextoAcesso}"
Style="{DynamicResource TextoPrimario}">
<Label.Margin>
<OnPlatform x:TypeArguments="Thickness">
<On Platform="iOS" Value="18, 8, 0, 8"/>
</OnPlatform>
</Label.Margin>
</Label>
</AbsoluteLayout>
</ContentView>
</ViewCell>

Please check your ListView's ItemsSource, the ListView should have at least one cell then the CustomViewRenderer will be called.
if I use ListView with GroupingEnabled=True, CustomViewRenderer is not
called, but GroupingEnabled=false, CustomViewCellRenderer is called
Whether you just add some groups in your ItemsSource like: new List<Group> { group1, group2 };, but each group contains none item. When you set the IsGroupingEnabled to false, the ListView has two cells in this case so renderer called. But if the IsGroupingEnabled is true, the ListView just has two sections without any cells then renderer will not be called.

Related

Frame CornerRadius just bottom corners from code xamarin [duplicate]

Simple question. I need a frame with only one rounded corner, instead of all four. How can I only round one of the corners of a frame (top right in my case)?
Another way to phrase it: How can I set the cornerradius of only one corner of a frame?
The easy way is to use the Nuget PancakeView.
You can specify the CornerRadius in each vertice, achieving the desired effect:
Example:
<yummy:PancakeView BackgroundColor="Orange"CornerRadius="60,0,0,60"/>
You can read more in the official page.
Another way it to use custom render for frame.
1.Create class name CustomFrame, inherit Frame class, add BindableProperty CornerRadiusProperty in PCL.
public class CustomFrame: Frame
{
public static new readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CustomFrame), typeof(CornerRadius), typeof(CustomFrame));
public CustomFrame()
{
// MK Clearing default values (e.g. on iOS it's 5)
base.CornerRadius = 0;
}
public new CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
}
create CustomFrameRender in Android.
using FrameRenderer = Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer;
[assembly: ExportRenderer(typeof(CustomFrame), typeof(CustomFrameRenderer))]
namespace Demo1.Droid
{
class CustomFrameRenderer : FrameRenderer
{
public CustomFrameRenderer(Context context)
: base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && Control != null)
{
UpdateCornerRadius();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(CustomFrame.CornerRadius) ||
e.PropertyName == nameof(CustomFrame))
{
UpdateCornerRadius();
}
}
private void UpdateCornerRadius()
{
if (Control.Background is GradientDrawable backgroundGradient)
{
var cornerRadius = (Element as CustomFrame)?.CornerRadius;
if (!cornerRadius.HasValue)
{
return;
}
var topLeftCorner = Context.ToPixels(cornerRadius.Value.TopLeft);
var topRightCorner = Context.ToPixels(cornerRadius.Value.TopRight);
var bottomLeftCorner = Context.ToPixels(cornerRadius.Value.BottomLeft);
var bottomRightCorner = Context.ToPixels(cornerRadius.Value.BottomRight);
var cornerRadii = new[]
{
topLeftCorner,
topLeftCorner,
topRightCorner,
topRightCorner,
bottomRightCorner,
bottomRightCorner,
bottomLeftCorner,
bottomLeftCorner,
};
backgroundGradient.SetCornerRadii(cornerRadii);
}
}
}
}
3.using custonframe in forms.
<StackLayout>
<controls:CustomFrame
BackgroundColor="Red"
CornerRadius="0,30,0,0"
HeightRequest="100"
HorizontalOptions="Center"
VerticalOptions="Center"
WidthRequest="100" />
</StackLayout>
More detailed info about this, please refer to:
https://progrunning.net/customizing-corner-radius/
Use the nuget package Xamarin.Forms.PancakeView.
Look at this answer for a similar question:
https://stackoverflow.com/a/59650125/5869384
This is for UWP renderer
I've used the solutions from Cherry Bu - MSFT and changed it for UWP. In my project im using it in Android, iOS and UWP and it is working fine.
using System.ComponentModel;
using Windows.UI.Xaml.Media;
using Xamarin.Forms;
using Xamarin.Forms.Platform.UWP;
[assembly: ExportRenderer(typeof(CustomFrame), typeof(yourNamespace.UWP.CustomFrameRenderer))]
namespace yourNamespace.UWP
{
public class CustomFrameRenderer : FrameRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
if (e.NewElement != null && Control != null)
{
UpdateCornerRadius();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == nameof(CustomFrame.CornerRadius) ||
e.PropertyName == nameof(CustomFrame))
{
UpdateCornerRadius();
}
}
private void UpdateCornerRadius()
{
var radius = ((CustomFrame)this.Element).CornerRadius;
Control.CornerRadius = new Windows.UI.Xaml.CornerRadius(radius.TopLeft, radius.TopRight, radius.BottomRight, radius.BottomLeft);
}
}
}
You can use BoxView instead of Frame
<Grid Margin="10,10,80,10">
<BoxView Color="#CCE4FF"
CornerRadius="10,10,10,0"
HorizontalOptions="Fill"
VerticalOptions="Fill" />
<Grid Padding="10">
<Label Text="This is my message"
FontSize="14"
TextColor="#434343"/>
</Grid>
</Grid>
result view
simple solution i have used is to set another frame behind the rounded frame something like this
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.05*"/>
<RowDefinition Height="0.05*"/>
<RowDefinition Height="0.8*"/>
<RowDefinition Height="0.05*"/>
<RowDefinition Height="0.05*"/>
</Grid.RowDefinitions>
<Frame
Grid.Row="4"
Padding="0"
BackgroundColor="Green"
CornerRadius="0"/>
<Frame
Grid.Row="3"
Grid.RowSpan="2"
Padding="0"
BackgroundColor="Green"
HasShadow="True"
CornerRadius="20">
</Frame>
</Grid>

How to prevent Editor to go behind the keyboard in Xamarin.Forms?

I have a Chat app. Currently, there is an Entry control for add chat text. Now I want to provide multiline Entry, same like Whatsapp.
If user type more than one line, it should automatic wrap the text to next line.
If user click on Nextline button in mobile keyboard, it should go to next line.
Height of Entry should be automatically increase upto 3 line and it should also decrease if user remove text.
To do that I have tried to replace Entry with Editor and implement following functionality.
1- Put an Editor in place of Entry.
2- Implement a functionality that keep keyboard open until user click on Message list screen or back button.
Now I am trying to implement auto height functioanlity but when user try to type, Editor goes behind the keyboard. Can anybody please suggest me how to keep Editor open and auto size?
Current code:
XAML:
<Grid x:Name="MessageControls" RowSpacing="1" ColumnSpacing="2" Padding="5"
Grid.Row="1" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<local:ChatEditorWithPlaceholder x:Name="txtMessage" Grid.Column="0" TextChanged="EnableSend" Text="{Binding OutGoingText}"/>
<Frame x:Name="SendButton" Grid.Column="1" Margin= "0" Padding="0" HasShadow="false" HeightRequest="25"
BackgroundColor="Transparent" HorizontalOptions="FillAndExpand">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="SendMessage_Click" NumberOfTapsRequired="1" />
</Frame.GestureRecognizers>
<Label Text="Send" x:Name="sendButton" TextColor="#1f88b7" HeightRequest="20"
HorizontalOptions="Center" VerticalOptions="Center"/>
</Frame>
</Grid>
Editor
public class ChatEditorWithPlaceholder : Editor
{
public ChatEditorWithPlaceholder()
{
this.TextChanged += (sender, e) => { this.InvalidateMeasure(); };
}
}
Editor Renderer:
public class ChatEditorRenderer : EditorRenderer
{
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if(Control != null) {
Control.ScrollEnabled = false;
}
var element = this.Element as ChatEditorWithPlaceholder;
Control.InputAccessoryView = null;
Control.ShouldEndEditing += DisableHidingKeyboard;
MessagingCenter.Subscribe<ConversationPage>(this, "FocusKeyboardStatus", (sender) =>
{
if (Control != null)
{
Control.ShouldEndEditing += EnableHidingKeyboard;
}
MessagingCenter.Unsubscribe<ConversationPage>(this, "FocusKeyboardStatus");
});
}
private bool DisableHidingKeyboard(UITextView textView)
{
return false;
}
private bool EnableHidingKeyboard(UITextView textView)
{
return true;
}
Screenshots:
Try this renderer for ios.
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly:ExportRenderer( typeof(CustomEditor), typeof(CustomEditorRenderer))]
namespace YourNameSpace.iOS
{
public class CustomEditorRenderer: EditorRenderer
{
public ChatEntryRenderer()
{
UIKeyboard.Notifications.ObserveWillShow ((sender, args) => {
if (Element != null)
{
Element.Margin = new Thickness(0,0,0, args.FrameEnd.Height); //push the entry up to keyboard height when keyboard is activated
}
});
UIKeyboard.Notifications.ObserveWillHide ((sender, args) => {
if (Element != null)
{
Element.Margin = new Thickness(0); //set the margins to zero when keyboard is dismissed
}
});
}
}
}
for android add this in MainActivity
App.Current.On<Xamarin.Forms.PlatformConfiguration.Android>().
UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
For Ios there is one plugin. You can use that. The link is Here.
For Andorid you have to just set below code in MainActivity after LoadApplication(new App()) method.
App.Current.On<Xamarin.Forms.PlatformConfiguration.Android().
UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
Updated answer for iOS :
For IOS you can use the following custom renderer to solve the keyboard overlapping issue. And please removed the keyboardoverlap nuget package from the project.
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using HBIClientFacingApp;
using HBIClientFacingApp.iOS;
[assembly:ExportRenderer( typeof(CustomEditor), typeof(CustomEditorRenderer))]
namespace YourNameSpace.iOS
{
public class CustomEditorRenderer: EditorRenderer
{
public ChatEntryRenderer()
{
UIKeyboard.Notifications.ObserveWillShow ((sender, args) => {
if (Element != null)
{
Element.Margin = new Thickness(0,0,0, args.FrameEnd.Height); //push the entry up to keyboard height when keyboard is activated
}
});
UIKeyboard.Notifications.ObserveWillHide ((sender, args) => {
if (Element != null)
{
Element.Margin = new Thickness(0); //set the margins to zero when keyboard is dismissed
}
});
}
}
}
You can try to change this line:
<local:ChatEditorWithPlaceholder x:Name="txtMessage" Grid.Column="0" TextChanged="EnableSend" Text="{Binding OutGoingText}"/>
For this one:
<Editor x:Name="txtMessage" Grid.Column="0" AutoSize="TextChanges" TextChanged="EnableSend" Text="{Binding OutGoingText}"/>

Xamarin ItemTemplate with a WebView control

I am trying to add a WebView control inside a ItemTemplate and set the height of the row. I know that I can't have the webview control scroll so I need to setting the height to the correct size to display the full html content. I have created an IValueConverter class that I was thinking can return the correct height needed but what height value to return depending on how long the content is?
Anyway I can load the webview and get the height needed to display the full content I get -1 for height in my writeline?
XAML Code
<telerikListView:ListViewTemplateCell>
<Grid BackgroundColor="{StaticResource LightBlueColor}"
Padding="10">
<telerikPrimitives:RadBorder Padding="10"
HorizontalOptions="Fill"
BorderThickness="2"
BorderColor="{StaticResource DarkBlueColor}"
BackgroundColor="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!--<RowDefinition Height="*"/>-->
<RowDefinition Height="{Binding AssetItem.Description, Converter={StaticResource DescriptionToHeightConverter}}" />
</Grid.RowDefinitions>
<!--<Grid Grid.Row="0" Grid.Column="0">
<HtmlLabelControl:HtmlLabel
Text="{Binding AssetItem.Description}"
HeightRequest="100"/>-->
<WebView HeightRequest="800" MinimumHeightRequest="300" HorizontalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding AssetItem.Description}"/>
</WebView.Source>
</WebView>
<!--</Grid>-->
<!--<WebView Grid.Column="0" Grid.Row="0" HeightRequest="200" HorizontalOptions="FillAndExpand">
<WebView.Source>
<HtmlWebViewSource Html="{Binding AssetItem.Description}"/>
</WebView.Source>
</WebView>-->
<!--<Label Text="{Binding AssetItem.Description}"
TextColor="{StaticResource GrayTextColor}"
Grid.Row="0"
Grid.Column="0"/>-->
<!--Star-->
<telerikPrimitives:RadPath
x:Name="path"
Grid.Row="0"
Grid.Column="1"
WidthRequest="40"
HeightRequest="35"
StrokeThickness="2"
VerticalOptions="Start"
Fill="{Binding AssetItem.IsBookmark, Converter={StaticResource FavFillColorConverter}}"
Stroke="#3e7dc5"
Geometry="{x:Static telerikInput:Geometries.Star}">
<telerikPrimitives:RadPath.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="BookmarkCommand" CommandParameter="{Binding AssetItem.AssetId}" />
</telerikPrimitives:RadPath.GestureRecognizers>
</telerikPrimitives:RadPath>
</Grid>
<!--</Grid>-->
</telerikPrimitives:RadBorder>
</Grid>
</telerikListView:ListViewTemplateCell>
CS Converter Logic
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var description = value as string;
//WebView wv = new WebView();
//wv.Source = description;
HtmlWebViewSource HtmlSource = new HtmlWebViewSource();
HtmlSource.Html = description;
WebView webView = new WebView()
{
Source = HtmlSource
};
Debug.WriteLine($"Web View Height: {webView.Height}");
if (!string.IsNullOrEmpty(description))
{
if (description.Length == 300)
{
return 50;
}
}
return 300;
}
Test code
HtmlWebViewSource HtmlSource = new HtmlWebViewSource();
HtmlSource.Html = "<html><body><div><h1>MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM</h1></div></body></html>";
WebView webView = new WebView()
{
Source = HtmlSource
};
string htmlheight = "";
Task.Run(async () => {
try
{
htmlheight = await webView.EvaluateJavaScriptAsync("document.body.scrollHeight");
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
});
//WebView_NavigatedAsync(webView);
Debug.WriteLine($"Web View Height: {htmlheight}");
If you want to get the height of the html .You can implement by using Custom Renderer
in Forms
public MainPage()
{
InitializeComponent();
HtmlWebViewSource HtmlSource = new HtmlWebViewSource();
HtmlSource.Html = #"<html><body>
<h1>Xamarin.Forms</h1>
<p>Welcome to WebView.</p>
</body></html>";
Webview webView = new Webview()
{
WidthRequest = 100,
HeightRequest = 20,
Source =HtmlSource
};
MessagingCenter.Subscribe<Object, float>(this,"webview_loaded",(sender,value)=>{
Console.WriteLine(value); //value is the height of html
});
Content = new StackLayout
{
Children =
{
webView,
},
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions=LayoutOptions.FillAndExpand
};
}
in iOS project
using Foundation;
using UIKit;
using CoreGraphics;
using xxx;
using xxx.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly:ExportRenderer(typeof(WebView),typeof(MyWebViewRenderer))]
namespace App7.iOS
{
public class MyWebViewRenderer:WebViewRenderer,IUIWebViewDelegate
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if(NativeView!=null)
{
// WeakDelegate = this;
}
}
[Export("webViewDidFinishLoad:")]
public void LoadingFinished(UIWebView webView)
{
string htmlHeight = webView.EvaluateJavascript("document.body.scrollHeight");
float height = float.Parse(htmlHeight);
MessagingCenter.Send<System.Object, float>(this, "webview_loaded", height);
}
}
}
in Android
using Android.Content;
using Android.Webkit;
using Android.Widget;
using xxx;
using xxx.Droid;
using Java.Lang;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(MyWebViewRenderer))]
namespace xxx.Droid
{
public class MyWebViewRenderer:WebViewRenderer
{
public MyWebViewRenderer(Context context):base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
Android.Webkit.WebView webview =(Android.Webkit.WebView) Control;
WebSettings settings = webview.Settings;
settings.JavaScriptEnabled = true;
webview.SetWebViewClient(new JavascriptWebViewClient());
}
}
}
public class JavascriptWebViewClient : WebViewClient
{
public override void OnPageFinished(Android.Webkit.WebView view, string url)
{
base.OnPageFinished(view, url);
view.EvaluateJavascript("javascript:document.body.scrollHeight;", new EvaluateBack() );
}
}
class EvaluateBack : Java.Lang.Object, IValueCallback
{
public void OnReceiveValue(Java.Lang.Object value)
{
string htmlHeight = value.ToString();
float height = float.Parse(htmlHeight);
MessagingCenter.Send<System.Object, float>(this,"webview_loaded",height);
}
}
}
Notes: in your test code ,you get call the method when the html didn't finish loading ,so the result is -1.

How can I set up different footers for TableSections when using a Custom TableView Renderer

I am using a renderer to allow me to set a custom footer in my TableView. The renderer works but I would like to have the capability to set up different footers for the different table sections. For example one footer for table section 0 and another for table section 1, all the way up to table section 5.
Here's the XAML that I am using:
<!-- <local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">-->
<TableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection Title="Cards1">
<ViewCell Height="50">
<Label Text="Hello1" />
</ViewCell>
<ViewCell Height="50">
<Label Text="Hello2" />
</ViewCell>
</TableSection>
<TableSection Title="Cards2">
<TextCell Height="50" Text="Hello"></TextCell>
</TableSection>
</TableSection>
<!-- </local:ExtFooterTableView>-->
</TableView>
and here is the C# class and renderer:
public class ExtFooterTableView : TableView
{
public ExtFooterTableView()
{
}
}
and:
using System;
using Japanese;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(ExtFooterTableView), typeof(Japanese.iOS.ExtFooterTableViewRenderer))]
namespace Japanese.iOS
{
public class ExtFooterTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var formsTableView = Element as TableView;
tableView.WeakDelegate = new CustomFooterTableViewModelRenderer(formsTableView);
}
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
Debug.WriteLine("xx");
if (section == 0)
{
return new UILabel()
{
// Text = TitleForFooter(tableView, section), // or use some other text here
Text = "abc",
TextAlignment = UITextAlignment.Left
// TextAlignment = NSTextAlignment.NSTextAlignmentJustified
};
}
else
{
return new UILabel()
{
// Text = TitleForFooter(tableView, section), // or use some other text here
Text = "def",
TextAlignment = UITextAlignment.Left
// TextAlignment = NSTextAlignment.NSTextAlignmentJustified
};
}
}
}
}
}
The code works but I would like to find out how I can set up a different footer text for different sections in the XAML. Something like this:
From what I see it looks like the code is partly there TitleForFooter(tableView, section) but I am not sure how to use it and how I could set it up. Note that I am not really looking for a view model solution. I would be happy to be simply able to specify the section footer text as part of the TableView XAML.
I'd appreciate if anyone could give me some advice on this.
First of all, in order to be able to specify the section footer text in XAML - simplest option would be to create a bindable property in TableSection. But as TableSection is sealed, we can't derive it to define our custom bindable properties.
So, the next option is to create a attached bindable property.
public class Ex
{
public static readonly BindableProperty FooterTextProperty =
BindableProperty.CreateAttached("FooterText", typeof(string), typeof(Ex), defaultValue: default(string));
public static string GetFooterText(BindableObject view)
{
return (string)view.GetValue(FooterTextProperty);
}
public static void SetFooterText(BindableObject view, string value)
{
view.SetValue(FooterTextProperty, value);
}
}
Next step would be to update renderer to retrieve this value for every section:
private class CustomFooterTableViewModelRenderer : TableViewModelRenderer
{
public CustomFooterTableViewModelRenderer(TableView model) : base(model)
{
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
return new UILabel()
{
Text = TitleForFooter(tableView, section), // or use some other text here
Font = UIFont.SystemFontOfSize(14),
ShadowColor = Color.White.ToUIColor(),
ShadowOffset = new CoreGraphics.CGSize(0, 1),
TextColor = Color.DarkGray.ToUIColor(),
BackgroundColor = Color.Transparent.ToUIColor(),
Opaque = false,
TextAlignment = UITextAlignment.Center
};
}
//Retrieves the footer text for corresponding section through the attached property
public override string TitleForFooter(UITableView tableView, nint section)
{
var tblSection = View.Root[(int)section];
return Ex.GetFooterText(tblSection);
}
}
Sample Usage
<local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection Title="Cards1" local:Ex.FooterText="Sample description">
<ViewCell Height="50">
<Label Margin="20,0,20,0" Text="Hello1" />
</ViewCell>
<ViewCell Height="50">
<Label Margin="20,0,20,0" Text="Hello2" />
</ViewCell>
</TableSection>
<TableSection Title="Cards2" local:Ex.FooterText="Disclaimer note">
<TextCell Height="50" Text="Hello"></TextCell>
</TableSection>
</local:ExtFooterTableView>
It is very simple. you need to add the bindable property for pass value from XAML to CustomRenderer in CustomControl like this:
Customer TableView
public class ExtFooterTableView : TableView
{
public ExtFooterTableView()
{
}
}
Xaml control code
<local:ExtFooterTableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
Renderer class
using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using yournamespace;
using System.ComponentModel;
[assembly: ExportRenderer(typeof(ExtFooterTableView), typeof(FooterTableViewRenderer))]
namespace yournamespace
{
public class FooterTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (ExtFooterTableView)Element;
if (e.PropertyName == ExtFooterTableView.IntentProperty.PropertyName)
{
string intent = view.Intent;
// Do your stuff for intent property
}
if (e.PropertyName == ExtFooterTableView.HasUnevenRowsProperty.PropertyName)
{
bool hasUnevenRows = view.HasUnevenRows;
// Do yout stuff for HasUnevenRow
}
}
}
}

How can I make a > in a cell with Xamarin.Forms?

I have an application where I can change the order and the way cards appear. For anyone who has iOS I need something very similar to the way the Settings > Contacts > Sort Order page works.
This shows two rows. One with First, Last and the other with Last, First. When a user clicks on a row it acts like a radio button and a tick mark appears at the end of the row.
I would like to try and implement this functionality but I am not sure where to start. Should I do this with a ViewCell or a TextCell and how does anyone have any ideas as to how it is implemented this
. 

EDIT 1: Simplified property changed logic in iOS renderer; now there are no references or handlers to cleanup.
In extension to #hankide's answer:
You can create a bindable property IsChecked while extending a TextCell or ViewCell and bind your VM state to it.
public class MyTextCell : TextCell
{
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create(
"IsChecked", typeof(bool), typeof(MyTextCell),
defaultValue: false);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
Next step would be to create renderer that listens to this property and shows a check-mark at iOS level.
[assembly: ExportRenderer(typeof(MyTextCell), typeof(SampleApp.iOS.MyTextCellRenderer))]
namespace SampleApp.iOS
{
public class MyTextCellRenderer : TextCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var nativeCell = base.GetCell(item, reusableCell, tv);
var formsCell = item as MyTextCell;
SetCheckmark(nativeCell, formsCell);
return nativeCell;
}
protected override void HandlePropertyChanged(object sender, PropertyChangedEventArgs args)
{
base.HandlePropertyChanged(sender, args);
System.Diagnostics.Debug.WriteLine($"HandlePropertyChanged {args.PropertyName}");
if (args.PropertyName == MyTextCell.IsCheckedProperty.PropertyName)
{
var nativeCell = sender as CellTableViewCell;
if (nativeCell?.Element is MyTextCell formsCell)
SetCheckmark(nativeCell, formsCell);
}
}
void SetCheckmark(UITableViewCell nativeCell, MyTextCell formsCell)
{
if (formsCell.IsChecked)
nativeCell.Accessory = UITableViewCellAccessory.Checkmark;
else
nativeCell.Accessory = UITableViewCellAccessory.None;
}
}
}
Sample usage 1
And, sample usage would like:
<TableView Intent="Settings">
<TableSection Title="Sort Order">
<local:MyTextCell Text="First Last" IsChecked="false" />
<local:MyTextCell Text="Last, First" IsChecked="true" />
</TableSection>
</TableView>
Sample usage 2
You can also listen to Tapped event to ensure IsChecked property works as expected.
For example, you bind this property to ViewModel:
<TableView Intent="Settings">
<TableSection Title="Sort Order">
<local:MyTextCell Tapped="Handle_Tapped" Text="{Binding [0].Name}" IsChecked="{Binding [0].IsSelected}" />
<local:MyTextCell Tapped="Handle_Tapped" Text="{Binding [1].Name}" IsChecked="{Binding [1].IsSelected}" />
</TableSection>
</TableView>
and handle tap event:
public SettingViewModel[] Settings = new []{
new SettingViewModel { Name = "First Last", IsSelected = false },
new SettingViewModel { Name = "Last First", IsSelected = true },
};
void Handle_Tapped(object sender, System.EventArgs e)
{
var cell = sender as TextCell;
if (cell == null)
return;
var selected = cell.Text;
foreach(var setting in Settings)
{
if (setting.Name == selected)
setting.IsSelected = true;
else
setting.IsSelected = false;
}
}
The sort order settings page you described is implemented using the UIKit's UITableView. In Xamarin.Forms, you can utilize the TableView control to get the same result.
As you will quickly notice, there's no way to set the checkmark icon with Xamarin.Forms so you'll probably need to create a custom cell, that has the text on the left and the checkmark image on the right.
If you really want to do everything by the book, you should probably create a custom renderer that allows you to set the Accessory property of the current ViewCell. However, this will get a bit complex for such a small feature.

Resources