LNPopupController 是一个框架,用于将视图控制器作为其他视图控制器的弹出窗口呈现,类似于 Apple Music 和 Podcasts 的迷你播放器。
对于 SwiftUI,请查看 LNPopupUI 库。
一旦使用内容视图控制器呈现了弹出栏,用户可以随时滑动或点击弹出栏来呈现弹出窗口。 完成后,用户可以通过滑动或点击弹出窗口关闭按钮来关闭弹出窗口。
该框架旨在具有很强的通用性,并在大多数情况下工作,因此它被实现为 UIViewController
的一个类别。 每个视图控制器都可以呈现一个弹出栏,停靠在底部视图上。 对于 UITabBarController
子类,默认的停靠视图是标签栏。 对于 UINavigationController
子类,默认的停靠视图是工具栏。 对于其他类,弹出栏显示在屏幕底部。 视图控制器子类可以提供它们自己的停靠视图。
该框架正确地维护了容器控制器的视图及其子控制器的安全区域插图,因为弹出栏被呈现和关闭。
显示在弹出栏上的信息是通过弹出项目对象(LNPopupItem
类的实例)动态提供的,这些对象与弹出内容视图控制器相关联。 要更改此信息,请更新视图控制器的弹出项目。
通常,建议在最外层的容器控制器上呈现弹出栏。 因此,如果您的视图控制器包含在导航控制器中,导航控制器又包含在标签栏控制器中,则建议在标签栏控制器上呈现弹出栏。
查看演示项目,了解该框架在各种场景中的许多常见用例。 它包含 Swift 和 Objective C 中的示例。
Swift 包管理器是将 LNPopupController
集成到您的项目中的推荐方法。
LNPopupController
支持 SPM 5.5 及以上版本。 要使用 SPM,您应该使用 Xcode 13 打开您的项目。 点击 File
-> Swift Packages
-> Add Package Dependency
,输入 https://github.com/LeoNatan/LNPopupController
。 选择您想要使用的版本。
您也可以手动将包添加到您的 Package.swift 文件中
.package(url: "https://github.com/LeoNatan/LNPopupController.git", from: "2.15.0")
以及目标中的依赖项
.target(name: "MyExampleApp", dependencies: ["LNPopupController"]),
将以下内容添加到您的 Cartfile 中
github "LeoNatan/LNPopupController"
请确保您遵循 此处 的 Carthage 集成说明。
将 LNPopupController.xcodeproj
项目拖到您的项目中,并将 LNPopupController.framework
添加到您的项目目标的 General 选项卡中的 Embedded Binaries 中。 Xcode 应该会自行对其他所有内容进行排序。
不支持 CocoaPods。 这有很多原因。 请使用 Xcode 中的 Swift 包管理器来代替 CocoaPods。 您可以继续使用 CocoaPods 处理其他依赖项,并使用 Swift 包管理器处理 LNPopupController
。
虽然该框架是用 Objective C 编写的,但它使用了现代 Objective C 语法,因此在 Swift 中使用该框架非常容易和直观。
在您的项目中导入该模块
import LNPopupController
弹出项目应始终反映与其关联的视图控制器的弹出信息。 当视图控制器作为弹出内容控制器呈现时,弹出项目应提供标题和副标题以在弹出栏中显示。 此外,该项目可能包含其他按钮,用于使用 leadingBarButtonItems
和 trailingBarButtonItems
在弹出栏的前缘和/或后缘上显示。
要呈现弹出栏,请创建一个内容控制器,更新其弹出项目,并使用 presentPopupBar(with:animated:completion:)
呈现弹出栏。
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(with: demoVC, animated: true, completion: nil)
您可以在呈现弹出栏时以及弹出窗口本身打开时呈现新的内容控制器。
要以编程方式打开和关闭弹出窗口,请分别使用 openPopup(animated:completion:)
和 closePopup(animated:completion:)
。
tabBarController?.openPopup(animated: true, completion: nil)
或者,您可以使用 presentPopupBar(with:openPopup:animated:completion:)
在一个动画中呈现弹出栏并打开弹出窗口。
tabBarController?.presentPopupBar(with: demoVC, openPopup:true, animated: true, completion: nil)
要关闭弹出栏,请使用 dismissPopupBar(animated:completion:)
。
tabBarController?.dismissPopupBar(animated: true, completion: nil)
如果在关闭弹出栏时弹出窗口已打开,则也会关闭弹出内容。
任何 UIViewController
子类都可以是弹出容器视图控制器。 弹出栏附加到底部停靠视图。 默认情况下,UITabBarController
和 UINavigationController
子类将其底部栏返回为停靠视图,而其他控制器则返回视图底部的一个隐藏的 0pt 高度视图。 在您的子类中,重写 bottomDockingViewForPopupBar
和 defaultFrameForBottomDockingView
并相应地返回您的视图和框架。 返回的视图必须附加到视图控制器的视图底部,否则结果是不确定的。
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
}
LNPopupController
提供了三种不同样式的弹出窗口外观,每种样式都基于 Apple 多年来推出的 Music 应用程序的外观。 弹出栏样式被标记为“floating”(浮动)、“prominent”(突出)和“compact”(紧凑),与相应的 Apple 样式相匹配。 弹出交互样式被标记为“snap”(吸附),用于现代样式的吸附弹出窗口,以及“drag”(拖动),用于 iOS 9 的交互式弹出交互。 弹出窗口关闭按钮样式被标记为“chevron”(chevron 箭头),用于现代样式的 chevron 箭头关闭按钮,以及“round”(圆形),用于 iOS 9 样式的关闭按钮。 对于每个样式,都有一个“default”(默认)样式,用于为当前平台和操作系统版本选择最合适的样式。
默认设置是
iOS 17
浮动栏样式
吸附交互样式
抓取器关闭按钮样式
无进度视图样式
iOS 16 及以下
吸附交互样式
Chevron 箭头关闭按钮样式
无进度视图样式
您还可以呈现完全自定义的弹出栏。 有关更多信息,请参见 自定义弹出栏。
默认情况下,对于导航栏和标签栏容器控制器,弹出栏的外观根据底部栏的外观确定。 对于其他容器控制器,使用最适合当前环境的默认外观。
要禁用继承底部栏的外观,请将 inheritsAppearanceFromDockingView
属性设置为 false
。
通过设置弹出栏的 barStyle
属性来实现自定义弹出栏样式。
navigationController?.popupBar.barStyle = .compact
通过设置弹出窗口呈现容器控制器的 popupInteractionStyle
属性来实现自定义弹出窗口交互样式。
navigationController?.popupInteractionStyle = .drag
通过设置弹出栏的 progressViewStyle
属性来实现自定义弹出栏进度视图样式。
navigationController?.popupBar.progressViewStyle = .top
要隐藏进度视图,请将 progressViewStyle
属性设置为 LNPopupBar.ProgressViewStyle.none
。
通过设置弹出内容视图的 popupCloseButtonStyle
属性来实现自定义弹出窗口关闭按钮样式。
navigationController.popupContentView.popupCloseButtonStyle = .round
要隐藏弹出窗口关闭按钮,请将 popupCloseButtonStyle
属性设置为 LNPopupCloseButton.Style.none
。
如果启用了文本跑马灯,为标题和/或副标题提供长文本将导致文本滚动。 否则,文本将被截断。
LNPopupBar
公开了许多 API 来自定义默认弹出栏的外观。 使用 LNPopupBarAppearance
对象来定义栏的标准外观。
请记住将 inheritsAppearanceFromDockingView
属性设置为 false
,否则您的某些自定义设置可能会被底部栏的外观覆盖。
let appearance = LNPopupBarAppearance()
appearance.titleTextAttributes = AttributeContainer()
.font(UIFontMetrics(forTextStyle: .headline).scaledFont(for: UIFont(name: "Chalkduster", size: 14)!))
.foregroundColor(UIColor.yellow)
appearance.subtitleTextAttributes = AttributeContainer()
.font(UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: UIFont(name: "Chalkduster", size: 12)!))
.foregroundColor(UIColor.green)
let floatingBarBackgroundShadow = NSShadow()
floatingBarBackgroundShadow.shadowColor = UIColor.red
floatingBarBackgroundShadow.shadowOffset = .zero
floatingBarBackgroundShadow.shadowBlurRadius = 8.0
appearance.floatingBarBackgroundShadow = floatingBarBackgroundShadow
let imageShadow = NSShadow()
imageShadow.shadowColor = UIColor.yellow
imageShadow.shadowOffset = .zero
imageShadow.shadowBlurRadius = 3.0
appearance.imageShadow = imageShadow
if navigationController?.popupBar.barStyle == .floating {
appearance.floatingBackgroundEffect = UIBlurEffect(style: .dark)
} else {
appearance.backgroundEffect = UIBlurEffect(style: .dark)
navigationController?.popupBar.inheritsAppearanceFromDockingView = false
}
navigationController?.popupBar.standardAppearance = appearance
navigationController?.popupBar.tintColor = .yellow
导航栏和标签栏控制器支持 hidesBottomBarWhenPushed
属性。 当设置为 true
时,弹出栏将过渡到推送控制器的视图底部。 也支持设置 isToolbarHidden = true
并调用 setToolbarHidden(_:animated:)
。
从 iPadOS 18 开始,该框架支持 UITabBarController
侧边栏。 当侧边栏替换基础内容时,弹出栏会移开。
当侧边栏覆盖基础内容时,弹出栏会与内容一起变暗
弹出内容视图控制器的状态栏管理受到尊重并在适当时应用。
主页指示器可见性控制受到尊重并在适当时应用。
支持上下文菜单。 将 UIContextMenuInteraction
交互对象添加到弹出栏,它将按预期运行。
支持指针交互,并且为系统栏样式提供了默认实现。
对于自定义弹出栏控制器,LNPopupCustomBarViewController
类实现了 UIPointerInteractionDelegate
协议。 在您的子类中实现该协议的方法,以实现自定义指针交互。
从 iOS 15 开始,无论内容滚动位置如何,当呈现弹出栏时,工具栏和标签栏的滚动边缘外观都会自动禁用。 关闭弹出栏后,滚动边缘外观将恢复。
该框架支持实现自定义弹出栏。
要实现自定义弹出栏,您可以继承 LNPopupCustomBarViewController
。
在您的 LNPopupCustomBarViewController
子类中,构建弹出栏的视图层次结构,并使用首选的弹出栏高度设置控制器的 preferredContentSize
属性。 重写任何 wantsDefaultTapGestureRecognizer
、wantsDefaultPanGestureRecognizer
和/或 wantsDefaultHighlightGestureRecognizer
属性,以禁用自定义弹出栏中的默认手势识别器功能。
在您的子类中,实现 popupItemDidUpdate()
方法以接收有关弹出内容视图控制器的项目更新的通知,或者当呈现新的弹出内容视图控制器时(带有新的弹出项目)。 您必须调用此方法的 super
实现。
最后,将弹出栏对象的 customBarViewController
属性设置为您的 LNPopupCustomBarViewController
子类的实例。 这将自动将栏样式更改为 LNPopupBar.Style.custom
。
包含的演示项目包括两个示例自定义弹出栏场景。
提示
只有当您需要与提供的标准弹出栏样式显著不同的设计时,才需要实现自定义弹出栏。 我们已经投入了大量的精力和心思,将这些弹出栏样式与 UIKit 系统集成,包括外观、感觉、过渡和交互。 自定义栏为您提供了一个空白的画布来实现您自己的栏,但如果您最终重新创建了一个与标准栏样式相似的栏设计,您很可能会失去多年来在标准实现中添加和完善的细微之处。 相反,请考虑使用许多自定义 API来调整标准栏样式以适应您的应用程序的设计。
LNPopupController
完全支持 iPhone 和 iPad 上的 ProMotion。
对于 iPhone 13 Pro 及更高版本,您需要将 CADisableMinimumFrameDurationOnPhone
键添加到您的 Info.plist 文件中,并将其设置为 true
。 有关更多信息,请参阅优化 iPhone 13 Pro 和 iPad Pro 的 ProMotion 刷新率。 如果缺少此键或设置为 false
,LNPopupController
将在控制台中记录一条警告消息。
LNPopupContentView
通过 popupInteractionGestureRecognizer
属性公开对弹出窗口交互手势识别器的访问。 此手势识别器用于通过向上滑动或平移弹出栏(当弹出窗口关闭时)来打开弹出窗口,以及通过向下滑动或平移弹出内容视图(当弹出栏打开时)来关闭弹出窗口。
打开弹出窗口时,系统会查询弹出内容视图控制器的 viewForPopupInteractionGestureRecognizer
属性,以确定将交互手势识别器添加到哪个视图。 默认情况下,该属性返回内容控制器的根视图。 覆盖该属性的 getter 以更改此行为。
系统会尽最大努力与其他手势(包括系统手势、控件和滚动)配合。 当用户在弹出内容视图层次结构中滚动时,系统将尽最大努力不干扰用户的手势,并且仅在滚动内容的边缘做出反应。
对于垂直滚动内容,只有当用户滑动或拖动超过滚动内容的边缘时,弹出窗口才会关闭。
对于水平滚动内容,只有当用户向下滑动或拖动且没有水平滚动时,弹出窗口才会关闭。
对于双向滚动内容,仅支持 isDirectionalLockEnabled = true
。 在这种情况下,如果满足以上两个条件,弹出窗口将关闭。
对于双向滚动内容,系统不会尝试在任何时候关闭弹出窗口。 用户仍然可以通过点击关闭按钮或在可滚动区域外滑动或拖动来关闭弹出窗口。
您可以实现交互手势识别器的委托,以影响其行为,例如当用户与弹出内容视图层次结构中的其他控件或视图交互时,阻止弹出窗口交互。
注意
如果在打开弹出窗口后禁用手势识别器,则必须监视弹出窗口的状态,并在用户关闭或通过代码关闭后重新启用手势识别器。 相反,请考虑实现手势识别器的委托并提供自定义逻辑以禁用交互。
该框架具有完整的从右到左支持。
默认情况下,弹出栏将遵循系统的用户界面布局方向,但会保留栏按钮项的顺序。 要自定义此行为,请修改弹出栏的 semanticContentAttribute
和 barItemsSemanticContentAttribute
属性。
该框架支持辅助功能,并将遵循辅助功能标签、提示和值。 默认情况下,弹出栏的辅助功能标签是弹出项提供的标题和副标题。
要修改弹出栏的辅助功能标签和提示,请设置弹出内容视图控制器的 LNPopupItem
对象的 accessibilityLabel
和 accessibilityHint
属性。
demoVC.popupItem.accessibilityLabel = NSLocalizedString("Custom popup bar accessibility label", comment: "")
demoVC.popupItem.accessibilityHint = NSLocalizedString("Custom popup bar accessibility hint", comment: "")
要向按钮添加辅助功能标签和提示,请设置 UIBarButtonItem
对象的 accessibilityLabel
和 accessibilityHint
属性。
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: "")
要修改弹出窗口关闭按钮的辅助功能标签和提示,请设置弹出窗口容器视图控制器的 LNPopupCloseButton
对象的 accessibilityLabel
和 accessibilityHint
属性。
tabBarController?.popupContentView.popupCloseButton.accessibilityLabel = NSLocalizedString("Custom popup close button accessibility label", comment: "")
tabBarController?.popupContentView.popupCloseButton.accessibilityHint = NSLocalizedString("Custom popup close button accessibility hint", comment: "")
要修改弹出栏进度视图的辅助功能标签和值,请设置弹出内容视图控制器的 LNPopupItem
对象的 accessibilityProgressLabel
和 accessibilityProgressValue
属性。
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)!)"
LNPopupController
的首要任务。UIBarAppearance.configureWithOpaqueBackground()
API,LNPopupController
支持该 API。isHidden = true
Apple 明确不鼓励这样做,并且框架不支持这样做; 它会导致框架出现未定义的行为。该框架使用
此外,演示项目使用