|
| 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 | +///  |
| 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