Last active
September 29, 2020 05:14
-
-
Save softlion/c5a80aa928975c6fd36aaf88fffbe575 to your computer and use it in GitHub Desktop.
Custom Tabbed Renderer for Xamarin Forms to display Svg icons using XamSvg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} | |
} | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
//} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
I've tried to work with it but its giving exception, where I am adding svg file in xaml.cs file
If I add like its said here, the action bar is coming as null
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
Is there a better way to do this now that Xamarin.Forms is open source?