Placeholder Margin on Xamarin Forms Editor using Custom Renderer - xamarin

I've an Editor on a page in my Xamarin Forms Project and I'm looking to amend the placeholder text position by adding a slight margin.
I was able to add a margin to the text the user types (but not the placeholder) using "TextContainerInset" in my iOS Custom renderer.
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
this.Control.InputAccessoryView = null;
this.Control.Layer.CornerRadius = 10;
this.Control.Layer.BorderColor = UIColor.LightGray.CGColor;
this.Control.Layer.BorderWidth = (nfloat)0.5;
this.Control.TextContainerInset = new UIEdgeInsets(15,15,15,15);
}
However, this inset doesn't apply to the placeholder position.
Is there a way to move the placeholder position using the custom renderer?

You could set the padding like:
public class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged (e);
if (Control != null) {
Control.LeftView = new UIView(new CGRect(0,0,15,0));
Control.LeftViewMode = UITextFieldViewMode.Always;
Control.RightView = new UIView(new CGRect(0, 0, 15, 0));
Control.RightViewMode = UITextFieldViewMode.Always;
}
}
}
Workaournd for editor:
warp the editor in the frame and set the padding like:
<StackLayout Margin="20">
<Frame CornerRadius="10" BorderColor="Gray" HeightRequest="50" Padding="5,10,5,10" HasShadow="False">
<Editor Placeholder="Enter the editor text" ></Editor>
</Frame>
</StackLayout>

The underlying UITextView does not implement the Placeholder. However if you look at the EditorRenderer code which derives from UITextView, Xamarin has implemented the Placeholder there.
private UILabel _placeholderLabel;
By using reflection, I can get a pointer to the UILabel _placeholderLabel, but I can't for the life of me move it.
If someone can help me move the UILabel, I believe we have the answer!
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if (Control != null)
{
editor = e.NewElement as EditorEx;
//Use reflection to access private placeholder in parent
//https://www.c-sharpcorner.com/UploadFile/6f0898/how-to-access-a-private-member-of-a-class-from-other-class/
System.Reflection.FieldInfo receivedObject = typeof(EditorRenderer).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)[1];
_placeholderLabel = receivedObject.GetValue(this) as UILabel;
_placeholderLabel.Text = "move me!!; //this works
_placeholderLabel.BackgroundColor = UIColor.Green; //this works
//put it on another message to see if that solves it (it doesn't)
Device.BeginInvokeOnMainThread(() =>
{
//HELP NEEDED HERE!!!
//_placeholderLabel.Frame = new CGRect(-15, -10, 500, 30); //doesn't work
//_placeholderLabel.Bounds = new CGRect(-15, -10, 500, 30); //doesn't work
_placeholderLabel.LayoutMargins = new UIEdgeInsets(-15, -10, 5, 5); //doesn't work
});
}
}

Related

Xamarin IOS Custom Renderer overriden Draw method not called

