Skip to content

SwiftEnvironment is a Swift library designed to simplify environment value management in SwiftUI applications

License

Notifications You must be signed in to change notification settings

hainayanda/SwiftEnvironment

Repository files navigation

SwiftEnvironment

SwiftEnvironment is a Swift library designed to simplify environment value management in SwiftUI applications. It provides convenient macros and utilities to streamline the process of defining and injecting environment values.

GitHub Release Codacy Badge SwiftPM Compatible Unit Test

Requirements

  • Swift 5.9 or higher
  • iOS 13.0 or higher
  • MacOS 10.15 or higher
  • TVOS 13.0 or higher
  • WatchOS 6.0 or higher
  • Xcode 15 or higher

Installation

Swift Package Manager (Xcode)

To install using Xcode's Swift Package Manager, follow these steps:

Swift Package Manager (Package.swift)

If you prefer using Package.swift, add SwiftEnvironment as a dependency in your Package.swift file:

dependencies: [
    .package(url: "https://github.com/hainayanda/SwiftEnvironment.git", .upToNextMajor(from: "2.3.0"))
]

Then, include it in your target:

 .target(
    name: "MyModule",
    dependencies: ["SwiftEnvironment"]
)

Usage

This library is designed to allow easier dependency management, using exisiting EnvironmentValues from SwiftUI. Adding new environment is very easy:

// protocol with stub
@Stubbed(type: .class)
protocol MyProtocol {
    func doSomething()
}

// add to EnvironmentValues
@EnvironmentValue
extension EnvironmentValues {
    static let myValue: MyProtocol = MyProtocolStub()
}

Then you can use the keyPath in SwiftUI Environment or GlobalEnvironment:

@Environment(\.myValue) var switUIValue
@GlobalEnvironment(\.myValue) var globalValue

You can inject the dependency to SwiftUI Enviromnent as usual:

SomeView()
    .environment(\.myValue, SomeDependency())

Different than SwiftUI Environment, GlobalEnvironment can be injected and accessed globally:

GlobalResolver.environment(\.myValue, MyImplementation())

GlobalEnvironment

GlobalEnvironment complements SwiftUI Environment. It allows EnvironmentValues to be accessed globally outside of SwiftUI injection scope. To use it, add EnvironmentValues just like how we add it for SwiftUI, and inject it into GlobalResolver:

@GlobalEnvironment(\.myValue) var myValue

To provide gobal environment use GlobalResolver static methods:

GlobalResolver
    .environment(\.myValue, SomeDependency())

You can connect multiple KeyPaths to one KeyPaths by a single call:

GlobalResolver
    .environment(\.this, \.that, use: \.myValue)

To resolve dependency manually from GlobalResolver, do this:

let myValue = GlobalResolver.resolve(\.myValue)

UIEnvironment

UIEnvirontment is similar as SwiftUI Environment but for UIKit. It allows UIKit be injected using EnvironmentValues just like SwiftUI. It will also allow shared environment on any it subview or child view controller:

// inject SomeDependency to window
window.environment(\.myValue, SomeDependency())

Then all of it's child view controller and view can access myValue injected from the same window:

class MyViewController: UIViewController {
    // this will be using the injected value from it's parent (UIViewController or UIWindow if it's a root)
    @UIEnvironment(\.myValue) var myValue
}

even the view and subview can access the value also:

class MyView: UIVIiew { 
    // this will be using the injected value from it's superview or it's viewController if its a root.
    @UIEnvironment(\.myValue) var myValue
}

Same like SwiftUI, if the ViewController is injected, it will use it's own value instead of from its parent. This value then will be inherited to it's child too:

// All of its child viewcontroller and view will use this value instead of the one coming from window
myViewController.environment(\.myValue, SomeOtherDependency())

Updating the enviroment will be reflect to all inheriting value just like SwiftUI. But this will only work on UIKit to UIKit, not UIKit to SwiftUI.

If you are presenting a SwiftUI from UIKit, you can inject the value to the SwiftUI also:

// this will inject all of the enviroment to the SwiftUI View
let hostingController = UIHostingController(
                            rootView: MySwiftUIView().inheritEnvironment(from: presentingViewController)
                        )
presentingViewController.present(hostingController, animated: true)

All of the Enviroment will be injected. But keep in mind that updating an Enviroment will not update the SwiftUI Environment, since it will just resolve all value during inherit.

EnvironmentValue macro

The EnvironmentValue macro is used to remove boilerplate code when adding a new variable to EnvironmentValue. To use it, simply add @EnvironmentValue to the extension of EnvironmentValues with static variable of your default environmentValue.

import SwiftEnvironment

@EnvironmentValue
extension EnvironmentValues {
    static let myValue: Dependency = MyDependency()
}

This allows you to use myValue as a SwiftUI Environment KeyPath argument:

@Environment(\.myValue) var myValue

or for view injection:

SomeView()
    .environment(\.myValue, SomeDependency())

Stubbed macro on protocols

The Stubbed macro simplifies the creation of stubs from protocols, reducing boilerplate code. To use the Stubbed macro, simply add @Stubbed to the protocol you wish to create a stub for:

import SwiftEnvironment

@Stubbed(type: .struct)
protocol MyProtocol { 
    var someValue: Int { get }
    func calculate(someValue: Int) -> Int
}

This will generate a structure similar to this:

struct MyProtocolStub { 
    let someValue: Int = 0
    init() { }
    func calculate(someValue: Int) -> Int { return 0 }
}

If the return type of the protocol's methods or variables is unknown or you want to customize the value, you can provide the default value using .value(for: <custom type>.self, <custom default value>):

import SwiftEnvironment

@Stubbed(type: .class, .value(for: MyType.self, MyType()))
protocol MyProtocol { 
    var someValue: MyType { get }
    func calculate(someValue: Int) -> MyType
}

You can provide multiple types as variadic parameters.

Stubbed macro on struct and class

The Stubbed macro simplifies the creation of stubs for class or struct, reducing boilerplate code. To use the Stubbed macro, simply add @Stubbed to the class or struct you wish to create a stub for:

import SwiftEnvironment

@Stubbed
struct MyStruct { 
    let someValue: Int
    let someString: String
}

Then you can get your stub by calling generated static variable .stub:

let myStructStub = MyStruct.stub

If the return type of the protocol's methods or variables is unknown or you want to customize the value, you can provide the default value using .value(for: <custom type>.self, <custom default value>):

import SwiftEnvironment

@Stubbed(.value(for: MyType.self, MyType()))
struct MyStruct {
    let someValue: MyType
}

You can provide multiple types as variadic parameters.

GlobalResolver environment

Injected values to GlobalResolver.environment are injected using autoclosure, so the value will be created lazily. This value will be stored as long as the app is alive. You can inject an explicit closure too if needed:

GlobalResolver
    .environment(\.myValue) { 
        SomeDependency()
    }

GlobalResolver transient

Another injection method for GlobalResolver is transient. This method ensures that the value will be newly created when first accessed from GlobalEnvironment property wrapper. To inject, simply call transient from GlobalEnvironment and proceed as usual:

GlobalResolver
    .transient(\.myValue, SomeDependency())

GlobalResolver weak

Another injection method for GlobalResolver is weak. This method ensures that the value will be stored in a weak variable and will be newly created only when the last resolved value is null. To inject, simply call weak from GlobalEnvironment and proceed as usual:

GlobalResolver
    .weak(\.myValue, SomeDependency())

Enabling Macro on error

XCode will try prevent usage of macros downloaded from the internet. To enable it, tap on the error and choose Trust & Enable.

SwiftEnvironment error

Contributing

Contributions are welcome! Please follow the guidelines in the CONTRIBUTING.md file.

License

MosaicGrid is available under the MIT license. See the LICENSE file for more info.

Credits

This project is maintained by Nayanda Haberty.

About

SwiftEnvironment is a Swift library designed to simplify environment value management in SwiftUI applications

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages