How can I add border/outline for label text? - xaml

I need to add borders for captions in my application, what's the best way to do it?
I've tried using outline fonts (examples) but they are transparent and I need white text with black border.

You could use custom renderer to achieve this effect.
For ios:
create a custom OutLineLabel which extends UILabel in ios project.
public class OutLineLabel:UILabel
{
public OutLineLabel(string text)
{
this.Text = text;
}
public override void DrawText(CGRect rect)
{
CGSize shadowOffset = this.ShadowOffset;
UIColor textColor = UIColor.White; //the text color
CGContext c = UIGraphics.GetCurrentContext();
c.SetLineWidth(2);
c.SetLineJoin(CGLineJoin.Round);
c.SetTextDrawingMode(CGTextDrawingMode.Stroke);
this.TextColor = UIColor.Black; //the outline border color
base.DrawText(rect);
c.SetTextDrawingMode(CGTextDrawingMode.Fill);
this.TextColor = textColor;
this.ShadowOffset = new CGSize(0, 0);
base.DrawText(rect);
this.ShadowOffset = shadowOffset;
}
}
create custom renderer OutLineLabelRenderer in ios project
[assembly: ExportRenderer(typeof(Label), typeof(OutLineLabelRenderer))]
namespace your namespace
{
class OutLineLabelRenderer:LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
OutLineLabel outLineLabel = new OutLineLabel(Element.Text);
SetNativeControl(outLineLabel);
}
}
}
For android
You could refer to this link.

Related

How to create multiple custom renderer with same type?

I wanted to create a page render with ContentPage type. I created so in android and it is working but in IOS there has custom page renderer with same type (ContentPage). It can be removed as this was from nuget package and working on different context.
Here is my code
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
namespace AlwinInvoiceGenerator.IOS
{
using CoreGraphics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
public class CustomPageRenderer : PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (Element == null || !(Element is ContentPage basePage) || basePage.BindingContext == null ||
!(basePage.BindingContext is BaseViewModel baseViewModel))
{
return;
}
SetCustomBackButton(baseViewModel);
}
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
OverrideUserInterfaceStyle = UIUserInterfaceStyle.Light;
}
private void SetCustomBackButton(BaseViewModel baseViewModel)
{
var fakeButton = new UIButton {Frame = new CGRect(0, 0, 50, 40), BackgroundColor = UIColor.Clear};
fakeButton.TouchDown += (sender, e) =>
{
baseViewModel.OnBackButtonClicked();
};
NavigationController?.NavigationBar.AddSubview(fakeButton);
}
}
It seems it not registering and that is why not calling.
I have another page renderer that is register in assembly
[assembly: ExportRenderer(typeof(ContentPage), typeof(IOSToolbarExtensions.iOS.Renderers.IOSToolbarExtensionsContentPageRenderer), Priority = short.MaxValue)]
If I removed this line then above code is working but not two in the same time.
Please help
Same type seems not working for multiple renderer.
I have created sub type of my custom renderer and override the methods which needed to. It is working well

How to display Drop Down in xamarin form. IOS

This Is My IOS IPad Page I want to display dropdown as show in image ,Pleases help me how can I write a code ,
I have use picker for that but the menu item is display on bottom ,How i resolve this issue
Here is a workaround that achieve such "Drop Down" via "control combination".
First, add a UITextField on the view to show the item selected.
UITextField _dropTextField;
UIView _dropDownView;
UIPickerView _pickerView;
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
this.View.BackgroundColor = UIColor.White;
// add textfield
_dropTextField = new UITextField();
_dropTextField.Frame = new CGRect(100, 100, 300, 30);
_dropTextField.BackgroundColor = UIColor.Gray;
this.View.AddSubview(_dropTextField);
// call CreateDropDownView
CreateDropDownView();
}
Second, define a method that create a custom UIView holds a UIPickerView.
// create a custom UIView to show pickView
private void CreateDropDownView()
{
_dropDownView = new UIView(new CGRect(_dropTextField.Frame.X, _dropTextField.Frame.Y,
_dropTextField.Frame.Width, 300));
_dropTextField.Delegate = this;
_pickerView = new UIPickerView();
_pickerView.Frame = new CGRect(_dropTextField.Bounds.X, _dropTextField.Bounds.Y + _dropTextField.Frame.Height, _dropTextField.Frame.Width, 300);
PeopleModel pickerModel = new PeopleModel(_dropTextField, _dropDownView);
_pickerView.Model = pickerModel;
_dropDownView.AddSubview(_pickerView);
}
Then, to show the UIView when TextField focused, you also need to implement interface IUITextFieldDelegate.
// show pickerview
[Export("textFieldShouldBeginEditing:")]
public bool ShouldBeginEditing(UITextField textField)
{
View.AddSubview(_dropDownView);
UIApplication.SharedApplication.KeyWindow.BringSubviewToFront(_pickerView);
return false;
}
As for the PeopleModel in the above code, please refer to "class PeopleModel" in this documentation. Also, you need to override some method in it.
private UITextField dropTextField;
private UIView dropDownView;
public PeopleModel(UITextField dropTextField, UIView dropDownView)
{
this.dropDownView = dropDownView;
this.dropTextField = dropTextField;
}
public override nint GetComponentCount(UIPickerView pickerView)
{
return 1;
}
// ...
public override void Selected(UIPickerView pickerView, nint row, nint component)
{
dropDownView.RemoveFromSuperview();
dropTextField.Text = $"{names[pickerView.SelectedRowInComponent(0)]}";
}
// ...

Is there any way to force execute the OnSizeAllocated method?

I have created a view using Xaml code behind. I did it using the code behind because I wanted to change the layout of the view based on the device orientation. So, the problem which I am facing is that the OnSizeAllocated method is being called after the view is loaded. So, it is unable to change the layout as per the device orientation. I just want to know if there is any way to invoke the OnSizeAllocated method before the view is loaded. Please click on the below link to view the code:
Please click Here to view the Code
1.Rearrange the Page
you could check if width is greater than height to determine if the device is now in landscape or portrait:
public partial class Page13 : ContentPage
{
private double _width ;
private double _height ;
private Grid grid;
private Label label;
private Entry entry;
private Button button;
public Page13 ()
{
_width = this.Width;
_height = this.Height;
label = new Label(){Text = "i am a laber"};
entry = new Entry(){WidthRequest = 200};
button = new Button(){Text = "Submit"};
grid = new Grid();
UpdateLayout();
StackLayout stackLayout = new StackLayout();
stackLayout.Children.Add(grid);
Content = stackLayout;
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (_width != width || _height != height)
{
_width = width;
_height = height;
UpdateLayout();
}
}
void UpdateLayout()
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
grid.Children.Clear();
if (_width > _height)
{
ScreenRotatedToLandscape();
}
else
{
ScreenRotatedToPortrait();
}
}
private void ScreenRotatedToLandscape()
{
grid.RowDefinitions.Add(new RowDefinition(){Height = new GridLength(1,GridUnitType.Auto)});
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1,GridUnitType.Auto)});
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
grid.Children.Add(label,0,0);
grid.Children.Add(entry, 1, 0);
grid.Children.Add(button, 0, 1);
Grid.SetColumnSpan(button,2);
}
private void ScreenRotatedToPortrait()
{
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
grid.Children.Add(label, 0, 0);
grid.Children.Add(entry, 0, 1);
grid.Children.Add(button, 0, 2);
}
}
This is the recommended implementation pulled right from the Xamarin.Forms documentation.
2.Using Xamarin.Essentials
It adds additional functionality to cross-platform applications built in Xamarin. One of these new features is the ability to ping the device for the current orientation by accessing the DeviceDisplay.ScreenMetrics.Orientation property. This returns the current device orientation, which can be used to determine which layout to render.
it's similar to the one above
private bool IsPortrait;
public Page13 ()
{
...
IsPortrait = DeviceDisplay.ScreenMetrics.Orientation == ScreenOrientation.Portrait;
UpdateLayout();
...
}
void UpdateLayout()
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
grid.Children.Clear();
if (IsPortrait)
{
ScreenRotatedToPortrait();
}
else
{
ScreenRotatedToLandscape();
}
}
You can't force run that since the SizeAllocation hasn't changed, but you could do this to get orientation on initial load:
If you add the Xamarin.Essentials nuget package, as you can see here, you can get the orientation using this line of code DeviceDisplay.MainDisplayInfo.Orientation and you will get Landscape, Portrait, Square, or Unknown.
If you don't want to add the package, you can just use Application.Current.MainPage.Width and Application.Current.MainPage.Height to figure out orientation.

Apply custom font on label in xamarin.iOS

I want to apply Montserrat-Light font style in all the label and entry, I am doing it by making a renderer of controls. EntryRenderer is working fine but LabelRenderer is giving ArgumentNullException with message: Value can not be null.
[assembly: ExportRenderer(typeof(Label), typeof(ExtendedLabelRenderer))]
namespace NewApp.iOS.Renderer
{
public class ExtendedLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
Control.Font = UIFont.FromName("Montserrat-Light", 10f);
}
}
}
}
Try below code. It will update if you haven't specified font-family and size in your XAML. Now you can set in your XAML also.
public class CustomLabelRender : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
if (e.NewElement != null)
{
if (!String.IsNullOrEmpty(Element.FontFamily))
Control.Font = UIFont.FromName(this.Element.FontFamily, (nfloat)e.NewElement.FontSize);
}
}
}
}
As an easier approach to this issue, I would like to recommend the Label element and/or the CustomFontEffect of the free, open source Forms9Patch NuGet package. It allows you to store your custom font as an Embedded Resource in your Xamarin.Forms application's cross platform project (.NetStandard, PCL, or Shared Library) and then set that font's Embedded Resource ID as the FontFamily for any Xamarin.Forms element that has the FontFamily property.
var entry = new Xamarin.Forms.Entry {
Text = "Xamarin.Forms.Entry element",
FontFamily = "Forms9PatchDemo.Resources.Fonts.Pacifico.ttf"
};
entry.Effects.Add(Effect.Resolve("Forms9Patch.CustomFontEffect"));
var label = new Forms9Patch.Label
{
Text = "Custom Font Text",
FontFamily = "Forms9PatchDemo.Resources.Fonts.Pacifico.ttf"
}
Full disclosure: I am the author of this package.

How to set TextColor Xamarin.Forms TableSection?

I am a fan of doing as much as possible in xaml, I have aTableView` with a TableSection.
<TableView Intent="Menu">
<TableRoot>
<TableSection Title="Test Section" TextColor="#FFFFFF">
<TextCell Text="Test Item" TextColor="#FFFFFF"/>
</TableSection>
</TableRoot>
</TableView>
For TextCell TextColor="#FFFFFF" seems to work, however whenever I use this attribute on a TableSection I get this:
An unhandled exception occurred.
Is it possible to change the color of the TableSection with xaml?
Custom Renderers! I have two blog posts on this here:
Android:Xamarin.Forms TableView Section Custom Header on Android
iOS: Xamarin.Forms TableView Section Custom Header on iOS
Basically, create a custom view that inherits TableView, then custom renderers that implement custom TableViewModelRenderer. From there you can override methods to get the header view for the section title.
Here's what that might look like for Android:
public class ColoredTableViewRenderer : TableViewRenderer
{
protected override TableViewModelRenderer GetModelRenderer(Android.Widget.ListView listView, TableView view)
{
return new CustomHeaderTableViewModelRenderer(Context, listView, view);
}
private class CustomHeaderTableViewModelRenderer : TableViewModelRenderer
{
private readonly ColoredTableView _coloredTableView;
public CustomHeaderTableViewModelRenderer(Context context, Android.Widget.ListView listView, TableView view) : base(context, listView, view)
{
_coloredTableView = view as ColoredTableView;
}
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
var view = base.GetView(position, convertView, parent);
var element = GetCellForPosition(position);
// section header will be a TextCell
if (element.GetType() == typeof(TextCell))
{
try
{
// Get the textView of the actual layout
var textView = ((((view as LinearLayout).GetChildAt(0) as LinearLayout).GetChildAt(1) as LinearLayout).GetChildAt(0) as TextView);
// get the divider below the header
var divider = (view as LinearLayout).GetChildAt(1);
// Set the color
textView.SetTextColor(_coloredTableView.GroupHeaderColor.ToAndroid());
textView.TextAlignment = Android.Views.TextAlignment.Center;
textView.Gravity = GravityFlags.CenterHorizontal;
divider.SetBackgroundColor(_coloredTableView.GroupHeaderColor.ToAndroid());
}
catch (Exception) { }
}
return view;
}
}
}
And the on iOS:
public class ColoredTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var coloredTableView = Element as ColoredTableView;
tableView.WeakDelegate = new CustomHeaderTableModelRenderer(coloredTableView);
}
private class CustomHeaderTableModelRenderer : UnEvenTableViewModelRenderer
{
private readonly ColoredTableView _coloredTableView;
public CustomHeaderTableModelRenderer(TableView model) : base(model)
{
_coloredTableView = model as ColoredTableView;
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
return new UILabel()
{
Text = TitleForHeader(tableView, section),
TextColor = _coloredTableView.GroupHeaderColor.ToUIColor(),
TextAlignment = UITextAlignment.Center
};
}
}
}
According to the official documentation for TableSection you are out of luck. I'm afraid you would have to write a custom renderer for a subclass of the TableSection class and expose an extra property of type Xamarin.Forms.Color. Then you would be able to set the color from XAML.
You can set this color in the base theme (may apply to other widgets too)
In /Resources/values/styles.xml
<style name="MainTheme.Base" parent="Theme.AppCompat.Light">
<item name="colorAccent">#434857</item>
For Individual Section Titles, the TextColor property now seems to work correctly:
<TableView Intent="Settings">
<TableRoot>
<TableSection Title="App Settings" TextColor="Red">