Entry render not adding underline in iOS Xamarin Forms - xamarin.forms

I have an Entry in Xamarin Forms and for Android, its displayed with an underline by default and when I test it for iOS it's always with a rectangle like this:
And I want it to be like just like in Android:
I used Custom render where I specified that I want an underline but its still displayed in a rectangle. This code doesn't work:
class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.Line;
}
}
}
I have solved this problem with the next code:
class MyEntryRenderer : EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.None;
//Create borders(bottom only)
CALayer border = new CALayer();
float width = 2.0f;
border.BorderColor = Color.FromHex(ColorConstants.BlueHex).ToCGColor();
border.Frame = new CGRect( 0, 40, 400, 2.0f);
border.BorderWidth = width;
Control.Layer.AddSublayer(border);
Control.Layer.MasksToBounds = true;
}
}
}
But still, don't understand why the previous code doesn't work, it should be simple as that. Any ideas?

https://developer.xamarin.com/api/type/UIKit.UITextBorderStyle/
The enum values for UITextBorderStyle are Bezel, Line, None, and RoundedRect. All of them (except for None) simply describe the style of the border that goes around the entire UITextView, so the Line value doesn't mean a single line, it means a solid line rectangle around the entire view, as opposed to a Bezel or rounded rectangle.

the implementation witch has solved the user question didn't work for me in these days, but this one has worked: https://social.msdn.microsoft.com/Forums/en-US/7ae2e5ad-3cc4-4761-ac39-06e8d8842cf4/entry-bottom-line?forum=xamarinforms
as jmoerdyk gently suggested this link explains how to write a custom renderer for iOS to render an entry with a bottom line instead of a complete border. the color of the line will change when the entry has focus.
below the piece of code with some null checks:
public class IOSBottomLineEntryRenderer : EntryRenderer
{
private CALayer _borderLayer;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
Control.BorderStyle = UITextBorderStyle.None;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
e.NewElement.Unfocused += (_, _) =>
{
DrawBorder(UIColor.FromRGB(156, 156, 156));
};
e.NewElement.Focused += (_, _) =>
{
DrawBorder(UIColor.FromRGB(245, 0, 47));
};
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
return;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
}
public override CGRect Frame
{
get { return base.Frame; }
set
{
base.Frame = value;
if (Control == null)
return;
if (Element == null)
return;
DrawBorder(UIColor.FromRGB(156, 156, 156));
}
}
private void DrawBorder(UIColor borderColor)
{
if (Frame.Height <= 0 || Frame.Width <= 0)
return;
if (_borderLayer == null)
{
_borderLayer = new CALayer
{
MasksToBounds = true,
Frame = new CGRect(0f, Frame.Height - 1, Frame.Width, 1f),
BorderColor = borderColor.CGColor,
BorderWidth = 1.0f
};
Control.Layer.AddSublayer(_borderLayer);
Control.BorderStyle = UITextBorderStyle.None;
}
else
{
_borderLayer.BorderColor = borderColor.CGColor;
_borderLayer.Frame = new CGRect(0f, Frame.Height - 1, Frame.Width, 1f);
}
}
}

Related

Is there a way to update child elements in custom renderer

