Swiftpack.co - Package - LeoNatan/LNPopupController
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.

LNPopupController

LNPopupController is a framework for presenting view controllers as popups of other view controllers, much like the Apple Music and Podcasts apps.

For SwiftUI, check out my LNPopupUI library.

GitHub release GitHub stars GitHub license PayPal Donation Button

GitHub issues GitHub contributors Carthage compatible

See a video of the modern popup look & feel here and a video of the classic popup look & feel here.

Once a popup bar is presented with a content view controller, the user can swipe or tap the popup at any point to present the content controller. After finishing, the user dismisses the popup by either swiping or tapping the popup close button.

The framework is intended to be very generic and work in most situations, so it is implemented as a category over UIViewController. Each view controller can present a popup bar, docked to a bottom view. For UITabBarController subclasses, the default dock view is the tab bar. For UINavigationController subclasses, the default dock view is the toolbar. For other classes, the popup bar is presented at the bottom of the screen. View controller subclasses can provide their own docking views.

The framework correctly maintains the safe area insets and guide of the container controller’s view and its child controllers, as the popup bar is presented and dismissed.

The contents of the popup bar is built dynamically using the popup item objects (instances of the LNPopupItem class) associated with the popup content view controllers. To change the contents of the popup bar, you must therefore configure the popup items of your view controllers.

Generally, it is recommended to present the popup bar on the outermost container controller. So if you have a view controller contained in a navigation controller, which is in turn contained in a tab bar controller, it is recommended to present the popup bar on the tab bar controller.

Check the demo project for many common use cases of the framework in various scenarios. It contains examples in Swift and Objective C.

Features

  • Available for iOS 11 and above, as an Xcode framework or an SPM package
  • Good citizen in modern UIKit world
  • Modern Objective C syntax and great Swift interoperability
  • For SwiftUI, check out my LNPopupUI library.

Adding to Your Project

Swift Package Manager

LNPopupController supports SPM versions 5.1.0 and above. To use SPM, you should use Xcode 11 to open your project. Click File -> Swift Packages -> Add Package Dependency, enter https://github.com/LeoNatan/LNPopupController. Select the version you’d like to use.

You can also manually add the package to your Package.swift file:

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

And the dependency in your target:

.target(name: "BestExampleApp", dependencies: ["LNPopupController"]),

Carthage

Add the following to your Cartfile:

github "LeoNatan/LNPopupController"

Make sure you follow the Carthage integration instructions here.

Manual

Drag the LNPopupController.xcodeproj project to your project, and add LNPopupController.framework to Embedded Binaries in your project target's General tab. Xcode should sort everything else on its own.

CocoaPods

CocoaPods is not supported. There are many reasons for this. Instead of CocoaPods, use Carthage. You can continue using CocoaPods for for your other dependencies and Carthage for LNPopupController.

Using the Framework

Swift

While the framework is written in Objective C, it uses modern Objective C syntax, so using the framework in Swift should be very easy and intuitive.

Project Integration

Import the module in your project:

import LNPopupController

Popup Items

A popup item should always reflect the popup information about the view controller with which it is associated. The popup item should provide a title and subtitles to display in the popup bar, when the view controller is presented as a popup content controller. In addition, the item may contain additional buttons to display on the leading and/or trailing edges of the popup bar using leadingBarButtonItems and trailingBarButtonItems.

Managing the Popup Bar

To present the popup bar, create a content controller, update its popup item and present the popup bar.

let demoVC = DemoPopupContentViewController()
demoVC.view.backgroundColor = .red
demoVC.popupItem.title = "Hello World"
demoVC.popupItem.subtitle = "And a subtitle!"
demoVC.popupItem.progress = 0.34
	
tabBarController?.presentPopupBar(withContentViewController: demoVC, animated: true, completion: nil)

You can present a new content controller while the popup bar is presented and when the popup itself is open.

To open and close the popup programatically, use openPopup(animated:completion:) and closePopup(animated:completion:) respectively.

tabBarController?.openPopup(animated: true, completion: nil)

Alternatively, you can present the popup bar and open the popup in one animation, using presentPopupBar(withContentViewController:openPopup:animated:completion:).

tabBarController?.presentPopupBar(withContentViewController: demoVC, openPopup:true, animated: true, completion: nil)

To dismiss the popup bar, use dismissPopupBarAnimated:completion:.

tabBarController?.dismissPopupBar(animated: true, completion: nil)

If the popup is open when dismissing the popup bar, the popup content will also be dismissed.

Popup Container View Controllers

