How can I change the font for the header of a Navigation page with Xamarin Forms? - xamarin

I can change the font color like this:
var homePage = new NavigationPage(new HomePage())
{
Title = "Home",
Icon = "ionicons_2_0_1_home_outline_25.png",
BarTextColor = Color.Gray,
};
But is there a way to change the font for the Title. I would like to change it for the iOS and Android platforms only. Hoping that someone knows of custom renderer code that can help me to do this.

You need Custom Renderer , refer to this sample
iOS
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.iOS.Renderers
{
public class CustomNavigationPageRenderer : NavigationRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
var att = new UITextAttributes();
UIFont customFont = UIFont.FromName("Trashtalk", 20);
UIFont systemFont = UIFont.SystemFontOfSize(20.0);
UIFont systemBoldFont = UIFont.SystemFontOfSize(20.0 , FontAttributes.Bold);
att.Font = font;
UINavigationBar.Appearance.SetTitleTextAttributes(att);
}
}
}
}
Android
[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.Droid.Renderers
{
public class CustomNavigationPageRenderer : NavigationPageRenderer
{
private Android.Support.V7.Widget.Toolbar _toolbar;
public override void OnViewAdded(Android.Views.View child)
{
base.OnViewAdded(child);
if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
{
_toolbar = (Android.Support.V7.Widget.Toolbar)child;
_toolbar.ChildViewAdded += Toolbar_ChildViewAdded;
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if(disposing)
{
_toolbar.ChildViewAdded -= Toolbar_ChildViewAdded;
}
}
private void Toolbar_ChildViewAdded(object sender, ChildViewAddedEventArgs e)
{
var view = e.Child.GetType();
if (e.Child.GetType() == typeof(Android.Widget.TextView))
{
var textView = (Android.Widget.TextView)e.Child;
var spaceFont = Typeface.CreateFromAsset(Forms.Context.ApplicationContext.Assets, "Trashtalk.ttf");
var systemFont = Typeface.DEFAULT;
var systemBoldFont = Typeface.DEFAULT_BOLD;
textView.Typeface = spaceFont;
_toolbar.ChildViewAdded -= Toolbar_ChildViewAdded;
}
}
}
}

There is no need in a custom renderer on iOS, you can just use the Appearance API:
UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes
{
Font = UIFont.FromName("MyCoolFont", 20)
});
In Android you do need a renderer, however you should check against Android.Support.V7.Widget.AppCompatTextView and not Android.Widget.TextView.
Tested on Xamarin.Forms 3.4.0

Related

How do I implement CustomMoreNavigationControllerDelegate using ShellRenderer?

Previously I was able to use TabbedRenderer to stylize the tabbed bar More menu page by implementing CustomMoreNavigationControllerDelegate as so...
public class ExtendedTabbedPageRenderer : TabbedRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
MoreNavigationController.Delegate = new CustomMoreNavigationControllerDelegate();
}
}
}
I'm now using Shell navigation instead, and so how can I assign/implement this class using ShellRenderer (since there is no OnElementChanged override)?
My More menu styling class
internal class CustomMoreNavigationControllerDelegate : UINavigationControllerDelegate
{
public override void WillShowViewController(UINavigationController navigationController, UIViewController viewController, bool animated)
{
viewController.NavigationController.NavigationBarHidden = false;
UILabel titleLabel = new UILabel();
if (viewController.NavigationItem.Title != "More")
{
titleLabel.Text = viewController.NavigationItem.Title;
}
else
{
titleLabel.Text = "More";
}
titleLabel.TextColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
viewController.NavigationItem.TitleView = titleLabel;
viewController.View.BackgroundColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? ExtendedTabbedPageRenderer.iOSDarkPageBackgroundColor : ExtendedTabbedPageRenderer.iOSLightPageBackgroundColor;
viewController.View.TintColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
if (viewController.View is UITableView tableView)
{
tableView.SeparatorColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
foreach (var cell in tableView.VisibleCells)
{
cell.BackgroundColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? ExtendedTabbedPageRenderer.iOSDarkPageBackgroundColor : ExtendedTabbedPageRenderer.iOSLightPageBackgroundColor;
cell.TextLabel.TextColor = cell.TintColor = Xamarin.Forms.Application.Current.UserAppTheme == OSAppTheme.Dark ? UIColor.White : UIColor.Black;
}
}
}
}
You need override the required methods to perform the required customization via a subclass of the ShellRenderer class to customize the Tabbar appearance.
For example,in iOS, if you want to set the text of the more menu of the Shell application.
Code snippet:
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(Xaminals.AppShell), typeof(Xaminals.iOS.MyShellRenderer))]
namespace Xaminals.iOS
{
public class MyShellRenderer : ShellRenderer
{
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new TabBarAppearance();
}
}
public class TabBarAppearance : IShellTabBarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(UITabBarController controller)
{
}
public void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
//set tabbar appearance
}
public void UpdateLayout(UITabBarController controller)
{
UITabBar tb = controller.MoreNavigationController.TabBarController.TabBar;
if (tb.Subviews.Length > 4)
{
UIView tbb = tb.Subviews[4];
UILabel label = (UILabel)tbb.Subviews[1];
label.Text = "CustomTab";
}
}
}
}
For more details, please refer to our MS official docs.

