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:
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 OnElementChanged
override:
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:
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.
Links that helped me finding my solution
- https://developer.apple.com/design/human-interface-guidelines/tab-bars
- https://learn.microsoft.com/en-us/dotnet/api/uikit.uiview.animationsenabled?view=xamarin-ios-sdk-12
- https://medium.com/@adityaramadhan.biz/new-tabbar-transition-animation-in-ios-18-and-xcode-16-ea4b2c4d84d4