Skip to content

Instantly share code, notes, and snippets.

@softlion
Last active September 29, 2020 05:14
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save softlion/c5a80aa928975c6fd36aaf88fffbe575 to your computer and use it in GitHub Desktop.
Save softlion/c5a80aa928975c6fd36aaf88fffbe575 to your computer and use it in GitHub Desktop.
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
Copy link

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

@shoaibsaikat
Copy link

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

@shoaibsaikat
Copy link

shoaibsaikat commented Jan 4, 2018

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

@softlion
Copy link
Author

softlion commented Sep 29, 2020

This is too old.
The correct way to do it now is to set IconImageSource:

   <views:MyTab Title="Tab title">
        <views:MyTab.IconImageSource>
            <svg:SvgImageSource Svg="resources.images.my_tab_icon" Height="50" />
        </views:MyTab.IconImageSource>
    </views:MyTab>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment