SwiftUI Overlay Container[1] 是一个用于 SwiftUI 的视图容器组件。一个可定制、高效、便捷的视图管理器。
仅需简单配置,SwiftUI Overlay Container 即可帮你完成从视图组织、队列处理、转场、动画、交互到显示样式配置等基础工作,让开发者可以将精力更多地投入到应用程序视图的实现本身。
2020 年夏天,在为 健康笔记[2] 添加侧向滑动菜单的过程中,我发现在开发中经常会碰到需要在一个视图的上方动态添加另一视图的场景,例如(提示信息、广告、浮动按钮、新手指南等等)。因此,我写了一个组件希望可以帮助开发者在 SwiftUI 中快速完成上述需求。但受限于当时的技术能力,很多的想法都没有能够很好地实现。
近期我重写了该组件,除了实现了以前尚未支持的功能外,更重要的是,以此检验一下自己这段时间的能力是否有所提高。
大家可以从 这里[3] 获取最新的版本。
本文的内容直接搬运自项目的 README 文档。
当我们需要在视图的上层显示新的内容(例如:弹出信息、侧边菜单、帮助提示等)时,有很多优秀的第三方解决方案可以帮助我们分别实现,但没有一个方案可以同时应对不同的场景需求。在 SwiftUI 中,描述视图已经变得十分的容易,因此我们完全可以将上述场景中的显示逻辑提炼出来,创建出一个可以覆盖更多使用场景的库,帮助开发者组织视图的显示风格和交互逻辑。
更详细的信息,可以参看库中的演示以及源代码中的注释。
在指定视图上层创建一个视图容器,此容器的尺寸同其附着的视图尺寸一致:
VStack{
// your view
}
.overlayContainer("containerA", containerConfiguration: AConfiguration())
当无需视图容器附着在某个视图时:
ViewContainer("containerB", configuration: BConfiguration())
在视图容器 containerA 显示视图 MessageView
.containerView(in: "containerA", configuration: MessageView(), isPresented: $show, content: ViewConfiguration())
或者使用视图管理器
struct ContentView1: View {
@Environment(\.overlayContainerManager) var manager
var body: some View {
VStack {
Button("push view in containerB") {
manager.show(view: MessageView(), in: "containerB", using: ViewConfiguration())
}
}
}
}
struct ContentView1: View {
@Environment(\.overlayContainerManager) var manager
var body: some View {
VStack {
Button("push view in containerB") {
manager.dismissAllView(in: ["containerA","containerB"], animated: true)
}
}
}
}
接收并显示视图的组件。至少需要为容器设定:名称、视图显示类型、视图队列类型。
可以为容器设定默认的视图风格,对于视图未指定的风格属性,会使用容器的默认设置替代。
ZStack
类似。
stacking
HStack
类似。
horizontal
VStack
类似。
vertical
multiple
oneByOne
oneByOneWaitFinish
容器的配置至少要对以下属性进行设置:
struct MyContainerConfiguration:ContainerConfigurationProtocol{
var displayType: ContainerViewDisplayType = .stacking
var queueType: ContainerViewQueueType = .multiple
}
其他可以设置的属性还有:
每个容器都为容器内的视图提供了一个环境值—— overlayContainer
。容器内的视图可以通过该值获取容器的信息(名称、尺寸、显示类型、队列类型)并执行撤销显示的行为。
struct MessageView: View {
@Environment(\.overlayContainer) var container
var body: some View {
RoundedRectangle(cornerRadius: 10)
.frame(width: 300, height: 10)
.overlay(
HStack {
Text("container Name:\(container.containerName)")
Button("Dismiss me"){
container.dismiss()
}
}
)
}
}
所有的 SwiftUI 视图都可以在容器内显示。你可以为类似功能的视图创建同一个视图配置,或者让某个特定视图遵循 ContainerViewConfigurationProtocol 协议,单独进行设置。
public protocol ContainerViewConfigurationProtocol {
var alignment: Alignment? { get }
var tapToDismiss: Bool? { get }
var backgroundStyle: ContainerBackgroundStyle? { get }
var backgroundTransitionStyle: ContainerBackgroundTransitionStyle { get }
var shadowStyle: ContainerViewShadowStyle? { get }
var dismissGesture: ContainerViewDismissGesture? { get }
var transition: AnyTransition? { get }
var autoDismiss: ContainerViewAutoDismiss? { get }
var disappearAction: (() -> Void)? { get }
var appearAction: (() -> Void)? { get }
var animation: Animation? { get }
}
background
eraseToAnyGestureForDismiss
对类型进行擦除。 let gesture = LongPressGesture(minimumDuration: 1, maximumDistance: 5).eraseToAnyGestureForDismiss()
在 tvOS 下,仅长按被支持
详情参看项目演示代码
gesture
.seconds(3)
表示 3 秒后视图会自动撤销。
详情参看项目演示代码容器管理器是程序代码与容器之间的桥梁。使用者通过调用容器管理器的特定方法,让指定的容器执行显示视图、撤销视图等工作。
在 SwiftUI 中,视图代码通过环境值调用容器管理器。
struct ContentView1: View {
@Environment(\.overlayContainerManager) var manager
var body: some View {
VStack {
Button("push view in containerB") {
manager.show(view: MessageView(), in: "containerB", using: ViewConfiguration())
}
}
}
}
容器管理器目前提供的方法有:
无论是直接调用容器管理器还是使用 View modifier,当将 animated 设为 false 时,均可强制取消转场动画。
对于处理例如 Deep Link 之类的场景时十分有效。
animation
如果想在 SwiftUI 视图之外调用容器管理器,可以直接调用 ContainerManager 的单例:
let manager = ContainerManager.share
manager.show(view: MessageView(), in: "containerB", using: ViewConfiguration())
安装 SwiftUIOverlayContainer 的首选方式是通过 Swift Package Manager。
dependencies: [
.package(url: "https://github.com/fatbobman/SwiftUIOverlayContainer.git", from: "2.0.0")
]
这个库是在 MIT 许可下发布的。详见 LICENSE[4]。
可以通过创建 Issues 来反馈你的意见或建议。也可以通过 Twitter @fatbobman[5] 与我联络。
[1] SwiftUI Overlay Container: https://github.com/fatbobman/SwiftUIOverlayContainer
[2] 健康笔记: https://www.fatbobman.com/healthnotes/
[5] @fatbobman: https://twitter.com/fatbobman