Skip to content

Modernize for iOS 26: SPM migration, Liquid Glass toolbar, settings polish#13

Open
leathalman wants to merge 6 commits into
v2from
fix/project-warnings
Open

Modernize for iOS 26: SPM migration, Liquid Glass toolbar, settings polish#13
leathalman wants to merge 6 commits into
v2from
fix/project-warnings

Conversation

@leathalman
Copy link
Copy Markdown
Owner

Summary

Foundational refresh to get the project building cleanly on Xcode 26 / iOS 26 and align with modern iOS conventions. The project hadn't been updated since ~2022 and didn't compile on current Xcode.

Dependency & build

  • CocoaPods → SPM. Firebase bumped 6.34 → 11.x; Pageboy, SwiftMessages, ViewAnimator, SPIndicator, SPPermissions, Blueprints all migrated. Podfile, Podfile.lock, Pods/, and Jotify.xcworkspace removed.
  • Firebase/DynamicLinks dropped — Google shut it down Aug 2025. Referral links now store the direct https://jotifyapp.com/?invitedby=<uid> URL.
  • Deployment target 13 → 15. Enables UIButton.Configuration and per-VC navigation appearance APIs, and cleared six dead if #available(iOS 14, *) branches.

iOS 26 rendering fixes

  • Nav bar color now correctly paints on detail views (iOS 26 prefers per-VC navigationItem appearance over UINavigationBar.standardAppearance). configureNavigationBar(bgColor:) moved from UINavigationController onto UIViewController.
  • iPhone simulators reappeared in Xcode's device picker — removed the obsolete EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 Firebase-era workaround.
  • New KeyboardAccessoryView replaces UIToolbar for the keyboard bar. On iOS 26+ it hosts a SwiftUI .buttonStyle(.glass) bar through UIHostingController; on iOS <26 it keeps the UIKit stack as fallback. Pinned to view.keyboardLayoutGuide.topAnchor for animation-in-lockstep with the keyboard.
  • SignInWithApple{Black,White} were final class : UIViewRepresentable, which modern SwiftUI asserts on. Now structs.

Native polish

  • Settings cells use UIListContentConfiguration + the system disclosure indicator (goodbye custom chevron.right.circle.fill); switch cells use accessoryView = UISwitch().
  • Search bar: unified content/date filter, UIMenu-driven scope, stacked placement forced (iOS 26 otherwise puts it in a bottom-integrated position meant for tab-bar apps).
  • Onboarding/paywall/privacy buttons use UIButton.Configuration.filled() with cornerStyle = .large.
  • Dynamic type: NoteCell, DetailOnboardingController, BuyPremiumController scale via UIFontMetrics / preferredFont(forTextStyle:).

Hacks removed

  • UIViewController.rootViewController force-unwrap (crashed during light/dark mode transitions); now falls back to self.
  • isNavigationBarHidden = true; isNavigationBarHidden = false redraw trick in EditingController.
  • Stale title-color resets in NoteCollectionController and SettingsController.
  • UIApplication.shared.windows.first (deprecated iOS 15) → firstKeyWindow helper.
  • UNNotificationPresentationOptions.alert (deprecated iOS 14) → [.banner, .list, .sound].
  • Firebase credential(withProviderID:…) / updateEmail(to:) → their non-deprecated iOS 26 replacements.

Known follow-ups (not in this PR)

  • Filter menu lives as a nav bar UIBarButtonItem rather than inside the search field — tried searchTextField.rightView but it didn't survive iOS 26's layout lifecycle. Worth revisiting.
  • WriteNoteController/EditingController could migrate to pure SwiftUI TextEditor for a more unified experience; out of scope here.

