titanium-sdk icon indicating copy to clipboard operation
titanium-sdk copied to clipboard

fix(ios): fixed TabBarItem update for iOS 18+

Open hbugdoll opened this issue 2 months ago β€’ 9 comments

Fixes #14145.

Introduction:

  • in (void)updateTabBarItem a new UITabBarItem is created and assigned to the ViewController with each property update
  • iOS 18/iPadOS 18 handles internally the tab bar in a slight different way and iPadOS 18 has also introduced a new "floating" tab bar
  • it turned out that under iPadOS 18+ it is necessary to use the "original" UITabBarItem to update the badge value

Description:

  • set tabBarItem on correct ViewController (NavigationController controller instead of RootViewController rootController)
    • so fixed the error Inconsistency in UITabBar items and view controllers detected.
  • reuse controller.tabBarItem when updating the properties (e.g. badge) of a tab bar item
    • needed for updating badge in "floating" tab bar under iPadOS 18+
    • no re-init every (void)updateTabBarItem call
    • re-init only in the case of switching to a system item (with system icon) and vice versa
  • BTW fixed a memory leak related to UITabBarAppearance
  • aligned call sequence for tab bar items with system icons and custom icons
  • added some comments for better clarity

hbugdoll avatar Oct 22 '25 20:10 hbugdoll

Tested it on iPhone and iPad and both badges are set correctly now :+1:

m1ga avatar Oct 23 '25 07:10 m1ga

In another Alloy project I see some issues:

  • I use $.tab_setting.icon = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory,"icon_profil.png").read(); to assign generated icons to the tabs in the open event and I don't see the icons.
  • when I click on a tab it will crash with
