Skip to content

Replace ScrollableSegmentedControl with our own page tab bar - #122

Merged
andiwand merged 2 commits into
mainfrom
modernize/11-page-tab-bar
Jul 26, 2026
Merged

Replace ScrollableSegmentedControl with our own page tab bar#122
andiwand merged 2 commits into
mainfrom
modernize/11-page-tab-bar

Conversation

@andiwand

@andiwand andiwand commented Jul 26, 2026

Copy link
Copy Markdown
Member

#113 vendored ScrollableSegmentedControl rather than drop CocoaPods support for it, and said the alternative was reimplementing the page tabs on UICollectionView in its own PR. This is that PR, so the 832 lines of third-party UIKit leave the repository.

PageTabBar

OpenDocumentReader/PageTabBar.swift is ~200 lines against the same UICollectionView the vendored control was built on, and implements only what the document view ever asked for: text tabs, an underline under the selection, horizontal scrolling once they no longer fit, and .valueChanged on a tap. The storyboard's custom class and the segmentedControl outlet are renamed accordingly.

Tab widths follow one rule in three cases:

an even share fits every title every tab gets an even share — the common case, and what it looked like before
they all fit, but not evenly each tab keeps its own width and shares out the slack, so the row still fills
they no longer fit each tab keeps its own width and the row scrolls

That is the first behaviour change: the old control gave every tab the width of the longest title, so a single long sheet name pushed the rest off screen — with the eight-sheet document I tested against, barely one and a half tabs were visible.

The second is dark mode. Labels are .label/.secondaryLabel and the bar is secondarySystemBackground, instead of hardcoded darkGray, black and #F9F9F9. Tab cells are accessibility elements now too, with the selected trait, and the row scales with Dynamic Type — it is the same 40 points at the default text size and grows from there.

Adjacent fixes the rewrite made obvious

  • documentPagesChanged appended segments and activated a fresh height constraint on every call, so a second announcement would have listed every page twice under conflicting constraints. Titles are assigned wholesale and the height constraint is created once.
  • The .valueChanged target was added in viewWillAppear, so a second appearance parsed the document twice for a single tap. It moves to viewDidLoad.
  • initialSelect is gone. Selecting a tab in code no longer sends .valueChanged — following UISegmentedControl — so there is nothing to suppress.
  • doc.pageNames was force-unwrapped.

Verification

  • ODR Full/Debug and ODR Lite/Debug Lite build green on the rebased tree, which includes the GoogleMobileAds 13.7 bump from Bump github.com/googleads/swift-package-manager-google-mobile-ads from 12.14.0 to 13.7.0 #120. Clean build, no new warnings.
  • 15 new tests, 20 total, all passing. They cover the three width cases, the height metric, the notify-on-tap-only contract, and — by going through Main.storyboard — that the outlet and the custom class actually resolve.
  • Driven end to end against a real 8-sheet .ods through Document/odrcore: all eight sheet names reached the bar, and selecting the fourth tab set document.page = 3 and scrolled that tab into view. Rendered the bar in both appearances to check the underline, the even-width case and the scrolling case.

One thing I could not automate on this machine: tapping the tabs by hand in the running app. The simulator is scriptable but osascript has no assistive-access permission here, so the interaction is covered by the tests above driving the real view hierarchy rather than by a literal finger on the screen.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1334276e72

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread OpenDocumentReader/PageTabBar.swift Outdated
Comment thread OpenDocumentReader/DocumentViewController.swift Outdated
andiwand and others added 2 commits July 26, 2026 11:20
#113 vendored ScrollableSegmentedControl because its upstream was
archived in 2022 and it never gained SPM support, and noted that the
alternative was reimplementing the page tabs ourselves. This does that,
so 832 lines of third-party UIKit leave the repository.

PageTabBar is ~180 lines against the same UICollectionView the vendored
control used, and implements only the four things the document view ever
asked for: text tabs, an underline under the selection, a horizontal
scroll once they no longer fit, and .valueChanged on a tap.

Two behaviour changes fall out of it:

- Tabs are only as wide as their own title once the row has to scroll.
  The old control gave every tab the width of the longest one, so a
  single long sheet name pushed everything else off screen. While they
  all fit they still share the width evenly, which is what the common
  case looked like before.
- Labels use .label/.secondaryLabel and the bar uses
  secondarySystemBackground instead of hardcoded dark grey, black and
  #F9F9F9, so the tabs are legible in dark mode. Tab cells are also
  accessibility elements now, with the selected trait.

Adjacent fixes the rewrite made obvious:

- documentPagesChanged appended segments and activated a fresh height
  constraint every time it ran, so a second announcement would have
  listed every page twice under conflicting constraints. Titles are
  assigned wholesale and the height constraint is created once.
- The .valueChanged target was added in viewWillAppear, so a second
  appearance parsed the document twice per tap. It moves to viewDidLoad.
- initialSelect is gone: selecting a tab in code no longer sends
  .valueChanged, following UISegmentedControl, so there is nothing to
  suppress.
- doc.pageNames was force-unwrapped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MhKU2kWm1cPW4GBq9gon5
Two findings from the Codex review on #122.

Sharing the width evenly is only fair while an even share is wide enough
for the longest title. With "Q4 Revenue Forecast" next to "A" and "B" the
three fit a 320 point bar comfortably, but an even third would truncate
the long one for no reason. Tabs now take an even share only while that
fits every title; failing that they keep their own widths and share out
whatever is left over, so the row still fills without truncating.

The tab bar's height was pinned at 40 points while the label scaled with
Dynamic Type, so accessibility text sizes were clipped. PageTabBar now
reports the height it needs -- which works out to the same 40 points at
the default text size -- and the document view follows it, including when
the text size changes while a document is open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011MhKU2kWm1cPW4GBq9gon5
@andiwand
andiwand force-pushed the modernize/11-page-tab-bar branch from 1334276 to 9a47f44 Compare July 26, 2026 09:21
@andiwand
andiwand changed the base branch from modernize/09-cd to main July 26, 2026 09:21
@andiwand
andiwand merged commit 09656ca into main Jul 26, 2026
3 checks passed
@andiwand
andiwand deleted the modernize/11-page-tab-bar branch July 26, 2026 09:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant