### Install FloatingPanel with CocoaPods Source: https://github.com/scenee/floatingpanel/blob/master/README.md Add the FloatingPanel pod to your project's Podfile for installation via CocoaPods. ```ruby pod 'FloatingPanel' ``` -------------------------------- ### Swift Package Manager Installation Source: https://context7.com/scenee/floatingpanel/llms.txt Add the FloatingPanel library to your project's dependencies in Package.swift. ```swift // Package.swift dependencies: [ .package(url: "https://github.com/scenee/FloatingPanel", from: "3.2.1"), ], targets: [ .target(name: "MyApp", dependencies: [ .product(name: "FloatingPanel", package: "FloatingPanel"), ]), ] ``` -------------------------------- ### Basic UIKit Setup with FloatingPanelController Source: https://context7.com/scenee/floatingpanel/llms.txt Initialize and embed FloatingPanelController as a child view controller. Set its layout, content, and optionally track a scroll view for coordinated scrolling. ```swift import UIKit import FloatingPanel class ViewController: UIViewController, FloatingPanelControllerDelegate { var fpc: FloatingPanelController! override func viewDidLoad() { super.viewDidLoad() // Initialize with optional delegate fpc = FloatingPanelController(delegate: self) // Set layout (optional — defaults to FloatingPanelBottomLayout) fpc.layout = MyPanelLayout() // Set content let contentVC = MyContentViewController() fpc.set(contentViewController: contentVC) // Track a scroll view for coordinated scrolling fpc.track(scrollView: contentVC.tableView) // Embed as child view controller fpc.addPanel(toParent: self, animated: true) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) fpc.removePanelFromParent(animated: true) } } ``` -------------------------------- ### Move Floating Panel with Animation Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Provides examples of moving a floating panel to a specific position (`.half` or `.full`) with and without animation. This is useful for coordinating panel movements with other UI elements. ```swift func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { ... fpc.move(to: .half, animated: true) } func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { ... fpc.move(to: .full, animated: true) } ``` ```swift UIView.animate(withDuration: 0.25) { self.fpc.move(to: .half, animated: false) } ``` -------------------------------- ### Modal Presentation with FloatingPanelCoordinator in SwiftUI Source: https://context7.com/scenee/floatingpanel/llms.txt Present a floating panel modally using `.overCurrentContext` from SwiftUI. This example uses a custom coordinator to handle the dismissal event. ```swift import SwiftUI import FloatingPanel class ModalPanelCoordinator: FloatingPanelCoordinator { enum Event { case dismissed } let action: (Event) -> Void let proxy: FloatingPanelProxy lazy var delegate: FloatingPanelControllerDelegate? = self required init(action: @escaping (Event) -> Void) { self.action = action self.proxy = FloatingPanelProxy(controller: FloatingPanelController()) } func setupFloatingPanel( mainHostingController: UIHostingController
, contentHostingController: UIHostingController ) { contentHostingController.view.backgroundColor = .clear controller.set(contentViewController: contentHostingController) controller.isRemovalInteractionEnabled = true controller.delegate = self // Present on next run loop to ensure view hierarchy is ready Task { @MainActor in mainHostingController.present(controller, animated: true) } } func onUpdate(context: UIViewControllerRepresentableContext) {} } extension ModalPanelCoordinator: FloatingPanelControllerDelegate { func floatingPanelDidEndRemove(_ fpc: FloatingPanelController) { action(.dismissed) } } struct HomeView: View { var body: some View { MainContentView() .floatingPanel( coordinator: ModalPanelCoordinator.self, onEvent: { event in if case .dismissed = event { print("Panel was dismissed") } } ) { proxy in ModalDetailView() } } } ``` -------------------------------- ### Implement Custom FloatingPanel Behavior Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Create a custom `FloatingPanelBehavior` to modify the panel's interaction and projection behavior. This example shows how to set a custom behavior and override methods like `shouldProjectMomentum`. ```swift class ViewController: UIViewController, FloatingPanelControllerDelegate { ... // Other ViewController code func viewDidLoad() { ... // Other viewDidLoad code fpc.behavior = CustomPanelBehavior() } } class CustomPanelBehavior: FloatingPanelBehavior { let springDecelerationRate = UIScrollView.DecelerationRate.fast.rawValue + 0.02 let springResponseTime = 0.4 func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool { return true } } ``` -------------------------------- ### Customize FloatingPanel Behavior with BouncyBehavior Source: https://context7.com/scenee/floatingpanel/llms.txt Attach a custom `FloatingPanelBehavior` to tune the physics of a panel. This example defines a `BouncyBehavior` that enhances the spring animation. ```swift import SwiftUI import FloatingPanel class BouncyBehavior: FloatingPanelBehavior { let springDecelerationRate: CGFloat = UIScrollView.DecelerationRate.fast.rawValue + 0.02 let springResponseTime: CGFloat = 0.3 func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool { return true } func allowsRubberBanding(for edge: UIRectEdge) -> Bool { return true } } struct ContentView: View { var body: some View { MainView() .floatingPanel { _ in PanelContent() } .floatingPanelBehavior(BouncyBehavior()) } } ``` -------------------------------- ### Lazy Initialization of Delegate Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel SwiftUI API Guide.md Use lazy initialization for the `delegate` property when your coordinator implements `FloatingPanelControllerDelegate` to ensure proper setup. ```swift lazy var delegate: FloatingPanelControllerDelegate? = self ``` -------------------------------- ### Present Floating Panel Modally in SwiftUI Source: https://github.com/scenee/floatingpanel/blob/master/README.md Define a custom coordinator to present a floating panel modally within a SwiftUI view. Ensure presentation occurs on the next run loop cycle for proper view hierarchy setup. ```swift struct HomeView: View { var view: some View { MainView() .floatingPanel( coordinator: MyPanelCoordinator.self ) { proxy in ... } } } class MyPanelCoordinator: FloatingPanelCoordinator { ... func setupFloatingPanel( mainHostingController: UIHostingController
, contentHostingController: UIHostingController ) where Main: View, Content: View { // Set the delegate object controller.delegate = delegate // Set up the content contentHostingController.view.backgroundColor = .clear controller.set(contentViewController: contentHostingController) /* =============== HERE ==================== */ // NOTE: // Present the floating panel on the next run loop cycle // to ensure proper view hierarchy setup. Task { @MainActor in mainHostingController.present(controller, animated: false) } } ... } ``` -------------------------------- ### Create Additional Floating Panels Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Demonstrates how to set up and add multiple floating panels to a parent view controller, such as a search panel and a detail panel. ```swift override func viewDidLoad() { // Setup Search panel self.searchPanelVC = FloatingPanelController() let searchVC = SearchViewController() self.searchPanelVC.set(contentViewController: searchVC) self.searchPanelVC.track(scrollView: contentVC.tableView) self.searchPanelVC.addPanel(toParent: self) // Setup Detail panel self.detailPanelVC = FloatingPanelController() let contentVC = ContentViewController() self.detailPanelVC.set(contentViewController: contentVC) self.detailPanelVC.track(scrollView: contentVC.scrollView) self.detailPanelVC.addPanel(toParent: self) } ``` -------------------------------- ### Modal Panel Presentation Coordinator Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel SwiftUI API Guide.md Implement a coordinator to present the FloatingPanel modally. This involves setting up the content view controller and using the main hosting controller to present the panel. ```swift class ModalPanelCoordinator: FloatingPanelCoordinator { enum Event { case dismissed } let action: (Event) -> Void let proxy: FloatingPanelProxy lazy var delegate: FloatingPanelControllerDelegate? = nil ... func setupFloatingPanel( mainHostingController: UIHostingController
, contentHostingController: UIHostingController ) where Main: View, Content: View { // Set up the content contentHostingController.view.backgroundColor = .clear controller.set(contentViewController: contentHostingController) // Present the panel modally Task { @MainActor in controller.isRemovalInteractionEnabled = true controller.delegate = self mainHostingController.present(controller, animated: true) } } ... } extension ModalPanelCoordinator: FloatingPanelControllerDelegate { func floatingPanelDidEndRemove(_ fpc: FloatingPanelController) { action(.dismissed) } } ``` -------------------------------- ### Coordinator Responding to Environment Changes Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel SwiftUI API Guide.md Implement a coordinator that accesses SwiftUI environment values to dynamically update the panel's state, such as moving to a full state. ```swift class EnvironmentAwarePanelCoordinator: FloatingPanelCoordinator { ... func onUpdate( context: UIViewControllerRepresentableContext ) where Representable: UIViewControllerRepresentable { // Access environment values and update the panel let shouldMoveToFullState = context.environment.someCustomValue if shouldMoveToFullState { proxy.move(to: .full, animated: true) } } } ``` -------------------------------- ### Specify Anchor Insets Relative to Superview Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Use `.superview` as the reference guide in `FloatingPanelLayoutAnchor` to specify insets relative to the FloatingPanelController's view frame for different states. ```swift class MyFullScreenLayout: FloatingPanelLayout { ... let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .superview), .half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview), .tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .superview), ] } ``` -------------------------------- ### Customize Surface Appearance with Shadows and Colors Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Define a custom `SurfaceAppearance` to modify the panel's surface design. This includes setting shadows, corner radius, and background color. ```swift // Create a new appearance. let appearance = SurfaceAppearance() // Define shadows let shadow = SurfaceAppearance.Shadow() shadow.color = UIColor.black shadow.offset = CGSize(width: 0, height: 16) shadow.radius = 16 shadow.spread = 8 appearance.shadows = [shadow] // Define corner radius and background color appearance.cornerRadius = 8.0 appearance.backgroundColor = .clear // Set the new appearance fpc.surfaceView.appearance = appearance ``` -------------------------------- ### Floating Panel Delegate Methods for Content Interaction Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Shows how to use `FloatingPanelControllerDelegate` methods to interact with the panel's content, such as resigning the first responder or hiding UI elements based on the panel's state or drag gestures. ```swift class ViewController: UIViewController, FloatingPanelControllerDelegate { ... func floatingPanelWillBeginDragging(_ vc: FloatingPanelController) { if vc.position == .full { searchVC.searchBar.showsCancelButton = false searchVC.searchBar.resignFirstResponder() } } func floatingPanelWillEndDragging(_ vc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer) { if targetState.pointee != .full { searchVC.hideHeader() } } } ``` -------------------------------- ### Create Transparent Surface Appearance for FloatingPanel Source: https://context7.com/scenee/floatingpanel/llms.txt Use the `SurfaceAppearance.transparent` helper to create a SwiftUI-friendly transparent surface with customizable borders, corner radius, and layered shadows. ```swift import SwiftUI import FloatingPanel struct ContentView: View { var body: some View { MainView() .floatingPanel { _ in VStack { Text("Panel Title").font(.headline) Divider() Text("Content").foregroundColor(.secondary) } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) } .floatingPanelSurfaceAppearance( .transparent( borderColor: .secondary.opacity(0.3), borderWidth: 1.0, cornerRadius: 16.0, shadows: [ .init(color: .black, radius: 20, opacity: 0.12, offset: CGSize(width: 0, height: 8)), .init(color: .black, radius: 4, opacity: 0.08, offset: CGSize(width: 0, height: 2)), ] ) ) } } ``` -------------------------------- ### Update FloatingPanelLayout Configuration Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel 2.0 Migration Guide.md Use the new `position`, `initialState`, and `anchors` properties for layout configuration in FloatingPanelLayout. ```swift class SearchPanelPadLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .top let initialState: FloatingPanelState = .tip var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] { return [ ... ] } func backdropAlpha(for state: FloatingPanelState) -> CGFloat { return 0.3 } ... } ``` -------------------------------- ### Implement FloatingPanelControllerDelegate Callbacks Source: https://context7.com/scenee/floatingpanel/llms.txt Implement these delegate methods to respond to panel state changes, drag gestures, and scrolling behavior. Ensure the ViewController conforms to FloatingPanelControllerDelegate. ```swift class ViewController: UIViewController, FloatingPanelControllerDelegate { // Return different layouts depending on trait collection (e.g. landscape) func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { return (newCollection.verticalSizeClass == .compact) ? LandscapePanelLayout() : MyBottomLayout() } // Custom animator for presenting func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator { return UIViewPropertyAnimator(duration: 0.4, dampingRatio: 0.8) } // Respond to state changes (called inside animation block) func floatingPanelDidChangeState(_ fpc: FloatingPanelController) { updateUI(for: fpc.state) } // Hide search bar when user starts dragging from full state func floatingPanelWillBeginDragging(_ fpc: FloatingPanelController) { if fpc.state == .full { searchBar.resignFirstResponder() } } // Inspect target state before animation commits func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer) { if targetState.pointee != .full { hideHeader() } } // Constrain surface movement to a custom range func floatingPanelDidMove(_ fpc: FloatingPanelController) { guard !fpc.isAttracting else { return } let loc = fpc.surfaceLocation let minY = fpc.surfaceLocation(for: .full).y - 6.0 let maxY = fpc.surfaceLocation(for: .tip).y + 6.0 fpc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY)) } // Control whether the tracked scroll view can scroll in a given state func floatingPanel(_ fpc: FloatingPanelController, shouldAllowToScroll scrollView: UIScrollView, in state: FloatingPanelState) -> Bool { return state == .full || state == .half } // Prevent panel removal on certain conditions func floatingPanel(_ fpc: FloatingPanelController, shouldRemoveAt location: CGPoint, with velocity: CGVector) -> Bool { return velocity.dy > 4.0 } func floatingPanelDidRemove(_ fpc: FloatingPanelController) { print("Panel removed from hierarchy") } } ``` -------------------------------- ### Customize FloatingPanel Gesture Dynamics Source: https://context7.com/scenee/floatingpanel/llms.txt Implement the FloatingPanelBehavior protocol to customize spring physics, momentum projection, rubber-band effects, and removal velocity. ```swift import FloatingPanel class MapsPanelBehavior: FloatingPanelBehavior { // Faster deceleration (snappier feel) let springDecelerationRate: CGFloat = UIScrollView.DecelerationRate.fast.rawValue + 0.02 // Time to settle after user releases let springResponseTime: CGFloat = 0.3 // Allow swiping from tip all the way to full in one gesture func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool { return true } // Enable rubber-band bounce at top and bottom edges func allowsRubberBanding(for edge: UIRectEdge) -> Bool { return true } // Require a slower swipe before removing the panel let removalInteractionVelocityThreshold: CGFloat = 8.0 // Make it harder to move backward (50% threshold → 30%) func redirectionalProgress(_ fpc: FloatingPanelController, from: FloatingPanelState, to: FloatingPanelState) -> CGFloat { return 0.3 } } // Assign to controller fpc.behavior = MapsPanelBehavior() ``` -------------------------------- ### Support Landscape Layout with FloatingPanel Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Implement `floatingPanel(_:layoutFor:)` to return a specific layout for landscape orientation, defined by `LandscapePanelLayout` which customizes anchors and preparation. ```swift class ViewController: UIViewController, FloatingPanelControllerDelegate { ... func floatingPanel(_ vc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { return (newCollection.verticalSizeClass == .compact) ? LandscapePanelLayout() : FloatingPanelBottomLayout() } } class LandscapePanelLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .tip let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), .tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea), ] func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { return [ surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0), surfaceView.widthAnchor.constraint(equalToConstant: 291), ] } } ``` -------------------------------- ### Present Floating Panel Modally in UIKit Source: https://github.com/scenee/floatingpanel/blob/master/README.md Instantiate a FloatingPanelController, set its content view controller, and optionally enable removal interaction. Then, present the panel modally. ```swift let fpc = FloatingPanelController() let contentVC = ... fpc.set(contentViewController: contentVC) fpc.isRemovalInteractionEnabled = true // Optional: Let it removable by a swipe-down self.present(fpc, animated: true, completion: nil) ``` -------------------------------- ### Add and Show Floating Panel Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Adds a FloatingPanelController to the view hierarchy and displays it. Ensure the panel is properly sized and constrained. The `didMove(toParent:)` call is crucial after the panel is shown. ```swift self.view.addSubview(fpc.view) // REQUIRED. It makes the floating panel view have the same size as the controller's view. fpc.view.frame = self.view.bounds // In addition, Auto Layout constraints are highly recommended. // Constraint the fpc.view to all four edges of your controller's view. // It makes the layout more robust on trait collection change. fpc.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ fpc.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0), fpc.view.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0.0), fpc.view.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0.0), fpc.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0), ]) // Add the floating panel controller to the controller hierarchy. self.addChild(fpc) // Show the floating panel at the initial position defined in your `FloatingPanelLayout` object. fpc.show(animated: true) { // Inform the floating panel controller that the transition to the controller hierarchy has completed. fpc.didMove(toParent: self) } ``` -------------------------------- ### FloatingPanelControllerDelegate Methods Source: https://context7.com/scenee/floatingpanel/llms.txt Implement these methods to respond to panel lifecycle events such as state changes, drag gestures, and scroll view interactions. ```APIDOC ## FloatingPanelControllerDelegate — State & Gesture Callbacks Respond to panel lifecycle events: state changes, drag start/end, attraction, removal, and scroll control. ```swift // Return different layouts depending on trait collection (e.g. landscape) func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout // Custom animator for presenting func floatingPanel(_ fpc: FloatingPanelController, animatorForPresentingTo state: FloatingPanelState) -> UIViewPropertyAnimator // Respond to state changes (called inside animation block) func floatingPanelDidChangeState(_ fpc: FloatingPanelController) // Hide search bar when user starts dragging from full state func floatingPanelWillBeginDragging(_ fpc: FloatingPanelController) // Inspect target state before animation commits func floatingPanelWillEndDragging(_ fpc: FloatingPanelController, withVelocity velocity: CGPoint, targetState: UnsafeMutablePointer) // Constrain surface movement to a custom range func floatingPanelDidMove(_ fpc: FloatingPanelController) // Control whether the tracked scroll view can scroll in a given state func floatingPanel(_ fpc: FloatingPanelController, shouldAllowToScroll scrollView: UIScrollView, in state: FloatingPanelState) -> Bool // Prevent panel removal on certain conditions func floatingPanel(_ fpc: FloatingPanelController, shouldRemoveAt location: CGPoint, with velocity: CGVector) -> Bool func floatingPanelDidRemove(_ fpc: FloatingPanelController) ``` ``` -------------------------------- ### Define Custom FloatingPanel Layout Source: https://context7.com/scenee/floatingpanel/llms.txt Implement `FloatingPanelLayout` to define custom snap positions. Specify the panel's position, initial state, and map states to `FloatingPanelLayoutAnchoring` objects. You can also customize backdrop alpha and constrain panel width. ```swift import FloatingPanel // Standard bottom panel with three snap positions class MyBottomLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .half var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { return [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), .half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea), .tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea), ] } // Optional: per-state backdrop alpha func backdropAlpha(for state: FloatingPanelState) -> CGFloat { switch state { case .full: return 0.3 default: return 0.0 } } // Optional: constrain panel width (e.g. landscape sidebar) func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { return [ surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), surfaceView.widthAnchor.constraint(equalToConstant: 320), ] } } // Usage fpc.layout = MyBottomLayout() fpc.invalidateLayout() ``` -------------------------------- ### Landscape Layout Support via Delegate Source: https://context7.com/scenee/floatingpanel/llms.txt Implement a custom FloatingPanelLayout by conforming to the FloatingPanelControllerDelegate protocol. Return a different layout when the device rotates to support landscape orientations, such as a sidebar layout. ```swift class LandscapeSidebarLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .tip var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { return [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), .tip: FloatingPanelLayoutAnchor(absoluteInset: 69.0, edge: .bottom, referenceGuide: .safeArea), ] } // Constrain to a sidebar width func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { return [ surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 8.0), surfaceView.widthAnchor.constraint(equalToConstant: 291), ] } } class ViewController: UIViewController, FloatingPanelControllerDelegate { func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { return (newCollection.verticalSizeClass == .compact) ? LandscapeSidebarLayout() : MyBottomLayout() } } ``` -------------------------------- ### Implement Custom Layout for FloatingPanelControllerDelegate Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel 2.0 Migration Guide.md Implement the floatingPanel(_:layoutFor:) delegate method to provide a custom FloatingPanelLayout based on the given size. ```swift func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout { if aCondition(for: size) { return SearchPanelLayout() } return SearchPanel2Layout() } ``` -------------------------------- ### FloatingPanelLayout Protocol Source: https://context7.com/scenee/floatingpanel/llms.txt Defines custom anchor positions for the Floating Panel. You can specify the panel's position, initial state, and a dictionary of anchors for different states. ```APIDOC ## FloatingPanelLayout Protocol ### Description Define snap positions using `FloatingPanelLayout`. Specify `position` (edge), `initialState`, and an `anchors` dictionary mapping states to `FloatingPanelLayoutAnchoring` objects. ### Properties - **position** (FloatingPanelPosition) - The edge where the panel is attached. - **initialState** (FloatingPanelState) - The initial state of the panel when it appears. - **anchors** ([FloatingPanelState: FloatingPanelLayoutAnchoring]) - A dictionary mapping panel states to their corresponding layout anchors. ### Methods - **backdropAlpha(for state: FloatingPanelState) -> CGFloat** - Optional: Returns the backdrop alpha for a given state. - **prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint]** - Optional: Allows for custom layout constraints for the panel's surface view. ### Usage Example ```swift // Standard bottom panel with three snap positions class MyBottomLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .half var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { return [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), .half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea), .tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea), ] } // Optional: per-state backdrop alpha func backdropAlpha(for state: FloatingPanelState) -> CGFloat { switch state { case .full: return 0.3 default: return 0.0 } } // Optional: constrain panel width (e.g. landscape sidebar) func prepareLayout(surfaceView: UIView, in view: UIView) -> [NSLayoutConstraint] { return [ surfaceView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), surfaceView.widthAnchor.constraint(equalToConstant: 320), ] } } // Usage fpc.layout = MyBottomLayout() fpc.invalidateLayout() ``` ``` -------------------------------- ### FloatingPanelController.move(to:animated:completion:) Source: https://context7.com/scenee/floatingpanel/llms.txt Allows programmatic movement of the panel to a defined state. This is useful for responding to user interactions or other events within your application. ```APIDOC ## FloatingPanelController.move(to:animated:completion:) ### Description Move the panel to any defined state programmatically. Useful when responding to user actions like activating a search bar. ### Parameters - **to** (FloatingPanelState) - The target state to move the panel to. - **animated** (Bool) - Whether the movement should be animated. - **completion** ((() -> Void)?) - An optional closure to be executed after the movement is completed. ### Request Example ```swift // Expand to full when search activates func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { fpc.move(to: .full, animated: true) } // Collapse to half when search is cancelled func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { fpc.move(to: .half, animated: true) } // Animate panel position alongside a UIView animation func collapseWithCustomAnimation() { UIView.animate(withDuration: 0.25) { self.fpc.move(to: .tip, animated: false) } } // Move with completion func expandAndLoadData() { fpc.move(to: .full, animated: true) { print("Panel reached full state, loading data...") } } ``` ``` -------------------------------- ### Use a Custom Grabber Handle View Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Replace the default grabber handle with a custom view by hiding the default handle and adding your custom view as a subview to the `surfaceView`. ```swift let myGrabberHandleView = MyGrabberHandleView() fpc.surfaceView.grabberHandle.isHidden = true fpc.surfaceView.addSubview(myGrabberHandleView) ``` -------------------------------- ### Modal Presentation with FloatingPanelController Source: https://context7.com/scenee/floatingpanel/llms.txt Present a FloatingPanelController modally. Enable swipe-down removal and backdrop tap to dismiss for interactive user experience. ```swift import UIKit import FloatingPanel class ViewController: UIViewController { func showDetailPanel() { let fpc = FloatingPanelController() let detailVC = DetailViewController() fpc.set(contentViewController: detailVC) // Allow removal by swiping down fpc.isRemovalInteractionEnabled = true // Enable backdrop tap to dismiss fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true present(fpc, animated: true) } } ``` -------------------------------- ### SwiftUI: Programmatic Control with FloatingPanelProxy Source: https://context7.com/scenee/floatingpanel/llms.txt Use `FloatingPanelProxy` within panel content to programmatically control panel movement and track scroll events. The proxy provides access to the underlying UIKit controller for advanced customization. ```swift import SwiftUI import FloatingPanel struct PanelContent: View { let proxy: FloatingPanelProxy var body: some View { VStack(spacing: 16) { // Move panel states from within content HStack { Button("Full") { proxy.move(to: .full, animated: true) } Button("Half") { proxy.move(to: .half, animated: true) } Button("Tip") { proxy.move(to: .tip, animated: true) } Button("Hidden") { proxy.move(to: .hidden, animated: true) } } // Move with completion Button("Expand and load") { proxy.move(to: .full, animated: true) { print("Now at full, loading data...") } } // Advanced: direct UIKit access Button("Disable gesture") { proxy.controller.panGestureRecognizer.isEnabled = false } } } } ``` -------------------------------- ### Customize Content Padding from Surface Edges Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Set the `contentPadding` for the `surfaceView` to define the spacing between the panel's content and its edges. ```swift fpc.surfaceView.contentPadding = .init(top: 20, left: 20, bottom: 20, right: 20) ``` -------------------------------- ### Implement Custom FloatingPanel Behavior Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel API Guide.md Create a custom class conforming to FloatingPanelBehavior to modify the panel's interaction dynamics. Assign an instance of your custom behavior to the fpc.behavior property. ```swift class ViewController: UIViewController, FloatingPanelControllerDelegate { ... func viewDidLoad() { ... fpc.behavior = CustomPanelBehavior() } } class CustomPanelBehavior: FloatingPanelBehavior { let springDecelerationRate = UIScrollView.DecelerationRate.fast.rawValue + 0.02 let springResponseTime = 0.4 func shouldProjectMomentum(_ fpc: FloatingPanelController, to proposedState: FloatingPanelState) -> Bool { return true } } ``` -------------------------------- ### Control Panel Content Mode Source: https://context7.com/scenee/floatingpanel/llms.txt Control whether the surface height stretches to fill the screen (.fitToBounds) or stays fixed at the tallest anchor height (.static). Configure Auto Layout constraints in the content view for .fitToBounds mode. ```swift // Default: surface height is fixed to the full-state height fpc.contentMode = .static // fitToBounds: surface grows/shrinks as the user drags // Use this when content should reflow (e.g. table view with variable height) fpc.contentMode = .fitToBounds // IMPORTANT: in fitToBounds mode, configure Auto Layout constraints in the // content view to not conflict with a changing surface height: class MyContentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() tableView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ tableView.topAnchor.constraint(equalTo: view.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } } ``` -------------------------------- ### Migrate FloatingPanelLayout to Anchors Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel 2.0 Migration Guide.md Replace `insetFor(position:)` with the `anchors` property using `FloatingPanelLayoutAnchor` for flexible layout configuration. ```swift // Before: class MyPanelLayout: FloatingPanelLayout { var initialPosition: FloatingPanelPosition { return .half } func insetFor(position: FloatingPanelPosition) -> CGFloat? { switch position { case .full: return 16.0 case .half: return 262.0 case .tip: return 44.0 case .hidden: return nil } } } // After: class MyPanelLayout: FloatingPanelLayout { var position: FloatingPanelPosition = .bottom var initialState: FloatingPanelState { .half } var anchors: [FloatingPanelState : FloatingPanelLayoutAnchoring] { return [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), .half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .superview), .tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea) ] } } ``` -------------------------------- ### View Hierarchy of FloatingPanelController Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel API Guide.md Illustrates the view hierarchy managed by FloatingPanelController. This structure is fundamental to understanding how the panel, backdrop, surface, content, and grabber views are organized. ```text FloatingPanelController.view (FloatingPanelPassThroughView) ├─ .backdropView (FloatingPanelBackdropView) └─ .surfaceView (FloatingPanelSurfaceView) ├─ .containerView (UIView) │ └─ .contentView (FloatingPanelController.contentViewController.view) └─ .grabber (FloatingPanelGrabberView) ``` -------------------------------- ### FloatingPanelLayoutAnchor Types Source: https://context7.com/scenee/floatingpanel/llms.txt Define panel snap positions using `FloatingPanelLayoutAnchor` or `FloatingPanelIntrinsicLayoutAnchor`. Choose between absolute inset, fractional inset, or intrinsic content size based on your layout needs. ```swift // Absolute inset: exactly 16pt from the top of the safe area let fullAnchor = FloatingPanelLayoutAnchor( absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea ) // Fractional inset: 50% up from the bottom of the safe area let halfAnchor = FloatingPanelLayoutAnchor( fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea ) // Intrinsic anchor: panel shows its full content height let intrinsicAnchor = FloatingPanelIntrinsicLayoutAnchor( absoluteOffset: 0, referenceGuide: .safeArea ) // Fractional intrinsic: show 50% of content height let halfIntrinsicAnchor = FloatingPanelIntrinsicLayoutAnchor( fractionalOffset: 0.5, referenceGuide: .safeArea ) class IntrinsicPanelLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .full var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { return [ .full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0, referenceGuide: .safeArea), .half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea), ] } } ``` -------------------------------- ### Enable Rubber-Banding Effect on Panel Edges Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Implement the `allowsRubberBanding(for:)` method in a custom `FloatingPanelBehavior` to enable the rubber-band effect on specified edges of the panel. ```swift class MyPanelBehavior: FloatingPanelBehavior { ... // Other MyPanelBehavior code func allowsRubberBanding(for edge: UIRectEdge) -> Bool { return true } } ``` -------------------------------- ### Control Panel Motion Range Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel 2.0 Migration Guide.md Implement `floatingPanelDidMove(_:)` to manually control the maximum and minimum range of panel motion, replacing deprecated buffer properties. ```swift func floatingPanelDidMove(_ fpc: FloatingPanelController) { if fpc.isAttracting == false { let loc = fpc.surfaceLocation let minY = fpc.surfaceLocation(for: .full).y - 6.0 let maxY = fpc.surfaceLocation(for: .tip).y + 6.0 fpc.surfaceLocation = CGPoint(x: loc.x, y: min(max(loc.y, minY), maxY)) } } ``` -------------------------------- ### Custom Floating Panel Layout Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Defines a custom layout for a FloatingPanelController by conforming to the `FloatingPanelLayout` protocol. This allows for precise control over the panel's position, initial state, and anchor points. ```swift class ViewController: UIViewController, FloatingPanelControllerDelegate { ... { fpc = FloatingPanelController(delegate: self) fpc.layout = MyFloatingPanelLayout() } } class MyFloatingPanelLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .tip let anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] = [ .full: FloatingPanelLayoutAnchor(absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea), .half: FloatingPanelLayoutAnchor(fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea), .tip: FloatingPanelLayoutAnchor(absoluteInset: 44.0, edge: .bottom, referenceGuide: .safeArea), ] } ``` -------------------------------- ### SurfaceAppearance Customization Source: https://context7.com/scenee/floatingpanel/llms.txt Customize the visual appearance of the floating panel's surface, including background, borders, shadows, and grabber handle. ```APIDOC ## SurfaceAppearance — Visual Customization Customize the surface view's background color, corner radius, border, shadows, and grabber handle. ```swift import FloatingPanel // Rich surface with multiple layered shadows let appearance = SurfaceAppearance() appearance.backgroundColor = .systemBackground appearance.cornerRadius = 16.0 // iOS 13+: smooth continuous curve if #available(iOS 13.0, *) { appearance.cornerCurve = .continuous } // Primary shadow let shadow1 = SurfaceAppearance.Shadow() shadow1.color = .black shadow1.offset = CGSize(width: 0, height: 8) shadow1.radius = 20 shadow1.opacity = 0.15 shadow1.spread = 4 // Secondary close shadow let shadow2 = SurfaceAppearance.Shadow() shadow2.color = .black shadow2.offset = CGSize(width: 0, height: 2) shadow2.radius = 4 shadow2.opacity = 0.1 appearance.shadows = [shadow1, shadow2] appearance.borderColor = UIColor.separator appearance.borderWidth = 0.5 fpc.surfaceView.appearance = appearance // Customize grabber handle fpc.surfaceView.grabberHandlePadding = 8.0 fpc.surfaceView.grabberHandleSize = CGSize(width: 40.0, height: 4.0) // Use custom grabber view instead fpc.surfaceView.grabberHandle.isHidden = true let customGrabber = MyGrabberView() fpc.surfaceView.addSubview(customGrabber) // Content padding inside the surface fpc.surfaceView.contentPadding = UIEdgeInsets(top: 20, left: 16, bottom: 20, right: 16) // Margins between surface edge and screen/container (AirPods-style panel) fpc.surfaceView.containerMargins = UIEdgeInsets(top: 24.0, left: 16.0, bottom: 16.0, right: 16.0) ``` ``` -------------------------------- ### Customize Backdrop Alpha per State Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Implement the `backdropAlpha(for:)` method in your `FloatingPanelLayout` to control the backdrop view's alpha for different panel states. ```swift class MyPanelLayout: FloatingPanelLayout { func backdropAlpha(for state: FloatingPanelState) -> CGFloat { switch state { case .full, .half: return 0.3 default: return 0.0 } } } ``` -------------------------------- ### Manual Show/Hide Control of FloatingPanelController Source: https://context7.com/scenee/floatingpanel/llms.txt Manually manage the visibility of a FloatingPanelController by adding its view to the hierarchy and using show/hide methods. This allows for finer control without repeated addition/removal from the parent view controller. ```swift // Add once with Auto Layout view.addSubview(fpc.view) fpc.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ fpc.view.topAnchor.constraint(equalTo: view.topAnchor), fpc.view.leftAnchor.constraint(equalTo: view.leftAnchor), fpc.view.rightAnchor.constraint(equalTo: view.rightAnchor), fpc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) addChild(fpc) fpc.show(animated: true) { fpc.didMove(toParent: self) } // Later — hide the panel without removing it fpc.hide(animated: true) // Show it again fpc.show(animated: true) // Remove completely from the hierarchy fpc.willMove(toParent: nil) fpc.hide(animated: true) { fpc.view.removeFromSuperview() fpc.removeFromParent() } ``` -------------------------------- ### Enable Tap-to-Dismiss on Backdrop View Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Enables the tap-to-dismiss action on the floating panel's backdrop view. This allows users to dismiss the panel by tapping outside of it. ```swift fpc.backdropView.dismissalTapGestureRecognizer.isEnabled = true ``` -------------------------------- ### FloatingPanelLayoutAnchor Source: https://context7.com/scenee/floatingpanel/llms.txt Defines concrete anchor types for panel snap positions, including absolute inset, fractional inset, and intrinsic content size. ```APIDOC ## FloatingPanelLayoutAnchor — Anchor Types ### Description Three concrete anchor types define panel snap positions: absolute inset, fractional inset, and intrinsic content size. ### Types - **FloatingPanelLayoutAnchor**: Configures anchors using absolute or fractional insets from a reference guide. - **FloatingPanelIntrinsicLayoutAnchor**: Configures anchors based on the intrinsic content size of the panel. ### Initializers #### FloatingPanelLayoutAnchor - **absoluteInset**: The absolute distance from the reference edge. - **fractionalInset**: The fractional distance from the reference edge (0.0 to 1.0). - **edge**: The edge of the reference guide to which the inset is applied. - **referenceGuide**: The guide to use for calculating the inset (e.g., `.safeArea`). #### FloatingPanelIntrinsicLayoutAnchor - **absoluteOffset**: An absolute offset from the reference guide. - **fractionalOffset**: A fractional offset from the reference guide (0.0 to 1.0). - **referenceGuide**: The guide to use for calculating the offset. ### Examples ```swift // Absolute inset: exactly 16pt from the top of the safe area let fullAnchor = FloatingPanelLayoutAnchor( absoluteInset: 16.0, edge: .top, referenceGuide: .safeArea ) // Fractional inset: 50% up from the bottom of the safe area let halfAnchor = FloatingPanelLayoutAnchor( fractionalInset: 0.5, edge: .bottom, referenceGuide: .safeArea ) // Intrinsic anchor: panel shows its full content height let intrinsicAnchor = FloatingPanelIntrinsicLayoutAnchor( absoluteOffset: 0, referenceGuide: .safeArea ) // Fractional intrinsic: show 50% of content height let halfIntrinsicAnchor = FloatingPanelIntrinsicLayoutAnchor( fractionalOffset: 0.5, referenceGuide: .safeArea ) class IntrinsicPanelLayout: FloatingPanelLayout { let position: FloatingPanelPosition = .bottom let initialState: FloatingPanelState = .full var anchors: [FloatingPanelState: FloatingPanelLayoutAnchoring] { return [ .full: FloatingPanelIntrinsicLayoutAnchor(absoluteOffset: 0, referenceGuide: .safeArea), .half: FloatingPanelIntrinsicLayoutAnchor(fractionalOffset: 0.5, referenceGuide: .safeArea), ] } } ``` ``` -------------------------------- ### Set Content Mode to FitToBounds Source: https://github.com/scenee/floatingpanel/blob/master/Sources/FloatingPanel.docc/FloatingPanel API Guide.md Configures the FloatingPanelController to scale its content view to fit the bounds of the panel's view when the surface position changes. This is useful when the surface height dynamically adjusts. ```swift fpc.contentMode = .fitToBounds ``` -------------------------------- ### Customize SurfaceView Appearance in Swift Source: https://github.com/scenee/floatingpanel/blob/master/Documentation/FloatingPanel 2.0 Migration Guide.md Use SurfaceAppearance and its nested Shadow struct to configure corner radius, background color, and layered shadows for the surface view. This replaces direct property access for appearance settings. ```swift fpc.surfaceView.cornerRadius = 6.0 fpc.surfaceView.backgroundColor = .clear fpc.surfaceView.shadowHidden = false fpc.surfaceView.shadowColor = .black fpc.surfaceView.shadowOffset = CGSize(width: 0, height: 16) fpc.surfaceView.shadowRadius = 16.0 ``` ```swift let appearance = SurfaceAppearance() appearance.cornerRadius = 8.0 appearance.backgroundColor = .clear let shadow = SurfaceAppearance.Shadow() shadow.color = .black shadow.offset = CGSize(width: 0, height: 16) shadow.radius = 16 shadow.spread = 8 appearance.shadows = [shadow] fpc.surfaceView.appearance = appearance ```