Post

How to disable iOS 18 tab transition animation with .NET MAUI

With iOS 18, Apple introduced a lot of new cool features we developer can profit from. However, with legacy code bases like in my old Fishing Knots + application, some of these new features can make your app worse than before. The new tab transition animation Apple introduced with the latest version of the OS is such a feature. While it looks awesome in the Apple’s Music app for example, it definitely did not in my app. Compare yourself:

Apple Music app on iOS 18 Fishing Knots + on iOS 18

As you’ll see, the tab transition is neither fast nor smooth in my app anymore. As I implemented already a nice transition animation myself years ago, the only logical consequence for me was to deactivate the default OS behavior in my particular app. This post will show you how to do so with a (non-Shell!) .NET MAUI app.

How the TabbedPage works in .NET MAUI

The .NET MAUI implementation of the TabbedPage utilizes the native UITabBarController from the iOS UIKit framework. It uses a compatibility renderer as its MAUI Handler, which can be viewed here. The renderer itself derives from the Xamarin.iOS Microsoft.iOS implementation of the UITabBarController and extends it with the IPlatformViewHandler interface.

The renderer does a lot of things to make the UITabBarController usable on the MAUI side, including handling of the current page - which will be important for the second step of my solution. More important is, though, that we have access to all the events and properties of the UITabBarController, in particular the ShouldSelectViewController event and the SelectedViewController property (each tab (= page) has its own ViewController).

Now that you have a high level understanding how things work, let’s have a look into my solution to deactivate the transition animation.

Create your own MAUI Handler

The first thing we need to do is creating a handler inside of the Platforms/iOS folder of your MAUI project and the two method overrides we need:

1
2
3
4
5
6
7
8
9
10
11
12
public class TabbedPageHandler : Microsoft.Maui.Controls.Handlers.Compatibility.TabbedRenderer
{
    public override void ViewDidLayoutSubviews()
    {
        base.ViewDidLayoutSubviews();
    }

    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
    }
}

I recommend to explicitly specify the full namespace of the TabbedRenderer base class. There is an obsolete Xamarin.Forms compatibility version in the MAUI bits, with which the following steps won’t work.

Handling tab switching from the TabBar

Whenever a user selects an item in the TabBar, the ShouldSelectViewController event will be raised in the renderer. We can use this event to modify the default behavior or even block tab switching entirely here. In my case, I used it to deactivate the new transition animation. I am overriding the ViewDidLayoutSubviews method of the render to handle the event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    //this is called only when the tab is switched by tapping the tabBar items!
    this.ShouldSelectViewController += (tabBarController, viewController) =>
    {
        if (viewController == null)
            return false;

        var fromController = tabBarController.SelectedViewController;

        UIView? fromView = fromController?.View;
        UIView? toView = viewController.View;

        if (fromView != toView)
        {
            UIView.Transition(fromView, toView, 0, UIViewAnimationOptions.TransitionCrossDissolve, () => { });
        }

        return true;
    };
}

Once you know what and where to do, it is pretty easy to fix the problem - all we have to do is adding a zero length transition for this change, which will override the existing transition baked into the operating system.

Important: adding this fix only deactivates the transition animation when switching tabs by selecting an item from the tab bar in your app!

Handling tab switching from code

As you may have guessed already, the above code is not all we need. If you are changing the current visible tab from your code, there is more to do. I do this in my app, as I implemented SwipeContainers to allow my users to switch tabs by swiping the screen.

Setting the CurrentPage on the MAUI end of things does not call into the ShouldSelectViewController event. If you have another look at the renderer in .NET MAUI, you will note, however, that it will set the SelectedViewController in the UITabBarController, nonetheless.

And this is where we need to hook into in our own handler. Add the following code to your OnElementChangedoverride:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
    base.OnElementChanged(e);

    this.Element.PropertyChanged += (sender, args) =>
    {
        if (args.PropertyName != nameof(MultiPage<Page>.CurrentPage))
            return;
        
        var current = Tabbed?.CurrentPage;
        if (current == null)
            return;

        var controller = GetViewController(current);
        if (controller == null)
            return;
        
        SetNeedsUpdateOfHomeIndicatorAutoHidden();
        SetNeedsStatusBarAppearanceUpdate();
        
        //remove animation on tab switch from code
        UIView.AnimationsEnabled = false;
        //set the new selected view controller
        this.SelectedViewController = controller;
        //enable animations again
        UIView.AnimationsEnabled = true;
    };
}

//copied from the base renderer
static UIViewController? GetViewController(Page page)
{
    return page?.Handler is not IPlatformViewHandler platformViewHandler ? null : platformViewHandler.ViewController;
}

By disabling animations just before setting the SelectedViewController we suppress the new flashy animation when changing the tab from our code. It is recommended to activate animations after the new ViewController has been set, as you can see above.

After I found the solution for tab switching by code, I immediately tried to do use the same method in the the first method. I am going to save you some time: It won’t work (at least it did not for me).

The result of implementing the above mentioned handler in my app is the same smooth transition animation I had for years now, no matter if the users swipes the screen or selects an item from the tab bar:

Fixed Fishing Knots + on iOS 18

Conclusion

Every now and then, we have to deal with breaking changes in our legacy code base. Some of them will be easy to fix by just replacing your code with a new way to do so. Others, like the problem above, may need some research and some patience to find a solution. Often, fellow developers who write their apps natively on iOS have or had similar/the same problems, so don’t research just with MAUI, but also iOS development in general in mind. In this case, this lead me to my final solution. Thanks to the handler architecture of .NET MAUI, it was just a matter of understanding and translate from native code to .NET (MAUI) code.

As this is a rather small solution and I hopefully explained everything you need to know above, this post does not get its own sample. As always I hope this post will be helpful for some of you.


Until the next post, happy coding, everyone!

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.