Skip to content

Commit e4687a2

Browse files
committed
Adds availability, docs and demo
1 parent 4444041 commit e4687a2

File tree

3 files changed

+158
-12
lines changed

3 files changed

+158
-12
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import SwiftUI
2+
3+
private extension EnvironmentValues {
4+
5+
func containsValue(forKey key: String) -> Bool {
6+
return value(forKey: key) != nil
7+
}
8+
9+
func value<T>(forKey key: String, from mirror: Mirror, as: T.Type) -> T? {
10+
// Found a match
11+
if let value = mirror.descendant("value", "some") {
12+
if let typedValue = value as? T {
13+
print("Found value")
14+
return typedValue
15+
} else {
16+
print("Value for key '\(key)' in the environment is of type '\(type(of: value))', but we expected '\(String(describing: T.self))'.")
17+
}
18+
} else {
19+
print("Found key '\(key)' in the environment, but it doesn't have the expected structure. The type hierarchy may have changed in your SwiftUI version.")
20+
}
21+
22+
return nil
23+
}
24+
25+
/// Extracts a value from the environment by the name of its associated EnvironmentKey.
26+
/// Can be used to grab private environment values such as foregroundColor ("ForegroundColorKey").
27+
func value<T>(forKey key: String, as: T.Type) -> T? {
28+
if let mirror = value(forKey: key) as? Mirror {
29+
return value(forKey: key, from: mirror, as: T.self)
30+
} else if let value = value(forKey: key) as? T {
31+
return value
32+
} else {
33+
return nil
34+
}
35+
}
36+
37+
func value(forKey key: String) -> Any? {
38+
func keyFromTypeName(typeName: String) -> String? {
39+
let expectedPrefix = "TypedElement<EnvironmentPropertyKey<"
40+
guard typeName.hasPrefix(expectedPrefix) else {
41+
print("Wrong prefix")
42+
return nil
43+
}
44+
let rest = typeName.dropFirst(expectedPrefix.count)
45+
let expectedSuffix = ">>"
46+
guard rest.hasSuffix(expectedSuffix) else {
47+
print("Wrong prefix")
48+
return nil
49+
}
50+
let middle = rest.dropLast(expectedSuffix.count)
51+
return String(middle)
52+
}
53+
54+
/// `environmentMember` has type (for example) `TypedElement<EnvironmentPropertyKey<ForegroundColorKey>>`
55+
/// TypedElement.value contains the value of the key.
56+
func extract(startingAt environmentNode: Any) -> Any? {
57+
let mirror = Mirror(reflecting: environmentNode)
58+
59+
let typeName = String(describing: type(of: environmentNode))
60+
if let nodeKey = keyFromTypeName(typeName: typeName) {
61+
if key == nodeKey {
62+
return mirror
63+
}
64+
}
65+
66+
// Environment values are stored in a doubly linked list. The "before" and "after" keys point
67+
// to the next environment member.
68+
if let linkedListMirror = mirror.superclassMirror,
69+
let nextNode = linkedListMirror.descendant("after", "some") {
70+
return extract(startingAt: nextNode)
71+
}
72+
73+
return nil
74+
}
75+
76+
let mirror = Mirror(reflecting: self)
77+
78+
if let firstEnvironmentValue = mirror.descendant("_plist", "elements", "some") {
79+
if let node = extract(startingAt: firstEnvironmentValue) {
80+
return node
81+
} else {
82+
return nil
83+
}
84+
} else {
85+
return nil
86+
}
87+
}
88+
89+
}
90+
91+
@propertyWrapper
92+
internal struct StringlyTypedEnvironment<Value> {
93+
final class Store<Value>: ObservableObject {
94+
var value: Value? = nil
95+
}
96+
97+
@Environment(\.self) private var env
98+
@ObservedObject private var store = Store<Value>()
99+
100+
var key: String
101+
102+
init(key: String) {
103+
self.key = key
104+
}
105+
106+
private(set) var wrappedValue: Value? {
107+
get { store.value }
108+
nonmutating set { store.value = newValue }
109+
}
110+
}
111+
112+
extension StringlyTypedEnvironment: DynamicProperty {
113+
func update() {
114+
wrappedValue = env.value(forKey: key, as: Value.self)
115+
}
116+
}
117+
118+
@propertyWrapper
119+
internal struct EnvironmentContains: DynamicProperty {
120+
final class Store: ObservableObject {
121+
var contains: Bool = false
122+
}
123+
124+
@Environment(\.self) private var env
125+
126+
var key: String
127+
@ObservedObject private var store = Store()
128+
129+
init(key: String) {
130+
self.key = key
131+
}
132+
133+
var wrappedValue: Bool {
134+
get { store.contains }
135+
nonmutating set { store.contains = newValue }
136+
}
137+
138+
func update() {
139+
wrappedValue = env.containsValue(forKey: key)
140+
}
141+
}

Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContentStyleConfiguration.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,29 @@ extension Backport where Wrapped == Any {
1111

1212
/// A type-erased label of a labeled content instance.
1313
public struct Label: View {
14-
@Environment(\.backportLabelStyle) private var labelHidden
14+
@EnvironmentContains(key: "LabelsHiddenKey") private var isHidden
1515
let view: AnyView
16-
public var body: some View { view }
17-
init<V: View>(_ view: V) {
18-
self.view = .init(Backport.Label(title: {
19-
view
20-
}, icon: {
16+
public var body: some View {
17+
if isHidden {
2118
EmptyView()
22-
}))
19+
} else {
20+
view
21+
}
22+
}
23+
init<V: View>(_ view: V) {
24+
self.view = .init(view)
2325
}
2426
}
2527

2628
/// A type-erased content of a labeled content instance.
2729
public struct Content: View {
30+
@EnvironmentContains(key: "LabelsHiddenKey") private var isHidden
2831
let view: AnyView
29-
public var body: some View { view }
32+
public var body: some View {
33+
view
34+
.foregroundColor(isHidden ? .primary : .secondary)
35+
.frame(maxWidth: .infinity, alignment: isHidden ? .leading : .trailing)
36+
}
3037
init<V: View>(_ view: V) {
3138
self.view = .init(view)
3239
}

Sources/SwiftUIBackports/Shared/LabeledContent/Styles/AutomaticLabeledContentStyle.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ import SwiftUI
33
extension Backport where Wrapped == Any {
44

55
public struct AutomaticLabeledContentStyle: BackportLabeledContentStyle {
6-
@Namespace private var namespace
7-
86
public func makeBody(configuration: Configuration) -> some View {
9-
VStack(alignment: .leading, spacing: 2) {
7+
HStack(alignment: .firstTextBaseline) {
108
configuration.label
11-
.foregroundColor(.secondary)
9+
Spacer(minLength: 0)
1210
configuration.content
1311
}
1412
}

0 commit comments

Comments
 (0)