Skip to content

Commit

Permalink
Add UIEnviroment property wrapper (#17)
Browse files Browse the repository at this point in the history
* Add UIEnviroment

* Fix unit test

* adjust test.yml
  • Loading branch information
hainayanda authored Dec 6, 2024
1 parent 11ac426 commit b8f8d6a
Show file tree
Hide file tree
Showing 25 changed files with 752 additions and 241 deletions.
17 changes: 12 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ on:
branches: [ "main" ]

jobs:
build:
unittest:

runs-on: macos-14

steps:
- uses: actions/checkout@v4
- name: Install Dependencies
run: swift package resolve
- name: Pick xcode 15.1
run: sudo xcode-select -s '/Applications/Xcode_15.1.app/Contents/Developer'
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
- name: Run Tests on MacOS
run: |
swift build -v
swift test -v
- name: Run Tests on iPhone 14 Simulator (iOS 16.0)
run: |
xcodebuild test \
-scheme 'SwiftEnvironment' \
-destination 'platform=iOS Simulator,name=iPhone 14,OS=16.0'
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,53 @@ 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:

```swift
// 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:

```swift
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:

```swift
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:

```swift
// 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:

```swift
// 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

Expand Down
114 changes: 0 additions & 114 deletions Sources/SwiftEnvironment/EnvironmentValuesResolver.swift

This file was deleted.

26 changes: 0 additions & 26 deletions Sources/SwiftEnvironment/GlobalEnvironment.swift

This file was deleted.

85 changes: 0 additions & 85 deletions Sources/SwiftEnvironment/InstanceResolver.swift

This file was deleted.

14 changes: 14 additions & 0 deletions Sources/SwiftEnvironment/InstanceResolver/InstanceResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// InstanceResolver.swift
// SwiftEnvironment
//
// Created by Nayanda Haberty on 14/3/24.
//

import Foundation
import SwiftUI

protocol InstanceResolver {
func resolve<V>(for type: V.Type) -> V?
func assign(to view: any View, for keyPath: AnyKeyPath) -> any View
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// SingletonInstanceResolver.swift
// SwiftEnvironment
//
// Created by Nayanda Haberty on 5/11/24.
//

import Foundation
import SwiftUI

final class SingletonInstanceResolver<Value>: InstanceResolver {

private(set) var instance: Value?
private var resolver: (() -> Value)?
private let queue: DispatchQueue?

@inlinable init(queue: DispatchQueue?, resolver: @escaping () -> Value) {
self.resolver = resolver
self.queue = queue
}

@inlinable func resolve<V>(for type: V.Type) -> V? {
guard let instance else {
// this resolver should not be nil in this line
let resolver = self.resolver!
let newInstance = queue?.safeSync(execute: resolver) ?? resolver()
self.resolver = nil
self.instance = newInstance
return newInstance as? V
}
return instance as? V
}

func assign(to view: any View, for keyPath: AnyKeyPath) -> any View {
guard let writableKeyPath = keyPath as? WritableKeyPath<EnvironmentValues, Value>,
let value = resolve(for: Value.self) else {
return view
}
return view.environment(writableKeyPath, value)
}
}
Loading

0 comments on commit b8f8d6a

Please sign in to comment.