Maui icon indicating copy to clipboard operation
Maui copied to clipboard

[Proposal] TabView

Open brminnick opened this issue 2 years ago • 19 comments

TabView

  • [x] Proposed
  • [ ] Prototype: Not Started
  • [ ] Implementation: Not Started
    • [ ] iOS Support
    • [ ] Android Support
    • [ ] macOS Support
    • [ ] Windows Support
  • [ ] Unit Tests: Not Started
  • [ ] Sample: Not Started
  • [ ] Documentation: Not Started

Summary

The TabView control allows the user to display a set of tabs and their content. The TabView is fully customizable, other than the native tab bars

Detailed Design

TabView.shared.cs

public class TabView : ContentView, IDisposable
{
  public static readonly BindableProperty TabItemsSourceProperty;
  public static readonly BindableProperty TabViewItemDataTemplateProperty;
  public static readonly BindableProperty TabContentDataTemplateProperty;
  public static readonly BindableProperty SelectedIndexProperty;
  public static readonly BindableProperty TabStripPlacementProperty;
  public static readonly BindableProperty TabStripBackgroundColorProperty;
  public static readonly BindableProperty TabStripBackgroundViewProperty;
  public static readonly BindableProperty TabStripBorderColorProperty;
  public static readonly BindableProperty TabContentBackgroundColorProperty;
  public static readonly BindableProperty TabStripHeightProperty;
  public static readonly BindableProperty IsTabStripVisibleProperty;
  public static readonly BindableProperty TabContentHeightProperty;
  public static readonly BindableProperty TabIndicatorColorProperty;
  public static readonly BindableProperty TabIndicatorHeightProperty;
  public static readonly BindableProperty TabIndicatorWidthProperty;
  public static readonly BindableProperty TabIndicatorViewProperty;
  public static readonly BindableProperty TabIndicatorPlacementProperty;
  public static readonly BindableProperty IsTabTransitionEnabledProperty;
  public static readonly BindableProperty IsSwipeEnabledProperty;
  
  public ObservableCollection<TabViewItem> TabItems { get; }
  public IList? TabItemsSource { get; set; }
  public DataTemplate? TabViewItemDataTemplate { get; set; }
  public DataTemplate? TabContentDataTemplate { get; set; }
  public int SelectedIndex { get; set; }
  public TabStripPlacement TabStripPlacement { get; set; }
  public Color TabStripBackgroundColor { get; set; }
  public View? TabStripBackgroundView { get; set; }
  public Color TabStripBorderColor { get; set; }
  public Color TabContentBackgroundColor { get; set; }
  public double TabStripHeight { get; set; }
  public bool IsTabStripVisible { get; set; }
  public double TabContentHeight { get; set; }
  public Color TabIndicatorColor { get; set; }
  public double TabIndicatorHeight { get; set; }
  public double TabIndicatorWidth  { get; set; }
  public View? TabIndicatorView { get; set; }
  public TabIndicatorPlacement TabIndicatorPlacement { get; set; }
  public bool IsTabTransitionEnabled { get; set; }
  public bool IsSwipeEnabled { get; set; }
  
  public event TabSelectionChangedEventHandler? SelectionChanged;
  public event TabViewScrolledEventHandler? Scrolled;
  
  public void Dispose();
}

Usage Syntax

XAML Usage

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
             x:Class="MyLittleApp.MainPage">

     <Grid>
        <xct:TabView
                TabStripPlacement="Bottom"
                TabStripBackgroundColor="Blue"
                TabStripHeight="60"
                TabIndicatorColor="Yellow"
                TabContentBackgroundColor="Yellow">

                <xct:TabViewItem
                    Icon="triangle.png"
                    Text="Tab 1"
                    TextColor="White"
                    TextColorSelected="Yellow"
                    FontSize="12">
                    <Grid 
                        BackgroundColor="Gray">
                        <Label
                            HorizontalOptions="Center"
                            VerticalOptions="Center"
                            Text="TabContent1" />
                    </Grid>
                </xct:TabViewItem>

                <xct:TabViewItem
                    Icon="circle.png"
                    Text="Tab 2"
                    TextColor="White"
                    TextColorSelected="Yellow"
                    FontSize="12">
                    <Grid>
                        <Label    
                            HorizontalOptions="Center"
                            VerticalOptions="Center"
                            Text="TabContent2" />
                    </Grid>
                </xct:TabViewItem>
        </xct:TabView>
  </Grid>

</ContentPage>

C# Usage

Content = new Grid
{
  new TabVIew
  {
    TabItems = 
    {
      new TabItem
      {
        Icon = "circle.png",
        Text = "Tab 2",
        TextColor = Colors.White,
        TextColorSelected = Colors.Yellow,
        FontSize = 12
        View = new Label { Text = "TabContent2" }.Center()
      }
    }
  }
}

brminnick avatar Sep 28 '21 23:09 brminnick

I am currently working on a project which requires a TabView component, so I have began to implement a prototype according to your specification. If you would like for me to do some work toward this issue I would be glad to contribute!

ChaplinMarchais avatar Jan 22 '22 10:01 ChaplinMarchais

Thanks @ChaplinMarchais! That'd be fantastic!

I'll assign this to you 👍

brminnick avatar Jan 22 '22 16:01 brminnick

@brminnick just a quick question for you about how you guys want events to be implemented in the TabView for SelectionChanged and Scrolled. Currently I am using the following to ensure that the lifetime of any handlers are correctly respected. Although I am not sure if this is the best way of doing it within the Maui framework.

    readonly WeakEventManager selectionChangedManager = new();
    readonly WeakEventManager tabViewScrolledManager = new();


    public event EventHandler<TabSelectionChangedEventArgs> SelectionChanged
    {
        add => selectionChangedManager.AddEventHandler(value);
        remove => selectionChangedManager.RemoveEventHandler(value);
    }


    public event EventHandler<TabViewScrolledEventArgs> Scrolled
    {
        add => tabViewScrolledManager.AddEventHandler(value);
        remove => tabViewScrolledManager.RemoveEventHandler(value);
    }

Any suggestions for improvements would be welcomed!

ChaplinMarchais avatar Jan 24 '22 06:01 ChaplinMarchais

Yup! That looks good 👍

brminnick avatar Jan 24 '22 06:01 brminnick

I really like how https://github.com/roubachof/Sharpnado.Tabs approaches this control. It decouples tab view items from TabStrip and only binds them together through SelectedIndex property. This enables a lot of flexibility, such as implementing a custom tab switching through buttons (if I want my tabs to look like android chips controls). What I lack in most TabView controls is the ability to switch out how the TabStrip is visualy represented - sometimes I want to underline the text, sometimes highlight an icon etc. Setting a TabStripPlacement and TextColorSelected just doesn't cut it sometimes. Would it be worth exploring more flexible options?

jakubjenis avatar Apr 26 '22 08:04 jakubjenis

How's this coming along? I've been trying to roll my own tab view but it feels like it's held together with popsicle sticks and glue...

eamonn-alphin avatar Apr 29 '22 16:04 eamonn-alphin

How's this coming along? I've been trying to roll my own tab view but it feels like it's held together with popsicle sticks and glue...

The description shows that this has not been started yet. This sadly won't make it in to our first official release because we are in a feature freeze ready so it won't be ready any time soon.

We would love your help writing the code! The Community Toolkit contains features for, and created by, the .NET MAUI Community. We are all volunteers, contributing code in our spare time on nights and weekends. If you'd like to contribute, you can learn more here: https://devblogs.microsoft.com/dotnet/contributing-to-net-maui-community-toolkit/

bijington avatar Apr 29 '22 16:04 bijington

Hello TabView and LazyView are crazy needed especially for the people who are using basic navigation, and want to have fully customized tabs. Looking forward to it <3

XamerDev avatar May 27 '22 11:05 XamerDev

Since it's not going to be in the first release, I downloaded the Xamarin community toolkit sample code and isolated the TabView and lazy view parts, then refactored them to work with maui. It took about 2 work days but wasn't too hard. Its really just a Grid with a carousel view and another grid (as the tab bar). The hard part was simulating stack navigation within a "tab"

eamonn-alphin avatar May 27 '22 15:05 eamonn-alphin

Here's the lazy view code:

using System.Threading.Tasks;
using Microsoft.Maui.Controls;

