Skip to content

Commit 265d0a5

Browse files
authored
Update SafeAreaInsets API (#294)
* Fix AnyAttributeFix name * Update SafeAreaInsets
1 parent dd35b51 commit 265d0a5

File tree

5 files changed

+249
-147
lines changed

5 files changed

+249
-147
lines changed

Sources/OpenSwiftUICore/Layout/SafeArea/SafeAreaInsets.swift

Lines changed: 0 additions & 44 deletions
This file was deleted.

Sources/OpenSwiftUICore/Layout/SafeArea/SafeAreaInsetsModifier.swift

Lines changed: 0 additions & 75 deletions
This file was deleted.

Sources/OpenSwiftUICore/Layout/SafeArea/SafeAreaRegions.swift

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
//
2+
// SafeAreaInsets.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: WIP
7+
// ID: C4DC82F2A500E9B6DEA3064A36584B42 (SwiftUICore)
8+
9+
import Foundation
10+
package import OpenGraphShims
11+
12+
// MARK: - SafeAreaRegions
13+
14+
/// A set of symbolic safe area regions.
15+
@frozen
16+
public struct SafeAreaRegions: OptionSet {
17+
public let rawValue: UInt
18+
19+
@inlinable
20+
public init(rawValue: UInt) { self.rawValue = rawValue }
21+
22+
/// The safe area defined by the device and containers within the
23+
/// user interface, including elements such as top and bottom bars.
24+
public static let container = SafeAreaRegions(rawValue: 1 << 0)
25+
26+
/// The safe area matching the current extent of any software
27+
/// keyboard displayed over the view content.
28+
public static let keyboard = SafeAreaRegions(rawValue: 1 << 1)
29+
30+
/// All safe area regions.
31+
public static let all = SafeAreaRegions(rawValue: .max)
32+
33+
package static let background = SafeAreaRegions(rawValue: 1 << 0)
34+
}
35+
36+
// MARK: - SafeAreaInsets [WIP]
37+
38+
package struct SafeAreaInsets: Equatable {
39+
package enum OptionalValue: Equatable {
40+
case empty
41+
indirect case insets(SafeAreaInsets)
42+
}
43+
44+
package struct Element: Equatable {
45+
package var regions: SafeAreaRegions
46+
package var insets: EdgeInsets
47+
48+
package init(regions: SafeAreaRegions, insets: EdgeInsets) {
49+
self.regions = regions
50+
self.insets = insets
51+
}
52+
}
53+
54+
package var space: CoordinateSpace.ID
55+
56+
package var elements: [Element]
57+
58+
package var next: OptionalValue
59+
60+
package init(space: CoordinateSpace.ID, elements: [Element]) {
61+
self.space = space
62+
self.elements = elements
63+
self.next = .empty
64+
}
65+
66+
package init(space: CoordinateSpace.ID, elements: [Element], next: OptionalValue) {
67+
self.space = space
68+
self.elements = elements
69+
self.next = next
70+
}
71+
72+
package func resolve(regions: SafeAreaRegions, in ctx: _PositionAwarePlacementContext) -> EdgeInsets {
73+
// preconditionFailure("TODO")
74+
.zero
75+
}
76+
77+
private func adjust(
78+
_ rect: inout CGRect,
79+
regions: SafeAreaRegions,
80+
to: _PositionAwarePlacementContext
81+
) {
82+
// preconditionFailure("TODO")
83+
}
84+
}
85+
86+
// MARK: - _SafeAreaInsetsModifier [WIP]
87+
88+
package struct _SafeAreaInsetsModifier: MultiViewModifier, PrimitiveViewModifier, Equatable {
89+
var elements: [SafeAreaInsets.Element]
90+
var nextInsets: SafeAreaInsets.OptionalValue?
91+
92+
package init() {
93+
elements = []
94+
nextInsets = nil
95+
}
96+
97+
package init(elements: [SafeAreaInsets.Element], nextInsets: SafeAreaInsets.OptionalValue? = nil) {
98+
self.elements = elements
99+
self.nextInsets = nextInsets
100+
}
101+
102+
nonisolated package static func _makeView(
103+
modifier: _GraphValue<Self>,
104+
inputs: _ViewInputs,
105+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
106+
) -> _ViewOutputs {
107+
var inputs = inputs
108+
let space = CoordinateSpace.ID()
109+
if inputs.needsGeometry {
110+
// TODO
111+
}
112+
inputs.transform = Attribute(
113+
Transform(
114+
space: space,
115+
transform: inputs.transform,
116+
position: inputs.position,
117+
size: inputs.size
118+
)
119+
)
120+
inputs.safeAreaInsets = OptionalAttribute(
121+
Attribute(
122+
Insets(
123+
space: space,
124+
modifier: modifier.value,
125+
next: inputs.safeAreaInsets
126+
)
127+
)
128+
)
129+
return body(_Graph(), inputs)
130+
}
131+
132+
private struct Insets: Rule, AsyncAttribute {
133+
let space: CoordinateSpace.ID
134+
@Attribute var modifier: _SafeAreaInsetsModifier
135+
@OptionalAttribute var next: SafeAreaInsets?
136+
137+
var value: SafeAreaInsets {
138+
let insets: SafeAreaInsets.OptionalValue
139+
if let nextInsets = modifier.nextInsets {
140+
insets = nextInsets
141+
} else {
142+
if let next {
143+
insets = .insets(next)
144+
} else {
145+
insets = .empty
146+
}
147+
}
148+
return SafeAreaInsets(space: space, elements: modifier.elements, next: insets)
149+
}
150+
}
151+
152+
private struct Transform: Rule, AsyncAttribute {
153+
let space: CoordinateSpace.ID
154+
@Attribute var transform: ViewTransform
155+
@Attribute var position: ViewOrigin
156+
@Attribute var size: ViewSize
157+
158+
var value: ViewTransform {
159+
var transform = transform
160+
transform.appendPosition(position.value)
161+
transform.appendSizedSpace(id: space, size: size.value)
162+
return transform
163+
}
164+
}
165+
}
166+
167+
extension _SafeAreaInsetsModifier {
168+
@MainActor
169+
@preconcurrency
170+
package init(insets: EdgeInsets, nextInsets: SafeAreaInsets.OptionalValue? = nil) {
171+
self.elements = [.init(regions: .container, insets: insets)]
172+
self.nextInsets = nextInsets
173+
}
174+
}
175+
176+
extension _PositionAwarePlacementContext {
177+
package func safeAreaInsets(matching regions: SafeAreaRegions = .all) -> EdgeInsets {
178+
guard let unadjustedSafeAreaInsets else {
179+
return .zero
180+
}
181+
return unadjustedSafeAreaInsets.resolve(regions: regions, in: self)
182+
}
183+
}
184+
185+
// MARK: - SafeAreaInsetsModifier
186+
187+
package typealias SafeAreaInsetsModifier = ModifiedContent<_PaddingLayout, _SafeAreaInsetsModifier>
188+
189+
extension View {
190+
@MainActor
191+
@preconcurrency
192+
public func _safeAreaInsets(_ insets: EdgeInsets) -> some View {
193+
safeAreaInsets(insets, next: nil)
194+
}
195+
196+
@MainActor
197+
@preconcurrency
198+
package func safeAreaInsets(_ insets: EdgeInsets, next: SafeAreaInsets.OptionalValue? = nil) -> ModifiedContent<Self, SafeAreaInsetsModifier> {
199+
modifier(
200+
_PaddingLayout(insets: insets)
201+
.concat(_SafeAreaInsetsModifier(insets: insets, nextInsets: next))
202+
)
203+
}
204+
}
205+
206+
// MARK: - ResolvedSafeAreaInsets
207+
208+
package struct ResolvedSafeAreaInsets: Rule, AsyncAttribute {
209+
let regions: SafeAreaRegions
210+
@Attribute var environment: EnvironmentValues
211+
@Attribute var size: ViewSize
212+
@Attribute var position: ViewOrigin
213+
@Attribute var transform: ViewTransform
214+
@OptionalAttribute var safeAreaInsets: SafeAreaInsets?
215+
216+
package init(
217+
regions: SafeAreaRegions,
218+
environment: Attribute<EnvironmentValues>,
219+
size: Attribute<ViewSize>,
220+
position: Attribute<ViewOrigin>,
221+
transform: Attribute<ViewTransform>,
222+
safeAreaInsets: OptionalAttribute<SafeAreaInsets>
223+
) {
224+
self.regions = regions
225+
self._environment = environment
226+
self._size = size
227+
self._position = position
228+
self._transform = transform
229+
self._safeAreaInsets = safeAreaInsets
230+
}
231+
232+
package var value: EdgeInsets {
233+
let context = AnyRuleContext(context)
234+
guard let safeAreaInsetsAttribute = $safeAreaInsets else {
235+
return .zero
236+
}
237+
return context[safeAreaInsetsAttribute].resolve(
238+
regions: regions,
239+
in: _PositionAwarePlacementContext(
240+
context: context,
241+
size: _size,
242+
environment: _environment,
243+
transform: _transform,
244+
position: _position,
245+
safeAreaInsets: _safeAreaInsets
246+
)
247+
)
248+
}
249+
}

0 commit comments

Comments
 (0)