Skip to content

scenee/FloatingPanel

Repository files navigation

Swift 5 Platform Version GitHub Workflow Status (with branch)

FloatingPanel

FloatingPanel is a simple and easy-to-use UI component designed for a user interface featured in Apple Maps, Shortcuts and Stocks app. The user interface displays related content and utilities alongside the main content.

Maps Stocks

Maps(Landscape)

Table of Contents

Click here.

Features

  • Simple container view controller
  • Fluid behavior using numeric springing
  • Scroll view tracking
  • Removal interaction
  • Multi panel support
  • Modal presentation
  • Support for 4 positions (top, left, bottom, right)
  • 1 or more magnetic anchors(full, half, tip and more)
  • Layout support for all trait environments(i.e. Landscape orientation)
  • Common UI elements: surface, backdrop and grabber handle
  • Free from common Auto Layout and gesture handling issues
  • Compatible with Objective-C
  • SwiftUI API support

Examples can be found here:

Requirements

FloatingPanel is written in Swift 5.0+ and compatible with iOS 12.0+.

Documentation

Installation

Swift Package Manager

Using Xcode

Just follow this documentation.

Using Package.swift

In your Package.swift Swift Package Manager manifest, add the following dependency to your dependencies argument:

.package(url: "https://github.com/scenee/FloatingPanel", from: "3.0.0"),

Add Numerics as a dependency for your target:

.target(name: "MyTarget", dependencies: [
  .product(name: "FloatingPanel", package: "FloatingPanel"),
  "AnotherModule"
]),

And then add import FloatingPanel in your source code.

CocoaPods

FloatingPanel is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'FloatingPanel'

Getting Started with SwiftUI

Adding a floating panel within a view

@State private var layout = MyFloatingPanelLayout()
@State private var state: FloatingPanelState?

var view: some View {
  MainView()
      .floatingPanel { proxy in
          ScrollView {
              VStack(spacing: 20) {
                  ForEach(items) { item in
                      ItemRow(item)
                  }
              }
              .padding()
          }
          .floatingPanelScrollTracking(proxy: proxy)
      }
      .floatingPanelState($state)
      .floatingPanelLayout(layout)
      .floatingPanelBehavior(MyCustomBehavior())
      .floatingPanelSurfaceAppearance(.transparent)
}

Presenting a floating panel modally within a view

Please define a custom coordinator object to present a floating panel as a modality.

struct HomeView: View {
  var view: some View {
    MainView()
        .floatingPanel(
          coordinator: MyPanelCoordinator.self
        ) { proxy in
          ...
        }
  }
}

class MyPanelCoordinator: FloatingPanelCoordinator {
    ...
    func setupFloatingPanel<Main, Content>(
        mainHostingController: UIHostingController<Main>,
        contentHostingController: UIHostingController<Content>
    ) 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)
        }
    }
    ...
}

Displaying multiple panels

Multiple floating panels can be displayed in the same view hierarchy. To customize the layout and behavior for each Floating Panel individually, place modifiers directly below each panel.

Color.orange
    .ignoresSafeArea()
    .floatingPanel(
        coordinator: MyPanelCoordinator.self
    ) { proxy in
        ContentView(proxy: proxy)
    }
    .floatingPanelSurfaceAppearance(.transparent())
    .floatingPanel(
        coordinator: MyPanelCoordinator.self
    ) { proxy in
        ContentView(proxy: proxy)
    }
    .floatingPanelSurfaceAppearance(.transparent(cornerRadius: 24))

Next step

For more details, see the FloatingPanel SwiftUI API Guide

Getting Started with UIKit

Adding a floating panel as a child view controller

import UIKit
import FloatingPanel

class ViewController: UIViewController, FloatingPanelControllerDelegate {
    var fpc: FloatingPanelController!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Initialize a `FloatingPanelController` object.
        fpc = FloatingPanelController()

        // Assign self as the delegate of the controller.
        fpc.delegate = self // Optional

        // Set a content view controller.
        let contentVC = ContentViewController()
        fpc.set(contentViewController: contentVC)

        // Track a scroll view(or the siblings) in the content view controller.
        fpc.track(scrollView: contentVC.tableView)

        // Add and show the views managed by the `FloatingPanelController` object to self.view.
        fpc.addPanel(toParent: self)
    }
}

Presenting a floating panel modally

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)

You can show a floating panel over UINavigationController from the container view controllers as a modality of .overCurrentContext style.

Note

FloatingPanelController has the custom presentation controller. If you would like to customize the presentation/dismissal, please see Transitioning.

Next step

For more details, see the FloatingPanel API Guide.

Maintainer

Shin Yamamoto [email protected] | @scenee

License

FloatingPanel is available under the MIT license. See the LICENSE file for more info.