Any UIViewController subclasses can be popup container view controllers. The popup bar is attached to a bottom docking view. By default, UITabBarController and UINavigationController subclasses return their bottom bars as docking view, while other controllers return a hidden 0pt height view on the bottom of the view. In your subclass, override bottomDockingViewForPopupBar and defaultFrameForBottomDockingView and return your view and frame accordingly. The returned view must be attached to the bottom of the view controller's view, or results are undefined.

override var bottomDockingViewForPopupBar: UIView? {
  return myCoolBottomView
}

override var defaultFrameForBottomDockingView: CGRect {
  var bottomViewFrame = myCoolBottomView.frame
  
  if isMyCoolBottomViewHidden {
    bottomViewFrame.origin = CGPoint(x: bottomViewFrame.x, y: view.bounds.height)
  } else {
    bottomViewFrame.origin = CGPoint(x: bottomViewFrame.x, y: view.bounds.height - bottomViewFrame.height)
  }
  
  return bottomViewFrame
}

Appearance and Behavior

Modern Look and Feel

LNPopupController provides two distinct style of popup look and feel, one based on modern Music app look and feel, and one based on the previous, iOS 9-style look and feel. Popup bar styles are arbitrarily labeled "prominent" for modern style popup bar and "compact" for iOS 9-style. Popup interaction styles are labeled "snap" for modern style snapping popups and "drag" for iOS 9 interactive popup interaction. Popup close buttons styles are labeled "chevron" for modern style chevron close button and "round" for iOS 9-style close buttons. For each, there is a "default" style for choosing the most suitable one for the current operating system version.

The defaults are:

  • Prominent bar style
  • Snap interaction style
  • Chevron close button style
  • No progress view style
Bar Style

Customizing the popup bar style is achieved by setting the popup bar's barStyle property.

navigationController?.popupBar.barStyle = .compact
Interaction Style

Customizing the popup interaction style is achieved by setting the popup presentation containing controller's popupInteractionStyle property.

navigationController?.popupInteractionStyle = .drag
Progress View Style

Customizing the popup bar progress view style is achieved by setting the popup bar's progressViewStyle property.

navigationController?.popupBar.progressViewStyle = .top

To hide the progress view, set the progressViewStyle property to LNPopupBarProgressViewStyle.none.

Close Button Style

Customizing the popup close button style is achieved by setting the popup content view's popupCloseButtonStyle property.

navigationController.popupContentView.popupCloseButtonStyle = .round

To hide the popup close button, set the popupCloseButtonStyle property to LNPopupCloseButtonStyle.none.

Popup Bar Appearance

For navigation and tab bar controller popup containers, the style of the popup bar is determined according to the bottom bar's appearance. For other container controllers, the style is the default style. For each style, title and button colors will be adjusted accordingly.

To update the popup bar appearance after updating the appearance of the bottom bar of the container controller, call the updatePopupBarAppearance() method.

Supplying long text for the title and/or subtitle will result in a scrolling text. Otherwise, the text will be centered.

The hidesBottomBarWhenPushed property is supported for navigation and tab bar controllers. When set, the popup bar will transition to the bottom of the container controller view. Setting isToolbarHidden = true and calling setToolbarHidden(_:animated:) are also supported.

Status bar management of the popup content view controller is respected and applied when appropriate.

Interaction Gesture Recognizer

LNPopupContentView exposes access to the popup interaction gesture recognizer in the way of the popupInteractionGestureRecognizer property. This gesture recognizer is shared between opening the popup content, by panning the popup bar up (when the popup bar is closed), and closing the popup content, by panning the popup content view (when the popup bar is open).

When opening the popup, the system queries the viewForPopupInteractionGestureRecognizer property of the popup content view controller to determine to which view to add the interaction gesture recognizer. By default, the property returns the controller's root view. Override the property's getter to change this behavior.

You can implement the delegate of the interaction gesture recognizer in order to influence its behavior, such as preventing popup interaction when the user is interacting with other controls or views inside the popup content.

Note: If you disable the gesture recognizer after opening the popup, you must monitor the state of the popup and reenable the gesture recognizer once closed by the user or through code. Instead, consider implementing the gesture recognizer's delegate and providing custom logic to disable the interaction.

Full Right-to-Left Support

The framework has full right-to-left support.

By default, the popup bar will follow the system's user interface layout direction, but will preserve the bar button items' order. To customize this behavior, modify the popup bar's semanticContentAttribute and barItemsSemanticContentAttribute properties.

Customization

Customization can be achieved through the LNPopupBar, LNPopupContentView and LNPopupCustomBarViewController classes.

