Skip to content

Commit 37f5c63

Browse files
committed
Add ViewThatFits interface
1 parent 44df008 commit 37f5c63

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//
2+
// ViewThatFits.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Blocked by Layout Computer
7+
// ID: F613AABF2A2A0496B46514894D5116C3 (SwiftUI)
8+
9+
public import OpenSwiftUICore
10+
11+
/// A view that adapts to the available space by providing the first
12+
/// child view that fits.
13+
///
14+
/// `ViewThatFits` evaluates its child views in the order you provide them
15+
/// to the initializer. It selects the first child whose ideal size on the
16+
/// constrained axes fits within the proposed size. This means that you
17+
/// provide views in order of preference. Usually this order is largest to
18+
/// smallest, but since a view might fit along one constrained axis but not the
19+
/// other, this isn't always the case. By default, `ViewThatFits` constrains
20+
/// in both the horizontal and vertical axes.
21+
///
22+
/// The following example shows an `UploadProgressView` that uses `ViewThatFits`
23+
/// to display the upload progress in one of three ways. In order, it attempts
24+
/// to display:
25+
///
26+
/// * An ``HStack`` that contains a ``Text`` view and a ``ProgressView``.
27+
/// * Only the `ProgressView`.
28+
/// * Only the `Text` view.
29+
///
30+
/// The progress views are fixed to a 100-point width.
31+
///
32+
/// struct UploadProgressView: View {
33+
/// var uploadProgress: Double
34+
///
35+
/// var body: some View {
36+
/// ViewThatFits(in: .horizontal) {
37+
/// HStack {
38+
/// Text("\(uploadProgress.formatted(.percent))")
39+
/// ProgressView(value: uploadProgress)
40+
/// .frame(width: 100)
41+
/// }
42+
/// ProgressView(value: uploadProgress)
43+
/// .frame(width: 100)
44+
/// Text("\(uploadProgress.formatted(.percent))")
45+
/// }
46+
/// }
47+
/// }
48+
///
49+
/// This use of `ViewThatFits` evaluates sizes only on the horizontal axis. The
50+
/// following code fits the `UploadProgressView` to several fixed widths:
51+
///
52+
/// VStack {
53+
/// UploadProgressView(uploadProgress: 0.75)
54+
/// .frame(maxWidth: 200)
55+
/// UploadProgressView(uploadProgress: 0.75)
56+
/// .frame(maxWidth: 100)
57+
/// UploadProgressView(uploadProgress: 0.75)
58+
/// .frame(maxWidth: 50)
59+
/// }
60+
///
61+
/// ![A vertical stack showing three expressions of progress, constrained by
62+
/// the available horizontal space. The first line shows the text, 75%, and a
63+
/// three-quarters-full progress bar. The second line shows only the progress
64+
/// view. The third line shows only the text.](ViewThatFits-1)
65+
@frozen
66+
public struct ViewThatFits<Content>: View, UnaryView, PrimitiveView where Content: View {
67+
@usableFromInline
68+
var _tree: _VariadicView.Tree<_SizeFittingRoot, Content>
69+
70+
/// Produces a view constrained in the given axes from one of several
71+
/// alternatives provided by a view builder.
72+
///
73+
/// - Parameters:
74+
/// - axes: A set of axes to constrain children to. The set may
75+
/// contain ``Axis/horizontal``, ``Axis/vertical``, or both of these.
76+
/// `ViewThatFits` chooses the first child whose size fits within the
77+
/// proposed size on these axes. If `axes` is an empty set,
78+
/// `ViewThatFits` uses the first child view. By default,
79+
/// `ViewThatFits` uses both axes.
80+
/// - content: A view builder that provides the child views for this
81+
/// container, in order of preference. The builder chooses the first
82+
/// child view that fits within the proposed width, height, or both,
83+
/// as defined by `axes`.
84+
@inlinable
85+
public init(in axes: Axis.Set = [.horizontal, .vertical], @ViewBuilder content: () -> Content) {
86+
_tree = .init(_SizeFittingRoot(axes: axes)) { content() }
87+
}
88+
89+
nonisolated public static func _makeView(view: _GraphValue<Self>, inputs: _ViewInputs) -> _ViewOutputs {
90+
_VariadicView.Tree<_SizeFittingRoot, Content>.makeDebuggableView(
91+
view: view[offset: { .of(&$0._tree) }],
92+
inputs: inputs
93+
)
94+
}
95+
96+
public typealias Body = Never
97+
}
98+
@available(*, unavailable)
99+
extension ViewThatFits: Sendable {}
100+
101+
@frozen
102+
public struct _SizeFittingRoot: _VariadicView.UnaryViewRoot {
103+
@usableFromInline
104+
var axes: Axis.Set
105+
106+
@inlinable
107+
init(axes: Axis.Set) { self.axes = axes }
108+
109+
nonisolated public static func _makeView(root: _GraphValue<Self>, inputs: _ViewInputs, body: (_Graph, _ViewInputs) -> _ViewListOutputs) -> _ViewOutputs {
110+
preconditionFailure("TODO")
111+
}
112+
113+
public typealias Body = Never
114+
}
115+
116+
// WIP
117+
final package class SizeFittingState {
118+
119+
}
120+
121+
// Blocked by LayoutComputer
122+
private struct SizeFittingLayoutComputer {
123+
struct Engine {}
124+
}
125+
126+
package protocol PlatformViewThatFitsRepresentable {
127+
static func shouldMakeRepresentation(inputs: _ViewInputs) -> Bool
128+
129+
static func makeRepresentation(inputs: _ViewInputs, state: SizeFittingState, outputs: inout _ViewOutputs)
130+
}
131+
132+
extension _ViewInputs {
133+
package var requestedViewThatFitsRepresentation: (any PlatformViewThatFitsRepresentable.Type)? {
134+
get { base.requestedViewThatFitsRepresentation }
135+
set { base.requestedViewThatFitsRepresentation = newValue }
136+
}
137+
}
138+
139+
extension _GraphInputs {
140+
private struct ViewThatFitsRepresentationKey: GraphInput {
141+
static var defaultValue: (any PlatformViewThatFitsRepresentable.Type)?
142+
}
143+
144+
package var requestedViewThatFitsRepresentation: (any PlatformViewThatFitsRepresentable.Type)? {
145+
get { self[ViewThatFitsRepresentationKey.self] }
146+
set { self[ViewThatFitsRepresentationKey.self] = newValue }
147+
}
148+
}

0 commit comments

Comments
 (0)