I am trying to load a customized slider control in a listview (with accordeon behaviour). When the View loads all the listview elements are collapsed so the slider control visibility is false. I observed that the overriden Draw method within the ios renderer is not called while the control is not visible so I end up having the native control within my listview.
I have reproduced the issue in a separate project:
I have the IOS custom renderer:
public class CustomGradientSliderRenderer : SliderRenderer
{
public CGColor StartColor { get; set; }
public CGColor CenterColor { get; set; }
public CGColor EndColor { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
if (Control == null)
{
var customSlider = e.NewElement as CustomGradientSlider;
StartColor = customSlider.StartColor.ToCGColor();
CenterColor = customSlider.CenterColor.ToCGColor();
EndColor = customSlider.EndColor.ToCGColor();
var slider = new SlideriOS
{
Continuous = true,
Height = (nfloat)customSlider.HeightRequest
};
SetNativeControl(slider);
}
base.OnElementChanged(e);
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (Control != null)
{
Control.SetMinTrackImage(CreateGradientImage(rect.Size), UIControlState.Normal);
}
}
void OnControlValueChanged(object sender, EventArgs eventArgs)
{
((IElementController)Element).SetValueFromRenderer(Slider.ValueProperty, Control.Value);
}
public UIImage CreateGradientImage(CGSize rect)
{
var gradientLayer = new CAGradientLayer()
{
StartPoint = new CGPoint(0, 0.5),
EndPoint = new CGPoint(1, 0.5),
Colors = new CGColor[] { StartColor, CenterColor, EndColor },
Frame = new CGRect(0, 0, rect.Width, rect.Height),
CornerRadius = 5.0f
};
UIGraphics.BeginImageContext(gradientLayer.Frame.Size);
gradientLayer.RenderInContext(UIGraphics.GetCurrentContext());
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return image.CreateResizableImage(UIEdgeInsets.Zero);
}
}
public class SlideriOS : UISlider
{
public nfloat Height { get; set; }
public override CGRect TrackRectForBounds(CGRect forBounds)
{
var rect = base.TrackRectForBounds(forBounds);
return new CGRect(rect.X, rect.Y, rect.Width, Height);
}
}
The View with codebehind:
Main.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="GradientSlider.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:GradientSlider">
<ContentPage.Content>
<Grid>
<StackLayout x:Name="SliderContainer">
<local:CustomGradientSlider
x:Name="mySlider"
CenterColor="#feeb2f"
CornerRadius="16"
EndColor="#ba0f00"
HeightRequest="20"
HorizontalOptions="FillAndExpand"
Maximum="10"
Minimum="0"
StartColor="#6bab29"
VerticalOptions="CenterAndExpand"
MaximumTrackColor="Transparent"
ThumbColor="green"
/>
<Label x:Name="lblText" Text="txt"
VerticalOptions="Center" HorizontalOptions="Center"/>
</StackLayout>
<Button Text="Magic" Clicked="Button_Tapped" WidthRequest="100" HeightRequest="50" VerticalOptions="Center" HorizontalOptions="Center"/>
</Grid>
</ContentPage.Content>
</ContentPage>
Main.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace GradientSlider
{
public partial class MainPage : ContentPage, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
SliderContainer.IsVisible = false;
}
void Button_Tapped(object sender,ClickedEventArgs a)
{
SliderContainer.IsVisible = !SliderContainer.IsVisible;
}
}
}
So in the scenario above you can see that when I load the main.xaml the control is invisible (SliderContainer.IsVisible = false;) in this case I get a native slider control and not my custom one. If I change in the constructor SliderContainer.IsVisible = true; then I get my custom control.
After an investigation I realised that if the control is not visible when the view loads the public override void Draw(CGRect rect) is not called. I could not find any solution to trigger the Draw method while the control is invisible.
Anybody has an idea how to load a custom renderer correctly while the control is not visible ?
Thank you!
Assuming the renderer is overriding OnElementPropertyChanged:
protected override void OnElementChanged(ElementChangedEventArgs<MyFormsSlider> e)
{
if (e.NewElement != null)
{
if (Control == null)
{
// Instantiate the native control and assign it to the Control property with
// the SetNativeControl method
SetNativeControl(new MyNativeControl(...
...
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//assuming MyFormsSlider derives from View / VisualElement; the latter has IsVisibleProperty
if (e.PropertyName == MyFormsSlider.IsVisibleProperty.PropertyName)
{
//Control is the control set with SetNativeControl
Control. ...
}
...
}

Bind height of BoxView or Frame to (hidden) NavigationBar in Xamarin

Instead of having the standard NavigationBar on the mainpage, I want to hide that one and load a custom one. I think using a BoxView (edit:) frame is the best method for this. However, How do I bind the height(request) of a BoxView to a (hidden) NavigationBar, preferably in Xaml?
(frame of boxview should have the same method)
For two common controls, you can bind a height of one control to another control like this(take BoxView and a Button as an example):
<BoxView Color="Yellow"
x:Name="testbox"
WidthRequest="160"
HeightRequest="60"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Button Text="test..."
BindingContext="{x:Reference testbox}"
WidthRequest="160"
HeightRequest="{Binding Path=HeightRequest}"/>
Update
You can get the NavigationBar height on each platform using custom renderer.
And Then binding the height of NavigationBar to Boxview by following the method above.
For Android:
public class NaviRendererForAndroid : NavigationPageRenderer
{
public CustomNaviForAndroid(Context context) : base(context)
{
}
protected override void
OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
{
base.OnElementChanged(e);
var height = 0;
Resources resources = Context.Resources;
int resourceId = resources.GetIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0)
{
height = resources.GetDimensionPixelSize(resourceId);
}
}
}
For IOS:
public class NaviRendererForiOS : NavigationRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var height = NavigationBar.Bounds.Height;
}
}

Unable to clear TextDecoration in Xamarin Forms UWP App

Using Xamarin Forms (version 2.5.0.121934), I'm working on an app targeting Android, iOS, and UWP. I need to add underlining and strikethrough to some text, which require custom renderers. For Android and iOS, everything is working fine, and on UWP, applying strikethrough or underline works correctly, but removing those decorations isn't working.
Here's the entirety of the UWP renderer:
[assembly: ExportRenderer(typeof(EnhancedLabel), typeof(EnhancedLabelRenderer))]
namespace myApp.UWP
{
public class EnhancedLabelRenderer : LabelRenderer
{
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var strikethrough = ((EnhancedLabel)sender).Strikethrough;
var underline = ((EnhancedLabel)sender).Underline;
if (strikethrough && underline)
{
Control.TextDecorations = TextDecorations.Strikethrough | TextDecorations.Underline;
}
else if (strikethrough)
{
Control.TextDecorations = TextDecorations.Strikethrough;
}
else if (underline)
{
Control.TextDecorations = TextDecorations.Underline;
}
else
{
Control.TextDecorations = TextDecorations.None;
}
}
}
}
EnhancedLabel is a simple class that extends Xamarin.Forms.Label and adds the simple BindableProperty fields that specify strikethrough or underlining.
The renderer is properly setting TextDecorations.None, but that isn't having an effect on the UI. I've worked through this in the debugger, and can actually see that the state of the TextBlock within the ExtendedLabel has TextDecorations.None, but the UI is still drawing it with underlining or strikethrough (essentially, either of those can be added, but neither can be removed).
I've gone through the Xamarin documentation and looked at the bugs in Bugzilla, and haven't found any clues. Has any one else encountered this? Wondering if there's a UWP-specific call I need to make that I missed, or if using TextDecorations is the wrong way to apply the styles, or if I've actually stumbled across a bug.
Bug in UWP as in Xaml below:
<TextBlock>
<Run Text="Decorations can be toggled on and off"/>
</TextBlock>
<TextBlock Text="Decorations will not toggle off"/>
It is the same issue if you code the TextBlock:
TextBlock textBlock = new TextBlock { FontSize = 18.0 };
textBlock.Inlines.Add(new Windows.UI.Xaml.Documents.Run { Text = "This text will not stick on text decoration." });
TextBlock textBlockBad = new TextBlock
{
FontSize = 18.0,
Text = "This text will not enable the TextDecorations to be turned off"
};
Same behaviour found with Typography.Capitals
Just need to use only Inlines for TextBlocks and presumably RichTextBlocks to avoid these issues.
Wondering if there's a UWP-specific call I need to make that I missed, or if using TextDecorations is the wrong way to apply the styles, or if I've actually stumbled across a bug.
If you want yo use TextDecorations, you could use the Run instance to pack the decorated text like the follow.
Underline ul = new Underline();
ul.TextDecorations = TextDecorations.Strikethrough;
Run r = new Run();
r.Text = "Here is an underlined text";
ul.Inlines.Add(r);
MyTextBlock.Inlines.Add(ul);
For you requirement, I have create a CustomLabel that you could use directly.
CustomLabel.cs
public class CustomLabel : Label
{
public static readonly BindableProperty DeckProperty = BindableProperty.Create(
propertyName: "Deck",
returnType: typeof(TextDeck),
declaringType: typeof(CustomLabel),
defaultValue: default(TextDeck));
public TextDeck Deck
{
get { return (TextDeck) GetValue(DeckProperty); }
set { SetValue(DeckProperty, value); }
}
}
public enum TextDeck
{
None = 0,
//
// Summary:
// Underline is applied to the text.
Underline = 1,
//
// Summary:
// Strikethrough is applied to the text.
Strikethrough = 2
}
CustomLabelRenderer.cs
public class CustomLabelRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var element = Element as CustomLabel;
var underline = new Underline();
var run = new Run();
switch (element.Deck)
{
case TextDeck.None:
underline.TextDecorations = TextDecorations.None;
break;
case TextDeck.Strikethrough:
underline.TextDecorations = TextDecorations.Strikethrough;
break;
case TextDeck.Underline:
underline.TextDecorations = TextDecorations.Underline;
break;
}
run.Text = element.Text;
underline.Inlines.Add(run);
Control.Inlines.Clear();
Control.Inlines.Add(underline);
}
}
}
Usage
<local:CustomLabel Deck="Underline" Text="Welcome to Xamarin.Forms!" />