[ERROR] Application received error: Inconsistency in UITabBar items and view controllers detected. No view controller matches the UITabBarItem '<UITabBarItem: 0x102f8a7d0> title='PlΓ€ne' selected'.
[ERROR]     0   CoreFoundation                      0x00000001804f39e8 __exceptionPreprocess + 172
[ERROR]     1   libobjc.A.dylib                     0x000000018009c084 objc_exception_throw + 72
[ERROR]     2   Foundation                          0x00000001810353c0 _userInfoForFileAndLine + 0
[ERROR]     3   UIKitCore                           0x00000001859c10c0 -[UITabBarController _viewControllerForTabBarItem:] + 388
[ERROR]     4   UIKitCore                           0x00000001859c1164 -[UITabBarController _tabBarDidChangeSelectionToItem:] + 28
[ERROR]     5   UIKitCore                           0x00000001857bdf90 -[UITabBar _didSelectButtonForItem:] + 308
[ERROR]     6   UIKitCore                           0x00000001852e0c78 block_destroy_helper.30 + 43056
[ERROR]     7   UIKitCore                           0x00000001851aea08 __swift_memcpy9_8 + 8720
[ERROR]     8   UIKitCore                           0x00000001861b0fcc -[UIAction performWithSender:target:] + 104
[ERROR]     9   UIKitCore                           0x0000000185ae7e6c -[UIControl _sendActionsForEvents:withEvent:] + 284
[ERROR]     10  UIKitCore                           0x00000001852d87e4 block_destroy_helper.30 + 9116
[ERROR]     11  UIKitCore                           0x00000001852e0924 block_destroy_helper.30 + 42204
[ERROR]     12  UIKitCore                           0x0000000185d408ec -[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 76
[ERROR]     13  UIKitCore                           0x0000000185d499ec _UIGestureRecognizerSendTargetActions + 88
[ERROR]     14  UIKitCore                           0x0000000185d46760 _UIGestureRecognizerSendActions + 296
[ERROR]     15  UIKitCore                           0x0000000185d46324 -[UIGestureRecognizer _updateGestureForActiveEvents] + 320
[ERROR]     16  UIKitCore                           0x0000000185d4af30 -[UIGestureRecognizer gestureNode:didUpdatePhase:] + 296
[ERROR]     17  Gestures                            0x000000022efbf64c GFGestureNodeDefaultValue + 4468
[ERROR]     18  Gestures                            0x000000022efdc858 _swift_stdlib_malloc_size + 26304
[ERROR]     19  Gestures                            0x000000022f0070ac __swift_memcpy24_8 + 20672
[ERROR]     20  Gestures                            0x000000022f02e90c GFGestureNodeCoordinatorCreate + 2148
[ERROR]     21  UIKitCore                           0x0000000185d37e18 -[UIGestureEnvironment _updateForEvent:window:] + 468
[ERROR]     22  UIKitCore                           0x000000018629b3d4 -[UIWindow sendEvent:] + 2796
[ERROR]     23  UIKitCore                           0x0000000186279714 -[UIApplication sendEvent:] + 376
[ERROR]     24  Fitness Plan                        0x00000001021f3cd8 -[TiUIApplication sendEvent:] + 404
[ERROR]     25  UIKitCore                           0x000000018630dc6c __dispatchPreprocessedEventFromEventQueue + 1184
[ERROR]     26  UIKitCore                           0x0000000186310920 __processEventQueue + 4800
[ERROR]     27  UIKitCore                           0x0000000186308ecc updateCycleEntry + 168
[ERROR]     28  UIKitCore                           0x0000000185773878 _UIUpdateSequenceRunNext + 120
[ERROR]     29  UIKitCore                           0x000000018617ec90 schedulerStepScheduledMainSectionContinue + 56
[ERROR]     30  UpdateCycle                         0x00000002509462b4 _ZN2UC10DriverCore18continueProcessingEv + 80
[ERROR]     31  CoreFoundation                      0x00000001804114ac __CFMachPortPerform + 164
[ERROR]     32  CoreFoundation                      0x000000018044dbe0 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 56
[ERROR]     33  CoreFoundation                      0x000000018044d1f8 __CFRunLoopDoSource1 + 480
[ERROR]     34  CoreFoundation                      0x000000018044c2c0 __CFRunLoopRun + 2100
[ERROR]     35  CoreFoundation                      0x0000000180446e24 _CFRunLoopRunSpecificWithOptions + 496
[ERROR]     36  GraphicsServices                    0x00000001925319bc GSEventRunModal + 116
[ERROR]     37  UIKitCore                           0x000000018625fc3c -[UIApplication _run] + 772
[ERROR]     38  UIKitCore                           0x0000000186263e64 UIApplicationMain + 124
[ERROR]     39  Fitness Plan                        0x00000001021845b0 main + 884
[ERROR]     40  dyld                                0x00000001029293d0 start_sim + 20
[ERROR]     41  ???                                 0x0000000102a64d54 0x0 + 4339420500
[ERROR] libc++abi: terminating due to uncaught exception of type NSException

13.0.0:

13.1.0:

m1ga avatar Oct 23 '25 16:10 m1ga

@m1ga Thanks for testing and reporting πŸ‘ I can now reproduce the issue and I'am working on it.

hbugdoll avatar Oct 25 '25 16:10 hbugdoll

@m1ga The new issue could be reproduced by modifying your example from https://github.com/tidev/titanium-sdk/issues/14145#issuecomment-3422338133 as follows

var win1 = Ti.UI.createWindow({
    backgroundColor: 'blue',
    title: 'Blue'
});
win1.add(Ti.UI.createLabel({text: 'I am a blue window.'}));

var win2 = Ti.UI.createWindow({
    backgroundColor: 'red',
    title: 'Red'
});
win2.add(Ti.UI.createLabel({text: 'I am a red window.'}));

var tab1 = Ti.UI.createTab({
    window: win1,
    title: 'Blue',
    badge: 10
}),
tab2 = Ti.UI.createTab({
    window: win2,
    title: 'Red'
}),
tabGroup = Ti.UI.createTabGroup({
    tabs: [tab1, tab2]
});
tabGroup.open();

setTimeout(function(){
    tab1.icon = '/assets/images/tab1.png';
    tab2.icon = '/assets/images/tab2.png';
    tab2.badge=2;
}, 2000);

which initially leads to [[UITabBarItem alloc] initWithTitle:title image:nil selectedImage:nil] in updateTabBarItem. Actually, and so far, no problem – the tab bar items are created without icons and the icons will be set later.

~~But iOS 18 has a modified caching mechanism for the tab bar, which leads to these strange problems... It appears that tab bar items with icons are handled differently than icon-less tab bar items. Adding an icon at a later time, apparently a new tab bar item is created internally and then no longer doesn't match its view controller.~~

~~Using an "empty" placeholder image at creation time has solved this problem.~~ see below

hbugdoll avatar Oct 27 '25 23:10 hbugdoll

It's not crashing anymore but please check the "Red" (2nd tab) title in your example: it's at the wrong position at first and then jumps into place when the timeout is running

In my app it looks like this when I start the app: and after I clicked on all items it's correct:

m1ga avatar Oct 28 '25 12:10 m1ga

It's not crashing anymore but please check the "Red" (2nd tab) title in your example: it's at the wrong position at first and then jumps into place when the timeout is running

Weird behavior on iOS 26!

~~Forcing a relayout of the TabBar is needed. I put it in didChangeTraitCollection:, so the relayouting isn't performed unnecessarily often and unnecessarily too early, when the TabBar is still being built up.~~ see below

Bildschirmfoto 2025-11-01 um 21 37 55

The result of the example above is as follows.

iOS 26: Bildschirmfoto 2025-11-01 um 21 38 51 iPadOS 26: Bildschirmfoto 2025-11-01 um 21 39 29

hbugdoll avatar Nov 01 '25 21:11 hbugdoll

Thank you for the update :+1: Now it looks fine here too.

m1ga avatar Nov 02 '25 19:11 m1ga

Thank you for the update πŸ‘ Now it looks fine here too.

Thanks for checking again.

hbugdoll avatar Nov 03 '25 06:11 hbugdoll

The updated fix requires neither a TabBar relayout nor a placeholder image.

The TabBarItem has always been set on the wrong ViewController (rootController instead of NavigationController controller). That causes the Inconsistency in UITabBar items and view controllers detected error.


Here some own debug logs for the example above...

Setting TabBarItem on the RootViewController [rootController setTabBarItem:tabBarItem]:

[INFO]  RootViewController: <TiViewController: 0x100f11ef0>
[INFO]  RootViewController's TabBarItem: <UITabBarItem: 0x100f07330> title='Blue' image=<UIImage:0x6000030181b0 anonymous {30, 30} renderingMode=alwaysTemplate>
[INFO]  TabBarController's ViewControllers (1):
[INFO]  - [0] VC: <UINavigationController: 0x13c811e00> | item=<UITabBarItem: 0x100f07330> title='Blue' image=<UIImage:0x6000030181b0 anonymous {30, 30} renderingMode=alwaysTemplate>
[INFO]  TabBarController's TabBarItems (1):
[INFO]  - [0] Item: title="Blue", image=(null)

Setting TabBarItem on the NavigationController [controller setTabBarItem:tabBarItem]

[INFO]  RootViewController: <TiViewController: 0x103946e10>
[INFO]  RootViewController's TabBarItem: <UITabBarItem: 0x103949fc0> title='Blue'
[INFO]  TabBarController's ViewControllers (1):
[INFO]  - [0] VC: <UINavigationController: 0x104039e00> | item=<UITabBarItem: 0x103949e70> title='Blue' image=<UIImage:0x600003028120 anonymous {30, 30} renderingMode=alwaysTemplate> selected
[INFO]  TabBarController's TabBarItems (1):
[INFO]  - [0] Item: title="Blue", image=<UIImage:0x600003028120 anonymous {30, 30} renderingMode=alwaysTemplate>

hbugdoll avatar Nov 24 '25 14:11 hbugdoll