I was creating a custom control it is more like TabPage, where it's derive from View, containing a list of CSMenuItems and foreach menuItem is derived from BaseMenuItem and has menuContent that is derived from ContentView, like this :
• CSView
• CSMenuItem
• MenuContent
• Content
• CSMenuItem
• MenuContent
• Content
My problem is whenever I Change the properties value of MenuContent in the xaml file, the propertyChanged won't fire and MenuContent won't update. I am pretty sure the problem is in my renderers. Is there any way to update the child element in custom renderer?
Here are my codes for controls:
class CSView : View
{
public CSView ()
{
var items = new ObservableCollection <CSMenuItem>();
items.CollectionChanged += OnItemsChanged;
Items = items;
}
public static readonly BindableProperty ItemsProperty = BindableProperty.Create ("Items", typeof (IList <CSMenuItem>), typeof (CSView));
void OnItemsChanged (object sender, NotifyCollectionChangedEventArgs e)
{
foreach (CSMenuItem item in e.NewItems)
item.Parent = this;
// Maybe setting the item parent to this would be good?
// cause whenever new item is add I want its parent to be this.
// Correct me if im wrong.
}
public IList <CSMenuItem> Items
{
get => (IList <CSMenuItem>)GetValue (ItemsProperty);
set => SetValue (ItemsProperty, value);
}
class CSMenuItem : BaseMenuItem
{
public CSMenuItem()
{
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create("Title", typeof(string), typeof(CSMenuItem), default);
public static readonly BindableProperty ContentProperty = BindableProperty.Create("Content", typeof(MenuContent), typeof(CSMenuItem));
public string Title
{
get => (string)GetValue (TitleProperty);
set => SetValue (TitleProperty, value);
}
public MenuContent Content
{
get => (MenuContent)GetValue (ContentProperty);
set => SetValue (ContentProperty, value);
}
}
class MenuContent : ContentView
{
public MenuContent ()
{
}
public static readonly BindableProperty TitleProperty = BindableProperty.Create ("Title", typeof (string), typeof (MenuContent));
public string Title
{
get => (string)GetValue (TitleProperty);
set => SetValue (TitleProperty, value);
}
}
and for renderers :
class CSViewRenderer : ViewRenderer<CSView, FrameLayout>, NavigationBarView.IOnItemSelectedListener
{
...
protected override void OnElementChanged (ElementChangedEventArgs <CSView> e)
{
if (e.OldElement != null)
UnhandleEvents (e.OldElement);
if (e.NewElement != null)
{
if (Control == null)
SetNativeControl (_control);
HandleEvents (e.NewElement)
}
}
void HandleEvents (CSView element)
=> element.PropertyChanged += OnElementPropertyChanged;
void UnhandleEvents (CSView element)
=> element.PropertyChanged -= OnElementPropertyChanged;
}
/// this is where the issue came from
class CSMenuItemRender : AbsItemRenderer
{
...
protected override View CreateNativeControl ()
{
_base = new LinearLayout (Context);
_toolBar = new ToolBar (Context)
_content = Platform.CreateRendererWithContext (Element.Content, Context); // where Element is type of MenuItem
_toolBar.Title = Element.Content.Title;
_base.Orientation = Orientation.Vertical;
_base.AddView (_toolBar, new LinearLayout.LayoutParams (-1, -2);
_base.AddView (_content.View, new LinearLayout.LayoutParams (-1, -2);
ElementRendererUtil.FitElement (Context, _base, _content);
Platform.SetRenderer (Element.Content, _content);
_content.ElementChanged += OnRendererElementChanged;
return _base;
}
void OnElementChanged (ElementChangedEventArgs <CSMenuItem> e)
{
if (e.OldElement != null)
UnhandleEvents (e.OldElement);
if (e.NewElement != null)
{
if (Control == null)
SetNativeControl (this)
HandleEvents (e.NewElement);
}
}
void OnRendererElementChanged (object sender, VisualElementChangedEventArgs e)
{
if (e.OldElement != null)
e.OldElement.PropertyChanged -= OnRendererElementPropertyChanged;
if (e.NewElement != null)
e.NewElement.PropertyChanged += OnRendererElementPropertyChanged;
}
void OnRendererElementPropertyChanged (object sender, PropertyChangedEventArgs e)
{
var menuContent = (MenuContent)sender;
if (e.PropertyName == MenuContent.TitleProperty.PropertyName)
_toolBar.Title = menuContent.Title;
else if (e.PropertyName == MenuContent.ContentProperty.PropertyName)
_content.Tracker.UpdateLayout ();
}
void HandleEvents (CSMenuItem element)
=> element.PropertyChanged += OnElementPropertyChanged;
void UnhandleEvents (CSMenuItem element)
=> element.PropertyChanged -= OnElementPropertyChanged;
}
I was able to change the MenuContent properties like Title when changing the derive type to Element, like this:
class MenuContent : Element
And setting its parent to CSMenuItem, like this :
public MenuContent Content
{
...
set {
if (value.Parent != this)
value.Parent = this;
SetValue (ContentProperty, value);
}
}
but still the Content property of MenuContent won't update when the value is changed. What I want is to update the MenuContent what ever the value changed in xaml file and to know why the propertyChanged won't fire.
Sorry for my bad English, hope you understand. May the Almighty Bless You😇 Stay safe.

iOS 14 DatePicker renderer

I am having some difficulty figuring out how to properly update my existing custom renderer I have for my DatePicker on iOS to display the DatePicker with a different preferred style as is mentioned in this article here (albeit it is for swift) https://medium.com/better-programming/introducing-swifts-new-modern-date-picker-37bb5e0a106
My renderer is as follows:
public class BorderlessDatePickerRenderer : DatePickerRenderer
{
public override void LayoutSubviews()
{
base.LayoutSubviews();
var element = Element as BorderlessDatePicker;
Control.BorderStyle = UITextBorderStyle.None;
if (element.Date.Year == 1900) {
Control.Text = "";
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var element = Element as BorderlessDatePicker;
if (Control != null && element.Date.Year == 1900) {
Control.Text = "";
}
}
The BorderlessDatePicker itself is just an empty class that extends the DatePicker Xamarin.Forms control. The root of my woes is that I am not sure how to properly set a PreferredDatePickerStyle on my Control object given that Control is a UITextField under the hood instead of a UIDatePicker. In essence what I would like to do is instead of displaying the Date picker using the compact style that seems to be default for iOS 14, I would like for it to be displayed as wheels instead by being to do something like:
PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
After some more researching, and browsing the xamarin github, I've come across this solution:
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
if(e.NewElement != null && this.Control != null)
{
try
{
if (UIDevice.CurrentDevice.CheckSystemVersion(14, 0))
{
UIDatePicker picker = (UIDatePicker)Control.InputView;
picker.PreferredDatePickerStyle = UIDatePickerStyle.Wheels;
}
} catch (Exception ex)
{
Log.Error(ex, "Failed to set PreferredDatePickerStyle to be UIDatePickerStyle.Wheels for iOS 14.0+");
}
}
}

how to implement tap gesture in webview to display html not website

I need my web view to be tappable and scrolable. Once I implement on touch the scroll doesnt work. This way i managed to get it working however now i dont know how to make the web view tappable? the ButtonPress does nothing and if i use Move then i am just scrolling
This my my render in mu droid project
class ExtendedWebViewClient : WebViewClient
{
WebView _webView;
public async override void OnPageFinished(WebView view, string url)
{
try
{
_webView = view;
if (_xwebView != null)
{
view.Settings.JavaScriptEnabled = true;
await Task.Delay(100);
string result = await _xwebView.EvaluateJavaScriptAsync("(function(){return document.body.scrollHeight;})()");
_xwebView.HeightRequest = Convert.ToDouble(result);
}
base.OnPageFinished(view, url);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, IWebResourceRequest request)
{
return true;
}
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
_xwebView = e.NewElement as ExtendedWebView;
_webView = Control;
if (e.OldElement == null)
{
_webView.SetWebViewClient(new ExtendedWebViewClient());
}
if (e.OldElement != null)
{
Control.Touch -= ControlOnTouch;
Control.ScrollChange -= ControlOnScrollChange;
}
if (e.NewElement != null)
{
Control.Touch += ControlOnTouch;
Control.ScrollChange += ControlOnScrollChange;
}
}
private void ControlOnScrollChange(object sender, ScrollChangeEventArgs scrollChangeEventArgs)
{
if (scrollChangeEventArgs.ScrollY > 0 && scrollChangeEventArgs.OldScrollY == 0)
{
Control.Parent.RequestDisallowInterceptTouchEvent(true);
}
}
private void ControlOnTouch(object sender, Android.Views.View.TouchEventArgs e)
{
// Executing this will prevent the Scrolling to be intercepted by parent views
switch (e.Event.Action)
{
case MotionEventActions.Down:
Control.Parent.RequestDisallowInterceptTouchEvent(true);
break;
case MotionEventActions.Up:
Control.Parent.RequestDisallowInterceptTouchEvent(false);
break;
case MotionEventActions.ButtonPress:
Console.WriteLine("press");
break;
case MotionEventActions.Mask:
Console.WriteLine("mask");
break;
}
// Calling this will allow the scrolling event to be executed in the WebView
Control.OnTouchEvent(e.Event);
}
Instead of using the gesture recognizer on your webview, you can use the Focused event like following . It will been invoked when you tap the WebView .
var wb = new WebView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
Source = "xxx",
};
wb.Focused += (sender, event) =>
{
//Handle your logic here!
wb.Unfocus();
};
Unfocus() is used if you want to implement your logic everytime the webview is tapped.

Add bottom line in android custom renderer code?

I know the default DatePicker already has a bottom line, but I'm trying to add a bottom line to the DatePicker in the custom renderer code (for some purpose).
I can set a full border of my GradientDrawable object by myGradientDrawable.SetStroke(3, myColor); but I don't know how to add only the bottom line so anyone can help me please?
Try this:
public class CustomPickerRenderer : PickerRenderer
{
public CustomPickerRenderer(Context context) : base(context)
{
}
private AlertDialog alert;
private CustomPicker element;
private int selectedIndex;
public LayerDrawable AddPickerStyles(string imagePath)
{
ColorDrawable borderColorDrawable = new ColorDrawable(Xamarin.Forms.Color.FromHex("#43addf").ToAndroid());
ColorDrawable backgroundColorDrawable = new ColorDrawable(Xamarin.Forms.Color.FromHex("#7e1b80").ToAndroid());
Drawable[] drawables = new Drawable[]
{
borderColorDrawable, backgroundColorDrawable
};
LayerDrawable layerDrawable = new LayerDrawable(drawables);
layerDrawable.SetLayerInset(1, 0, 0, 0, 5);
return layerDrawable;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
element = (CustomPicker)this.Element;
if (Control != null && this.Element != null)
{
Control.Background = AddPickerStyles(element.Image);
}
}
}

Is it possible to change the border of a single Entry in xamarin forms?

I was wondering if someone can point out how I can change a single entry?. I've made a custom renderer which changes the border of an entry to red but what I really want is only to change one entry if validation fails from black to red.
Picture of entries:
My renderer:
[assembly: ExportRenderer(typeof(App.RedFrameEntry), typeof(RedFrameEntryRenderer))]
namespace App.iOS
{
public class RedFrameEntryRenderer : EntryRenderer
{
public bool isInvalid = false;
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.BorderStyle = UITextBorderStyle.RoundedRect;
Control.Layer.CornerRadius = 4;
Control.Layer.BorderColor = Color.FromHex("#c60303").ToCGColor();
Control.Layer.BorderWidth = 0;
if (isInvalid)
{
Control.Layer.BorderWidth = 2;
}
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
}
}
And my code:
private void ChangeEntryOnValidationFail(string text, Entry entry, int numberOfChar)
{
if (String.IsNullOrEmpty(text) || text.Length < numberOfChar)
{
// TODO: Change to RedFrameEntry
}
else
{
// TODO: Change back to default
}
}
I'd suggest you create a CustomEntry class which has a base class of Entry, so instead of changing the border of all entries, you can just call the CustomEntry whenever you need it.
public class CustomEntry : Entry{
}
then use:
[assembly: ExportRenderer(typeof(CustomEntry), typeof(RedFrameEntryRenderer))]
Hope it helps!

Resources