Skip to content

nikolay-dementiev/DMUnLoader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

DMUnLoader

Universal Loader & Result Handler

Build Status Swift Version Platform SPM Compatible CocoaPods Compatible FOSSA Status

DMAction-SDK-logo

Overview

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.

Key features include:

  • 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 the DMLoadingViewProvider protocol (by using dependency invertion pronciple).
  • 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 DMVariableBlurView library to apply dynamic blur effects.

Table of Contents


🎬 Demo: Loading, Success & Error States

Watch how DMUnLoader behaves in different states with default (left) and custom (right) settings:

  • Loading State:

Loading Demo

  • Success State:

Success Demo

  • Error State:

Error Demo


🧭 Very High-Level Architecture in Picture

click on the image to view it in full screen mode

High-Level Architecture


πŸ“₯ Installation

πŸ“¦ Swift Package Manager

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")
]

🍫 CocoaPods

To integrate DMUnLoader using CocoaPods, add the following line to your Podfile:

pod 'DMUnLoader', :git => 'https://github.com/nikolay-dementiev/DMUnLoader.git'

πŸ›  Usage

SwiftUI Integration

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)
        }
    }
}

UIKit Integration

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)
    }
}

🎨 Customization

πŸ–Œ Custom Views

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")
    }
    ...
}

βš™ Settings Configuration

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)

πŸ§ͺ Example Project

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 with SwiftUI.
  • Debug-UIKit: Demonstrates integration with UIKit.

To run the example project:

  1. Clone the repository.
  2. run pod install; select the appropriate Depenency manager (POD or SMP) if neded; POD will be used by default (the commad like: DEPENDENCY_MANAGER=POD pod install)
  3. Open DMUnLoaderPodSPMExample.xcworkspace in Xcode.
  4. Select the desired scheme and run the app.

🧩 Implementation Details

πŸͺŸ 1. Separate System Window

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.

πŸ“¦πŸ« 2. Dual Dependency Manager Usage in Test Project

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 SDK itself supports installation via either SPM or CocoaPods, but not both simultaneously in a single target.

🎯 3. TDD Approach

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.

🌫 4. Custom Blur Effects

The SDK leverages the DMVariableBlurView library to apply dynamic blur effects to views.

πŸ” 5. Retry and Fallback Actions

The SDK supports retry and fallback mechanisms for handling failed actions. For more information, see the DMAction GitHub page.


🚧 Future Enhancements

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.

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request or open an issue for any bugs or feature requests.


πŸ“¬ Contact

For questions or feedback, feel free to contact me via @-mail.


πŸ™ Acknowledgments

  1. Inspiration for the separate window approach:SwiftUI HUD and SwiftUI Windows
  2. Managing dependencies with both SPM and CocoaPods dependency manager: this article
  3. Retry and Fallback Logic: DMAction GitHub page
  4. Dynamic Blur Effects: DMVariableBlurView GitHub page

πŸ“œ License

This project is licensed under the MIT License - see the LICENSE file for details.

FOSSA Status

About

Universal Loader & Result Handler

Resources

License

Stars

Watchers

Forks

Packages

No packages published