diff --git a/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContent.swift b/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContent.swift index a6364c19..0990dcad 100644 --- a/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContent.swift +++ b/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContent.swift @@ -123,40 +123,62 @@ extension Backport where Wrapped == Any { /// /// You can set label styles using the ``View/labeledContentStyle(_:)`` /// modifier. You can also build custom styles using ``LabeledContentStyle``. - public struct LabeledContent { + public struct LabeledContent: View { + @EnvironmentContains(key: "LabelsHiddenKey") private var isHidden @Environment(\.backportLabeledContentStyle) private var style + let config: LabeledContentStyleConfiguration + public var body: some View { - style.makeBody(configuration: config) + style.makeBody(configuration: config.labelHidden(isHidden)) + } + + /// Creates labeled content based on a labeled content style configuration. + /// + /// You can use this initializer within the + /// ``LabeledContentStyle/makeBody(configuration:)`` method of a + /// ``LabeledContentStyle`` to create a labeled content instance. + /// This is useful for custom styles that only modify the current style, + /// as opposed to implementing a brand new style. + /// + /// For example, the following style adds a red border around the labeled + /// content, but otherwise preserves the current style: + /// + /// struct RedBorderLabeledContentStyle: LabeledContentStyle { + /// func makeBody(configuration: Configuration) -> some View { + /// LabeledContent(configuration) + /// .border(.red) + /// } + /// } + /// + /// - Parameter configuration: The properties of the labeled content + public init(_ config: Backport.LabeledContentStyleConfiguration) { + self.config = config } } } -extension Backport.LabeledContent where Wrapped == Any, Label == Backport.LabeledContentStyleConfiguration.Label, Content == Backport.LabeledContentStyleConfiguration.Content { +extension Backport.LabeledContent where Wrapped == Any, Label: View, Content: View { - /// Creates labeled content based on a labeled content style configuration. - /// - /// You can use this initializer within the - /// ``LabeledContentStyle/makeBody(configuration:)`` method of a - /// ``LabeledContentStyle`` to create a labeled content instance. - /// This is useful for custom styles that only modify the current style, - /// as opposed to implementing a brand new style. - /// - /// For example, the following style adds a red border around the labeled - /// content, but otherwise preserves the current style: + /// Creates a labeled view that generates its label from a localized string + /// key. /// - /// struct RedBorderLabeledContentStyle: LabeledContentStyle { - /// func makeBody(configuration: Configuration) -> some View { - /// LabeledContent(configuration) - /// .border(.red) - /// } - /// } + /// This initializer creates a ``Text`` label on your behalf, and treats the + /// localized key similar to ``Text/init(_:tableName:bundle:comment:)``. See + /// `Text` for more information about localizing strings. /// - /// - Parameter configuration: The properties of the labeled content - public init(_ config: Backport.LabeledContentStyleConfiguration) { - self.config = config + /// - Parameters: + /// - titleKey: The key for the view's localized title, that describes + /// the purpose of the view. + /// - content: The value content being labeled. + public init(@ViewBuilder content: () -> Content, @ViewBuilder label: () -> Label) { + config = .init( + label: label(), + content: content() + ) } + } extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content : View { @@ -173,10 +195,11 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content : /// the purpose of the view. /// - content: The value content being labeled. public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) { - config = .init( - label: Text(titleKey), - content: content() - ) + self.init { + content() + } label: { + Text(titleKey) + } } /// Creates a labeled view that generates its label from a string. @@ -189,32 +212,11 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content : /// - title: A string that describes the purpose of the view. /// - content: The value content being labeled. public init(_ title: S, @ViewBuilder content: () -> Content) where S: StringProtocol { - config = .init( - label: Text(title), - content: content() - ) - } - -} - -extension Backport.LabeledContent: View where Wrapped == Any, Label: View, Content: View { - - /// Creates a labeled view that generates its label from a localized string - /// key. - /// - /// This initializer creates a ``Text`` label on your behalf, and treats the - /// localized key similar to ``Text/init(_:tableName:bundle:comment:)``. See - /// `Text` for more information about localizing strings. - /// - /// - Parameters: - /// - titleKey: The key for the view's localized title, that describes - /// the purpose of the view. - /// - content: The value content being labeled. - public init(@ViewBuilder content: () -> Content, @ViewBuilder label: () -> Label) { - config = .init( - label: label(), - content: content() - ) + self.init { + content() + } label: { + Text(title) + } } } @@ -238,9 +240,11 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content = /// the purpose of the view. /// - value: The value being labeled. public init(_ titleKey: LocalizedStringKey, value: S) { - config = .init( - label: Text(titleKey), - content: Text(value)) + self.init { + Text(value) + } label: { + Text(titleKey) + } } /// Creates a labeled informational view. @@ -259,10 +263,11 @@ extension Backport.LabeledContent where Wrapped == Any, Label == Text, Content = /// - title: A string that describes the purpose of the view. /// - value: The value being labeled. public init(_ title: S1, value: S2) { - config = .init( - label: Text(title), - content: Text(value) - ) + self.init { + Text(value) + } label: { + Text(title) + } } } diff --git a/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContentStyleConfiguration.swift b/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContentStyleConfiguration.swift index 4a5586d8..29b19b55 100644 --- a/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContentStyleConfiguration.swift +++ b/Sources/SwiftUIBackports/Shared/LabeledContent/LabeledContentStyleConfiguration.swift @@ -8,53 +8,39 @@ extension Backport where Wrapped == Any { /// The properties of a labeled content instance. public struct LabeledContentStyleConfiguration { + private struct Label: View { + let isHidden: Bool + let content: Content - /// A type-erased label of a labeled content instance. - public struct Label: View { - @EnvironmentContains(key: "LabelsHiddenKey") private var isHidden - let view: AnyView public var body: some View { - if isHidden { - EmptyView() - } else { - view + if !isHidden { + content } } - init(_ view: V) { - self.view = .init(view) - } } - /// A type-erased content of a labeled content instance. - public struct Content: View { - @EnvironmentContains(key: "LabelsHiddenKey") private var isHidden - let view: AnyView - public var body: some View { - view - .foregroundColor(isHidden ? .primary : .secondary) - .frame(maxWidth: .infinity, alignment: isHidden ? .leading : .trailing) - } - init(_ view: V) { - self.view = .init(view) - } - } + var labelHidden: Bool = false + + private let _label: AnyView /// The label of the labeled content instance. - public let label: Label + public var label: some View { + Label(isHidden: labelHidden, content: _label) + } /// The content of the labeled content instance. - public let content: Content + public let content: AnyView internal init(label: L, content: C) { - self.label = .init(label) + _label = .init(label) self.content = .init(content) } - internal init(@ViewBuilder content: () -> C, @ViewBuilder label: () -> L) { - self.content = .init(content()) - self.label = .init(label()) + func labelHidden(_ hidden: Bool) -> Self { + var copy = self + copy.labelHidden = hidden + return copy } - } } diff --git a/Sources/SwiftUIBackports/Shared/LabeledContent/Styles/AutomaticLabeledContentStyle.swift b/Sources/SwiftUIBackports/Shared/LabeledContent/Styles/AutomaticLabeledContentStyle.swift index fdd4e634..382ab7c0 100644 --- a/Sources/SwiftUIBackports/Shared/LabeledContent/Styles/AutomaticLabeledContentStyle.swift +++ b/Sources/SwiftUIBackports/Shared/LabeledContent/Styles/AutomaticLabeledContentStyle.swift @@ -3,14 +3,26 @@ import SwiftUI extension Backport where Wrapped == Any { public struct AutomaticLabeledContentStyle: BackportLabeledContentStyle { - public func makeBody(configuration: Configuration) -> some View { - HStack(alignment: .firstTextBaseline) { - configuration.label - Spacer() - configuration.content - .multilineTextAlignment(.trailing) + private struct Content: View { + let configuration: Configuration + + var body: some View { + if configuration.labelHidden { + configuration.content + } else { + HStack(alignment: .firstTextBaseline) { + configuration.label + Spacer() + configuration.content + .foregroundColor(.secondary) + } + } } } + + public func makeBody(configuration: Configuration) -> some View { + Content(configuration: configuration) + } } }