Popup Bar Customization

LNPopupBar exposes API to customize the default popup bar's appearance, either through UIAppearance API or directly on popup bar objects.

let appearanceProxy = LNPopupBar.appearance(whenContainedInInstancesOf: [UINavigationController.self])
appearanceProxy.titleTextAttributes = [.font: UIFont(name: "Chalkduster", size: 14)!, .foregroundColor: UIColor.yellow]
appearanceProxy.subtitleTextAttributes = [.font: UIFont(name: "Chalkduster", size: 12)!, .foregroundColor: UIColor.green]
appearanceProxy.backgroundStyle = .systemChromeMaterialDark
appearanceProxy.tintColor = .yellow

Custom Popup Bar

The framework supports implementing custom popup bars.

To implement a custom popup bar, subclass LNPopupCustomBarViewController.

In your LNPopupCustomBarViewController subclass, build your popup bar's view hierarchy and set the controller's preferredContentSize property with the preferred popup bar height. Override the wantsDefaultTapGestureRecognizer and/or wantsDefaultPanGestureRecognizer properties to disable adding the default gesture recognizers.

In your subclass, implement the popupItemDidUpdate method to be notified of updates to the popup content view controller's item, or when a new popup content view controller is presented (with a new popup item). You must call the super implementation of this method.

Finally, set the customBarViewController property of the popup bar object to an instance of your LNPopupCustomBarViewController subclass. This will change the bar style to LNPopupBarStyle.custom.

The included demo project includes an example custom popup bar scene.

Accessibility

The framework supports accessibility and will honor accessibility labels, hints and values. By default, the accessibility label of the popup bar is the title and subtitle provided by the popup item.

To modify the accessibility label and hint of the popup bar, set the accessibilityLabel and accessibilityHint properties of the LNPopupItem object of the popup content view controller.

demoVC.popupItem.accessibilityLabel = NSLocalizedString("Custom popup bar accessibility label", comment: "")
demoVC.popupItem.accessibilityHint = NSLocalizedString("Custom popup bar accessibility hint", comment: "")

To add accessibility labels and hints to buttons, set the accessibilityLabel and accessibilityHint properties of the UIBarButtonItem objects.

