Skip to content

Commit 141e356

Browse files
authored
Add Coordinate Space API (#171)
1 parent 369a6ee commit 141e356

6 files changed

+240
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// CoordinateSpace.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
/// A resolved coordinate space created by the coordinate space protocol.
9+
///
10+
/// You don't typically use `CoordinateSpace` directly. Instead, use the static
11+
/// properties and functions of `CoordinateSpaceProtocol` such as `.global`,
12+
/// `.local`, and `.named(_:)`.
13+
public enum CoordinateSpace {
14+
/// The global coordinate space at the root of the view hierarchy.
15+
case global
16+
17+
/// The local coordinate space of the current view.
18+
case local
19+
20+
/// A named reference to a view's local coordinate space.
21+
case named(AnyHashable)
22+
23+
@_spi(ForOpenSwiftUIOnly)
24+
public struct ID: Equatable, Sendable {
25+
let value: UniqueID
26+
27+
public init() {
28+
value = .init()
29+
}
30+
}
31+
32+
@_spi(ForOpenSwiftUIOnly)
33+
case id(CoordinateSpace.ID)
34+
35+
package static let root: CoordinateSpace = .global
36+
37+
package var canonical: CoordinateSpace { self }
38+
39+
package enum Name: Equatable {
40+
case name(AnyHashable)
41+
case id(CoordinateSpace.ID)
42+
package var space: CoordinateSpace {
43+
switch self {
44+
case let .name(name): .named(name)
45+
case let .id(id): .id(id)
46+
}
47+
}
48+
}
49+
}
50+
51+
@available(*, unavailable)
52+
extension CoordinateSpace: Sendable {}
53+
54+
extension CoordinateSpace {
55+
public var isGlobal: Bool { self == .global }
56+
57+
public var isLocal: Bool { self == .local }
58+
}
59+
60+
extension CoordinateSpace: Equatable, Hashable {
61+
public func hash(into hasher: inout Hasher) {
62+
switch self {
63+
case .global:
64+
hasher.combine(0)
65+
case .local:
66+
hasher.combine(1)
67+
case let .named(anyHashable):
68+
hasher.combine(2)
69+
anyHashable.hash(into: &hasher)
70+
case let .id(id):
71+
hasher.combine(3)
72+
hasher.combine(id.value)
73+
}
74+
}
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// CoordinateSpaceProtocol.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
/// A frame of reference within the layout system.
9+
///
10+
/// All geometric properties of a view, including size, position, and
11+
/// transform, are defined within the local coordinate space of the view's
12+
/// parent. These values can be converted into other coordinate spaces
13+
/// by passing types conforming to this protocol into functions such as
14+
/// `GeometryProxy.frame(in:)`.
15+
///
16+
/// For example, a named coordinate space allows you to convert the frame
17+
/// of a view into the local coordinate space of an ancestor view by defining
18+
/// a named coordinate space using the `coordinateSpace(_:)` modifier, then
19+
/// passing that same named coordinate space into the `frame(in:)` function.
20+
///
21+
/// VStack {
22+
/// GeometryReader { geometryProxy in
23+
/// let distanceFromTop = geometryProxy.frame(in: "container").origin.y
24+
/// Text("This view is \(distanceFromTop) points from the top of the VStack")
25+
/// }
26+
/// .padding()
27+
/// }
28+
/// .coordinateSpace(.named("container"))
29+
///
30+
/// You don't typically create types conforming to this protocol yourself.
31+
/// Instead, use the system-provided `.global`, `.local`, and `.named(_:)`
32+
/// implementations.
33+
public protocol CoordinateSpaceProtocol {
34+
/// The resolved coordinate space.
35+
var coordinateSpace: CoordinateSpace { get }
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// GlobalCoordinateSpace.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
/// The global coordinate space at the root of the view hierarchy.
9+
public struct GlobalCoordinateSpace: CoordinateSpaceProtocol {
10+
public init() {}
11+
12+
public var coordinateSpace: CoordinateSpace { .global }
13+
}
14+
15+
@available(*, unavailable)
16+
extension GlobalCoordinateSpace: Sendable {}
17+
18+
extension CoordinateSpaceProtocol where Self == GlobalCoordinateSpace {
19+
/// The global coordinate space at the root of the view hierarchy.
20+
public static var global: GlobalCoordinateSpace { .init() }
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// LocalCoordinateSpace.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
/// The local coordinate space of the current view.
9+
public struct LocalCoordinateSpace: CoordinateSpaceProtocol {
10+
public init() {}
11+
12+
public var coordinateSpace: CoordinateSpace { .local }
13+
}
14+
15+
@available(*, unavailable)
16+
extension LocalCoordinateSpace: Sendable {}
17+
18+
extension CoordinateSpaceProtocol where Self == LocalCoordinateSpace {
19+
/// The local coordinate space of the current view.
20+
public static var local: LocalCoordinateSpace { .init() }
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// NamedCoordinateSpace.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
/// A named coordinate space.
9+
///
10+
/// Use the `coordinateSpace(_:)` modifier to assign a name to the local
11+
/// coordinate space of a parent view. Child views can then refer to that
12+
/// coordinate space using `.named(_:)`.
13+
public struct NamedCoordinateSpace: CoordinateSpaceProtocol, Equatable {
14+
package var name: CoordinateSpace.Name
15+
16+
package init(name: CoordinateSpace.Name) {
17+
self.name = name
18+
}
19+
20+
public var coordinateSpace: CoordinateSpace { name.space }
21+
}
22+
23+
@available(*, unavailable)
24+
extension NamedCoordinateSpace: Sendable {}
25+
26+
extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace {
27+
/// Creates a named coordinate space using the given value.
28+
///
29+
/// Use the `coordinateSpace(_:)` modifier to assign a name to the local
30+
/// coordinate space of a parent view. Child views can then refer to that
31+
/// coordinate space using `.named(_:)`.
32+
///
33+
/// - Parameter name: A unique value that identifies the coordinate space.
34+
///
35+
/// - Returns: A named coordinate space identified by the given value.
36+
public static func named(_ name: some Hashable) -> NamedCoordinateSpace {
37+
NamedCoordinateSpace(name: .name(name))
38+
}
39+
40+
package static func id(_ id: CoordinateSpace.ID) -> NamedCoordinateSpace {
41+
NamedCoordinateSpace(name: .id(id))
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// ScrollCoordinateSpace.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
8+
package enum ScrollCoordinateSpace {
9+
package static let vertical: CoordinateSpace.ID = .init()
10+
package static let horizontal: CoordinateSpace.ID = .init()
11+
package static let all: CoordinateSpace.ID = .init()
12+
package static let content: CoordinateSpace.ID = .init()
13+
}
14+
15+
extension CoordinateSpace {
16+
package static var verticalScrollView: CoordinateSpace { .id(ScrollCoordinateSpace.vertical) }
17+
package static var horizontalScrollView: CoordinateSpace { .id(ScrollCoordinateSpace.horizontal) }
18+
package static var scrollView: CoordinateSpace { .id(ScrollCoordinateSpace.all) }
19+
package static var scrollViewContent: CoordinateSpace { .id(ScrollCoordinateSpace.content) }
20+
}
21+
22+
extension CoordinateSpace.Name {
23+
package static var verticalScrollView: CoordinateSpace.Name { .id(ScrollCoordinateSpace.vertical) }
24+
package static var horizontalScrollView: CoordinateSpace.Name { .id(ScrollCoordinateSpace.horizontal) }
25+
package static var scrollView: CoordinateSpace.Name { .id(ScrollCoordinateSpace.all) }
26+
package static var scrollViewContent: CoordinateSpace.Name { .id(ScrollCoordinateSpace.content) }
27+
}
28+
29+
extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace {
30+
/// The named coordinate space that is added by the system for the innermost
31+
/// containing scroll view that allows scrolling along the provided axis.
32+
public static func scrollView(axis: Axis) -> Self {
33+
switch axis {
34+
case .horizontal: NamedCoordinateSpace(name: .horizontalScrollView)
35+
case .vertical: NamedCoordinateSpace(name: .verticalScrollView)
36+
}
37+
}
38+
39+
/// The named coordinate space that is added by the system for the innermost
40+
/// containing scroll view.
41+
public static var scrollView: NamedCoordinateSpace { NamedCoordinateSpace(name: .scrollView) }
42+
43+
package static var scrollViewContent: NamedCoordinateSpace { NamedCoordinateSpace(name: .scrollViewContent) }
44+
}

0 commit comments

Comments
 (0)