From 164f905a3f9bf652508f321b89f414a5b6e4abe8 Mon Sep 17 00:00:00 2001 From: Muukii Date: Thu, 15 Jan 2026 18:03:59 +0900 Subject: [PATCH 1/3] Patch --- .claude/settings.local.json | 14 ++++ CLAUDE.md | 63 ++++++++++++++++++ .../xcdebugger/Breakpoints_v2.xcbkptlist | 9 --- .../DemoRideauIntegrationViewController.swift | 64 +++++++++++++++++-- .../ViewController/FluidViewController.swift | 27 ++++++-- 5 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 CLAUDE.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..0c7df5dca --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(done)", + "Bash(lldb:*)", + "mcp__plugin_ios_mobile-mcp__mobile_list_available_devices", + "mcp__plugin_ios_mobile-mcp__mobile_take_screenshot", + "mcp__plugin_ios_mobile-mcp__mobile_list_apps", + "mcp__plugin_ios_mobile-mcp__mobile_launch_app", + "mcp__plugin_ios_mobile-mcp__mobile_list_elements_on_screen", + "mcp__plugin_ios_mobile-mcp__mobile_click_on_screen_at_coordinates" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..0509d2ecf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build Commands + +```bash +# Update git submodules (required before first build) +make checkout + +# Build for iOS +make build + +# Run tests +xcodebuild -scheme "FluidInterfaceKit-Package" test \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.0.1' | xcbeautify + +# Run a single test (example) +xcodebuild -scheme "FluidInterfaceKit-Package" test \ + -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.0.1' \ + -only-testing:FluidStackTests/FluidStackControllerTests | xcbeautify +``` + +## Architecture + +FluidInterfaceKit is a UIKit-based framework providing advanced view controller management with customizable transitions. The primary component **FluidStackController** replaces UINavigationController with flexible stacking behavior. + +### Core Modules (SPM Libraries) + +- **FluidStack** - Main container replacing UINavigationController. Key classes: `FluidStackController`, `FluidViewController`, `FluidGestureHandlingViewController` +- **FluidGesture** - Makes views draggable with `makeDraggable(descriptor:)` +- **FluidPortal** - Portal/layer display system for floating views +- **FluidSnackbar** - Toast/snackbar notifications with gesture support +- **FluidKeyboardSupport** - Keyboard frame tracking and integration +- **FluidTooltipSupport** - Floating tooltips over specific points +- **FluidPictureInPicture** - PiP floating view support +- **FluidStackRideauSupport** - Integration with Rideau modal library + +### Transition System + +Adding transitions: `AnyAddingTransition` with presets (`.noAnimation`, `.navigationStyle`, `.fadeIn`, `.popup`, `.contextualExpanding`, `.modalIdiom`) + +Removing transitions: `AnyRemovingTransition` with presets (`.noAnimation`, `.navigationStyle`, `.fadeOut`, `.vanishing`, `.contextual`, `.modalIdiom`) + +Context objects: `AddingTransitionContext`, `RemovingTransitionContext` provide state for animations. + +### Extension Pattern + +All UIViewControllers gain fluid methods via extension protocol: +- `fluidPush()` / `fluidPop()` - Safe navigation +- `fluidPushUnsafely()` - Unsafe variants +- `fluidStackController(with:)` - Finding strategies + +## Code Style + +- **Indentation:** 2 spaces +- **MainActor:** Extensively used for thread safety +- **MARK sections:** Properties, Initializers, Functions, ViewController lifecycle +- **Naming:** `Fluid` prefix for all types, camelCase for methods + +## Dependencies + +- GeometryKit, ResultBuilderKit, Rideau, swiftui-Hosting, swift-rubber-banding (all from FluidGroup) diff --git a/Development/FluidInterfaceKit.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist b/Development/FluidInterfaceKit.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist index d96fd727a..ea1fdbd33 100644 --- a/Development/FluidInterfaceKit.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Development/FluidInterfaceKit.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist @@ -31,15 +31,6 @@ moduleName = "UIKitCore" usesParentBreakpointCondition = "Yes"> - - Void) -> UIView { let button = UIButton(type: .system) button.setTitle(title, for: .normal) @@ -143,6 +181,14 @@ import SwiftUI import SwiftUISupport import SwiftUIHosting +private final class SimpleContentViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .neonRandom() + } +} + private final class SwiftUIContentViewController: UIViewController { override func viewDidLoad() { @@ -178,8 +224,18 @@ private final class ContentViewController: FluidStackController { ) { self._dismiss = dismiss super.init() - + navigationItem.title = "Rideau" + navigationItem.rightBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .done, + target: nil, + action: nil + ) + navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .close, + target: nil, + action: nil + ) } required init?( diff --git a/Sources/FluidStack/ViewController/FluidViewController.swift b/Sources/FluidStack/ViewController/FluidViewController.swift index 443418e8a..31e4a2f01 100644 --- a/Sources/FluidStack/ViewController/FluidViewController.swift +++ b/Sources/FluidStack/ViewController/FluidViewController.swift @@ -179,20 +179,34 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation subscriptions.append( navigationBar.observe(\.bounds, options: [.initial, .old, .new]) { [weak self] view, _ in guard let self else { return } - self.additionalSafeAreaInsets.top = view.frame.height + MainActor.assumeIsolated { + self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + view.invalidateIntrinsicContentSize() + } } + ) - - view.addSubview(navigationBar) - + + subscriptions.append( + navigationBar.observe(\.intrinsicContentSize, options: [.initial, .old, .new]) { [weak self] view, _ in + guard let self else { return } + MainActor.assumeIsolated { + self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + view.invalidateIntrinsicContentSize() + } + } + ) + navigationBar.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(navigationBar) NSLayoutConstraint.activate([ navigationBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor), navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor), ]) - + let targetNavigationItem = navigation.usesBodyViewController ? (content.bodyViewController?.navigationItem ?? navigationItem) : navigationItem @@ -274,7 +288,8 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation if !state.isTopBarHidden && state.isTopBarAvailable { topBar.isHidden = false - additionalSafeAreaInsets.top = topBar.frame.height + additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + topBar.invalidateIntrinsicContentSize() } else { topBar.isHidden = true additionalSafeAreaInsets.top = 0 From 35ecda8c492c49044f790f38a816501b00e459ac Mon Sep 17 00:00:00 2001 From: Muukii Date: Thu, 22 Jan 2026 02:58:32 +0900 Subject: [PATCH 2/3] Update --- .claude/settings.local.json | 3 ++- .../DemoRideauIntegrationViewController.swift | 13 +++++++-- .../ViewController/FluidViewController.swift | 27 +++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0c7df5dca..beae380fa 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,8 @@ "mcp__plugin_ios_mobile-mcp__mobile_list_apps", "mcp__plugin_ios_mobile-mcp__mobile_launch_app", "mcp__plugin_ios_mobile-mcp__mobile_list_elements_on_screen", - "mcp__plugin_ios_mobile-mcp__mobile_click_on_screen_at_coordinates" + "mcp__plugin_ios_mobile-mcp__mobile_click_on_screen_at_coordinates", + "Bash(xcbeautify)" ] } } diff --git a/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift b/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift index c1365260e..05f67ae42 100644 --- a/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift +++ b/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift @@ -80,8 +80,17 @@ final class DemoRideauIntegrationViewController: FluidStackController { instance.fluidPop(transition: nil, completion: nil) } + // Use topPadding: 12 to match System Sheet's 56pt (44pt + 12pt) + let fluidConfig = FluidViewController.Configuration( + transition: .modalStyle, + topBar: .navigation(.init( + navigationBarClass: UINavigationBar.self, + topPadding: 12 + )) + ) + let rideauController = FluidRideauViewController( - bodyViewController: body.fluidWrapped(configuration: .defaultModal), + bodyViewController: body.fluidWrapped(configuration: fluidConfig), configuration: .init( snapPoints: [.pointsFromTop(200)], topMarginOption: .fromSafeArea(0) @@ -89,7 +98,7 @@ final class DemoRideauIntegrationViewController: FluidStackController { initialSnapPoint: .pointsFromTop(200), resizingOption: .noResize ) - + fluidPush(rideauController, target: .current) } diff --git a/Sources/FluidStack/ViewController/FluidViewController.swift b/Sources/FluidStack/ViewController/FluidViewController.swift index 31e4a2f01..bdb1e5927 100644 --- a/Sources/FluidStack/ViewController/FluidViewController.swift +++ b/Sources/FluidStack/ViewController/FluidViewController.swift @@ -177,21 +177,21 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation navigationBar.delegate = self subscriptions.append( - navigationBar.observe(\.bounds, options: [.initial, .old, .new]) { [weak self] view, _ in + navigationBar.observe(\.bounds, options: [.initial, .old, .new]) { [weak self, topPadding = navigation.topPadding] view, _ in guard let self else { return } MainActor.assumeIsolated { - self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + topPadding view.invalidateIntrinsicContentSize() } } - + ) - + subscriptions.append( - navigationBar.observe(\.intrinsicContentSize, options: [.initial, .old, .new]) { [weak self] view, _ in + navigationBar.observe(\.intrinsicContentSize, options: [.initial, .old, .new]) { [weak self, topPadding = navigation.topPadding] view, _ in guard let self else { return } MainActor.assumeIsolated { - self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + topPadding view.invalidateIntrinsicContentSize() } } @@ -288,7 +288,11 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation if !state.isTopBarHidden && state.isTopBarAvailable { topBar.isHidden = false - additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + if case .navigation(let nav) = configuration.topBar { + additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + nav.topPadding + } else { + additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + } topBar.invalidateIntrinsicContentSize() } else { topBar.isHidden = true @@ -458,6 +462,10 @@ extension FluidViewController { public let navigationBarClass: UINavigationBar.Type + /// Additional value to add to `additionalSafeAreaInsets.top`. + /// Use this to match System Sheet's UINavigationBar height (56pt vs 44pt). + public var topPadding: CGFloat + let _activityHandler: @Sendable @MainActor (Activity) -> Void /// Initializer @@ -468,11 +476,13 @@ extension FluidViewController { displayMode: DisplayMode = .automatic, usesBodyViewController: Bool = true, navigationBarClass: NavigationBar.Type, + topPadding: CGFloat = 0, activityHandler: @escaping @MainActor (Activity) -> Void = { _ in } ) { self.displayMode = displayMode self.usesBodyViewController = usesBodyViewController self.navigationBarClass = navigationBarClass + self.topPadding = topPadding self._activityHandler = { activity in switch activity { case .didLoad(let controller, let navigationBar): @@ -487,8 +497,9 @@ extension FluidViewController { displayMode: .automatic, usesBodyViewController: true, navigationBarClass: UINavigationBar.self, + topPadding: 0, activityHandler: { _ in - + } ) From 85f3a85f9cbe2cdfa45986cc4eaf643c2dd16992 Mon Sep 17 00:00:00 2001 From: Muukii Date: Fri, 30 Jan 2026 00:55:37 +0900 Subject: [PATCH 3/3] Update --- .claude/settings.local.json | 9 +++- .../DemoRideauIntegrationViewController.swift | 4 +- .../ViewController/FluidViewController.swift | 42 +++++++++---------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index beae380fa..0480df653 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,5 +11,12 @@ "mcp__plugin_ios_mobile-mcp__mobile_click_on_screen_at_coordinates", "Bash(xcbeautify)" ] - } + }, + "enabledMcpjsonServers": [ + "XcodeBuildMCP", + "mobile-mcp", + "atlassian", + "maestro" + ], + "enableAllProjectMcpServers": true } diff --git a/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift b/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift index 05f67ae42..7ab2ff6ae 100644 --- a/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift +++ b/Development/Sources/FluidInterfaceKit-Demo/DemoRideauIntegrationViewController.swift @@ -80,12 +80,10 @@ final class DemoRideauIntegrationViewController: FluidStackController { instance.fluidPop(transition: nil, completion: nil) } - // Use topPadding: 12 to match System Sheet's 56pt (44pt + 12pt) let fluidConfig = FluidViewController.Configuration( transition: .modalStyle, topBar: .navigation(.init( - navigationBarClass: UINavigationBar.self, - topPadding: 12 + navigationBarClass: UINavigationBar.self )) ) diff --git a/Sources/FluidStack/ViewController/FluidViewController.swift b/Sources/FluidStack/ViewController/FluidViewController.swift index bdb1e5927..da958e94d 100644 --- a/Sources/FluidStack/ViewController/FluidViewController.swift +++ b/Sources/FluidStack/ViewController/FluidViewController.swift @@ -102,7 +102,7 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation } // MARK: - Functions - + @objc open func triggerFluidPop() { fluidPop(transition: nil) @@ -161,7 +161,7 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation } // MARK: - UIViewController - + open override func viewDidLoad() { super.viewDidLoad() @@ -177,10 +177,10 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation navigationBar.delegate = self subscriptions.append( - navigationBar.observe(\.bounds, options: [.initial, .old, .new]) { [weak self, topPadding = navigation.topPadding] view, _ in + navigationBar.observe(\.bounds, options: [.initial, .old, .new]) { [weak self] view, _ in guard let self else { return } MainActor.assumeIsolated { - self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + topPadding + self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + navigation._topPaddingProvider(self) view.invalidateIntrinsicContentSize() } } @@ -188,10 +188,10 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation ) subscriptions.append( - navigationBar.observe(\.intrinsicContentSize, options: [.initial, .old, .new]) { [weak self, topPadding = navigation.topPadding] view, _ in + navigationBar.observe(\.intrinsicContentSize, options: [.initial, .old, .new]) { [weak self] view, _ in guard let self else { return } MainActor.assumeIsolated { - self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + topPadding + self.additionalSafeAreaInsets.top = view.intrinsicContentSize.height + navigation._topPaddingProvider(self) view.invalidateIntrinsicContentSize() } } @@ -288,10 +288,8 @@ open class FluidViewController: FluidGestureHandlingViewController, UINavigation if !state.isTopBarHidden && state.isTopBarAvailable { topBar.isHidden = false - if case .navigation(let nav) = configuration.topBar { - additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + nav.topPadding - } else { - additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + if case .navigation(let navigation) = configuration.topBar { + additionalSafeAreaInsets.top = topBar.intrinsicContentSize.height + navigation._topPaddingProvider(self) } topBar.invalidateIntrinsicContentSize() } else { @@ -462,27 +460,29 @@ extension FluidViewController { public let navigationBarClass: UINavigationBar.Type - /// Additional value to add to `additionalSafeAreaInsets.top`. - /// Use this to match System Sheet's UINavigationBar height (56pt vs 44pt). - public var topPadding: CGFloat - let _activityHandler: @Sendable @MainActor (Activity) -> Void + let _topPaddingProvider: @Sendable @MainActor (FluidViewController) -> CGFloat + /// Initializer /// /// - Parameters: - /// - updateNavigationBar: A closure to update the navigation bar with the owner. + /// - displayMode: Controls when the navigation bar is visible. + /// - usesBodyViewController: Whether to use the body view controller's navigation item. + /// - navigationBarClass: The class of navigation bar to use. + /// - topPaddingProvider: A closure that returns additional top padding above the navigation bar. + /// - activityHandler: A closure called when navigation bar lifecycle events occur. public init( displayMode: DisplayMode = .automatic, usesBodyViewController: Bool = true, navigationBarClass: NavigationBar.Type, - topPadding: CGFloat = 0, + topPaddingProvider: @escaping @MainActor @Sendable (FluidViewController) -> CGFloat = { _ in 0 }, activityHandler: @escaping @MainActor (Activity) -> Void = { _ in } ) { self.displayMode = displayMode self.usesBodyViewController = usesBodyViewController self.navigationBarClass = navigationBarClass - self.topPadding = topPadding + self._topPaddingProvider = topPaddingProvider self._activityHandler = { activity in switch activity { case .didLoad(let controller, let navigationBar): @@ -492,15 +492,13 @@ extension FluidViewController { } } } - + public static let `default`: Self = .init( displayMode: .automatic, usesBodyViewController: true, navigationBarClass: UINavigationBar.self, - topPadding: 0, - activityHandler: { _ in - - } + topPaddingProvider: { _ in 0 }, + activityHandler: { _ in } ) }