UIKit UITabBarController migration + pull-to-dismiss polish
Created by: jedmund
Summary
Replaces the signed-in root's SwiftUI TabView with a UIKit UITabBarController so tab-bar visibility can be driven by the first-party setTabBarHidden(_:animated:) API instead of SwiftUI's .toolbar(.hidden, for: .tabBar), which ran a multi-frame internal layout reconciliation that couldn't be suppressed from the outside. Bundled alongside the migration are several pull-to-dismiss visual/interaction fixes that share the IllustDetailViewController surface and benefit from the new tab-controller hooks.
Tab-bar migration
- New
PixillateTabBarControllersubclass owns threeUITabs (Home, Search, Bookmarks) withviewControllerProviderclosures that lazily buildPixillateNavigationControllers via a newPixillateNavCoordinator(the per-tab nav builder lifted out of the retiredNavigationControllerHostRepresentable). - New
MainTabBarHost(UIViewControllerRepresentable) presented fromContentView.SignedInRoot. Owns the sharedCellRectRegistry,PixivImageSizeRegistry,BookmarksChromeState, and a newTabBarHostStateobservable that mirrors tab selection + visibility so the SwiftUI iPad Bookmarks chrome overlay can stay in SwiftUI. -
PixillateNavigationController.navigationController(_:willShow:animated:)delegate hook callssetTabBarContentHiddenon every push/pop boundary. Non-root VC → hide; root → show. Authoritative control point, replacing per-VCviewWillAppearcalls that were proving unreliable. -
setTabBarContentHidden(_:animated:)forwards tosuper.setTabBarHidden— the one iPadOS 26 lever that actually hides the top strip.self.tabBarturns out to be a zero-sized compatibility shadow at the bottom of the screen on iPadOS 26; the visible top strip is rendered via private views we can't reach, but the UITabBarController API handles it correctly. - Deletes
TabBarVisibility.swift,MainTabView.swift, theNavigationControllerHoststruct + its nestedCoordinator, and thetabBarVisibility:init param onPixillateNavigationController. - Extracts
BookmarksChromeStateandBookmarksTabBarRowChromeinto their own files.
Pull-to-dismiss polish
-
pedestalRect(in:)now converts viaCALayer.convertinstead ofUIView.convert— the latter is documented as undefined when any view in the chain has a non-identity transform, and the pedestal's pull-to-dismiss transform was causing the un-transformed rect to be returned, making the hero snapshot pop to "top-middle" on release. - Pedestal scale anchors around the finger's
pan.beganposition inside the pedestal's bounds, so the grabbed pixel tracks the finger through the scale-down. - Trigger threshold 220→110pt; phase B release always commits (no velocity branch, no cancel-after-trigger). Tab bar is force-revealed at trigger fire.
-
pedestal.backgroundColorpermanently.clear— detail VC's own background still provides the at-rest dark backdrop, but phase A drag no longer paints a moving grey rectangle over the metadata card. -
NukeUIImageView.placeholderColordefault →.clear, plus removed per-pedestal-page palette assignment. No more light-grey backdrop peeking through during spring animations. - Hero snapshot's colored backdrop dropped in
makeHeroLayer— only the aspect-fit image flies during dismiss. -
contentStack.bringSubviewToFront(pedestal)so a scaled/translated pedestal renders above the below-pedestal metadata card. - Diagnostic logs added to
IllustFeedViewModel+IllustFeedView(kept for next-round debugging).
Test plan
-
Tab bar hides cleanly on push into detail, restores cleanly on pop. No flash/flicker. -
Home hero push animates from the tapped cell (not from top-left). -
Pull-to-dismiss: phase A drag does NOT paint a grey pedestal rectangle over the metadata card. Pedestal follows finger with scale anchored at the touch point. -
Pull-to-dismiss commit: snapshot flies from wherever-the-finger-was to the source cell, not from top-middle. -
Profile pushed from inside a detail: tab bar stays hidden; popping profile back to detail doesn't reveal the strip (refcount/delegate chaining correct). -
iPhone compact: tab bar at bottom works; Bookmarks chrome renders inline in ProfileView. -
iPad regular: top strip + Bookmarks overlay chrome works.