Adding a bottom border to an Entry in Xamarin Forms iOS with an image at the end

Now before anyone ignores this as a duplicate please read till the end. What I want to achieve is this
I've been doing some googling and looking at objective c and swift responses on stackoverflow as well. And this response StackOverFlowPost seemed to point me in the right direction. The author even told me to use ClipsToBounds to clip the subview and ensure it's within the parents bounds. Now here's my problem, if I want to show an image on the right side of the entry(Gender field), I can't because I'm clipping the subview.
For clipping, I'm setting the property IsClippedToBounds="True" in the parent stacklayout for all textboxes.
This is the code I'm using to add the bottom border
Control.BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, 1000, 1))
{
BackgroundColor = view.BorderColor.ToUIColor(),
};
Control.AddSubview(myBox);
This is the code I'm using to add an image at the beginning or end of an entry
private void SetImage(ExtendedEntry view)
{
if (!string.IsNullOrEmpty(view.ImageWithin))
{
UIImageView icon = new UIImageView
{
Image = UIImage.FromFile(view.ImageWithin),
Frame = new CGRect(0, -12, view.ImageWidth, view.ImageHeight),
ClipsToBounds = true
};
switch (view.ImagePos)
{
case ImagePosition.Left:
Control.LeftView.AddSubview(icon);
Control.LeftViewMode = UITextFieldViewMode.Always;
break;
case ImagePosition.Right:
Control.RightView.AddSubview(icon);
Control.RightViewMode = UITextFieldViewMode.Always;
break;
}
}
}
After analysing and debugging, I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size. So I subclassed UITextField like this
public class ExtendedUITextField : UITextField
{
public UIColor BorderColor;
public bool HasBottomBorder;
public override void Draw(CGRect rect)
{
base.Draw(rect);
if (HasBottomBorder)
{
BorderStyle = UITextBorderStyle.None;
var myBox = new UIView(new CGRect(0, 40, Frame.Size.Width, 1))
{
BackgroundColor = BorderColor
};
AddSubview(myBox);
}
}
public void InitInhertedProperties(UITextField baseClassInstance)
{
TextColor = baseClassInstance.TextColor;
}
}
And passed the hasbottomborder and bordercolor parameters like this
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
var view = e.NewElement as ExtendedEntry;
if (view != null && Control != null)
{
if (view.HasBottomBorder)
{
var native = new ExtendedUITextField
{
BorderColor = view.BorderColor.ToUIColor(),
HasBottomBorder = view.HasBottomBorder
};
native.InitInhertedProperties(Control);
SetNativeControl(native);
}
}
But after doing this, now no events fire :(
Can someone please point me in the right direction. I've already built this for Android, but iOS seems to be giving me a problem.
I figured out that when OnElementChanged function of the Custom Renderer is called, the control is still not drawn so it doesn't have a size.
In older versions of Xamarin.Forms and iOS 9, obtaining the control's size within OnElementChanged worked....
You do not need the ExtendedUITextField, to obtain the size of the control, override the Frame in your original renderer:
public override CGRect Frame
{
get
{
return base.Frame;
}
set
{
if (value.Width > 0 && value.Height > 0)
{
// Use the frame size now to update any of your subview/layer sizes, etc...
}
base.Frame = value;
}
}

How to add a visual prefix to an entry in Xamarin Forms?

Say I want to add a number prefix based on a country, for a phone entry? Like the one on the image:
How can I achieve that?
I would do something like this
<StackLayout Orientation="Horizontal" BackgroundColor="Gray">
<Label Text="+995 |" BackgroundColor="Transparent" />
<Editor Text="699999999" BackgroundColor="Transparent"></Editor>
</StackLayout>
A Horizontal stacklayout with a label for the prefix and an editor for the entry.
As you can see yourself i am using the same approach for my app in order to display the arrow down icon next to the picker.
var datectrl = new NoBorderPicker()
{
VerticalOptions = LayoutOptions.Center,
FontSize = Device.GetNamedSize(NamedSize.Default, typeof(Label)) * FontSizes.EditFormControlFactor,
HeightRequest = 40,
BackgroundColor = Color.White,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
var icon = new IconView()
{
Source = "ic_keyboard_arrow_right_black_24dp",
Foreground = Palette._019,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.End,
Margin = new Thickness(0, 0, 5, 0)
};
var stack = new StackLayout()
{
Orientation = StackOrientation.Horizontal,
Children =
{
datectrl,
icon
}
};
The NoBorderPicker is a custom renderer in order to remove the border for the picker control
[assembly: ExportRenderer(typeof(NoBorderPicker), typeof(CustomPicker))]
namespace ThesisSFA.Droid.Renderers
{
public class CustomPicker : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (Control != null)
{
var customBG = new GradientDrawable();
customBG.SetColor(Android.Graphics.Color.Transparent);
customBG.SetCornerRadius(3);
Control.SetBackground(customBG);
var custdatepicker = (NoBorderPicker) this.Element;
this.Control.TextSize = (float)custdatepicker.FontSize;
}
}
}
}
You can just use masking behavior.
<Entry.Behaviors>
<behavior:MaskedBehavior Mask="(995) XX-XXX-XXXX" />
</Entry.Behaviors>
enter code here
and to add behavior just use the following link.
https://xamarinhelp.com/masked-entry-in-xamarin-forms/

Resources