namespace Xamarin.CommunityToolkit.UI.Views
{
	/// <summary>
	/// Abstract base class for <see cref="LazyView{TView}"/>
	/// Taken from Xamarin's community toolkit to use with their Tabview
	/// <notes>
	/// Eamonn Alphin: April 2022: Imported and refactored to work. 
	/// </notes>
	/// </summary>
	public abstract class BaseLazyView : ContentView, IDisposable
	{
		internal static readonly BindablePropertyKey IsLoadedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsLoaded), typeof(bool), typeof(BaseLazyView), default);

		/// <summary>
		/// This is a read-only <see cref="BindableProperty"/> that indicates when the view is loaded.
		/// </summary>
		public static readonly BindableProperty IsLoadedProperty = IsLoadedPropertyKey.BindableProperty;

		/// <summary>
		/// This is a read-only property that indicates when the view is loaded.
		/// </summary>
		public new bool IsLoaded => (bool)GetValue(IsLoadedProperty);

		/// <summary>
		/// This method change the value of the <see cref="IsLoaded"/> property.
		/// </summary>
		/// <param name="isLoaded"></param>
		protected void SetIsLoaded(bool isLoaded) => SetValue(IsLoadedPropertyKey, isLoaded);

		/// <summary>
		/// Use this method to do the initialization of the <see cref="View"/> and change the status IsLoaded value here.
		/// </summary>
		/// <returns><see cref="ValueTask"/></returns>
		public abstract ValueTask LoadViewAsync();

		/// <summary>
		/// This method dispose the <see cref="ContentView.Content"/> if it's <see cref="IDisposable"/>.
		/// </summary>
		public void Dispose()
		{
			if (Content is IDisposable disposable)
			{
				disposable.Dispose();
				GC.SuppressFinalize(this); //todo: check this if lazy loaded views aren't getting disposed correctly. 
			}
		}

		protected override void OnBindingContextChanged()
		{
			if (Content is not null and not ActivityIndicator)
			{
				Content.BindingContext = BindingContext;
			}

		}
	}
}


using System.Threading.Tasks;

namespace Xamarin.CommunityToolkit.UI.Views
{
	/// <summary>
	/// This a basic implementation of the LazyView based on <see cref="BaseLazyView"/> use this an exemple to create yours
	/// </summary>
	/// <notes>
	/// Eamonn Alphin April 2022: Imported and refactored. 
	/// </notes>
	/// <typeparam name="TView">Any <see cref="View"/></typeparam>
	public class LazyView<TView> : BaseLazyView where TView : View, new()
	{
		/// <summary>
		/// This method initializes your <see cref="LazyView{TView}"/>.
		/// </summary>
		/// <returns><see cref="ValueTask"/></returns>
		public override ValueTask LoadViewAsync()
		{
			View view = new TView { BindingContext = BindingContext };

			Content = view;

			SetIsLoaded(true);
			return new ValueTask(Task.FromResult(true));
		}
	}
}

eamonn-alphin avatar May 27 '22 15:05 eamonn-alphin

@eamonn-alphin that is interesting to hear! Would you be willing to assist with the TabView implementation in the toolkit?

My fear with the TabView is that there are a lot of issues open against the Xamarin Community Toolkit which we should look to avoid inheriting https://github.com/xamarin/XamarinCommunityToolkit/issues?q=is%3Aissue+is%3Aopen+tabview+

bijington avatar May 27 '22 15:05 bijington

Extending the @bijington comment, if you want to do the migration of the LazyView to here, you're more than welcome <3

Here's the issue for lazy view #112

pictos avatar May 27 '22 15:05 pictos

@eamonn-alphin that is interesting to hear! Would you be willing to assist with the TabView implementation in the toolkit?

My fear with the TabView is that there are a lot of issues open against the Xamarin Community Toolkit which we should look to avoid inheriting https://github.com/xamarin/XamarinCommunityToolkit/issues?q=is%3Aissue+is%3Aopen+tabview+

I'm willing to share the code I have for the sake of helping better minds than mine move tabviews along, but it still comes with whatever issues the latest Xamarin CT version has. But it runs at least.

eamonn-alphin avatar May 27 '22 15:05 eamonn-alphin

Thanks @eamonn-alphin!

We would love your help writing the code! The Community Toolkit contains features for, and created by, the .NET MAUI Community. We are all volunteers, contributing code in our spare time on nights and weekends. If you'd like to contribute, you can learn more here: https://devblogs.microsoft.com/dotnet/contributing-to-net-maui-community-toolkit/

For future reference, you don’t need to copy/paste any code from Xamarin.CommunityToolkit to get to work with .NET MAUI. We created Xamarin.CommunityToolkit.MauiCompat which is the exact Xamarin.CommunityToolkit library ported to .NET MAUI: https://devblogs.microsoft.com/xamarin/introducing-net-maui-compatibility-for-the-xamarin-community-toolkit

brminnick avatar May 27 '22 15:05 brminnick

FYI - I’m hiding the LazyView comments as “off-topic” to avoid creating noise for future devs interested in helping with this TabView Proposal

brminnick avatar May 27 '22 15:05 brminnick

I would like to implement this by porting the old TabView with some upgrades but first I need this issue to be fixed - https://github.com/dotnet/maui/issues/6412

AswinPG avatar Jun 07 '22 12:06 AswinPG

@ChaplinMarchais What is the status of your progress? Would you like to continue working on TabView, or would you prefer to hand off the work to another developer?

