-
-
Notifications
You must be signed in to change notification settings - Fork 724
feat(iOS, Tabs): Migrate to UITab API for iOS >= 26.1 #4675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kmichalikk
wants to merge
29
commits into
main
Choose a base branch
from
@kmichalikk/tabs-new-uikit-api
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
14c53c5
feat(iOS, Tabs): Migrate to UITab API for iOS >= 18
kmichalikk 898e5ad
refactor the implementation to be closer to 4706
kmichalikk c78e705
use UITab for iOS >= 26.1
kmichalikk 0ed8a5a
update comments
kmichalikk a7eac47
cleanup
kmichalikk 688bb38
handle badges init
kmichalikk 68002cc
extract workaround
kmichalikk b2c7f8a
bring back updateNavigationStateOnModelUpdate and generalize
kmichalikk 70f4aa7
add separate macros for UITab enabling
kmichalikk 1f8362d
reorder functions
kmichalikk aaeb0b6
remove tab count check
kmichalikk 58ff129
add comment explaining more navigation entrance check
kmichalikk 5135ea3
query viewController.tab instead of searching
kmichalikk 3587014
reword comment on stale system item
kmichalikk 311fba9
rework badge assignment
kmichalikk c337d9f
use installedScreenControllers
kmichalikk 1a8da36
bring back updateSelectedViewControllerTo
kmichalikk 6a46716
merge viewController and tab api where possible
kmichalikk f6afb31
move tab item config to separate coordinator invoked later
kmichalikk cad5455
refactor api checks
kmichalikk 3d5a2f8
offload all tab delegate calls to viewController equivalents
kmichalikk 3fdd70d
source title for ipad
kmichalikk 1be84c9
refactor isScreenControllerCurrentlySelected
kmichalikk e046518
Find the effctive displayed viewController, emit correct events
kmichalikk 13917a7
use setTabs:animated
kmichalikk 690b090
defensively clear isHandlingUserTabSelection flag
kmichalikk 7cfdd70
fix stale comments
kmichalikk 06ac680
set the tab title in addition to item title
kmichalikk 157f9e8
correctly render symbols and images on tabs on iPad
kmichalikk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| #pragma once | ||
|
|
||
| #import <Foundation/Foundation.h> | ||
| #import "RNSTabsScreenViewController.h" | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @interface RNSTabBarItemCoordinator : NSObject | ||
|
|
||
| - (void)createTabBarItemsForTabScreenControllers:(nullable NSArray<RNSTabsScreenViewController *> *)tabScreenCtrls; | ||
|
|
||
| - (void)updateTabBarItemsForTabScreenControllers:(nullable NSArray<RNSTabsScreenViewController *> *)tabScreenCtrls; | ||
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| #import "RNSTabBarItemCoordinator.h" | ||
| #import <React/RCTLog.h> | ||
| #import "RNSConversions-Tabs.h" | ||
| #import "RNSDefines.h" | ||
| #import "RNSTabsScreenComponentView.h" | ||
|
|
||
| @implementation RNSTabBarItemCoordinator | ||
|
|
||
| /** | ||
| * Creates UITabBarItem instance with set systemItem. Needs to be called before UITab creation for the same | ||
| * viewController. | ||
| */ | ||
| - (void)createTabBarItemsForTabScreenControllers:(nullable NSArray<RNSTabsScreenViewController *> *)tabScreenCtrls | ||
| { | ||
| for (RNSTabsScreenViewController *tabScreenCtrl in tabScreenCtrls) { | ||
| RNSTabsScreenComponentView *screenView = tabScreenCtrl.tabScreenComponentView; | ||
| if (screenView == nil) { | ||
| RCTLogWarn(@"[RNScreens] Nullish component view of TabScreen while tab bar item creation!"); | ||
| continue; | ||
| } | ||
|
|
||
| if (screenView.tabBarItemNeedsRecreation) { | ||
| screenView.tabBarItemNeedsRecreation = NO; | ||
| [self createTabBarItemForTabScreenController:tabScreenCtrl]; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Updates the runtime properties of UITabBarItem: title and badge. If used with UITab API, it should be called after | ||
| * the tab is built. | ||
| */ | ||
| - (void)updateTabBarItemsForTabScreenControllers:(nullable NSArray<RNSTabsScreenViewController *> *)tabScreenCtrls | ||
| { | ||
| for (RNSTabsScreenViewController *tabScreenCtrl in tabScreenCtrls) { | ||
| RNSTabsScreenComponentView *screenView = tabScreenCtrl.tabScreenComponentView; | ||
| if (screenView == nil) { | ||
| RCTLogWarn(@"[RNScreens] Nullish component view of TabScreen while tab bar item update!"); | ||
| continue; | ||
| } | ||
|
|
||
| if (screenView.tabBarItemNeedsUpdate) { | ||
| screenView.tabBarItemNeedsUpdate = NO; | ||
| [self updateTabBarItemForTabScreenController:tabScreenCtrl]; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| - (void)createTabBarItemForTabScreenController:(nonnull RNSTabsScreenViewController *)tabScreenCtrl | ||
| { | ||
| RNSTabsScreenComponentView *screenView = tabScreenCtrl.tabScreenComponentView; | ||
|
|
||
| UITabBarItem *tabBarItem = nil; | ||
| if (screenView.systemItem != RNSTabsScreenSystemItemNone) { | ||
| std::optional<UITabBarSystemItem> systemItem = | ||
| rnscreens::conversion::RNSTabsScreenSystemItemToUITabBarSystemItem(screenView.systemItem); | ||
| if (!systemItem) { | ||
| RCTLogError( | ||
| @"[RNScreens] Conversion from tabs screen systemItem to UITabBarSystemItem failed for systemItem [%ld]", | ||
| (long)screenView.systemItem); | ||
| return; | ||
| } | ||
| tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:systemItem.value() tag:0]; | ||
| } else { | ||
| tabBarItem = [[UITabBarItem alloc] init]; | ||
| } | ||
|
|
||
| [self applyTabBarItemRepaintWorkaroundForTabScreenController:tabScreenCtrl]; | ||
| tabScreenCtrl.tabBarItem = tabBarItem; | ||
| } | ||
|
|
||
| /** | ||
| * TODO: This is an ugly workaround and I would love to see it replaced. | ||
| * With UITab-managed children (iOS >= 26.1) any change to the systemItem for the first time | ||
| * results in missing icon and wrong title. Assigning a throwaway item first flips the internal logic | ||
| * so that the real assignment that follows paints synchronously. | ||
| * Remove once UIKit internals no longer require it. | ||
| */ | ||
| - (void)applyTabBarItemRepaintWorkaroundForTabScreenController:(nonnull RNSTabsScreenViewController *)tabScreenCtrl | ||
| { | ||
| #if RNS_UITAB_API_SDK_AVAILABLE | ||
| if (RNS_UITAB_API_ENABLED) { | ||
| tabScreenCtrl.tabBarItem = [[UITabBarItem alloc] init]; | ||
| } | ||
| #endif // RNS_UITAB_API_SDK_AVAILABLE | ||
| } | ||
|
|
||
| - (void)updateTabBarItemForTabScreenController:(nonnull RNSTabsScreenViewController *)tabScreenCtrl | ||
| { | ||
| RNSTabsScreenComponentView *screenView = tabScreenCtrl.tabScreenComponentView; | ||
|
|
||
| NSString *evaluatedTitle = screenView.title; | ||
| if (screenView.title == nil && screenView.systemItem != RNSTabsScreenSystemItemNone) { | ||
| // Restore default system item title | ||
| std::optional<UITabBarSystemItem> systemItem = | ||
| rnscreens::conversion::RNSTabsScreenSystemItemToUITabBarSystemItem(screenView.systemItem); | ||
| if (!systemItem) { | ||
| RCTLogError( | ||
| @"[RNScreens] Conversion from tabs screen systemItem to UITabBarSystemItem failed for systemItem [%ld]", | ||
| (long)screenView.systemItem); | ||
| return; | ||
| } | ||
| evaluatedTitle = [[UITabBarItem alloc] initWithTabBarSystemItem:systemItem.value() tag:0].title; | ||
| } | ||
|
|
||
| [self updateTabBarItemTitle:evaluatedTitle forTabScreenController:tabScreenCtrl]; | ||
| [self updateTabBarItemBadge:screenView.badgeValue forTabScreenController:tabScreenCtrl]; | ||
| } | ||
|
|
||
| - (void)updateTabBarItemTitle:(NSString *)newTitle | ||
| forTabScreenController:(nonnull RNSTabsScreenViewController *)tabScreenCtrl | ||
| { | ||
| // Setting controller title updates also controller's tabBarItem.title but only if there | ||
| // is a change to controller title. After creating new tabBarItem, controller title | ||
| // remains the same but tabBarItem.title is nil. For consistency, we always | ||
| // update both. | ||
| if (![tabScreenCtrl.tabBarItem.title isEqualToString:newTitle] || ![tabScreenCtrl.title isEqualToString:newTitle]) { | ||
| tabScreenCtrl.title = newTitle; | ||
| tabScreenCtrl.tabBarItem.title = newTitle; | ||
| #if RNS_UITAB_API_SDK_AVAILABLE | ||
| if (RNS_UITAB_API_ENABLED) { | ||
| // iPad sources the rendered title from the tab, not the item. | ||
| tabScreenCtrl.tab.title = newTitle; | ||
| } | ||
| #endif // RNS_UITAB_API_SDK_AVAILABLE | ||
| } | ||
| } | ||
|
|
||
| - (void)updateTabBarItemBadge:(NSString *)badgeValue | ||
| forTabScreenController:(nonnull RNSTabsScreenViewController *)tabScreenCtrl | ||
| { | ||
| if (![tabScreenCtrl.tabBarItem.badgeValue isEqualToString:badgeValue]) { | ||
| // The badge must land on both the item (viewController API) and the tab (UITab API) to render. | ||
| tabScreenCtrl.tabBarItem.badgeValue = badgeValue; | ||
| #if RNS_UITAB_API_SDK_AVAILABLE | ||
| if (RNS_UITAB_API_ENABLED) { | ||
| tabScreenCtrl.tab.badgeValue = badgeValue; | ||
| } | ||
| #endif // RNS_UITAB_API_SDK_AVAILABLE | ||
| } | ||
| } | ||
|
|
||
| @end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Runtime title changes never reach
UITab.title, so iPad shows stale titles.updateTabBarItemTitle:forTabScreenController:writes onlycontroller.titleandtabBarItem.title.tab.titleis set once, inmakeTabForTabScreenController:. Commit 3fdd70d ("source title for ipad") shows that iPad reads the title from theUITabitself, so:titleprop at runtime on iPad (iOS 26.1+) leaves the bar showing the title from when the tab was created;systemItemtab with notitlegetstab.title = @""(the component view title is nil). The evaluated system title only lands on the item, so iPad shows an empty label.The badge path already writes to both item and tab ("must land on both to render"). The title should do the same: set
tabScreenCtrl.tab.title = newTitleunderRNS_UITAB_API_ENABLED, including the evaluated system-item title.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more thing here I don't understand is why do we set both. Setting
UITabBarItem.titleshould be enough, right? IIRCUIViewController.titleis used only as a default in case theUITabBarItem.titleis not set:1, 2.
Documentation also gives important context here - the title MUST be initialized before the tabbaritem is added to the tab bar.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually needed to set also the tab.title 06ac680 for tabSidebar on iPad to work on first render
haven't verified the other claims, will do later