Test plan

  • Builds cleanly on Xcode 26, Debug + Release, generic iOS device + iPhone 17 Pro simulator.
  • Open an existing note → detail view nav bar matches the note color.
  • Keyboard toolbar on iOS 26 shows individual glass pill buttons above the predictive text row.
  • Create a new note via the + button.
  • Search from the Notes list → filter menu opens from the nav bar button; scope selection filters the results.
  • Settings → rows use the standard gray disclosure chevron; toggles sit at the trailing edge.
  • Crank Accessibility → Larger Text — note cells, onboarding text, paywall copy all scale.
  • Sign in with Apple (on a real device — simulator can't test this).
  • Referral link creation writes the plain URL to Firestore.

🤖 Generated with Claude Code

leathalman and others added 6 commits April 17, 2026 23:53
Foundational refresh to get the project building cleanly on Xcode 26 /
iOS 26 and to match modern iOS conventions:

- Migrate CocoaPods → SPM. Drop Firebase/DynamicLinks (shut down 2025-08);
  store referral URLs directly. Remove Podfile, Podfile.lock, Pods/, and
  Jotify.xcworkspace.
- Raise deployment target 13 → 15. Enables UIButton.Configuration and
  per-VC navigation appearance APIs, and clears six dead iOS 14
  availability guards.
- Fix multiple iOS 26 rendering issues:
  - Nav bar color wasn't applied in detail views because iOS 26 prefers
    per-VC navigationItem appearance; configureNavigationBar moved from
    UINavigationController onto UIViewController.
  - `EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64` was hiding iPhone
    simulators on Apple Silicon.
  - UIToolbar input accessory view produced SwiftUI-bridge constraint
    spam on iOS 26; replaced with a new KeyboardAccessoryView that hosts
    a SwiftUI `.buttonStyle(.glass)` bar via UIHostingController on iOS
    26+ and falls back to the existing UIKit stack on older iOS.
  - Bar is now pinned to view.keyboardLayoutGuide.topAnchor so it tracks
    the keyboard with matching animation.
- Settings cells use UIListContentConfiguration and the system
  disclosure indicator instead of a custom chevron.circle.fill image;
  switch cell uses `accessoryView = UISwitch()` instead of a manual
  70pt trailing offset.
- Search bar: unified content/date filter, UIMenu-driven scope picker,
  stacked placement forced so the iOS 26 bottom-integrated search
  doesn't end up behind the keyboard.
- Filled-style buttons on onboarding, paywall, and privacy-unlock VCs
  via UIButton.Configuration.filled() with cornerStyle = .large.
- Dynamic type: text in NoteCell, DetailOnboardingController, and
  BuyPremiumController scales with UIFontMetrics / preferredFont.
- Remove defensive hacks that iOS 26 now handles correctly: force-
  unwrap-crash in UIViewController.rootViewController (falls back to
  self), hide/show nav-bar redraw trick in EditingController, stale
  title-color hacks in NoteCollectionController and SettingsController.
- SignInWithApple{Black,White} were `final class : UIViewRepresentable`
  which newer SwiftUI runtimes assert on; now structs.
- Replace deprecated APIs: UIApplication.shared.windows → firstKeyWindow
  helper; UNNotificationPresentationOptions.alert → .banner/.list/.sound;
  Firebase `credential(withProviderID:…)` → `credential(providerID:…)`;
  `updateEmail(to:)` → `sendEmailVerification(beforeUpdatingEmail:)`.

Also: strip CocoaPods BoringSSL-GRPC `-GCC_WARN_INHIBIT_ALL_WARNINGS`
flag (clang 17 rejects it) and add a .gitignore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the UIMenu-based Content/Date scope filter and the nav bar button
  that hosted it. Search now always matches against both note body and
  displayed date — simpler, fewer controls, nothing to configure.
- Remove the custom `transition = Transition(style: .push, duration:
  0.15)` in PageBoyController. In Pageboy 4+ a non-nil transition
  replaces UIPageViewController's gesture-tracked animator, which made
  swipes feel laggy after the dependency bump. iOS 26's keyboard
  animation is fast enough that the artificial slow-down (originally
  added to let the keyboard catch up when programmatically scrolling to
  the write-note page) is no longer needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`cellForItemAt` was running per-dequeue work that added up quickly:

- A new UILongPressGestureRecognizer was attached to every cell's
  contentView on every reuse. Reused cells accumulated recognizers
  (one per scroll-by), and every touch had to consult all of them.
  Replaced with a single long-press recognizer on the collection view —
  the existing handler already uses `sender.location(in: collectionView)`
  to resolve the index path.
- `note?.color.getColor()` was called five times per cell, and
  `.isDarkColor` recomputed luminance each time. Cache both once.
- `shouldRasterize = true` was set on both `cell.layer` and
  `cell.contentView.layer`. For solid-color rounded rectangles the
  rasterization cost outweighs the draw cost, and toggling it on every
  dequeue invalidates the cache anyway.
- Corner radius was re-applied on every dequeue. Moved to the cell's
  init so it's set once.

No behavior change, just fewer allocations per frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The search controller was being installed on `viewWillAppear` and torn
out on `viewDidDisappear`. On iOS 26's stacked search bar layout, this
forces a nav bar rebuild on every transition away from the Notes list —
hence the consistent stagger whenever the user tapped the gear, the +
button, or a note card. Transitions inside Settings (push/pop between
plain UITableViewControllers with no search bar) didn't have this
overhead, which is why they felt snappy by comparison.

Also remove the ViewAnimator dependency and its `animateVisibleCells()`
call. It was running in `viewDidLoad` before Firebase had delivered any
notes, so it was animating zero cells on the initial load anyway.

- Move setupSearchBar() and setupNavigationBar() from viewWillAppear to
  viewDidLoad so they run once.
- Drop the `navigationItem.searchController = nil` teardown on
  viewDidDisappear.
- Remove ViewAnimator import + the `animateVisibleCells` helper;
  unregister the SPM package from the target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ead notifications

Cleanup pass on patterns left over from older Swift/iOS conventions:

- `ColorManager.noteColor = UIColor()` and `bgColor = UIColor()` used
  the placeholder-UIColor default, which crashes on `isEqual:` because
  it has no color space. Replaced with `.systemBlue` / `.systemBackground`
  — both are always overwritten before observation, the defaults just
  prevent latent crashes during early launch.
- `arc4random_uniform(UInt32(count))` → `Int.random(in: 0..<count)`.
- Replace the manual `Timer.scheduledTimer` idle auto-save in both
  EditingController and WriteNoteController with a Combine debounce.
  WriteNoteController resets its subscription inside `handleSend` so a
  pending debounced value from the just-sent note can't fire against
  the fresh blank note that follows.
- `"disableSwipe"` / `"enableSwipe"` NotificationCenter posts were
  posted but never observed anywhere in the codebase — pure dead code.
  Removed.
- `"updatePureDarkMode"` NotificationCenter pattern replaced with
  natural `viewWillAppear` refresh of `view.backgroundColor`. The user
  toggles Pure Dark Mode in CustomizationSettingsController and pops
  back; each ancestor VC picks up the new `ColorManager.bgColor` on
  appearance without needing a broadcast.
- Magic `tag = 007` on the GradientAnimator subview replaced with a
  type-based `UIView.gradientAnimator` lookup. Both set/remove and
  the iPad rotation resize now go through the typed property.
- `try! SwiftMessages.viewFromNib(named:)` wrapped in do/try/catch
  with a graceful bail + log. Also tightened the surrounding
  force-unwraps on `indexPathForItem` and array indexing to guards.
- Pin text field bottom to `keyboardAccessory.topAnchor` in both
  EditingController and WriteNoteController. That lets us drop the
  keyboardWillShow/Hide observers entirely — Auto Layout + the
  accessory's `keyboardLayoutGuide.topAnchor` pin handle keyboard
  tracking without manual contentInset math or alpha fades.
- Audited `setNeedsStatusBarAppearanceUpdate()` calls: the
  child-notifies-root pattern used here is load-bearing; no removals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… tags, dead notifications"

This reverts commit f5b744c.
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