Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,82 +28,95 @@ public struct _EnvironmentKeyTransformModifier<Value>: ViewModifier, _GraphInput
self.transform = transform
}

public static func _makeInputs(modifier: _GraphValue<Self>, inputs: inout _GraphInputs) {
let childEnvironment = ChildEnvironment(
modifier: modifier.value,
environment: inputs.cachedEnvironment.wrappedValue.environment,
oldValue: nil,
oldKeyPath: nil
public static func _makeInputs(
modifier: _GraphValue<Self>,
inputs: inout _GraphInputs
) {
let childEnvironment = Attribute(
ChildEnvironment(
modifier: modifier.value,
environment: inputs.environment
)
)
inputs.environment = Attribute(childEnvironment)
inputs.environment = childEnvironment
}
}

@available(*, unavailable)
extension _EnvironmentKeyTransformModifier: Sendable {}

extension View {
/// Transforms the environment value of the specified key path with the
/// given function.
@inlinable
nonisolated public func transformEnvironment<V>(
_ keyPath: WritableKeyPath<EnvironmentValues, V>,
transform: @escaping (inout V) -> Void
) -> some View {
modifier(_EnvironmentKeyTransformModifier(
keyPath: keyPath,
transform: transform
))
}
}

// MARK: - ChildEnvironment [6.5.4]

private struct ChildEnvironment<Value>: StatefulRule, AsyncAttribute, CustomStringConvertible {
@Attribute private var modifier: _EnvironmentKeyTransformModifier<Value>
@Attribute private var environment: EnvironmentValues
private var oldValue: Value?
private var oldKeyPath: WritableKeyPath<EnvironmentValues, Value>?
@Attribute var modifier: _EnvironmentKeyTransformModifier<Value>
@Attribute var environment: EnvironmentValues
var oldValue: Value?
var oldKeyPath: WritableKeyPath<EnvironmentValues, Value>?

init(
modifier: Attribute<_EnvironmentKeyTransformModifier<Value>>,
environment: Attribute<EnvironmentValues>,
oldValue: Value?,
oldKeyPath: WritableKeyPath<EnvironmentValues, Value>?
oldValue: Value? = nil,
oldKeyPath: WritableKeyPath<EnvironmentValues, Value>? = nil
) {
_modifier = modifier
_environment = environment
self.oldValue = oldValue
self.oldKeyPath = oldKeyPath
}

var description: String {
"EnvironmentTransform: EnvironmentValues"
}

typealias Value = EnvironmentValues

mutating func updateValue() {
var (environment, environmentChanged) = _environment.changedValue()
let keyPath = modifier.keyPath
var newValue = environment[keyPath: keyPath]
let (environment, environmentChanged) = $environment.changedValue()
let newKeyPath = modifier.keyPath
var newValue = environment[keyPath: newKeyPath]
$modifier.syncMainIfReferences { modifier in
withObservation {
modifier.transform(&newValue)
}
}
guard !environmentChanged,
let valueChanged = oldValue.map({ compareValues($0, newValue, mode: .equatableUnlessPOD) }), !valueChanged,
let keyPathChanged = oldKeyPath.map({ $0 == keyPath }), !keyPathChanged,
hasValue
else {
environment[keyPath: keyPath] = newValue
value = environment
let valueChanged = if !environmentChanged {
// FIXME: The map logic is buggy
oldValue.map{ compareValues($0, newValue, mode: .equatableUnlessPOD) } ?? true
} else {
true
}
let keyPathChanged = if !valueChanged {
// FIXME: The map logic is buggy
oldKeyPath.map({ $0 == newKeyPath }) ?? true
} else {
true
}
if environmentChanged || valueChanged || keyPathChanged || !hasValue {
var env = environment
env[keyPath: newKeyPath] = newValue
value = env
oldValue = newValue
oldKeyPath = keyPath
return
oldKeyPath = newKeyPath
}
}
}

@available(*, unavailable)
extension _EnvironmentKeyTransformModifier: Sendable {}

extension View {
/// Transforms the environment value of the specified key path with the
/// given function.
@inlinable
nonisolated public func transformEnvironment<V>(
_ keyPath: WritableKeyPath<EnvironmentValues, V>,
transform: @escaping (inout V) -> Void
) -> some View {
modifier(_EnvironmentKeyTransformModifier(
keyPath: keyPath,
transform: transform
))
}
}

// MARK: - EnvironmentModifier

package protocol EnvironmentModifier: _GraphInputsModifier {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// EnvironmentKeyWritingModifier.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: 3B04936F6043A290A3E53AE94FE09355 (SwiftUICore)

import OpenAttributeGraphShims

// MARK: EnvironmentKeyWritingModifier

/// A modifier that sets a value for an environment keyPath.
@available(OpenSwiftUI_v1_0, *)
@frozen
public struct _EnvironmentKeyWritingModifier<Value>: ViewModifier, _GraphInputsModifier, PrimitiveViewModifier {

/// The environment keyPath to set.
public var keyPath: WritableKeyPath<EnvironmentValues, Value>

/// The environment value to set for `keyPath`.
public var value: Value

/// Creates an instance that sets a `value` for an environment `keyPath`.
@inlinable
public init(keyPath: WritableKeyPath<EnvironmentValues, Value>, value: Value) {
self.keyPath = keyPath
self.value = value
}

public static func _makeInputs(
modifier: _GraphValue<Self>,
inputs: inout _GraphInputs
) {
let childEnvironment = Attribute(
ChildEnvironment(
modifier: modifier.value,
env: inputs.environment
)
)
inputs.environment = childEnvironment
}
}

@available(*, unavailable)
extension _EnvironmentKeyWritingModifier: Sendable {}

@available(OpenSwiftUI_v1_0, *)
extension View {

/// Sets the environment value of the specified key path to the given value.
///
/// Use this modifier to set one of the writable properties of the
/// ``EnvironmentValues`` structure, including custom values that you
/// create. For example, you can set the value associated with the
/// ``EnvironmentValues/truncationMode`` key:
///
/// MyView()
/// .environment(\.truncationMode, .head)
///
/// You then read the value inside `MyView` or one of its descendants
/// using the ``Environment`` property wrapper:
///
/// struct MyView: View {
/// @Environment(\.truncationMode) var truncationMode: Text.TruncationMode
///
/// var body: some View { ... }
/// }
///
/// OpenSwiftUI provides dedicated view modifiers for setting most
/// environment values, like the ``View/truncationMode(_:)``
/// modifier which sets the ``EnvironmentValues/truncationMode`` value:
///
/// MyView()
/// .truncationMode(.head)
///
/// Prefer the dedicated modifier when available, and offer your own when
/// defining custom environment values, as described in
/// ``Entry()``.
///
/// This modifier affects the given view,
/// as well as that view's descendant views. It has no effect
/// outside the view hierarchy on which you call it.
///
/// - Parameters:
/// - keyPath: A key path that indicates the property of the
/// ``EnvironmentValues`` structure to update.
/// - value: The new value to set for the item specified by `keyPath`.
///
/// - Returns: A view that has the given value set in its environment.
@inlinable
nonisolated public func environment<V>(_ keyPath: WritableKeyPath<EnvironmentValues, V>, _ value: V) -> some View {
modifier(_EnvironmentKeyWritingModifier(keyPath: keyPath, value: value))
}
}

// MARK: - ChildEnvironment

private struct ChildEnvironment<Value>: StatefulRule, AsyncAttribute, CustomStringConvertible {
@Attribute var modifier: _EnvironmentKeyWritingModifier<Value>
@Attribute var env: EnvironmentValues
var oldModifier: _EnvironmentKeyWritingModifier<Value>?

init(
modifier: Attribute<_EnvironmentKeyWritingModifier<Value>>,
env: Attribute<EnvironmentValues>,
oldModifier: _EnvironmentKeyWritingModifier<Value>? = nil
) {
self._modifier = modifier
self._env = env
self.oldModifier = oldModifier
}

var description: String {
"EnvironmentWriting: \(Value.self)"
}

typealias Value = EnvironmentValues

mutating func updateValue() {
let (modifier, modifierChanged) = $modifier.changedValue()
let (environment, environmentChanged) = $env.changedValue()
let modifierValueChanged: Bool
if !environmentChanged, modifierChanged {
modifierValueChanged = oldModifier.map { oldModifier in
!(oldModifier.keyPath == modifier.keyPath && compareValues(oldModifier.value, modifier.value, mode: .equatableUnlessPOD))
} ?? true
} else {
modifierValueChanged = false
}

if environmentChanged || modifierValueChanged || !hasValue {
var env = environment
env[keyPath: modifier.keyPath] = modifier.value
value = env
oldModifier = modifier
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// EnvironmentKeyTransformModifierCompatibilityTests.swift
// OpenSwiftUICompatibilityTests

import OpenSwiftUITestsSupport
import Testing

extension EnvironmentValues {
@Entry fileprivate var customValue = 0
}

@MainActor
struct EnvironmentKeyTransformModifierCompatibilityTests {
@Test
func environmentKeyTransform() async throws {
struct ContentView: View {
var body: some View {
Subview()
.transformEnvironment(\.customValue) { $0 = 1 }
}
}

struct Subview: View {
@Environment(\.customValue) private var value

var body: some View {
Color.red
.onAppear {
#expect(value == 1)
}
}
}
try await triggerLayoutWithWindow(expectedCount: 0) { _ in
PlatformHostingController(
rootView: ContentView()
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// EnvironmentKeyWritingModifierCompatibilityTests.swift
// OpenSwiftUICompatibilityTests

import OpenSwiftUITestsSupport
import Testing

extension EnvironmentValues {
@Entry fileprivate var customValue = 0
}

@MainActor
struct EnvironmentKeyWritingModifierCompatibilityTests {
@Test
func environmentKeyWriting() async throws {
struct ContentView: View {
var body: some View {
Subview()
.environment(\.customValue, 1)
}
}

struct Subview: View {
@Environment(\.customValue) private var value

var body: some View {
Color.red
.onAppear {
#expect(value == 1)
}
}
}
try await triggerLayoutWithWindow(expectedCount: 0) { _ in
PlatformHostingController(
rootView: ContentView()
)
}
}
}
Loading