dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.21k stars 1.74k forks source link

[BUG] iPadOS/iOS 18 Forced TabView/Tab Bar/Tabbar #23380

Open imadofficial opened 4 months ago

imadofficial commented 4 months ago

Description

Upon iPadOS 18's Dev Beta's release. They introduced a new way to navigate through various pages, "TabView". Although it can be useful, you can't really get rid of it in .NET MAUI.

So instead you'll experience weird behaviors that aren't intended like so:

image

It looks like the iPadOS version of the app assumes there is a tabview available but I can't really say that for sure.

Steps to Reproduce

Step 1) Create a new MAUI Project. Step 2 [Optional]) Set the " Shell.NavBarIsVisible="False" " flag in the MainPage. Step 3) Run it under an iPad Simulator on iPadOS 18.

If you did all of the above you'll see this TabView showing up out of nowhere with no way to get rid of it.

Link to public reproduction project repository

https://github.com/imadofficial/TabViewIssue

Version with bug

8.0.21 SR4.1

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

Unknown/Other

Affected platforms

iOS

Affected platform versions

iPadOS 18 (iOS 18 isn't affected)

Did you find any workaround?

I wasn't able to find a workaround for this.

Relevant log output

No response

github-actions[bot] commented 4 months ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

ninachen03 commented 4 months ago

I can repro this issue at iOS platform on the iPadOS 18 Simulator

image

drasticactions commented 4 months ago

This behavior could change between releases since iOS 18 is still in development.

iOS 18 also isn't bound for .NET iOS yet, so if there were settings to change this behavior for the underlying control implementation, they couldn't be changed at the platform level yet.

imadofficial commented 1 month ago

Any updates on this issue?

kiranbasvaraj commented 1 month ago

Do we have any work around for this.even I'm facing this issue in IOS 18 Ipad

imadofficial commented 1 month ago

Do we have any work around for this.even I'm facing this issue in IOS 18 Ipad

As far as i know, there is no fix yet..

VladimirPlatun commented 1 month ago

Good afternoon! After updating VIsual Studio for mac to version 17.6.14 and updating xcode to version 16, my Xamarin Forms app stopped seeing ios device simulators. Displays the following message:

Lower the 'Deployment Target' to see older simulators or check your Apple SDK path warning after update.

What are the possible solutions or how to add an old version of the Apple SDK?

jfversluis commented 1 month ago

Indeed no fix yet, we're currently looking into it. I think it's this. ~But it looks like .NET for iOS hasn't exposed the APIs for this yet, that means also no way to disable it.~ I was mistaken with this! Looking further...

For now, targeting against a lower iOS SDK will probably make it go away.

Edit: OK looked into this more, it seems like Apple has offered no way to revert back to the old behavior. They simply said, if you want the old behavior, build your own custom control. An iOS native equivalent of that can be found here. So the question is... How are we going about this?

jfversluis commented 1 month ago

I guess one solution/workaround for the time being is to hide this tabbar, however, keep in mind; this is the new tabbar, this is what it will look like. If you hide it, people will not be able to switch between tabs.

The one thing I could see us do is to hide it when there is only one tab or at least offer the option to do that, because in the situation of the original author here it looks weird.

If you want to play around with this you can use this custom ShellRenderer

using CoreGraphics;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.Controls.Platform.Compatibility;
using UIKit;

namespace ios18test;

public sealed class MyShellRenderer : ShellRenderer
{
    private sealed class MyShellTabBarAppearanceTracker : ShellTabBarAppearanceTracker
    {
        public override void SetAppearance(UITabBarController controller, ShellAppearance appearance)
        {
            base.SetAppearance(controller, appearance);

            //controller.Mode = UITabBarControllerMode.TabBar;
            controller.TabBarHidden = true;
        }
    }
    protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker() => new MyShellTabBarAppearanceTracker();
}

And in your MauiProgram.cs add:

        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            #if IOS
            .ConfigureMauiHandlers(handlers =>
            {
                handlers.AddHandler<Shell, MyShellRenderer>();
            })
            #endif
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

That way you can access this new functionality and play around with it according to your own needs while we try to figure out the next steps from in the .NET MAUI SDK. If you have any thoughts or opinions, please let us know!

kiranbasvaraj commented 1 month ago

@jfversluis First of all I appreciate your time in looking into this issue.

if we hide the tabs sole purpose of using the tab bar will be gone..we have 26% of our customers who are into IOS 18 .so, I don't think hiding the tab bar is ideal to me.

I would be thrilled if I get a solution to show the tabar at the bottom.

kindly give any workaround until this gets fixed from MAUI.

Thanks

jfversluis commented 1 month ago

@kiranbasvaraj you don't need to hide it if you indeed use tabs in your application, then this is just the new way tabs look as of iOS 18 going forward. There is not much we can do about that.

There are some scenarios in .NET MAUI right now where it look a bit funny and we need to fix those. Other than that, I don't think we will invest in mimicking the (now) old behavior by making a control that moves the tabs to the bottom.

If you want to revert it right now you can build your app against the latest Xcode 15 version, that will give you the bottom tabs, but Apple will stop accepting apps built against Xcode 15 probably as of May 2025. So this will only be a temporary solution.

jfversluis commented 1 month ago

OK, good news, seems like Apple did provide a way to revert back to the old behavior (for now).

It seems that you can set UITabBarController.TraitOverrides.HorizontalSizeClass = UIUserInterfaceSizeClass.Compact; and the bottom tabs will be back.

Combine that with the post above and you should be able to make this work for your .NET MAUI app today. In the above code replace the controller.TabBarHidden line with controller.TraitOverrides.HorizontalSizeClass = UIUserInterfaceSizeClass.Compact;.

Can you confirm that works without side-effects @kiranbasvaraj ?

drasticactions commented 1 month ago

@jfversluis Tried it. It seems to work on iPad (although I'm not sure what that would do in their application using that constraint) but it doesn't do anything for Mac Catalyst. It'll show the updated NSToolbar with the Tabs built in unless you hide the tab bar outright. Also, I'm not sure if that's a deliberate option to revert "back" to the previous behavior, or if it just so happens to.

As @jfversluis mentioned, these are new Apple control implementations for iOS and Catalyst 18.0. It looks like it has some nice quality of life features... and also breaks existing Applications that just so happen to use UITabBarController in a way their engineers probably intended. To be clear, there's no breaking change in MAUI, nor bug introduced. Apple changed how that control works when compiled for iOS and Catalyst 18.0. MAUI has to adapt to it. It sucks, but there's not much else to it unless there's enough pressure on Apple to make it opt out and let you/us revert back to the previous implementation.

That said, the tab bar does work if you have tabs in your shell. Depending on how it's set up.

https://github.com/user-attachments/assets/d8d2ba80-7252-42b7-ba7a-8cde8af0fa8a

The break here is that if you have no tabs and only content views, then it will show up as one item, which apparently is the default behavior of this UITabBarController when it has one item, it will always show it and there is no way to hide it, besides hiding the whole toolbar yourself with TabBarHidden.

So the short term fix for MAUI would probably be to handle how many items are present in the UITabBarController TabBar and if it's one, to hide it, and if it's more than one, to show it. As a workaround until that's done, you could use the Renderer @jfversluis wrote and either hide the TabBar if you're not using it, or write that logic yourself for when to show it.

kiranbasvaraj commented 1 month ago

Hello @jfversluis the solution is working for me. Thank you very much this was much needed for me

drasticactions commented 4 weeks ago

The other way to handle this that I found also works in Mac Catalyst is

public class TempShellRenderer : ShellRenderer
{
    private sealed class TempShellTabBarAppearanceTracker : ShellTabBarAppearanceTracker
    {
        public override void SetAppearance(UITabBarController controller, ShellAppearance appearance)
        {
            base.SetAppearance(controller, appearance);
            // Should apply to iOS and Catalyst
            if (OperatingSystem.IsIOSVersionAtLeast(18))
            {
                controller.Mode = UITabBarControllerMode.TabSidebar;
                controller.Sidebar.Hidden = true;
                controller.TabBarHidden = true;
            }
        }
    }

    protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker() => new TempShellTabBarAppearanceTracker();
}

Because TabBarHidden on its own would not cause the NSToolbar to disappear, and I hit the same issue trying to do it with straight Catalyst (As in, Swift Catalyst, not MAUI). And I only saw that this worked due to someone else hitting it.

But this should at least get the Tab bar items out.

imadofficial commented 3 weeks ago

The other way to handle this that I found also works in Mac Catalyst is

public class TempShellRenderer : ShellRenderer
{
    private sealed class TempShellTabBarAppearanceTracker : ShellTabBarAppearanceTracker
    {
        public override void SetAppearance(UITabBarController controller, ShellAppearance appearance)
        {
            base.SetAppearance(controller, appearance);
            // Should apply to iOS and Catalyst
            if (OperatingSystem.IsIOSVersionAtLeast(18))
            {
                controller.Mode = UITabBarControllerMode.TabSidebar;
                controller.Sidebar.Hidden = true;
                controller.TabBarHidden = true;
            }
        }
    }

    protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker() => new TempShellTabBarAppearanceTracker();
}

Because TabBarHidden on its own would not cause the NSToolbar to disappear, and I hit the same issue trying to do it with straight Catalyst (As in, Swift Catalyst, not MAUI). And I only saw that this worked due to someone else hitting it.

But this should at least get the Tab bar items out.

That's only with MacCatalyst. I observed weird behaviour on the iPad. Although Gerald's fix does work.

PureWeen commented 3 weeks ago

I've moved this issue to .NET 10 planning for bigger picture discussions

I've created an issue here to cover the more immediate changes

Toine-db commented 2 weeks ago

When you are not using the Shell, here is a simple renderer that fixes the issue. (unfortunately there is no handler that works well for the TabbedPage yet)

public class MyTabbedRenderer : Microsoft.Maui.Controls.Handlers.Compatibility.TabbedRenderer
{
    public MyTabbedRenderer()
    {
        TraitOverrides.HorizontalSizeClass = UIUserInterfaceSizeClass.Compact;
    }
}
Zalkovalsky commented 2 weeks ago

Unfortunately the workaround with the TraitOverrides has some unintended side effects - traits overrides are going down the views hierarchy. In our case, this breaks the popovers, they also have the 'compact' appearance. Setting the override to UIUserInterfaceClass.Regular in the presented views doesn't seem to work either.

reid-kirkpatrick commented 1 day ago

This was happening to us on ShellItems outside our TabBar even with the workaround from Gerald. Our solution was to override CreateShellItemRenderer instead.

public class CustomShellRenderer : ShellRenderer
{
    protected override IShellItemRenderer CreateShellItemRenderer(ShellItem item)
    {
        var renderer = base.CreateShellItemRenderer(item);

        if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad && UIDevice.CurrentDevice.CheckSystemVersion(18, 0) && renderer is ShellItemRenderer shellItemRenderer)
            shellItemRenderer.TraitOverrides.HorizontalSizeClass = UIUserInterfaceSizeClass.Compact;

        return renderer;
    }
}