How to set picker row item long text in two lines using custom renderer in xamarin forms android?

I want to show long text of picker item in two lines using custom render how can I achieve it?
First , you need a custom renderer for picker .
To override the item you need to replace the original view with a AlertDialog .
Then custom a ListView and set it as AlertDialog.View .
Then you can customize everything in the Adapter(here we need to customize the textview ).
Sample code
[assembly: ExportRenderer(typeof(Picker), typeof(MyPickerRenderer))]
namespace FormsApp.Droid
{
public class MyAdapter : ArrayAdapter
{
private IList<string> _data;
Context _context;
public MyAdapter(Context context, int resource , IList<string> data) : base(context,resource)
{
_context = context;
_data = data;
}
public override int Count => _data.Count;
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
TextView textview = new TextView(_context);
textview.TextSize = 18;
textview.SetTextColor(Android.Graphics.Color.DarkGray);
textview.Ellipsize = TruncateAt.End;
textview.SetMaxLines(2); //this line
textview.Text = _data[position];
return textview;
}
}
public class MyListView : Android.Widget.ListView
{
public MyListView(Context context, IList<string> data) : base(context)
{
this.DividerHeight = 0;
this.Adapter = new MyAdapter(context, 0, data);
}
}
class MyPickerRenderer : Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer
{
IElementController ElementController => Element as IElementController;
public MyPickerRenderer(Context context):base(context)
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new MyListView(Context, model.Items);
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.SetPadding(35, 30, 35, 0);
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton("Cancel ", (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
}
}
}
Testing code in Forms xaml
<Picker x:Name="picker" Title="Select a monkey">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>abc</x:String>
<x:String>aaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbccccccccccccccccdddddddddddddddddddddd</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
Before
After

How can I enable video autoplay in WKWebView on iOS

I have a WebView that contains an iframe (an HTML element). this iframe has a Peertube video plyaer, I pass &autoplay=1 to that iframe and it works good on android, iPad and browser, but on iPhone the video can not be autoplayed.
I tried custom render with WKWebView with this code:
class FullScreenEnabledWebViewRenderer : WkWebViewRenderer
{
WKUserContentController userController;
public FullScreenEnabledWebViewRenderer() : this(new WKWebViewConfiguration() { MediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypes.None, AllowsInlineMediaPlayback = true, MediaPlaybackRequiresUserAction = false, RequiresUserActionForMediaPlayback = false })
{
}
public FullScreenEnabledWebViewRenderer(WKWebViewConfiguration config) : base(config)
{
config.AllowsInlineMediaPlayback = true;
config.AllowsAirPlayForMediaPlayback = true;
config.AllowsPictureInPictureMediaPlayback = true;
config.MediaPlaybackAllowsAirPlay = true;
config.MediaPlaybackRequiresUserAction = false;
config.RequiresUserActionForMediaPlayback = false;
config.MediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypes.None;
}
}
and still does not work.
Can you help me with that please.
Webview in iOS couldn't support iframe very well . So it could better to use <video> like
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = #"<html><body><video width='300' height='500' muted='muted' controls autoplay='autoplay'><source src='https://ia800201.us.archive.org/12/items/BigBuckBunny_328/BigBuckBunny_512kb.mp4' type='video/mp4'></video></body></html>";
webview.Source = htmlSource;
If it still could not auto play in iOS , we could invoked JS to play it .
using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using App4;
using App4.iOS;
using WebKit;
using ObjCRuntime;
[assembly: ExportRenderer(typeof(WebView), typeof(FullScreenEnabledWebViewRenderer))]
namespace App4.iOS
{
public class FullScreenEnabledWebViewRenderer: ViewRenderer<WebView, WKWebView>
{
WKWebView wkWebView;
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var config = new WKWebViewConfiguration();
config.AllowsInlineMediaPlayback = true;
config.AllowsAirPlayForMediaPlayback = true;
config.AllowsPictureInPictureMediaPlayback = true;
config.MediaPlaybackAllowsAirPlay = true;
config.MediaPlaybackRequiresUserAction = false;
config.RequiresUserActionForMediaPlayback = false;
config.MediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypes.None;
wkWebView = new WKWebView(Frame, config);
wkWebView.NavigationDelegate = new WkWebviewPolicy();
SetNativeControl(wkWebView);
}
}
}
public class WkWebviewPolicy : WKNavigationDelegate
{
public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, WKWebpagePreferences preferences, [BlockProxy(typeof(NIDActionArity2V120))] Action<WKNavigationActionPolicy, WKWebpagePreferences> decisionHandler)
{
decisionHandler.Invoke(WKNavigationActionPolicy.Allow, preferences);
//base.DecidePolicy(webView, navigationAction, preferences, decisionHandler);
}
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
base.DidFinishNavigation(webView, navigation);
var JSstr = #"var videos = document.querySelectorAll('video'); for (var i = videos.length - 1; i >= 0; i--) { var ivideo = videos[i]; ivideo.setAttribute('webkit-playsinline','\'); ivideo.play(); };";
webView.EvaluateJavaScript(JSstr,null);
}
}
}

Xamarin Forms Gradient Renderer not working on iOS

I'm trying to use a Gradient Renderer for which I have written a class in PCL and written a renderer for both Android and iOS. Android renderer is working but iOS renderer is not showing the gradient colour at all.
I'm using this Gradient code from XLabs. I'm not sure what's broken. A hint in the right direction would help.
PCL Code:
using Xamarin.Forms;
namespace gradient
{
public enum GradientOrientation
{
Vertical,
Horizontal
}
public class GradientContentView : ContentView
{
public GradientOrientation Orientation
{
get { return (GradientOrientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly BindableProperty OrientationProperty =
BindableProperty.Create<GradientContentView, GradientOrientation>(x => x.Orientation, GradientOrientation.Vertical, BindingMode.OneWay);
public Color StartColor
{
get { return (Color)GetValue(StartColorProperty); }
set { SetValue(StartColorProperty, value); }
}
public static readonly BindableProperty StartColorProperty =
BindableProperty.Create<GradientContentView, Color>(x => x.StartColor, Color.White, BindingMode.OneWay);
public Color EndColor
{
get { return (Color)GetValue(EndColorProperty); }
set { SetValue(EndColorProperty, value); }
}
public static readonly BindableProperty EndColorProperty =
BindableProperty.Create<GradientContentView, Color>(x => x.EndColor, Color.Black, BindingMode.OneWay);
}
}
iOS Renderer code:
using CoreAnimation;
using CoreGraphics;
using gradient;
using gradient.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(GradientContentView), typeof(GradientContentViewRenderer))]
namespace gradient.iOS
{
class GradientContentViewRenderer : VisualElementRenderer<ContentView>
{
private GradientContentView GradientContentView
{
get { return (GradientContentView)Element; }
}
protected CAGradientLayer GradientLayer { get; set; }
protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
{
base.OnElementChanged(e);
if (GradientContentView != null &&
NativeView != null)
{
// Create a gradient layer and add it to the
// underlying UIView
GradientLayer = new CAGradientLayer();
GradientLayer.Frame = NativeView.Bounds;
GradientLayer.Colors = new CGColor[]
{
GradientContentView.StartColor.ToCGColor(),
GradientContentView.EndColor.ToCGColor()
};
SetOrientation();
NativeView.Layer.InsertSublayer(GradientLayer, 0);
}
}
protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (GradientLayer != null && GradientContentView != null)
{
// Turn off Animations
CATransaction.Begin();
CATransaction.DisableActions = true;
if (e.PropertyName == GradientContentView.StartColorProperty.PropertyName)
GradientLayer.Colors[0] = GradientContentView.StartColor.ToCGColor();
if (e.PropertyName == GradientContentView.EndColorProperty.PropertyName)
GradientLayer.Colors[1] = GradientContentView.EndColor.ToCGColor();
if (e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
GradientLayer.Frame = NativeView.Bounds;
if (e.PropertyName == GradientContentView.OrientationProperty.PropertyName)
SetOrientation();
CATransaction.Commit();
}
}
void SetOrientation()
{
if (GradientContentView.Orientation == GradientOrientation.Horizontal)
{
GradientLayer.StartPoint = new CGPoint(0, 0.5);
GradientLayer.EndPoint = new CGPoint(1, 0.5);
}
else
{
GradientLayer.StartPoint = new CGPoint(0.5, 0);
GradientLayer.EndPoint = new CGPoint(0.5, 1);
}
}
}
}
This is my code for rendering a gradient background, i am not using orientation, but maybe it helps.
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement == null) // perform initial setup
{
ModernOrderCalendar page = e.NewElement as ModernOrderCalendar;
var gradientLayer = new CAGradientLayer();
gradientLayer.Name = "gradient";
CGRect rect = View.Bounds;
gradientLayer.Frame = rect;
gradientLayer.Colors = new CGColor[] { page.StartColor.ToCGColor(), page.EndColor.ToCGColor() };
View.Layer.InsertSublayer(gradientLayer, 0);
}
}
public override void ViewWillLayoutSubviews()
{
base.ViewWillLayoutSubviews();
if (Xamarin.Forms.Device.Idiom == TargetIdiom.Tablet)
{
var gradientLayer = View.Layer.Sublayers.FirstOrDefault(l => l.Name == "gradient");
gradientLayer.Frame = View.Bounds;
View.Layer.Sublayers[0] = gradientLayer;
CGRect frame = View.Bounds;
View.Bounds = frame;
}
}
The main difference I see is that you don't seem to be overriding the ViewWillLayoutSubviews method. I had the same issue, which caused the gradient layer to be created with no height and width during the creation of the window (at that point the View has not layouted, yet).
Therefore I adapt the gradientlayer width and height when layouting the subviews, because at that point width and height of the view are definitely known.
You must update the layer's size in VisualElementRenderer.LayoutSubviews:
public override void LayoutSubviews()
{
base.LayoutSubviews();
CATransaction.Begin();
CATransaction.DisableActions = true;
GradientLayer.Frame = NativeView.Bounds;
CATransaction.Commit();
}

Removing Padding from Entry

So Entry does not have a padding attribute, however there is some definite padding that goes on the Entry.
Example
I have the "Michigan" Entry lined up with the "Select" Label below, however they look misaligned because the entry has some padding to the left. I tried the margin attribute that entry does have, however it did not work.
How do I get rid of that gap/padding?
I'd like to add that adding an offset margin does not working.
You need to make a custom renderer for the entry and set the Android EditText's PaddingLeft to 0 using the SetPadding method.
Excerpt from CustomEntryRenderer on Android:
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement == null) return;
Control.SetPadding(0, Control.PaddingTop, Control.PaddingRight, Control.PaddingBottom);
}
For me the custom render that worked was:
[assembly: Xamarin.Forms.ExportRenderer(typeof(MyApp.Views.Controls.CustomEntry), typeof(MyApp.Droid.Views.Controls.CustomRenderer.Android.CustomEntryRenderer))]
namespace MyApp.Droid.Views.Controls
{
namespace CustomRenderer.Android
{
public class CustomEntryRenderer : EntryRenderer
{
public CustomEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
Control.Background = new ColorDrawable(Color.Transparent);
Control.SetPadding(0, 0, 0, 0);
Control.Gravity = GravityFlags.CenterVertical | GravityFlags.Left;
Control.TextAlignment = TextAlignment.Gravity;
}
}
}
}
}
I was struggling with the same issue, but I solved it by creating a custom Entry type which adds a Padding property to Xamarin Forms' Entry:
public class CustomEntry : Entry
{
public static readonly BindableProperty PaddingProperty =
BindableProperty.Create(
nameof(Padding),
typeof(Thickness),
typeof(CustomEntry),
new Thickness());
public Thickness Padding
{
get { return (Thickness)this.GetValue(PaddingProperty); }
set { this.SetValue(PaddingProperty, value); }
}
}
Then I render this CustomEntry with a custom renderer, just as Danilow proposed, with the only difference, that I read the PaddingProperty from CustomEntry and apply it in the CustomEntryRenderer.
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
namespace CrossPlatformLibrary.Forms.Android.Renderers
{
public class CustomEntryRenderer : EntryRenderer
{
public CustomEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
{
return;
}
if (this.Element is CustomEntry customEntry)
{
var paddingLeft = (int)customEntry.Padding.Left;
var paddingTop = (int)customEntry.Padding.Top;
var paddingRight = (int)customEntry.Padding.Right;
var paddingBottom = (int)customEntry.Padding.Bottom;
this.Control.SetPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
}
}
}
Beware: This code needs to be extended if you want to react on PaddingProperty changes - AND - you will need to write a custom renderer for IOS if you want to support the Padding property there too.
Here's the same thing implemented on iOS for anyone who needs it there too. It's basically setting the left view to have 0 width that does it, but you can also play with the "LeftViewMode" to hide it completely.
this.Control.LeftView = new UIView(new CGRect(0, 0, 0, this.Control.Frame.Height));
Full code
using CoreGraphics;
using UIKit;
using YourNamespace.iOS.CustomRenderers;
using YourNamespace.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace YourNamespace.iOS.CustomRenderers
{
public class BorderlessEntryRenderer : Xamarin.Forms.Platform.iOS.EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
this.Control.LeftView = new UIView(new CGRect(0, 0, 0, this.Control.Frame.Height));
this.Control.LeftViewMode = UITextFieldViewMode.Always;
}
}
}
}

Resources