let upNext = UIBarButtonItem(image: UIImage(named: "next"), style: .plain, target: self, action: #selector(nextItem))
upNext.accessibilityLabel = NSLocalizedString("Up Next", comment: "")
upNext.accessibilityHint = NSLocalizedString("Double tap to show up next list", comment: "")

To modify the accessibility label and hint of the popup close button, set the accessibilityLabel and accessibilityHint properties of the LNPopupCloseButton object of the popup container view controller.

tabBarController?.popupContentView.popupCloseButton.accessibilityLabel = NSLocalizedString("Custom popup close button accessibility label", comment: "")
tabBarController?.popupContentView.popupCloseButton.accessibilityHint = NSLocalizedString("Custom popup close button accessibility hint", comment: "")

To modify the accessibility label and value of the popup bar progress view, set the accessibilityProgressLabel and accessibilityProgressValue properties of the LNPopupItem object of the popup content view controller.

demoVC.popupItem.accessibilityImageLabel = NSLocalizedString("Custom image label", comment: "")
demoVC.popupItem.accessibilityProgressLabel = NSLocalizedString("Custom accessibility progress label", comment: "")
demoVC.popupItem.accessibilityProgressValue = "\(accessibilityDateComponentsFormatter.stringFromTimeInterval(NSTimeInterval(popupItem.progress) * totalTime)!) \(NSLocalizedString("of", comment: "")) \(accessibilityDateComponentsFormatter.stringFromTimeInterval(totalTime)!)"

Notes

  • Non-translucent bars are not supported and can cause visual artifacts or layout glitches. Apple has many problem with such bars themselves, and supporting those is not a priority for LNPopupController.
    • Instead, either use translucent bars, set a background color to your bar instead of setting it as not translucent or set extendedLayoutIncludesOpaqueBars to true for contained controllers
  • Manually hiding tab bars is not supported by the framework or by Apple. Do not hide the tab bar using tabBar.hidden = YES.

Acknowledgements

The framework uses:

Additionally, the demo project uses:

Github

link
Stars: 2654

Related Packages

Releases

v2.10.27 - 2021-01-10T10:08:37

Improve bar tint color handling for iOS 13 and above

v2.10.26 - 2020-12-09T03:35:40

Correctly link popupContentViewController and popupPresentationContainerViewController for custom popup bar controllers

v2.10.25 - 2020-11-23T22:44:41

Better state management

v2.10.24 - 2020-11-05T18:38:27

Forward popup bar interactions to the content view

v2.10.23 - 2020-10-31T10:26:51

Support Mac Catalyst on macOS Big Sur

v2.10.22 - 2020-10-28T08:30:28

Mac Catalyst improvements:

  • The default styles have changed somewhat for Mac Catalyst, so make the system more Mac-like:

    • Default close button is .round instead of .chevron
    • Default interaction style is .scroll instead of .snap
  • Mouse scroll events are ignored now, and only pan/swipe gesture is counted for popup interaction

  • The visual style of the bar background is changed somewhat

  • The .none interaction style is better handled

v2.10.21 - 2020-10-26T12:02:58

Layout improvements

v2.10.20 - 2020-10-10T20:41:12

Updates to effect grouping logic

v2.10.19 - 2020-10-04T21:13:01

  • Rework how additional safe area insets are calculated for container controllers
  • Improve toolbar hiding transition

v2.10.18 - 2020-10-04T17:39:52

  • Rework how additional safe area insets are calculated for container controllers
  • Improve toolbar hiding transition

v2.10.17 - 2020-10-02T22:43:00

Improved Mac Catalyst support and layout

v2.10.16 - 2020-10-02T21:51:56

Layout improvements

v2.10.15 - 2020-09-27T22:31:11

Visual effect views of the popup bar, bar extension view and popup content view are now grouped for a consistent blur effect. If the popup bottom bar supports effect grouping (UITabBar, UIToolbar), aforementioned effect views are synchronized with the popup bottom bar as well.

v2.10.14 - 2020-09-27T17:43:04

Visual effect views of the popup bar, bar extension view and popup content view are now grouped for a consistent blur effect. If the popup bottom bar supports effect grouping (UITabBar, UIToolbar), aforementioned effect views are synchronized with the popup bottom bar as well.

v2.10.13 - 2020-09-25T22:34:50

Fixed a memory leak in UIKit.

v2.10.12 - 2020-09-16T19:53:58

Fixed a tab bar layout issue on iOS 12.x.

v2.10.11 - 2020-09-11T19:14:29

Improved interaction with UIHostingController popup content controllers.

v2.10.10 - 2020-09-10T23:45:01

  • Layout improvements (thanks @iDevelopper)

v2.10.9 - 2020-09-05T22:54:16

  • Use the shadow color from the bottom bar appearance, if possible (using iOS 13's UIToolbarAppearance)
  • Improve safe area management of content controllers
  • Improve popup close button positioning

v2.10.8 - 2020-09-03T19:43:29

Fixed a crash introduced by a previous release.

v2.10.7 - 2020-09-02T20:32:27

  • More custom bar layout improvements
  • Add the ability to allow popup content controllers to embed the popup close button inside their hierarchies (for example, to support vibrancy)

v2.10.6 - 2020-09-01T18:26:49

Improved custom bar controller layout management. The system now takes into account the translatesAutoresizingMaskIntoConstraints property of the controller's view when laying out the popup bar.

v2.10.5 - 2020-08-25T22:33:49

Fixed a crash on operating systems lower than 13.5.

v2.10.4 - 2020-08-22T18:20:57

  • Support SwiftUI image controllers for use with the SwiftUI-native Image type

v2.10.3 - 2020-08-22T18:20:24

  • Additional layout improvements, fixing issues with autolayout in popup content views
  • SwiftUI interaction improvements

v2.10.2 - 2020-08-22T18:19:21

  • Improved the drag interaction style handling
  • Layout improvements of the content view and title views in the bar
  • Popup close buttons can be configured by setting the tintColor.

v2.10.1 - 2020-08-18T18:48:59

Fixed a layout issue that could cause constraint issues when presenting a popup content controller.

v2.10.0 - 2020-08-16T22:19:46

LNPopupController now finally supports extending the popup bar background under the bottom safe area (to the bottom of the screen). A cool transition animation is provided when using hidesBottomBarWhenPushed with a navigation or a tab bar controller.

The new behavior is enabled by default, and is supported on iOS 11.0 and above. In some cases involving split view controllers, iOS 11 and 12 may display issues. If this is the case, disable bar extension for your scenario.

To disable popup bar extension, set shouldExtendPopupBarUnderSafeArea = false

v2.9.6 - 2020-08-14T19:36:45

  • Improved popup bar layout in certain cases

v2.9.5 - 2020-08-10T20:56:01

  • Fixes to safe area inset management with child view controllers
  • Progress view style is set to .bar now by default