The SDK simplifies the integration of common dialog states (Error, Loading, Success) in iOS applications. Only one state can be displayed at a time, ensuring a sequential and consistent behavior.
Built with SwiftUI, it supports both UIKit and SwiftUI environments, making it suitable for modern app development.
- Separate System Window: Dialogs are displayed in a dedicated system window, ensuring they overlay the entire app interface without leaving interactive elements (e.g., Tab Bars) active. Inspired by fivestars.blog:
- Customizable Views: Replace default views (
DMErrorView,DMProgressView,DMSuccessView) with custom implementations via theDMLoadingViewProviderprotocol (by usingdependency invertionpronciple). - Settings Configuration: Fine-tune the appearance of built-in views using settings like text properties, colors, and layout.
- Retry & Fallback Logic: Incorporates robust action handling using retry and fallback mechanisms. For more information, refer to DMAction documentation or DMAction github page.
- Dynamic Blur Effects: Integrates with the
DMVariableBlurViewlibrary to apply dynamic blur effects.
- π¬ Demo: Loading, Success & Error States
- π§ Very High-Level Architecture in Picture
- π₯ Installation
- π Usage
- π¨ Customization
- π§ͺ Example Project
- π Advanced Features
- π§© Implementation Details
- π§ Future Enhancements
- π Acknowledgments
- π License
Watch how DMUnLoader behaves in different states with default (left) and custom (right) settings:
- Loading State:
- Success State:
- Error State:
click on the image to view it in full screen mode
To integrate DMUnLoader using Swift Package Manager, add the following dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/nikolay-dementiev/DMUnLoader.git", from: "1.0.0")
]To integrate DMUnLoader using CocoaPods, add the following line to your Podfile:
pod 'DMUnLoader', :git => 'https://github.com/nikolay-dementiev/DMUnLoader.git'Here is an pseudocode example of how to use DMUnLoader in a SwiftUI project (for real code example, please check the Example Project):
import SwiftUI
import DMUnLoader
@main
struct DMUnLoaderPodSPMExampleApp: App {
@UIApplicationDelegateAdaptor private var delegate: DMAppDelegateType
var body: some Scene {
WindowGroup {
DMRootLoadingView { loadingManager in // DMRootLoadingView - handles `sceneDelegate` by itself
LoadingContentViewSwiftUI(
loadingManager: loadingManager,
provider: DefaultDMLoadingViewProvider() // OR CustomDMLoadingViewProvider() --> need to be implemented by the client! See `DMUnLoaderPodSPMExample` for details
)
}
}
}
}
struct LoadingContentViewSwiftUI<Provider: DMLoadingViewProvider,
LM: DMLoadingManager>: View {
var loadingManager: LM
var provider: Provider
var body: some View {
VStack {
Button("Simulate Loading", action: showDownloads)
.buttonStyle(.dmBorderedCorner)
Button("Simulate Error", action: simulateAnError)
.buttonStyle(.dmBorderedCorner)
Button("Simulate Success", action: simulateSuccess)
.buttonStyle(.dmBorderedCorner)
}
}
}For UIKit projects, here is an pseudocode example of how to use DMUnLoader (for real code example, please check the Example Project):
import UIKit
import DMUnLoader
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: "Default Configuration",
sessionRole: connectingSceneSession.role)
configuration.delegateClass = DMSceneDelegateTypeUIKit<AppDelegateHelper>.self
return configuration
}
}
extension AppDelegateHelper: DMSceneDelegateHelper {
static func makeUIKitRootViewHierarhy<LM: DMLoadingManager>(loadingManager: LM) -> UIViewController {
DefaultSettingsViewController(
loadingManager: loadingManager
)
}
}
final class DefaultSettingsViewController<LM: DMLoadingManager>: UIViewController {
private(set) weak var loadingManager: LM?
init(loadingManager: LM?) {
self.loadingManager = loadingManager
...
}
override func loadView() {
...
let newCustedView = LoadingContentViewUIKit()
newCustedView.configure(
loadingManager: loadingManager,
provider: DefaultDMLoadingViewProvider() // OR CustomDMLoadingViewProvider() --> need to be implemented by the client! See `DMUnLoaderPodSPMExample` for details
)
view = newCustedView
...
}
}
final class LoadingContentViewUIKit<
Provider: DMLoadingViewProvider,
LM: DMLoadingManager
>: UIView {
private var loadingManager: LM?
private var provider: Provider?
private let stackView = UIStackView()
...
func configure(loadingManager: LM?, provider: Provider?) {
self.loadingManager = loadingManager
self.provider = provider
}
private func setupUI() {
....
addSubview(stackView)
// Buttons
let buttonShowDownloads = UIButton(type: .system)
buttonShowDownloads.addTarget(self, action: #selector(showDownloads), for: .touchUpInside)
stackView.addArrangedSubview(buttonShowDownloads)
let buttonSimulateAnError = UIButton(type: .system)
buttonSimulateAnError.addTarget(self, action: #selector(buttonSimulateAnError), for: .touchUpInside)
stackView.addArrangedSubview(buttonSimulateAnError)
let buttonSimulateSuccess = UIButton(type: .system)
buttonSimulateSuccess.addTarget(self, action: #selector(buttonSimulateSuccess), for: .touchUpInside)
stackView.addArrangedSubview(buttonSimulateSuccess)
...
}
@objc func showDownloads() {
loadingManager?.showSuccess("Data successfully loaded!",
provider: provider)
}
@objc func simulateAnError() {
let error = DMAppError.custom("Some test Error occured!")
loadingManager?.showFailure(error,
provider: provider,
onRetry: DMButtonAction { [weak self] completion in
....
}
.retry(2)
.fallbackTo(DMButtonAction(...)))
}
@objc func simulateSuccess() {
loadingManager?.showSuccess("Data successfully loaded!",
provider: provider)
}
}You can replace the default views (DMErrorView, DMProgressView, DMSuccessView) with your own custom views by conforming to the DMLoadingViewProvider protocol. Here's an example:
struct CustomDMLoadingViewProvider: DMLoadingViewProvider {
func getLoadingView() -> some View {
Text("Custom Loading View")
.padding()
.background(Color.blue)
}
func getErrorView(error: Error, onRetry: DMAction?, onClose: DMAction) -> some View {
VStack {
Text("Custom Error View")
if let onRetry = onRetry {
Button("Retry", action: onRetry.simpleAction)
}
Button("Close this", action: onClose.simpleAction)
}
}
func getSuccessView(object: DMLoadableTypeSuccess) -> some View {
Text("Custom Success View")
}
...
}The SDK allows you to customize the appearance of the built-in views by configuring settings such as text properties, colors, and layout. All these settings will be picked up by using Provider object. Only interested to you settings can be overridden - the rest will be used as default ones (because DMLoadingViewProvider protocol has default implementation on SDK side). Here's an example:
struct CustomDMLoadingViewProvider: DMLoadingViewProvider {
...
var loadingManagerSettings: DMLoadingManagerSettings { CustomLoadingManagerSettings() }
private struct CustomLoadingManagerSettings: DMLoadingManagerSettings {
var autoHideDelay: Duration = .seconds(4)
}
var successViewSettings: DMSuccessViewSettings {
DMSuccessDefaultViewSettings(successImageProperties: SuccessImageProperties(foregroundColor: .green))
}
}
let provider = CustomDMLoadingViewProvider()
loadingManager.showSuccess("Data successfully loaded!",
provider: provider)The DMUnLoaderPodSPMExample project (./Examples/DMUnLoaderPodSPMExample/DMUnLoaderPodSPMExample.xcworkspace) demonstrates how to use the SDK in both SwiftUI and UIKit environments. It includes two schemes:
Debug-SwiftUI: Demonstrates integration withSwiftUI.Debug-UIKit: Demonstrates integration withUIKit.
To run the example project:
- Clone the repository.
- run
pod install; select the appropriate Depenency manager (PODorSMP) if neded;PODwill be used by default (the commad like:DEPENDENCY_MANAGER=POD pod install) - Open
DMUnLoaderPodSPMExample.xcworkspacein Xcode. - Select the desired scheme and run the app.
All dialogs (Error, Loading, Success) are displayed in a separate system window, ensuring they overlay the entire app interface without leaving interactive elements (e.g., Tab Bars) active. This approach is inspired by SwiftUI HUD HUD and SwiftUI Windows.
The DMUnLoaderPodSPMExample test project demonstrates how to use the SDK with both Swift Package Manager (SPM) and CocoaPods simultaneously. This ensures seamless integration of the SDK regardless of the dependency manager used. For more details, refer to this article.
Important Note: This dual dependency manager setup is specific to this DMUnLoaderPodSPMExample test project and is not intended for production use. The main
SDKitself supports installation via either SPM or CocoaPods, but not both simultaneously in a single target.
Huge parts of the SDK were rewritten using a Test-Driven Development (BDD/TDD) approach to ensure robustness and reliability. The suporting documnents can be found in DocumentationAndBluePrints folder.
The SDK leverages the DMVariableBlurView library to apply dynamic blur effects to views.
The SDK supports retry and fallback mechanisms for handling failed actions. For more information, see the DMAction GitHub page.
The following features are planned for future releases:
- Accessibility IDs for views.
- Support for Dark/Light themes.
- Localization for text on views.
- Analytics integration to track events within the SDK.
- Enhanced loggin capabilities for error reporting.
Contributions are welcome! Please feel free to submit a Pull Request or open an issue for any bugs or feature requests.
For questions or feedback, feel free to contact me via @-mail.
- Inspiration for the separate window approach:SwiftUI HUD and SwiftUI Windows
- Managing dependencies with both
SPMandCocoaPodsdependency manager: this article - Retry and Fallback Logic: DMAction GitHub page
- Dynamic Blur Effects: DMVariableBlurView GitHub page
This project is licensed under the MIT License - see the LICENSE file for details.



