DDDialog is a SwiftUI library for presenting a dialog
- Flexible on set a root point on how dialog to be presented
- Custom animation and transition for each of dialog
- Dismiss by using environment or binding itself
- Support 3 placements: top, center and bottom
Platform | Minimum Swift Version | Installation |
---|---|---|
iOS 14.0+ | 5.0.0 / Xcode 14.1 | CocoaPods, Swift Package Manager |
The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift
compiler.
Once you have your Swift package set up, adding DDDialog as a dependency is as easy as adding it to the dependencies
value of your Package.swift
or the Package list in Xcode.
dependencies: [
.package(url: "https://github.com/duyduong/dddialog.git", .upToNextMajor(from: "1.0.0"))
]
Normally you'll want to depend on the DDDialog
target:
.product(name: "DDDialog", package: "DDDialog")
CocoaPods is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate DDDialog into your Xcode project using CocoaPods, specify it in your Podfile
:
pod 'SwiftUIDialog'
DDDialog is simple to use, just need one more setup setup step then it is ready to use
To start making DDDialog to work, we need to setup a root point for the dialog to be presented. A root point can be in:
- NavigationView
- TabView
- or the view itself
Basically we call setupDialogs
in the view that we want the dialog to be covered. The root point can be overriden, the nearest calls setup root point will be used.
For example:
- If we want the dialog to cover the navigation bar, call setup root point in NavigationView (See example project)
NavigationView {
...
}
.setupDialogs()
- If we want the dialog to cover the tab bar, call setup root point in TabView (See example project)
TabView(selection: $selection) {
...
}
.setupDialogs()
DDDialog provides a Dialog
protocol to let us present different dialog type in the same view
public protocol Dialog: Hashable {
var id: String { get }
/// Dialog placement `top`, `center` or `bottom`
var placement: DialogPlacement { get }
/// Dialog overlay (dim background), supports mono color or `UIBlurEffect.Style`
var overlay: DialogOverlay { get }
/// Whether allow to tap outside (dim background) to dismiss the dialog
var shouldDismissOnTapOutside: Bool { get }
/// Custom dialog animation
var animation: Animation? { get }
/// Custom dialog transition
var transition: AnyTransition? { get }
}
The protocol contains neccessary properties to help use define the behavor of a dialog. We just need to define a dialog type to conform Dialog
protocol, for example
enum MyDialog: Dialog {
case top(message: String)
case center(message: String)
case bottom(message: String)
var placement: DialogPlacement {
switch self {
case .top: return .top
case .center: return .center
case .bottom: return .bottom
}
}
var shouldDismissOnTapOutside: Bool {
false
}
var overlay: DialogOverlay {
switch self {
case .center: return .effect(.dark)
default: return .color(.black.opacity(0.5))
}
}
}
Then in view, we can use this modifier to present the dialog
func dialog<Item: Dialog, Content: View>(
item: Binding<Item?>,
@ViewBuilder content: @escaping (Item) -> Content
)
Below is the example for using the Dialog
protocol
struct ExampleView: View {
enum MyDialog: Dialog {
case top(message: String)
case center(message: String)
case bottom(message: String)
var placement: DialogPlacement {
switch self {
case .top: return .top
case .center: return .center
case .bottom: return .bottom
}
}
var shouldDismissOnTapOutside: Bool {
false
}
var overlay: DialogOverlay {
switch self {
case .center: return .effect(.dark)
default: return .color(.black.opacity(0.5))
}
}
}
var navigationBarColor: Color = .blue
@State private var dialog: MyDialog?
var body: some View {
if #available(iOS 16, *) {
contentView
.toolbarBackground(navigationBarColor, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
} else {
contentView
}
}
var contentView: some View {
VStack(spacing: 16) {
Button {
dialog = .top(message: "Example top dialog is showing")
} label: {
Text("Show top dialog")
}
Button {
dialog = .center(message: "Example center dialog is showing")
} label: {
Text("Show center dialog")
}
Button {
dialog = .bottom(message: "Example bottom dialog is showing")
} label: {
Text("Show bottom dialog")
}
}
.dialog(item: $dialog) { // Binding the dialog item for building dialog view
switch $0 {
case let .top(message): TopDialogView(message: message)
case let .center(message): CenterDialogView(message: message)
case let .bottom(message): BottomDialogView(message: message)
}
}
}
}
Same as Dialog
protocol, but using Bool
binding is the useful way to just present one dialog, as define using below modifier
func dialog<Content: View>(
isPresented: Binding<Bool>,
placement: DialogPlacement = .bottom,
overlay: DialogOverlay = .color(.black.opacity(0.4)),
shouldDismissOnTapOutside: Bool = true,
animation: Animation? = nil,
transition: AnyTransition? = nil,
@ViewBuilder content: @escaping () -> Content
)
It supports all the properties same Dialog
protocol, below the the example for using Bool
binding
struct ExampleView: View {
var navigationBarColor: Color = .blue
@State private var isShowingDialog = false
var body: some View {
if #available(iOS 16, *) {
contentView
.toolbarBackground(navigationBarColor, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
} else {
contentView
}
}
var contentView: some View {
VStack(spacing: 16) {
Button {
isShowingDialog = true
} label: {
Text("Show center dialog")
}
}
.dialog(isPresented: $isShowingDialog) { // Binding the flag for building the dialog view
CenterDialogView(message: "Example bottom dialog is showing")
}
}
}
It is very easy to dismiss a dialog
- Dismiss from the place we presented it: just set the state
dialog
to nil or turn off the bool flagisShowingDialog = false
- Dismiss the dialog from the dialog itself: DDDialog provides an environment called
dialogPresenter
, we can use it to dismiss. For example
struct ExampleDialogView: View {
@Environment(\.dialogPresenter) private var dialogPresenter
var body: some View {
Button {
dialogPresenter.dismiss()
// or dismiss with a completion
/* dialogPresenter.dismiss {
// Handle completion after the animation is finished
} */
} label: {
Text("Dismiss")
}
}
}
To see example project, just open DDDialog.xcworkspace
DDDialog is released under the MIT license. See LICENSE for details.