brminnick avatar Jun 07 '22 14:06 brminnick

@brminnick

For future reference, you don’t need to copy/paste any code from Xamarin.CommunityToolkit to get to work with .NET MAUI. We created Xamarin.CommunityToolkit.MauiCompat which is the exact Xamarin.CommunityToolkit library ported to .NET MAUI:

It doesn't work, unfortunately... https://github.com/xamarin/XamarinCommunityToolkit/issues/1873

And @VladislavAntonyuk mentions here, that it's not possible to use it at all, if I understand that correctly: https://github.com/xamarin/XamarinCommunityToolkit/issues/1750#issuecomment-985645536

Dreamescaper avatar Jun 15 '22 21:06 Dreamescaper

@ChaplinMarchais @brminnick Will this component be compatible with Shell Navigation?

ewerspej avatar Sep 28 '22 16:09 ewerspej

Since it's not going to be in the first release, I downloaded the Xamarin community toolkit sample code and isolated the TabView and lazy view parts, then refactored them to work with maui. It took about 2 work days but wasn't too hard. Its really just a Grid with a carousel view and another grid (as the tab bar). The hard part was simulating stack navigation within a "tab"

@eamonn-alphin this sounds awesome. Do you have a code sample you would be happy to share please? I am still in XF but I have a large maui refactor on the horizon, so I am wary to add a dependency to something from the XF toolkit that will be unsupported when going to MAUI.

AdamDiament avatar Oct 27 '22 09:10 AdamDiament

btw for someone wanting a XF and Maui compatible solution now (Oct 2022) there is https://github.com/roubachof/Sharpnado.Tabs

AdamDiament avatar Oct 27 '22 10:10 AdamDiament

btw for someone wanting a XF and Maui compatible solution now (Oct 2022) there is https://github.com/roubachof/Sharpnado.Tabs

That's indeed a very powerful implementation. However, it is not compatible with Shell and uses Views instead of Pages. I do not regard it as a substitute for the native Shell.TabBar

ewerspej avatar Oct 27 '22 12:10 ewerspej

@ChaplinMarchais What is the status of your progress? Would you like to continue working on TabView, or would you prefer to hand off the work to another developer?

@brminnick Sorry about going totally MIA! Life ended up throwing some major personal things my way. Long story short I wasn't able to get access to a computer for the last 5 months or so. That being said, I will begin work again on the implementation for this proposal!

ChaplinMarchais avatar Nov 02 '22 00:11 ChaplinMarchais

@ChaplinMarchais glad to have you back!

brminnick avatar Nov 02 '22 01:11 brminnick

@ChaplinMarchais @brminnick Will this component be compatible with Shell Navigation?

@ewerspej What exactly do you mean, as far as functionality goes, by "compatible"? Are you looking for the TabView to generate ShellContent items for the Shell or just to have the TabView integrate your navigation history with the Shell? A little bit more detail would help to determine the feasibility of integrating with this feature!

ChaplinMarchais avatar Nov 27 '22 01:11 ChaplinMarchais

@ChaplinMarchais If this control should be a way to substitute the native TabBar, then it would make sense that it also taps into Shell's navigation so that the content of the TabView can be controlled using Routes/URIs using Shell.Current.GoToAsync(). That way it would fully integrate into Shell and can actually replace the native TabBar while still being able to use things like Shell's built-in dependency injection.

ewerspej avatar Nov 27 '22 20:11 ewerspej

@ewerspej ok I am going to take a look at the Shell implementation over the next couple days, and see how exactly they are integrating with the NavigationManager and make sure there is nothing that is going to blow up in our faces. I know that currently the Shell is pretty limited as to what kind of Types it will accept as a navigation target. For instance I believe that we would have to provide the TabViewItem.Content as a Page rather than currently allowing it to be any IView implementation. That is the only thing that I have discovered so far that may possibly restrict us from integrating with it.

@brminnick do you have any further insight into the inner-workings of Shell or know who I could talk to about what would be required for us to integrate in a fully supported manner?

ChaplinMarchais avatar Dec 03 '22 00:12 ChaplinMarchais

Any updates on this issue?

mouralabank avatar Apr 21 '23 11:04 mouralabank

Any updates on this issue?

It doesn't look like it. Would you be willing to provide assistance?

bijington avatar Apr 21 '23 13:04 bijington

@ewerspej it would be easier to make shell looks like TabView rather then integrating navigation. Here is an example how you can customize shell appearance: https://vladislavantonyuk.github.io/articles/Customizing-.NET-MAUI-Shell

VladislavAntonyuk avatar May 22 '23 22:05 VladislavAntonyuk