Skip to content

Commit 59c3ae4

Browse files
committed
Add LabeledView and LabeledContent
1 parent 27998ec commit 59c3ae4

File tree

2 files changed

+333
-0
lines changed

2 files changed

+333
-0
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//
2+
// LabeledContent.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Blocked by Text
7+
8+
/// A container for attaching a label to a value-bearing view.
9+
///
10+
/// The instance's content represents a read-only or read-write value, and its
11+
/// label identifies or describes the purpose of that value.
12+
/// The resulting element has a layout that's consistent with other framework
13+
/// controls and automatically adapts to its container, like a form or toolbar.
14+
/// Some styles of labeled content also apply styling or behaviors to the value
15+
/// content, like making ``Text`` views selectable.
16+
///
17+
/// The following example associates a label with a custom view and has
18+
/// a layout that matches the label of the ``Picker``:
19+
///
20+
/// Form {
21+
/// LabeledContent("Custom Value") {
22+
/// MyCustomView(value: $value)
23+
/// }
24+
/// Picker("Selected Value", selection: $selection) {
25+
/// Text("Option 1").tag(1)
26+
/// Text("Option 2").tag(2)
27+
/// }
28+
/// }
29+
///
30+
/// ### Custom view labels
31+
///
32+
/// You can assemble labeled content with an explicit view for its label
33+
/// using the ``init(content:label:)`` initializer. For example, you can
34+
/// rewrite the previous labeled content example using a ``Text`` view:
35+
///
36+
/// LabeledContent {
37+
/// MyCustomView(value: $value)
38+
/// } label: {
39+
/// Text("Custom Value")
40+
/// }
41+
///
42+
/// The `label` view builder accepts any kind of view, like a ``Label``:
43+
///
44+
/// LabeledContent {
45+
/// MyCustomView(value: $value)
46+
/// } label: {
47+
/// Label("Custom Value", systemImage: "hammer")
48+
/// }
49+
///
50+
/// For cases where adding a subtitle to the label is desired, use a view
51+
/// builder that creates multiple `Text` views where the first text represents
52+
/// the title and the second text represents the subtitle:
53+
///
54+
/// LabeledContent {
55+
/// MyCustomView(value: $value)
56+
/// } label: {
57+
/// Text("Custom Value")
58+
/// Text("Custom Subtitle Value")
59+
/// }
60+
///
61+
/// ### Textual labeled content
62+
///
63+
/// You can construct labeled content with string values or formatted values
64+
/// to create read-only displays of textual values:
65+
///
66+
/// Form {
67+
/// Section("Information") {
68+
/// LabeledContent("Name", value: person.name)
69+
/// LabeledContent("Age", value: person.age, format: .number)
70+
/// LabeledContent("Height", value: person.height,
71+
/// format: .measurement(width: .abbreviated))
72+
/// }
73+
/// if !person.pets.isEmpty {
74+
/// Section("Pets") {
75+
/// ForEach(pet) { pet in
76+
/// LabeledContent(pet.species, value: pet.name)
77+
/// }
78+
/// }
79+
/// }
80+
/// }
81+
///
82+
/// Wherever possible, OpenSwiftUI makes this text selectable.
83+
///
84+
/// ### Compositional elements
85+
///
86+
/// You can use labeled content as the label for other elements. For example,
87+
/// a ``NavigationLink`` can present a summary value for the destination it
88+
/// links to:
89+
///
90+
/// Form {
91+
/// NavigationLink(value: Settings.wifiDetail) {
92+
/// LabeledContent("Wi-Fi", value: ssidName)
93+
/// }
94+
/// }
95+
///
96+
/// In some cases, the styling of views used as the value content is
97+
/// specialized as well. For example, while a ``Toggle`` in an inset group
98+
/// form on macOS is styled as a switch by default, it's styled as a checkbox
99+
/// when used as a value element within a surrounding `LabeledContent`
100+
/// instance:
101+
///
102+
/// Form {
103+
/// LabeledContent("Source Control") {
104+
/// Toggle("Refresh local status automatically",
105+
/// isOn: $refreshLocalStatus)
106+
/// Toggle("Fetch and refresh server status automatically",
107+
/// isOn: $refreshServerStatus)
108+
/// Toggle("Add and remove files automatically",
109+
/// isOn: $addAndRemoveFiles)
110+
/// Toggle("Select files to commit automatically",
111+
/// isOn: $selectFiles)
112+
/// }
113+
/// }
114+
///
115+
/// ### Controlling label visibility
116+
///
117+
/// A label communicates the identity or purpose of the value, which is
118+
/// important for accessibility. However, you might want to hide the label
119+
/// in the display, and some controls or contexts may visually hide their label
120+
/// by default. The ``View/labelsHidden()`` modifier allows controlling that
121+
/// visibility. The following example hides both labels, producing only a
122+
/// group of the two value views:
123+
///
124+
/// Group {
125+
/// LabeledContent("Custom Value") {
126+
/// MyCustomView(value: $value)
127+
/// }
128+
/// Picker("Selected Value", selection: $selection) {
129+
/// Text("Option 1").tag(1)
130+
/// Text("Option 2").tag(2)
131+
/// }
132+
/// }
133+
/// .labelsHidden()
134+
///
135+
/// ### Styling labeled content
136+
///
137+
/// You can set label styles using the ``View/labeledContentStyle(_:)``
138+
/// modifier. You can also build custom styles using ``LabeledContentStyle``.
139+
@available(OpenSwiftUI_v4_0, *)
140+
public struct LabeledContent<Label, Content> {
141+
var label: Label
142+
143+
var content: Content
144+
145+
var accessibilityPresentation: AccessibilityLabeledContentPresentation?
146+
}
147+
148+
@available(*, unavailable)
149+
extension LabeledContent: Sendable {}
150+
151+
@available(OpenSwiftUI_v4_0, *)
152+
extension LabeledContent: View where Label: View, Content: View {
153+
154+
/// Creates a standard labeled element, with a view that conveys
155+
/// the value of the element and a label.
156+
///
157+
/// - Parameters:
158+
/// - content: The view that conveys the value of the resulting labeled
159+
/// element.
160+
/// - label: The label that describes the purpose of the result.
161+
nonisolated public init(
162+
@ViewBuilder content: () -> Content,
163+
@ViewBuilder label: () -> Label
164+
) {
165+
self.content = content()
166+
self.label = label()
167+
self.accessibilityPresentation = nil
168+
}
169+
170+
public var body: some View {
171+
ResolvedLabeledContent(
172+
configuration: LabeledContentStyleConfiguration(
173+
label: .init(),
174+
content: .init(),
175+
accessibilityPresentation: accessibilityPresentation
176+
)
177+
).viewAlias(LabeledContentStyleConfiguration.Label.self) {
178+
label
179+
}.viewAlias(LabeledContentStyleConfiguration.Content.self) {
180+
content
181+
}
182+
}
183+
}
184+
185+
//@available(OpenSwiftUI_v4_0, *)
186+
//extension LabeledContent where Label == Text, Content: View {
187+
// public init(
188+
// _ titleKey: LocalizedStringKey,
189+
// @ViewBuilder content: () -> Content
190+
// ) {
191+
// _openSwiftUIUnimplementedFailure()
192+
// }
193+
//
194+
// @_disfavoredOverload
195+
// public init<S>(
196+
// _ title: S,
197+
// @ViewBuilder content: () -> Content
198+
// ) where S: StringProtocol {
199+
// _openSwiftUIUnimplementedFailure()
200+
// }
201+
//}
202+
//
203+
//@available(OpenSwiftUI_v4_0, *)
204+
//extension LabeledContent where Label == Text, Content == Text {
205+
// public init<S>(
206+
// _ titleKey: LocalizedStringKey,
207+
// value: S
208+
// ) where S: StringProtocol {
209+
// _openSwiftUIUnimplementedFailure()
210+
// }
211+
//
212+
// @_disfavoredOverload
213+
// public init<S1, S2>(
214+
// _ title: S1,
215+
// value: S2
216+
// ) where S1: StringProtocol, S2: StringProtocol {
217+
// _openSwiftUIUnimplementedFailure()
218+
// }
219+
//
220+
// public init<F>(
221+
// _ titleKey: LocalizedStringKey,
222+
// value: F.FormatInput,
223+
// format: F
224+
// ) where F: FormatStyle, F.FormatInput: Equatable, F.FormatOutput == String {
225+
// _openSwiftUIUnimplementedFailure()
226+
// }
227+
//
228+
// public init<S, F>(
229+
// _ title: S,
230+
// value: F.FormatInput,
231+
// format: F
232+
// ) where S: StringProtocol, F: FormatStyle, F.FormatInput: Equatable, F.FormatOutput == String {
233+
// _openSwiftUIUnimplementedFailure()
234+
// }
235+
//}
236+
//
237+
//@available(OpenSwiftUI_v4_0, *)
238+
//extension LabeledContent where Label == LabeledContentStyleConfiguration.Label, Content == LabeledContentStyleConfiguration.Content {
239+
// public init(_ configuration: LabeledContentStyleConfiguration) {
240+
// _openSwiftUIUnimplementedFailure()
241+
// }
242+
//}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//
2+
// LabeledView.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for 6.5.4
6+
// Status: Blocked by Text
7+
8+
@_spi(Private)
9+
@available(OpenSwiftUI_v4_0, *)
10+
@available(*, deprecated, renamed: "LabeledContent")
11+
public struct LabeledView<Label, Content>: View where Label: View, Content: View {
12+
var label: Label
13+
14+
var content: Content
15+
16+
public init(
17+
@ViewBuilder content: () -> Content,
18+
@ViewBuilder label: () -> Label
19+
) {
20+
self.label = label()
21+
self.content = content()
22+
}
23+
24+
public var body: some View {
25+
LabeledContent {
26+
content
27+
} label: {
28+
label
29+
}
30+
}
31+
}
32+
33+
@_spi(Private)
34+
@available(*, unavailable)
35+
extension LabeledView: Sendable {}
36+
37+
//@_spi(Private)
38+
//@available(OpenSwiftUI_v4_0, *)
39+
//@available(*, deprecated, renamed: "LabeledContent")
40+
//extension LabeledView where Label == Text {
41+
// public init(
42+
// _ titleKey: LocalizedStringKey,
43+
// @ViewBuilder content: () -> Content
44+
// ) {
45+
// _openSwiftUIUnimplementedFailure()
46+
// }
47+
//
48+
// @_disfavoredOverload
49+
// public init<S>(
50+
// _ title: S,
51+
// @ViewBuilder content: () -> Content
52+
// ) where S: StringProtocol {
53+
// _openSwiftUIUnimplementedFailure()
54+
// }
55+
//}
56+
//
57+
//@_spi(Private)
58+
//@available(OpenSwiftUI_v4_0, *)
59+
//@available(*, deprecated, renamed: "LabeledContent")
60+
//extension LabeledView where Label == Text, Content == Text {
61+
// public init<S>(
62+
// _ titleKey: LocalizedStringKey,
63+
// value: S
64+
// ) where S: StringProtocol {
65+
// _openSwiftUIUnimplementedFailure()
66+
// }
67+
//
68+
// @_disfavoredOverload
69+
// public init<S1, S2>(
70+
// _ title: S1,
71+
// value: S2
72+
// ) where S1: StringProtocol, S2: StringProtocol {
73+
// _openSwiftUIUnimplementedFailure()
74+
// }
75+
//
76+
// public init<F>(
77+
// _ titleKey: LocalizedStringKey,
78+
// value: F.FormatInput,
79+
// format: F
80+
// ) where F: FormatStyle, F.FormatInput: Equatable, F.FormatOutput == String {
81+
// _openSwiftUIUnimplementedFailure()
82+
// }
83+
//
84+
// public init<S, F>(
85+
// _ title: S,
86+
// value: F.FormatInput,
87+
// format: F
88+
// ) where S: StringProtocol, F: FormatStyle, F.FormatInput: Equatable, F.FormatOutput == String {
89+
// _openSwiftUIUnimplementedFailure()
90+
// }
91+
//}

0 commit comments

Comments
 (0)