LNPopupController

repository·master·Indexed 25 days ago

https://github.com/leonatan/lnpopupcontroller

A UIKit framework that enables developers to present view controllers as interactive popups, similar to the mini-player interface in Apple Music and Podcasts. It is implemented as a category over UIViewController and supports docking the popup bar to container views like tab bars or toolbars. Features include support for popup item paging via LNPopupDataSource, customizable bar styles, progress views, and image transitions.

Tokens
4.6K
Snippets
13
Records
19
Agent score
35%

What's inside LNPopupController

  1. Overview of LNPopupController

    master

    LNPopupController is a UIKit framework that allows you to present view controllers as popups, mimicking the behavior of Apple Music or Podcasts mini-players.

    Key characteristics:

    • It is implemented as a category over UIViewController.
    • It allows a view controller to present a popup bar docked to a specific view (e.g., a tab bar or toolbar).
    • It handles safe area insets automatically during presentation and dismissal.
    • Popup bar information is driven by LNPopupItem objects.
    • For SwiftUI projects, use the LNPopupUI library instead.
  2. Use Mode 1: Content Controllers as Popup Item Source

    master

    In this default mode, the UIViewController representing the content manages its own LNPopupItem. This is the simplest implementation and does not support paging between items. The popup bar automatically updates whenever the content controller's popupItem is modified.

    To use this mode:

    1. Create a UIViewController.
    2. Configure its popupItem properties (title, subtitle, progress, etc.).
    3. Call presentPopupBar(with:animated:completion:) on your container controller.
    class PopupContentViewController: UIViewController {
      init() {
        // ...
        
        popupItem.title = "Hello Title"
        popupItem.subtitle = "And a Subtitle!"
        popupItem.progress = 0.34
        popupItem.barButtonItems = [/* ... */]
      }
    }
    
    func presentPopupBar() {
      let contentVC = PopupContentViewController()
      tabBarController?.presentPopupBar(with: contentVC)
    }
  3. Implement Popup Image Transitions

    master

    The framework supports smooth transitions between the popup bar's image and an image in the content view.

    Automatic Discovery: If you include an LNPopupImageView in your popup content view hierarchy, the system will automatically attempt to use it as the transition target/source. Ensure there is only one such instance in the hierarchy.

    Manual Implementation: For advanced scenarios, implement viewForPopupTransition(from:to:) in your popup content controller to return the specific view to be used for the transition. The returned view must be part of the content controller's view hierarchy and should ideally implement the LNPopupTransitionView protocol.

  4. Use Mode 2: Popup Item Data Source (Advanced)

    master

    This mode decouples the popup item from the content controller, allowing for advanced features like popup item paging. To activate this mode, set the popup bar's usesContentControllersAsDataSource property to false.

    Key requirements:

    • You must provide an initial popupItem or implement the LNPopupDataSource protocol's initialPopupItem(for:) method.
    • The content controller can respond to item changes by overriding popupItemDidChange(_:).
    • Updates to the popupItem on the bar are automatically tracked.
    class PopupContentViewController: UIViewController {
      // ...
    
      override func popupItemDidChange(_ previousPopupItem: LNPopupItem?) {
        // Handle updating the content view hierarchy with the new popup item
        // or update self.popupItem as needed.
      }
    }
    
    class PopupContainerController: UIViewController {
      // ...
    
      func presentPopupBar() {
        tabBarController?.popupBar.usesContentControllersAsDataSource = false
        
        let initialPopupItem = LNPopupItem()
        initialPopupItem.title = "Hello Title"
        initialPopupItem.subtitle = "And a Subtitle!"
        initialPopupItem.progress = 0.34
        initialPopupItem.barButtonItems = [/* ... */]
        
        tabBarController?.popupBar.popupItem = initialPopupItem
        
        let contentVC = PopupContentViewController()
        tabBarController?.presentPopupBar(with: contentVC)
      }
    }
  5. Configure Accessibility for Popups

    master

    The framework honors accessibility labels, traits, and hints.

    • Popup Bar: Set accessibilityLabel and accessibilityHint on the LNPopupItem of the content view controller.
    • Close Button: Set accessibilityLabel and accessibilityHint on the LNPopupCloseButton object of the popup container view controller.
    • Progress View: Use accessibilityProgressLabel and accessibilityProgressValue on the LNPopupItem.
    • Bar Buttons: Set properties directly on the UIBarButtonItem objects.
    // Popup Item accessibility
    demoVC.popupItem.accessibilityLabel = NSLocalizedString("Custom popup bar accessibility label", comment: "")
    demoVC.popupItem.accessibilityHint = NSLocalizedString("Custom popup bar accessibility hint", comment: "")
    
    // Close Button accessibility
    tabBarController?.popupContentView.popupCloseButton.accessibilityLabel = NSLocalizedString("Custom popup close button accessibility label", comment: "")
    tabBarController?.popupContentView.popupCloseButton.accessibilityHint = NSLocalizedString("Custom popup close button accessibility hint", comment: "")
  6. Install LNPopupController via Swift Package Manager

    master

    You can add LNPopupController to your project using Swift Package Manager (SPM).

    Via Xcode UI

    1. Click FileAdd Package Dependencies….
    2. Enter https://github.com/LeoNatan/LNPopupController.
    3. Select your desired version.

    Via Package.swift

    Add the package to your Package.swift file:

    .package(url: "https://github.com/LeoNatan/LNPopupController.git", from: "4.0.0")

    And add the dependency to your target:

    .target(name: "MyExampleApp", dependencies: ["LNPopupController"]),
  7. Enable Popup Bar Minimization

    master

    Starting with iOS 26, minimization is supported for UITabBarController containers. Enable it by setting the tabBarMinimizeBehavior property.

    You can listen for environment changes (like the bar minimizing) by registering for LNPopupBar.EnvironmentTrait trait changes in your content controller.

    // Enable minimization
    self.tabBarController?.tabBarMinimizeBehavior = .onScrollDown
    
    // Listen for changes
    registerForTraitChanges([LNPopupBar.EnvironmentTrait.self]) { (self: Self, previousTraitCollection) in
      self.popupItem.barButtonItems?.last?.isHidden = self.traitCollection.popupBarEnvironment == .inline
    }
  8. Implement Popup Item Paging

    master

    To allow users to swipe between different popup items in Mode 2, you must set the dataSource of the LNPopupBar and implement both popupBar(_:popupItemBefore:) and popupBar(_:popupItemAfter:) from the LNPopupDataSource protocol.

    You can optionally set a delegate and implement popupBar(_:didDisplay:previous:) to be notified when a new item is displayed.

    // Setup
    func presentPopupBar() {
      // ...
      tabBarController?.popupBar.dataSource = self.model
      tabBarController?.popupBar.delegate = self
      // ...
    }
    
    // MARK: LNPopupDataSource
    
    func popupBar(_ popupBar: LNPopupBar, popupItemBefore popupItem: LNPopupItem) -> LNPopupItem? {
      // Return a popop item representing the content before `popupItem` or `nil`
    }
    
    func popupBar(_ popupBar: LNPopupBar, popupItemAfter popupItem: LNPopupItem) -> LNPopupItem? {
      // Return a popop item representing the content after `popupItem` or `nil`
    }
    
    // MARK: LNPopupDelegate
      
    func popupBar(_ popupBar: LNPopupBar, didDisplay newPopupItem: LNPopupItem, previous previousPopupItem: LNPopupItem?) {
      // Called when the popup bar's popup item changes
    }
  9. Implement a Custom Popup Bar

    master

    To create a completely custom bar design, subclass LNPopupCustomBarViewController.

    1. Build your view hierarchy and set preferredContentSize to the desired bar height.
    2. Optionally override wantsDefaultTapGestureRecognizer, wantsDefaultPanGestureRecognizer, or wantsDefaultHighlightGestureRecognizer to disable default behaviors.
    3. Implement popupItemDidUpdate() (calling super) to respond to item updates.
    4. Assign an instance of your subclass to the customBarViewController property of the popup bar object. This sets the bar style to .custom.
  10. Hide bottom bars in LNPopupController

    master

    Do not manually set isHidden = true on a tab bar or toolbar, as this is explicitly discouraged by Apple and not supported by the framework, leading to undefined behavior. Instead, use the standard Apple APIs which are fully supported by LNPopupController:

    • UINavigationController.setToolbarHidden(_:animated:)
    • UITabBarController.setTabBarHidden(_:animated:)
  11. Configure opaque bar appearance in LNPopupController

    master
    Legacy non-translucent tab bars and toolbars are not supported and may cause visual artifacts. To achieve an opaque bar, use the UIBarAppearance.configureWithOpaqueBackground() API, which is the supported method for LNPopupController.