This library offers a lightweight and powerful solution for integrating dependency injection into SwiftUI applications. Leveraging the latest native APIs, it facilitates a modular architecture by allowing dependencies to be injected directly into SwiftUI views and view models. This approach simplifies testing and development by decoupling your app's components.
- Type-Safe Dependencies: Utilize Swift's type system to ensure that your dependencies are correctly resolved at compile time.
- SwiftUI Integration: Designed with SwiftUI in mind, it seamlessly integrates into the SwiftUI lifecycle and environment.
- Observability: Supports the injection of observable objects, allowing your views to react dynamically to changes in your dependencies.
- Ease of Use: Simplify the management of your app's dependencies with minimal boilerplate code.
Platform | Minimum Version |
---|---|
iOS | 17.0 |
macOS | 14.0 |
tvOS | 17.0 |
watchOS | 10.0 |
Swift Package Manager is a tool for managing the distribution of Swift code. It integrates with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
To integrate DependencyInjectable
into your Xcode project using Xcode 15 or later, follow these steps:
- Open your project in Xcode.
- Select
File
>Swift Packages
>Add Package Dependency...
- Enter the package repository URL:
https://github.com/JamesSedlacek/DependencyInjectable.git
- Choose the version rule that makes sense for your project.
- Select
Add Package
.
If you are developing a Swift package or have a project that already uses Package.swift
, you can add DependencyInjectable
as a dependency:
// swift-tools-version:5.10
import PackageDescription
let package = Package(
name: "DependencyInjectable",
dependencies: [
.package(url: "https://github.com/JamesSedlacek/DependencyInjectable.git", branch: "main")
],
targets: [
.target(
name: "YourTargetName",
dependencies: ["DependencyInjectable"])
]
)
To set up dependency injection for your project, follow these steps:
- Define a protocol and implementation for your dependencies.
- For each of your dependencies, create a dependency key that conforms to the
DependencyKey
protocol. - Extend
DependencyValues
to include your dependencies for easy key path access throughout your application.
Define a protocol for your service and an implementation:
protocol ExampleServiceable {
var someProperty: String { get }
}
struct ExampleService: ExampleServiceable {
let someProperty = "Wow it works!"
}
Define a key for your service in the dependency container:
struct ExampleServiceKey: DependencyKey {
static var defaultValue: ExampleServiceable = ExampleService()
}
Extend DependencyValues
for dot notation access to your service:
extension DependencyValues {
var exampleService: ExampleServiceable {
get { self[ExampleServiceKey.self] }
set { self[ExampleServiceKey.self] = newValue }
}
}
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.environment(
\.dependencies.exampleService,
ExampleService("Testing")
)
}
}
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.inject(ContentViewModel())
}
}
}
Create a view model that injects your service using the @Observable
macro and the DependencyInjectable
protocol:
Note - onInject(dependencies:)
gets called automatically by the .inject(:)
view modifier.
@Observable
final class ContentViewModel: DependencyInjectable {
var service: ExampleServiceable?
var someProperty: String {
service?.someProperty ?? "default value"
}
func onInject(dependencies: DependencyValues) {
service = dependencies.exampleService
}
}
struct ContentView: View {
@Environment(ContentViewModel.self)
private var viewModel
var body: some View {
Text(viewModel.someProperty)
}
}
struct ContentView: View {
@Environment(\.dependencies.exampleService)
private var service
var body: some View {
Text(service.someProperty)
}
}