Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Custom Tabbed Renderer for Xamarin Forms to display Svg icons using XamSvg
using System.Threading;
using Android.App;
using Android.Graphics;
using Android.OS;
using Android.Util;
using Android.Views;
using Vapolia.Droid.Lib.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XamSvg;
using Path = System.IO.Path;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedRenderer))]
namespace Vapolia.Droid.Lib.Renderers
{
/// <summary>
/// There is a problem in Xamarin.Forms: icons are disappearing when opening a new page and coming back.
/// https://forums.xamarin.com/discussion/17654/tabbedpage-icons-not-visible-android
/// </summary>
public sealed class MyTabbedRenderer : TabbedRenderer
{
private int availableHeight;
public MyTabbedRenderer()
{
base.SaveEnabled = true;
}
protected override void OnDraw(Canvas canvas)
{
//Log.Debug("wsm", $"OnDraw {(Context as Activity)?.ActionBar?.Title}");
base.OnDraw(canvas);
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
float actionBarSize;
using (var styledAttributes = Context.Theme.ObtainStyledAttributes(new[] { Android.Resource.Attribute.ActionBarSize }))
{
actionBarSize = styledAttributes.GetDimension(0, 0);
styledAttributes.Recycle();
}
availableHeight = (int)(0.5f * actionBarSize);
UpdateIcons();
//Log.Debug("wsm", $"OnAttachedToWindow {(Context as Activity)?.ActionBar?.Title}");
}
protected override void OnVisibilityChanged(Android.Views.View changedView, ViewStates visibility)
{
base.OnVisibilityChanged(changedView, visibility);
if (visibility == ViewStates.Visible)
UpdateIcons();
//Log.Debug("wsm", $"OnVisibilityChanged {(Context as Activity)?.ActionBar?.Title}");
}
public override void OnWindowFocusChanged(bool hasWindowFocus)
{
base.OnWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus)
UpdateIcons();
//Log.Debug("wsm",$"OnWindowFocusChanged {(Context as Activity)?.ActionBar?.Title}");
}
protected override void OnDisplayHint(int hint)
{
base.OnDisplayHint(hint);
UpdateIcons();
//Log.Debug("wsm", $"OnDisplayHint {(Context as Activity)?.ActionBar?.Title}");
}
protected override void OnRestoreInstanceState(IParcelable state)
{
base.OnRestoreInstanceState(state);
UpdateIcons();
//Log.Debug("wsm", $"OnRestoreInstanceState {(Context as Activity)?.ActionBar?.Title}");
}
protected override void DrawableStateChanged()
{
UpdateIcons();
base.DrawableStateChanged();
}
protected override void DispatchDraw(Canvas canvas)
{
//Log.Debug("wsm", $"DispatchDraw {(Context as Activity)?.ActionBar?.Title}");
//SetTabIcons();
//(Context as Activity)?.InvalidateOptionsMenu();
//Invalidate();
base.DispatchDraw(canvas);
UpdateIcons();
}
private void UpdateIcons()
{
var activity = Context as Activity;
var tabbedPage = Element;
if (tabbedPage == null || activity == null)
return;
var actionBar = activity?.ActionBar;
var existingTabCount = actionBar?.TabCount ?? 0;
if (existingTabCount > 0 && existingTabCount == tabbedPage.Children.Count)
{
for (var i=0; i < tabbedPage.Children.Count; i++)
{
var tab = i<existingTabCount ? actionBar.GetTabAt(i) : null;
var page = tabbedPage.Children[i];
if (tab != null && page.Icon != null)
{
var iconName = page.Icon.File;
if (iconName.StartsWith("res:"))
{
var iconDrawable = SvgFactory.GetDrawable(Context, iconName, CancellationToken.None);
//Default tab layout uses wrap_content for both width/height of icon, resulting in a call to SetBounds with IntrinsicWidth/Height
//So force the intrinsic size !
//An alternative would be to create a custom layout with the svg layout_height set to math_parent (and width to wrap_content).
iconDrawable.ForcedSize = new System.Drawing.Size(iconDrawable.IntrinsicWidth * availableHeight / iconDrawable.IntrinsicHeight, availableHeight);
tab.SetIcon(iconDrawable);
}
else
{
var resName = Path.GetFileNameWithoutExtension(iconName);
var resId = Resources.GetIdentifier(resName, "drawable", Context.PackageName);
if (resId != 0)
tab.SetIcon(resId);
}
if (!tab.Text.StartsWith(" "))
tab.SetText(" " + tab.Text);
}
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Reflection;
using CoreGraphics;
using UIKit;
using Vapolia.iOS.Lib.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Internals;
using Xamarin.Forms.Platform.iOS;
using XamSvg;
using Color = Xamarin.Forms.Color;
using Rectangle = Xamarin.Forms.Rectangle;
using Size = Xamarin.Forms.Size;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(MyTabbedRenderer))]
namespace Vapolia.iOS.Lib.Renderers
{
/// <summary>
/// Custom version able to display SVG files
/// Xamarin.Forms 2.3.3 (vnext)
/// </summary>
public class MyTabbedRenderer : UITabBarController, IVisualElementRenderer //, IEffectControlProvider
{
bool _barBackgroundColorWasSet;
bool _barTextColorWasSet;
UIColor _defaultBarTextColor;
bool _defaultBarTextColorSet;
UIColor _defaultBarColor;
bool _defaultBarColorSet;
bool _loaded;
Size _queuedSize;
IPageController PageController => Element as IPageController;
IElementController ElementController => Element as IElementController;
public override UIViewController SelectedViewController
{
get { return base.SelectedViewController; }
set
{
base.SelectedViewController = value;
UpdateCurrentPage();
}
}
protected TabbedPage Tabbed => (TabbedPage)Element;
public VisualElement Element { get; private set; }
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
return NativeView.GetSizeRequest(widthConstraint, heightConstraint);
}
public UIView NativeView => View;
public void SetElement(VisualElement element)
{
var oldElement = Element;
Element = element;
FinishedCustomizingViewControllers += HandleFinishedCustomizingViewControllers;
Tabbed.PropertyChanged += OnPropertyChanged;
Tabbed.PagesChanged += OnPagesChanged;
OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
OnPagesChanged(null, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
if (element != null)
{
var sendViewInitialized = typeof(Forms).GetMethod("SendViewInitialized", BindingFlags.NonPublic | BindingFlags.Static);
sendViewInitialized.Invoke(null, new[] { element, (object)NativeView});
//element.SendViewInitialized(NativeView);
}
//disable edit/reorder of tabs
CustomizableViewControllers = null;
UpdateBarBackgroundColor();
UpdateBarTextColor();
//EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
}
public void SetElementSize(Size size)
{
if (_loaded)
Element.Layout(new Rectangle(Element.X, Element.Y, size.Width, size.Height));
else
_queuedSize = size;
}
public UIViewController ViewController => this;
public override void DidRotate(UIInterfaceOrientation fromInterfaceOrientation)
{
base.DidRotate(fromInterfaceOrientation);
View.SetNeedsLayout();
}
public override void ViewDidAppear(bool animated)
{
PageController.SendAppearing();
base.ViewDidAppear(animated);
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
PageController.SendDisappearing();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
if (Element == null)
return;
if (!Element.Bounds.IsEmpty)
{
View.Frame = new System.Drawing.RectangleF((float)Element.X, (float)Element.Y, (float)Element.Width, (float)Element.Height);
}
var frame = View.Frame;
var tabBarFrame = TabBar.Frame;
PageController.ContainerArea = new Rectangle(0, 0, frame.Width, frame.Height - tabBarFrame.Height);
if (!_queuedSize.IsZero)
{
Element.Layout(new Rectangle(Element.X, Element.Y, _queuedSize.Width, _queuedSize.Height));
_queuedSize = Size.Zero;
}
_loaded = true;
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//if (!Forms.IsiOS7OrNewer)
// WantsFullScreenLayout = false;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
PageController.SendDisappearing();
Tabbed.PropertyChanged -= OnPropertyChanged;
Tabbed.PagesChanged -= OnPagesChanged;
FinishedCustomizingViewControllers -= HandleFinishedCustomizingViewControllers;
}
base.Dispose(disposing);
}
protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
{
var changed = ElementChanged;
if (changed != null)
changed(this, e);
}
UIViewController GetViewController(Page page)
{
var renderer = Platform.GetRenderer(page);
if (renderer == null)
return null;
return renderer.ViewController;
}
void HandleFinishedCustomizingViewControllers(object sender, UITabBarCustomizeChangeEventArgs e)
{
if (e.Changed)
UpdateChildrenOrderIndex(e.ViewControllers);
}
void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == Page.TitleProperty.PropertyName)
{
var page = (Page)sender;
var renderer = Platform.GetRenderer(page);
if (renderer == null)
return;
if (renderer.ViewController.TabBarItem != null)
renderer.ViewController.TabBarItem.Title = page.Title;
}
else if (e.PropertyName == Page.IconProperty.PropertyName)
{
var page = (Page)sender;
var renderer = Platform.GetRenderer(page);
if (renderer == null)
return;
if (renderer.ViewController.TabBarItem == null)
return;
var icons = GetIcon(page);
// the new UITabBarItem forces redraw, setting the UITabBarItem.Image does not
renderer.ViewController.TabBarItem = new UITabBarItem(page.Title, icons.Item1, icons.Item2);
icons.Item1?.Dispose();
icons.Item2?.Dispose();
}
}
protected virtual Tuple<UIImage, UIImage> GetIcon(Page page)
{
if (!string.IsNullOrEmpty(page.Icon))
{
var iconName = page.Icon.File;
if (iconName.StartsWith("res:"))
{
var icon = SvgFactory.FromBundle(iconName, 0, TabBar?.Bounds.Height / 2 ?? 30).ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
var iconSelected = SvgFactory.FromBundle(iconName, 0, TabBar?.Bounds.Height / 2 ?? 30, SvgColorMapperFactory.FromString("000000=007AFF")).ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);
return Tuple.Create(icon, iconSelected);
}
return Tuple.Create(new UIImage(page.Icon), (UIImage)null);
}
return null;
}
void OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
{
e.Apply((o, i, c) => SetupPage((Page)o, i), (o, i) => TeardownPage((Page)o, i), Reset);
SetControllers();
UIViewController controller = null;
if (Tabbed.CurrentPage != null)
controller = GetViewController(Tabbed.CurrentPage);
if (controller != null && controller != base.SelectedViewController)
base.SelectedViewController = controller;
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TabbedPage.CurrentPage))
{
var current = Tabbed.CurrentPage;
if (current == null)
return;
var controller = GetViewController(current);
if (controller == null)
return;
SelectedViewController = controller;
}
else if (e.PropertyName == TabbedPage.BarBackgroundColorProperty.PropertyName)
UpdateBarBackgroundColor();
else if (e.PropertyName == TabbedPage.BarTextColorProperty.PropertyName)
UpdateBarTextColor();
}
void Reset()
{
var i = 0;
foreach (var page in Tabbed.Children)
SetupPage(page, i++);
}
void SetControllers()
{
var list = new List<UIViewController>();
for (var i = 0; i < ElementController.LogicalChildren.Count; i++)
{
var child = ElementController.LogicalChildren[i];
var v = child as VisualElement;
if (v == null)
continue;
if (Platform.GetRenderer(v) != null)
list.Add(Platform.GetRenderer(v).ViewController);
}
ViewControllers = list.ToArray();
}
void SetupPage(Page page, int index)
{
var renderer = Platform.GetRenderer(page);
if (renderer == null)
{
renderer = Platform.CreateRenderer(page);
Platform.SetRenderer(page, renderer);
}
page.PropertyChanged += OnPagePropertyChanged;
var icons = GetIcon(page);
var tabItem = new UITabBarItem(page.Title, icons.Item1, icons.Item2)
{
Tag = index
};
renderer.ViewController.TabBarItem = tabItem;
icons.Item1?.Dispose();
icons.Item2?.Dispose();
}
void TeardownPage(Page page, int index)
{
page.PropertyChanged -= OnPagePropertyChanged;
Platform.SetRenderer(page, null);
}
void UpdateBarBackgroundColor()
{
if (Tabbed == null || TabBar == null)
return;
var barBackgroundColor = Tabbed.BarBackgroundColor;
var isDefaultColor = barBackgroundColor == Color.Default;
if (isDefaultColor && !_barBackgroundColorWasSet)
return;
if (!_defaultBarColorSet)
{
//if (Forms.IsiOS7OrNewer)
_defaultBarColor = TabBar.BarTintColor;
//else
// _defaultBarColor = TabBar.TintColor;
_defaultBarColorSet = true;
}
if (!isDefaultColor)
_barBackgroundColorWasSet = true;
//if (Forms.IsiOS7OrNewer)
{
TabBar.BarTintColor = isDefaultColor ? _defaultBarColor : barBackgroundColor.ToUIColor();
}
//else
//{
// TabBar.TintColor = isDefaultColor ? _defaultBarColor : barBackgroundColor.ToUIColor();
//}
}
void UpdateBarTextColor()
{
if (Tabbed == null || TabBar == null || TabBar.Items == null)
return;
var barTextColor = Tabbed.BarTextColor;
var isDefaultColor = barTextColor == Color.Default;
if (isDefaultColor && !_barTextColorWasSet)
return;
if (!_defaultBarTextColorSet)
{
_defaultBarTextColor = TabBar.TintColor;
_defaultBarTextColorSet = true;
}
if (!isDefaultColor)
_barTextColorWasSet = true;
var attributes = new UITextAttributes();
if (isDefaultColor)
attributes.TextColor = _defaultBarTextColor;
else
attributes.TextColor = barTextColor.ToUIColor();
foreach (UITabBarItem item in TabBar.Items)
{
item.SetTitleTextAttributes(attributes, UIControlState.Normal);
}
// set TintColor for selected icon
// setting the unselected icon tint is not supported by iOS
//if (Forms.IsiOS7OrNewer)
{
TabBar.TintColor = isDefaultColor ? _defaultBarTextColor : barTextColor.ToUIColor();
}
}
void UpdateChildrenOrderIndex(UIViewController[] viewControllers)
{
//TabbedPage.SetIndex is internal
//for (var i = 0; i < viewControllers.Length; i++)
//{
// var originalIndex = -1;
// if (int.TryParse(viewControllers[i].TabBarItem.Tag.ToString(CultureInfo.InvariantCulture), out originalIndex))
// {
// var page = (TabbedPage)((IPageController)Tabbed).InternalChildren[originalIndex];
// TabbedPage.SetIndex(page, i);
// }
//}
}
void UpdateCurrentPage()
{
var count = ((IPageController)Tabbed).InternalChildren.Count;
var index = (int)SelectedIndex;
((TabbedPage)Element).CurrentPage = index >= 0 && index < count ? Tabbed.Children[index] : null;
}
//void IEffectControlProvider.RegisterEffect(Effect effect)
//{
// var platformEffect = effect as PlatformEffect;
// if (platformEffect != null)
// platformEffect.Container = View;
//}
}
}
Usage:
to use XamSvg icons in you tabs:
- add these files to you ios and android project respectively
- Use res:xxx as the Icon on you tab's content page to use xxx.svg as the tab icon
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
Title="Tab name"
Icon="res:images.settings">
...
</ContentPage>
@OpticNectar

This comment has been minimized.

Copy link

OpticNectar commented Aug 17, 2017

Is there a better way to do this now that Xamarin.Forms is open source?

@shoaibsaikat

This comment has been minimized.

Copy link

shoaibsaikat commented Jan 4, 2018

I've tried to work with it but its giving exception, where I am adding svg file in xaml.cs file

@shoaibsaikat

This comment has been minimized.

Copy link

shoaibsaikat commented Jan 4, 2018

If I add like its said here, the action bar is coming as null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.