Skip to content

Build the tab bar's tabs once - #26301

Draft
jkmassel wants to merge 2 commits into
trunkfrom
jkmassel/wptabbarcontroller-build-tabs-once
Draft

jkmassel wants to merge 2 commits into
trunkfrom
jkmassel/wptabbarcontroller-build-tabs-once

Conversation

@jkmassel

@jkmassel jkmassel commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

WPTabBarController now builds each tab once. When the account or the app UI type changes, it replaces the tab's first screen and keeps the tab. Stacked on #26300.

Why

Before this change, a tab was thrown away and rebuilt whenever its first screen had to change:

When What was rebuilt
Sign-in finishes (reloadTabs) All four tabs
WordPress.com account changes (defaultAccountDidChange:) The Reader tab
Notifications switches to the Moved-to-Jetpack poster (setShouldUseStaticNotifications:) The Notifications tab

Each rebuild created a new navigation controller, which also meant a new tab bar item. Two problems came from that:

Changes

  • WPTabBarController.m: build the Reader, Notifications and Me navigation controllers once, in init. Two new methods replace only a tab's first screen: showFirstReaderScreenInNavigationController: and showFirstNotificationsScreenInNavigationController:. The lazy getters and tabViewControllers are gone.
  • Reader: every WordPress.com account change now gives the tab a new presenter and first screen, whether or not the notification carries an account. reloadTabs no longer resets the Reader, since the account change already has. This is the fix for Reader menu and Reader links stop working on iPhone after signing in to WordPress.com again #26228.
  • Notifications and Me: unchanged behaviour. Sign-in still gives Notifications a new list and pops Me to its first screen. The poster switch keeps the selected tab without re-selecting it.
  • ReaderPresenter.swift: prepareForTabBarPresentation(in:) fills the tab's existing navigation controller instead of returning a new one.
  • WPTabBarController.h: readerNavigationController and notificationsNavigationController are now nonnull. They could never return nil.

Test plan

  • New tests in RootViewCoordinatorTests:
  • Three of the four new tests fail on the parent commit, every time across three retries: Executed 28 tests, with 12 failures. The poster-switch test passes on both. It guards the selected tab, which the old code also kept.
  • WordPressTest/RootViewCoordinatorTests and WordPressTest/NotificationsViewControllerTests pass: Executed 41 tests, with 0 failures.
    Simulator checks, all on iOS 27.0, signed in to a local self-hosted site and/or WordPress.com. A temporary debug hook logged the tab bar's address and its four tab addresses at every step. In every row below they stayed the same.

iPhone 18 Pro

  • Jetpack (WPTabBarController(staticScreens: false)):
    • Self-hosted only: the Reader tab shows the logged-out screen.
    • Sign In from that screen: the same tab switches to the live Reader menu, and Discover opens from it.
    • Re-post the account change with the account attached (the Reader menu and Reader links stop working on iPhone after signing in to WordPress.com again #26228 trigger): Saved opens from the menu, and a /read/search link opens Reader Search.
    • Log out of WordPress.com while the self-hosted site stays: the Reader goes back to the logged-out screen, the Me tab goes back to the default icon, and the tab bar stays.
  • WordPress app, static screens (StaticScreensTabBarWrapper, both posters): both tabs show the Moved-to-Jetpack poster with no Reader presenter. A re-posted account change keeps both posters and the selected tab. Logout goes to the sign-in screen.
  • WordPress app, staticScreensWithReader (live Reader, Notifications poster): Saved opens from the menu. After a re-posted account change Likes opens from the menu, and a wpdebug:///read/search link opens Reader Search. Notifications stays on the poster.
  • WordPress app, self-hosted only: no tab bar to test. That state uses the simplified UI.

iPad Pro 13-inch (M5), using the compact tab bar inside SplitViewRootPresenter:

  • Jetpack, wide window, self-hosted only: the hidden compact tab bar already has the logged-out Reader.
  • Jetpack, narrowed window: Sign In from the compact Reader gives the live Reader in the same tab, and Saved opens from its menu.
  • Jetpack, the collapse hand-off after an account change: widen, choose Likes in the wide Reader, re-post the account change while wide (the hidden tab bar gets a new presenter), then narrow. The compact Reader opens Likes through the new presenter.
  • Jetpack, log out while narrow: the compact Reader shows the logged-out screen. A widen and narrow cycle afterwards keeps it, with no assertions in the log.
  • WordPress app, staticScreensWithReader (appUITypeDidChange → shouldUseStaticNotifications):
    • Log out while on the Notifications tab: the same tab bar swaps in the live list, Notifications stays selected, and the bell updates.
    • Sign in again: the poster swaps back in, in the same tab bar.
  • On a fresh install the remote readerInWordPressApp flag isn't cached during the first session, so that session runs as staticScreens. There, sign-in replaces the whole split-view presenter, and the compact tab bar keeps the live tabs. This is existing behaviour; it doesn't go through the code this PR changes.
iPhone, Jetpack, self-hosted only iPhone, Jetpack, after re-post iPhone, Jetpack, /read/search link iPhone, Jetpack, after logout
Reader tab showing ‘Join the conversation’ and a Sign In button Saved screen opened from the Reader menu Reader Search opened from a link Reader tab back on ‘Join the conversation’, Me tab showing the default icon
iPhone, WordPress, static screens iPad compact, Jetpack, Reader menu iPad compact, Jetpack, after logout iPad compact, WordPress, Notifications poster
Reader tab showing the Moved-to-Jetpack poster Saved screen in the narrow iPad window Logged-out Reader in the narrow iPad window Notifications tab showing the Moved-to-Jetpack poster in the narrow iPad window

Not tried: a physical device. Every path above ran on simulators only.

Related issues

Nothing references the new-post URL parameter keys, the tab bar button
class name, the icon inset helper or the empty viewDidLayoutSubviews
override. Nothing sets the tab bar controller as a transitioning
delegate, so its presentation controller method never runs. Also drop
imports the file no longer uses.
WPTabBarController replaced whole tabs when the account or app UI type
changed: sign-in rebuilt all four, logout rebuilt the Reader tab, and
switching to the Notifications poster rebuilt that tab. Each rebuild
threw away the tab bar item and its configuration, and a tab released
without being reinstalled left a stale screen on display.

Create each tab's navigation controller once and replace only its first
screen. Every WordPress.com account change now gives the Reader tab a
new presenter and first screen, including the same account signing in
again, which used to leave the tab without a presenter.
@jkmassel jkmassel self-assigned this Oct 29, 2026
@dangermattic

Copy link
Copy Markdown
Collaborator
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

@jkmassel jkmassel added this to the 27.5 milestone Oct 29, 2026
Base automatically changed from jkmassel/wptabbarcontroller-dead-code to trunk October 30, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants