Skip to content

Replace CocoaPods with Swift Package Manager - #113

Merged
andiwand merged 1 commit into
mainfrom
modernize/06-spm
Jul 26, 2026
Merged

Replace CocoaPods with Swift Package Manager#113
andiwand merged 1 commit into
mainfrom
modernize/06-spm

Conversation

@andiwand

Copy link
Copy Markdown
Member

Stacked on #111.

CocoaPods has been in maintenance mode since 2024, and after #110 only two dependencies were left.

Google Mobile Ads

From googleads/swift-package-manager-google-mobile-ads, pinned to 12.14.0..<13.0.0 so this is a package-manager change and not also a major SDK bump. Package.resolved is committed (GMA 12.14.0, UserMessagingPlatform 3.1.0).

ScrollableSegmentedControl

No SPM support — upstream was archived in February 2022. It was the only thing keeping CocoaPods alive.

It is 832 lines of MIT-licensed UIKit, so it moves to OpenDocumentReader/Vendor/ScrollableSegmentedControl/ verbatim, with its LICENSE and a README recording the provenance. The storyboard now instantiates it from the app module (customModule="OpenDocumentReader"), and import ScrollableSegmentedControl is gone.

If you would rather not own this file, the alternative is reimplementing the page tabs on UICollectionView — happy to do that instead, but it is a behavior change and belongs in its own PR.

What falls out

  • The .xcworkspace is gone; the project builds standalone again.
  • The four configs/*.xcconfig files existed only to include the matching Pods xcconfig and then blank out its LIBRARY_SEARCH_PATHS. They collapse into a single configs/conan.xcconfig.
  • The ENABLE_BITCODE = NO post_install hook goes with the Podfile. Bitcode was removed in Xcode 14, so it had been dead for years.
  • The test target no longer inherits its build settings from a Pods xcconfig.
  • CI no longer runs pod install; fastlane and the build job use -project.
  • dependabot.yml: cocoapodsswift.

Adjacent fix: the conan resources build phase carried CocoaPods' [CP] prefix and declared no outputs, even though conandeployer.py writes output-files.xcfilelist next to the input list. It now declares them, so it stops rsyncing every odrcore asset on every build.

Verification

  • ODR Full/Debug and ODR Lite/Debug Lite build green.
  • Unit tests pass on the SPM project (1 test, 0 failures).
  • Both flavors installed and launched in a simulator and stayed running — this is the important one, since the storyboard instantiates both the vendored control and GADBannerView from the SPM framework.
  • Built bundle's Frameworks/ contains exactly GoogleMobileAds.framework and UserMessagingPlatform.framework; no Pods_*.framework.

Aside, not part of this PR

SwiftPM could not resolve any package on this machine — swift-argument-parser failed identically to GMA with no versions match. It works with a clean HOME, so something in ~/Library/Caches/org.swift.swiftpm / ~/Library/org.swift.swiftpm is wedged. I verified using an isolated HOME rather than deleting your caches; swift package purge-cache would likely clear it.

@andiwand
andiwand force-pushed the modernize/05-deployment-target branch from ac50eb6 to e990efc Compare July 26, 2026 08:15
@andiwand
andiwand force-pushed the modernize/05-deployment-target branch from e990efc to f00e7b6 Compare July 26, 2026 08:37
Base automatically changed from modernize/05-deployment-target to main July 26, 2026 08:39
CocoaPods has been in maintenance mode since 2024 and only two
dependencies were left after dropping Firebase.

Google Mobile Ads comes from
googleads/swift-package-manager-google-mobile-ads, pinned to the 12 line
so this is not also a major SDK bump. Package.resolved is committed.

ScrollableSegmentedControl has no SPM support: upstream was archived in
February 2022. It is 832 lines of MIT licensed UIKit, so it moves into
OpenDocumentReader/Vendor with its license and a note on where it came
from. The storyboard now instantiates it from the app module.

Falling out of this:
- the .xcworkspace is gone, the project builds on its own again
- the four configs/*.xcconfig files existed only to include the matching
  Pods xcconfig and then undo its LIBRARY_SEARCH_PATHS. They collapse into
  one configs/conan.xcconfig
- the ENABLE_BITCODE = NO post_install hook is gone with the Podfile.
  Bitcode was removed in Xcode 14, so it had been dead for years
- the test target no longer inherits its build settings from a Pods
  xcconfig
- CI does not run pod install any more

Unrelated but adjacent: the conan resources phase carried CocoaPods'
[CP] prefix and declared no outputs even though conandeployer.py writes
output-files.xcfilelist. It now declares them, so it stops rsyncing every
odrcore asset on each build.
@andiwand
andiwand merged commit 91eb47c into main Jul 26, 2026
3 checks passed
andiwand added a commit that referenced this pull request Jul 26, 2026
conan installs happily on python 3.11, and then conan/setup-all.sh calls the
conan-odr-index helper scripts and dies on a SyntaxError, because those use
PEP 701 nested quotes in f-strings. CI already pins 3.12 for exactly this
reason; only the README was silent about it.

The documented venv lives inside the checkout, so .venv/ joins .gitignore.
Without it, following the setup top to bottom leaves every clone with a dirty
tree and a large machine-specific directory one `git add .` away from being
committed.

The step reordering this branch also carried is now redundant: #113 moved the
submodule checkout ahead of the pip install while landing SPM.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019978mDv7veoaSpByQPCnNe
andiwand added a commit that referenced this pull request Jul 26, 2026
conan installs happily on python 3.11, and then conan/setup-all.sh calls the
conan-odr-index helper scripts and dies on a SyntaxError, because those use
PEP 701 nested quotes in f-strings. CI already pins 3.12 for exactly this
reason; only the README was silent about it.

The documented venv lives inside the checkout, so .venv/ joins .gitignore.
Without it, following the setup top to bottom leaves every clone with a dirty
tree and a large machine-specific directory one `git add .` away from being
committed.

The step reordering this branch also carried is now redundant: #113 moved the
submodule checkout ahead of the pip install while landing SPM.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
andiwand added a commit that referenced this pull request Jul 26, 2026
#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
andiwand added a commit that referenced this pull request Jul 26, 2026
* Replace ScrollableSegmentedControl with our own page tab bar

#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>

* Address review: uneven tab titles and accessibility text sizes

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>
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