UITab view controller not being displayed when tapped from within a UITabGroup

The following:

tabBarController.tabs = [
			UITab(title: "First",
				  image: UIImage(...),
				  identifier: "First Tab") { _ in
					  myViewController()
				  }

Works as expected. Tap on a a tab or sidebar item, and myViewController appears.

However, the following:

let collectionsGroup = UITabGroup(
			title: "Lists",
			image: nil,
			identifier: "Tabs.CollectionsGroup",
			children: [
				UITab(title: "First", image: nil, identifier: "First Tab") { _ in
					myViewController()
				}
			]) { _ in
				fallbackViewController()
			}

Does not work as expected. Tapping on any tab in the sidebar always displays fallbackViewController, instead of myViewController or any other specified view controllers in the given tab.

Am I doing something wrong, or is this a bug with the current betas?

Answered by Frameworks Engineer in 798795022

By default, UITabBarController will only change the view controller for root tabs. Apps can manage how the view controller hierarchy changes within that root tab through system callbacks from UITabBarControllerDelegate.

That being said, starting in iOS 18 beta 5, there is new UITabBarController API that will manage this for you automatically.

Setting managingNavigationController on UITabGroup will opt-in the specified group to allow UITabBarController to automatically manage its hierarchy for you. Once set, UITabGroup will basically create a navigation stack based on the selected hierarchy in that group.

let group = UITabGroup(...)
group.managingNavigationController = UINavigationController()

Let's consider the following UITab hierarchy:

Group A
    | Tab 1
    | Tab 2
    | Group B
        | Tab B1
        | Tab B2
    | Tab 3

When selection occurs, UITabBarController will rebuild the navigation stack to represent the hierarchy as needed. For example, selecting Tab B2 will create a navigation stack of:

GroupAVC → GroupBVC → TabB2VC

Selecting Tab 3 will change the hierarchy to:

GroupAVC → Tab3VC

To customize the displayed view controller, there is also new API in iOS 18 beta 5 in UITabBarControllerDelegate:

func tabBarController(
    _ tabBarController: UITabBarController,
    displayedViewControllersFor tab: UITab,
    proposedViewControllers: [UIViewController]
) -> [UIViewController]

This API allows you to insert or remove view controllers when UITabBarController is rebuilding the tab group's navigation stack. Additional view controllers pushed from the leaf view controller (that are not represented by a UITab) will also be preserved as part of proposedViewControllers when switching between regular and compact size classes. This makes it easy for apps to alter its navigation hierarchies without manually managing everything themselves.

I hope that helps. :)

Accepted Answer

By default, UITabBarController will only change the view controller for root tabs. Apps can manage how the view controller hierarchy changes within that root tab through system callbacks from UITabBarControllerDelegate.

That being said, starting in iOS 18 beta 5, there is new UITabBarController API that will manage this for you automatically.

Setting managingNavigationController on UITabGroup will opt-in the specified group to allow UITabBarController to automatically manage its hierarchy for you. Once set, UITabGroup will basically create a navigation stack based on the selected hierarchy in that group.

let group = UITabGroup(...)
group.managingNavigationController = UINavigationController()

Let's consider the following UITab hierarchy:

Group A
    | Tab 1
    | Tab 2
    | Group B
        | Tab B1
        | Tab B2
    | Tab 3

When selection occurs, UITabBarController will rebuild the navigation stack to represent the hierarchy as needed. For example, selecting Tab B2 will create a navigation stack of:

GroupAVC → GroupBVC → TabB2VC

Selecting Tab 3 will change the hierarchy to:

GroupAVC → Tab3VC

To customize the displayed view controller, there is also new API in iOS 18 beta 5 in UITabBarControllerDelegate:

func tabBarController(
    _ tabBarController: UITabBarController,
    displayedViewControllersFor tab: UITab,
    proposedViewControllers: [UIViewController]
) -> [UIViewController]

This API allows you to insert or remove view controllers when UITabBarController is rebuilding the tab group's navigation stack. Additional view controllers pushed from the leaf view controller (that are not represented by a UITab) will also be preserved as part of proposedViewControllers when switching between regular and compact size classes. This makes it easy for apps to alter its navigation hierarchies without manually managing everything themselves.

I hope that helps. :)

I'm really struggling to use these new APIs in a manner that works properly with iPadOS and switching between the multi-tasking modes. It would be great if there was some example code that demonstrates an app where the visible view controller's state is retain during these layout mode transitions.

It seems like there might be some bugs with the new tab bar APIs and iPadOS multi-tasking modes (iOS 18.4), unless I'm not using the APIs correctly?

Here's what I've found so far:

1. More controller issues

I noticed that when you have enough tabs (5+) to trigger the "more" controller in compact layout, the app will crash when you switch from a child group tab (associated with a managingNavigationController. This is presumably because the underlying implementation is moving the view controller(s) from the managingNavigationController to moreNavigationController.

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Layout requested for visible navigation bar, <UINavigationBar: 0x102022520; frame = (0 24; 375 50); opaque = NO; autoresize = W; gestureRecognizers = <NSArray: 0x600000c536c0>; layer = <CALayer: 0x600000296400>> delegate=0x101820a00, when the top item belongs to a different navigation bar.

2. Compact tab bar "tap to pop navigation controller to root" issues

When you tap on your tab group tab button in compact mode twice to trigger it to pop to the root view controller, it shows the "section" view controller for the tab group and then instantly pushes the last selected tab group child's view controller. Also, if you do this and switch back to the regular layout, it selects the first child of tab group (rather than retaining the correct child selection) or selecting first root tab.

UITab view controller not being displayed when tapped from within a UITabGroup
 
 
Q