From a50ccdb74ee186bb238e22cab3209186a46249de Mon Sep 17 00:00:00 2001 From: Nathan Tannar Date: Wed, 7 Dec 2022 22:24:36 -0800 Subject: [PATCH] 0.1.2 --- .../xcschemes/Turbocharger.xcscheme | 67 ++++++++++++ .../Alignment/VariadicAlignmentID.swift | 103 ++++++++++++++++++ .../Sources/Core/AlignmentKey.swift | 45 -------- .../DynamicProperty/BindingTransform.swift | 3 + .../DynamicProperty/FormatTransform.swift | 2 + .../DynamicProperty/IsNilTransform.swift | 6 + .../DynamicProperty/IsNotNilTransform.swift | 6 + .../DynamicProperty/MapTransform.swift | 3 + .../OptionalObservedObject.swift | 57 ++++++++++ .../DynamicProperty/OptionalStateObject.swift | 57 ++++++++++ .../DynamicProperty/PublishedState.swift | 51 +++++++++ .../DynamicProperty/UnwrapTransform.swift | 2 + .../OptionalPreferenceKey.swift | 1 + .../Sources/View/LabeledView.swift | 2 +- .../ViewModifier/AlignmentOffset.swift | 37 ++++--- .../Sources/ViewModifier/Badge.swift | 86 ++++++--------- .../Sources/ViewModifier/Hidden.swift | 3 + .../Sources/ViewModifier/Mask.swift | 3 + .../Sources/ViewModifier/SafeArea.swift | 9 ++ 19 files changed, 429 insertions(+), 114 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Turbocharger.xcscheme create mode 100644 Sources/Turbocharger/Sources/Alignment/VariadicAlignmentID.swift delete mode 100644 Sources/Turbocharger/Sources/Core/AlignmentKey.swift create mode 100644 Sources/Turbocharger/Sources/DynamicProperty/OptionalObservedObject.swift create mode 100644 Sources/Turbocharger/Sources/DynamicProperty/OptionalStateObject.swift create mode 100644 Sources/Turbocharger/Sources/DynamicProperty/PublishedState.swift rename Sources/Turbocharger/Sources/{Core => PreferenceKey}/OptionalPreferenceKey.swift (90%) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Turbocharger.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Turbocharger.xcscheme new file mode 100644 index 0000000..55394a0 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Turbocharger.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/Turbocharger/Sources/Alignment/VariadicAlignmentID.swift b/Sources/Turbocharger/Sources/Alignment/VariadicAlignmentID.swift new file mode 100644 index 0000000..56e09f5 --- /dev/null +++ b/Sources/Turbocharger/Sources/Alignment/VariadicAlignmentID.swift @@ -0,0 +1,103 @@ +// +// Copyright (c) Nathan Tannar +// + +import SwiftUI + +/// An `AlignmentID` that is resolved from multiple values +/// +/// > Tip: Use ``VariadicAlignmentID`` to create alignments +/// similar to `.firstTextBaseline` +public protocol VariadicAlignmentID: AlignmentID { + static func reduce(value: inout CGFloat?, n: Int, nextValue: CGFloat) +} + +private struct DefaultAlignmentID: AlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { 0 } +} + +extension VariadicAlignmentID { + public static func reduce(value: inout CGFloat?, n: Int, nextValue: CGFloat) { + DefaultAlignmentID._combineExplicit(childValue: nextValue, n, into: &value) + } + + public static func _combineExplicit( + childValue: CGFloat, + _ n: Int, + into parentValue: inout CGFloat? + ) { + reduce(value: &parentValue, n: n, nextValue: childValue) + } +} + +extension View { + + /// A modifier that transforms a vertical alignment to another + @inlinable + public func alignmentGuide( + _ g: VerticalAlignment, + value: VerticalAlignment + ) -> some View { + alignmentGuide(g) { $0[value] } + } + + /// A modifier that transforms a horizontal alignment to another + @inlinable + public func alignmentGuide( + _ g: HorizontalAlignment, + value: HorizontalAlignment + ) -> some View { + alignmentGuide(g) { $0[value] } + } +} + +// MARK: - Previews + +struct SecondTextBaseline: VariadicAlignmentID { + static func defaultValue(in context: ViewDimensions) -> CGFloat { + context[.firstTextBaseline] + } + + static func reduce(value: inout CGFloat?, n: Int, nextValue: CGFloat) { + if n == 1 { + value = nextValue + } + } +} + +extension VerticalAlignment { + static let secondTextBaseline = VerticalAlignment(SecondTextBaseline.self) +} + +struct VariadicAlignmentID_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 48) { + HStack(alignment: .firstTextBaseline) { + Text("Label") + + VStack(alignment: .trailing) { + Text("One") + Text("Two") + Text("Three") + } + .font(.title) + } + + HStack(alignment: .secondTextBaseline) { + Text("Label") + + VStack(alignment: .trailing) { + Group { + Text("One") + Text("Two") + Text("Three") + } + .alignmentGuide(.secondTextBaseline) { d in + d[VerticalAlignment.firstTextBaseline] + } + } + .font(.title) + } + } + } +} diff --git a/Sources/Turbocharger/Sources/Core/AlignmentKey.swift b/Sources/Turbocharger/Sources/Core/AlignmentKey.swift deleted file mode 100644 index e9d2c57..0000000 --- a/Sources/Turbocharger/Sources/Core/AlignmentKey.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright (c) Nathan Tannar -// - -import SwiftUI - -public protocol AlignmentKey: AlignmentID { - static func reduce(value: inout CGFloat?, n: Int, nextValue: CGFloat) -} - -private struct AbstractAlignmentKey: AlignmentID { - static func defaultValue(in context: ViewDimensions) -> CGFloat { 0 } -} - -extension AlignmentKey { - public static func reduce(value: inout CGFloat?, n: Int, nextValue: CGFloat) { - AbstractAlignmentKey._combineExplicit(childValue: nextValue, n, into: &value) - } - - public static func _combineExplicit( - childValue: CGFloat, - _ n: Int, - into parentValue: inout CGFloat? - ) { - reduce(value: &parentValue, n: n, nextValue: childValue) - } -} - -extension View { - @inlinable - public func alignmentGuide( - _ g: VerticalAlignment, - value: VerticalAlignment - ) -> some View { - alignmentGuide(g) { $0[value] } - } - - @inlinable - public func alignmentGuide( - _ g: HorizontalAlignment, - value: HorizontalAlignment - ) -> some View { - alignmentGuide(g) { $0[value] } - } -} diff --git a/Sources/Turbocharger/Sources/DynamicProperty/BindingTransform.swift b/Sources/Turbocharger/Sources/DynamicProperty/BindingTransform.swift index e737c62..8a6ea19 100644 --- a/Sources/Turbocharger/Sources/DynamicProperty/BindingTransform.swift +++ b/Sources/Turbocharger/Sources/DynamicProperty/BindingTransform.swift @@ -5,6 +5,7 @@ import SwiftUI import os.log +/// A protocol for defining a transform for a `Binding` public protocol BindingTransform { associatedtype Input associatedtype Output @@ -14,6 +15,8 @@ public protocol BindingTransform { } extension Binding { + + /// Projects a `Binding` with the `transform` @inlinable public func projecting( _ transform: Transform diff --git a/Sources/Turbocharger/Sources/DynamicProperty/FormatTransform.swift b/Sources/Turbocharger/Sources/DynamicProperty/FormatTransform.swift index 4040fdd..d10aaa8 100644 --- a/Sources/Turbocharger/Sources/DynamicProperty/FormatTransform.swift +++ b/Sources/Turbocharger/Sources/DynamicProperty/FormatTransform.swift @@ -4,6 +4,8 @@ import SwiftUI +/// A ``BindingTransform`` that transforms the value +/// with a `ParseableFormatStyle` @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) public struct FormatTransform: BindingTransform where F.FormatInput: Equatable, F.FormatOutput == String { public typealias Input = F.FormatInput diff --git a/Sources/Turbocharger/Sources/DynamicProperty/IsNilTransform.swift b/Sources/Turbocharger/Sources/DynamicProperty/IsNilTransform.swift index 3a64358..2bbce76 100644 --- a/Sources/Turbocharger/Sources/DynamicProperty/IsNilTransform.swift +++ b/Sources/Turbocharger/Sources/DynamicProperty/IsNilTransform.swift @@ -4,6 +4,10 @@ import SwiftUI +/// A ``BindingTransform`` that transforms the value to a `Bool` +/// +/// The transform will return `true` when the value is `nil`. If the projected +/// value is set to `true`, the value will be set to `nil`. @frozen public struct IsNilTransform: BindingTransform { @@ -23,6 +27,8 @@ public struct IsNilTransform: BindingTransform { } extension Binding { + + /// A ``BindingTransform`` that transforms the value to `true` when `nil` @inlinable public func isNil() -> Binding where Optional == Value { projecting(IsNilTransform()) diff --git a/Sources/Turbocharger/Sources/DynamicProperty/IsNotNilTransform.swift b/Sources/Turbocharger/Sources/DynamicProperty/IsNotNilTransform.swift index 5b98b93..00f0c08 100644 --- a/Sources/Turbocharger/Sources/DynamicProperty/IsNotNilTransform.swift +++ b/Sources/Turbocharger/Sources/DynamicProperty/IsNotNilTransform.swift @@ -4,6 +4,10 @@ import SwiftUI +/// A ``BindingTransform`` that transforms the value to a `Bool` +/// +/// The transform will return `true` when the value is not `nil`. If the projected +/// value is set to `false`, the value will be set to `nil`. @frozen public struct IsNotNilTransform: BindingTransform { @@ -23,6 +27,8 @@ public struct IsNotNilTransform: BindingTransform { } extension Binding { + + /// A ``BindingTransform`` that transforms the value to `true` when not `nil` @inlinable public func isNotNil() -> Binding where Optional == Value { projecting(IsNotNilTransform()) diff --git a/Sources/Turbocharger/Sources/DynamicProperty/MapTransform.swift b/Sources/Turbocharger/Sources/DynamicProperty/MapTransform.swift index 6414a04..b23fb87 100644 --- a/Sources/Turbocharger/Sources/DynamicProperty/MapTransform.swift +++ b/Sources/Turbocharger/Sources/DynamicProperty/MapTransform.swift @@ -4,6 +4,7 @@ import SwiftUI +/// A ``BindingTransform`` that transforms the value from a `WritableKeyPath` @frozen public struct MapTransform: BindingTransform { @@ -27,6 +28,8 @@ public struct MapTransform: BindingTransform { } extension Binding { + + /// A ``BindingTransform`` that transforms the value from a `WritableKeyPath` @inlinable public func map(_ keyPath: WritableKeyPath) -> Binding { projecting(MapTransform(keyPath: keyPath)) diff --git a/Sources/Turbocharger/Sources/DynamicProperty/OptionalObservedObject.swift b/Sources/Turbocharger/Sources/DynamicProperty/OptionalObservedObject.swift new file mode 100644 index 0000000..6fb5904 --- /dev/null +++ b/Sources/Turbocharger/Sources/DynamicProperty/OptionalObservedObject.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) Nathan Tannar +// + +import SwiftUI +import Combine + +/// A property wrapper that subscribes to an optional observable +/// object and invalidates a view whenever the observable object changes. +@propertyWrapper +@frozen +public struct OptionalObservedObject: DynamicProperty { + + @usableFromInline + class Storage: ObservableObject { + weak var value: ObjectType? { + didSet { + if oldValue !== value { + value.map { bind(to: $0) } + objectWillChange.send() + } + } + } + + var cancellable: AnyCancellable? + + @usableFromInline + init(value: ObjectType?) { + self.value = value + value.map { bind(to: $0) } + } + + func bind(to object: ObjectType) { + cancellable = object.objectWillChange + .sink { [unowned self] _ in + self.objectWillChange.send() + } + } + } + + @usableFromInline + var storage: ObservedObject + + @inlinable + public init(wrappedValue: ObjectType?) { + storage = ObservedObject(wrappedValue: Storage(value: wrappedValue)) + } + + public var wrappedValue: ObjectType? { + get { storage.wrappedValue.value } + nonmutating set { storage.wrappedValue.value = newValue } + } + + public var projectedValue: Binding { + storage.projectedValue.value + } +} diff --git a/Sources/Turbocharger/Sources/DynamicProperty/OptionalStateObject.swift b/Sources/Turbocharger/Sources/DynamicProperty/OptionalStateObject.swift new file mode 100644 index 0000000..e5f23b9 --- /dev/null +++ b/Sources/Turbocharger/Sources/DynamicProperty/OptionalStateObject.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) Nathan Tannar +// + +import SwiftUI +import Combine + +/// A property wrapper that instantiates an optional observable object +/// and invalidates a view whenever the observable object changes. +@propertyWrapper +@frozen +public struct OptionalStateObject: DynamicProperty { + + @usableFromInline + class Storage: ObservableObject { + var value: ObjectType? { + didSet { + if oldValue !== value { + value.map { bind(to: $0) } + objectWillChange.send() + } + } + } + + var cancellable: AnyCancellable? + + @usableFromInline + init(value: ObjectType?) { + self.value = value + value.map { bind(to: $0) } + } + + func bind(to object: ObjectType) { + cancellable = object.objectWillChange + .sink { [unowned self] _ in + self.objectWillChange.send() + } + } + } + + @usableFromInline + var storage: ObservedObject + + @inlinable + public init(wrappedValue: ObjectType?) { + storage = ObservedObject(wrappedValue: Storage(value: wrappedValue)) + } + + public var wrappedValue: ObjectType? { + get { storage.wrappedValue.value } + nonmutating set { storage.wrappedValue.value = newValue } + } + + public var projectedValue: Binding { + storage.projectedValue.value + } +} diff --git a/Sources/Turbocharger/Sources/DynamicProperty/PublishedState.swift b/Sources/Turbocharger/Sources/DynamicProperty/PublishedState.swift new file mode 100644 index 0000000..83ff4da --- /dev/null +++ b/Sources/Turbocharger/Sources/DynamicProperty/PublishedState.swift @@ -0,0 +1,51 @@ +// +// Copyright (c) Nathan Tannar +// + +import SwiftUI +import Combine + +/// A property wrapper that can read and write a value but does +/// not invalidate a view when changed. +/// +/// > Tip: Use ``PublishedState`` to improve performance +/// when your view does not need to be invalidated for every change. +/// Instead, use ``View/onReceive`` with ``PublishedState/publisher`` +/// +@propertyWrapper +@frozen +public struct PublishedState: DynamicProperty { + + public typealias Publisher = Published.Publisher + + @usableFromInline + final class Storage: ObservableObject { + @Published var value: Value + + @usableFromInline + init(value: Value) { + self.value = value + } + } + + @usableFromInline + var storage: State + + @inlinable + public init(wrappedValue: Value) { + storage = State(wrappedValue: Storage(value: wrappedValue)) + } + + public var wrappedValue: Value { + get { storage.wrappedValue.value } + nonmutating set { storage.wrappedValue.value = newValue } + } + + public var projectedValue: Binding { + storage.projectedValue.value + } + + public var publisher: Publisher { + storage.wrappedValue.$value + } +} diff --git a/Sources/Turbocharger/Sources/DynamicProperty/UnwrapTransform.swift b/Sources/Turbocharger/Sources/DynamicProperty/UnwrapTransform.swift index 8858e70..a7124f9 100644 --- a/Sources/Turbocharger/Sources/DynamicProperty/UnwrapTransform.swift +++ b/Sources/Turbocharger/Sources/DynamicProperty/UnwrapTransform.swift @@ -5,6 +5,8 @@ import SwiftUI extension Binding { + + /// Unwraps a `Binding` with an optional wrapped value to an optional `Binding` @inlinable public func unwrap() -> Binding? where Optional == Value { guard let value = self.wrappedValue else { return nil } diff --git a/Sources/Turbocharger/Sources/Core/OptionalPreferenceKey.swift b/Sources/Turbocharger/Sources/PreferenceKey/OptionalPreferenceKey.swift similarity index 90% rename from Sources/Turbocharger/Sources/Core/OptionalPreferenceKey.swift rename to Sources/Turbocharger/Sources/PreferenceKey/OptionalPreferenceKey.swift index 3b33057..5062c1e 100644 --- a/Sources/Turbocharger/Sources/Core/OptionalPreferenceKey.swift +++ b/Sources/Turbocharger/Sources/PreferenceKey/OptionalPreferenceKey.swift @@ -4,6 +4,7 @@ import SwiftUI +/// A preference key that defaults to `nil` public protocol OptionalPreferenceKey: PreferenceKey where Value == Optional { associatedtype WrappedValue: Equatable } diff --git a/Sources/Turbocharger/Sources/View/LabeledView.swift b/Sources/Turbocharger/Sources/View/LabeledView.swift index 8a0fdff..bc3902e 100644 --- a/Sources/Turbocharger/Sources/View/LabeledView.swift +++ b/Sources/Turbocharger/Sources/View/LabeledView.swift @@ -86,7 +86,7 @@ extension LabeledViewStyle where Self == DefaultLabeledViewStyle { } extension VerticalAlignment { - private struct LabelAlignment: AlignmentKey { + private struct LabelAlignment: AlignmentID { static func defaultValue(in context: ViewDimensions) -> CGFloat { context[VerticalAlignment.firstTextBaseline] } diff --git a/Sources/Turbocharger/Sources/ViewModifier/AlignmentOffset.swift b/Sources/Turbocharger/Sources/ViewModifier/AlignmentOffset.swift index aaa1e4c..f316977 100644 --- a/Sources/Turbocharger/Sources/ViewModifier/AlignmentOffset.swift +++ b/Sources/Turbocharger/Sources/ViewModifier/AlignmentOffset.swift @@ -4,29 +4,40 @@ import SwiftUI +/// A modifier that scales the edge alignment guides by an offset +public struct AlignmentGuideAdjustmentModifier: ViewModifier { + + public var anchor: UnitPoint + public var offset: CGPoint + + public init(anchor: UnitPoint, offset: CGPoint) { + self.anchor = anchor + self.offset = offset + } + + public func body(content: Content) -> some View { + content + .alignmentGuide(.top) { $0[.top] + ($0.height * anchor.y) + offset.y } + .alignmentGuide(.bottom) { $0[.bottom] - ($0.height * anchor.y) - offset.y } + .alignmentGuide(.trailing) { $0[.trailing] - ($0.width * anchor.x) - offset.x } + .alignmentGuide(.leading) { $0[.leading] + ($0.width * anchor.x) + offset.x } + } +} + extension View { + + /// A modifier that scales the edge alignment guides by an offset public func alignmentGuideAdjustment(anchor: UnitPoint) -> some View { modifier(AlignmentGuideAdjustmentModifier(anchor: anchor, offset: .zero)) } + /// A modifier that scales the edge alignment guides by an offset public func alignmentGuideAdjustment(x: CGFloat, y: CGFloat) -> some View { modifier(AlignmentGuideAdjustmentModifier(anchor: .zero, offset: CGPoint(x: x, y: y))) } + /// A modifier that scales the edge alignment guides by an offset public func alignmentGuideAdjustment(anchor: UnitPoint, x: CGFloat, y: CGFloat) -> some View { modifier(AlignmentGuideAdjustmentModifier(anchor: anchor, offset: CGPoint(x: x, y: y))) } } - -struct AlignmentGuideAdjustmentModifier: ViewModifier { - var anchor: UnitPoint - var offset: CGPoint - - func body(content: Content) -> some View { - content - .alignmentGuide(.top) { $0[.top] + ($0.height * anchor.y) + offset.y } - .alignmentGuide(.bottom) { $0[.bottom] - ($0.height * anchor.y) - offset.y } - .alignmentGuide(.trailing) { $0[.trailing] - ($0.width * anchor.x) - offset.x } - .alignmentGuide(.leading) { $0[.leading] + ($0.width * anchor.x) + offset.x } - } -} diff --git a/Sources/Turbocharger/Sources/ViewModifier/Badge.swift b/Sources/Turbocharger/Sources/ViewModifier/Badge.swift index e5532dc..c6ee980 100644 --- a/Sources/Turbocharger/Sources/ViewModifier/Badge.swift +++ b/Sources/Turbocharger/Sources/ViewModifier/Badge.swift @@ -4,13 +4,14 @@ import SwiftUI +/// A modifier that adds a view as a badge @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) public struct BadgeModifier: ViewModifier { - var alignment: Alignment - var anchor: UnitPoint - var scale: CGFloat - var label: Label + public var alignment: Alignment + public var anchor: UnitPoint + public var scale: CGFloat + public var label: Label public init( alignment: Alignment, @@ -39,17 +40,20 @@ public struct BadgeModifier: ViewModifier { @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension View { + + /// A modifier that adds a view as a badge @inlinable public func badge( alignment: Alignment = .topTrailing, anchor: UnitPoint = UnitPoint(x: 0.25, y: 0.25), - scale: CGFloat = 1.2, + scale: CGFloat = 1.2, @ViewBuilder label: () -> Label ) -> some View { modifier( BadgeModifier( alignment: alignment, anchor: anchor, + scale: scale, label: label ) ) @@ -63,79 +67,51 @@ struct BadgeModifier_Previews: PreviewProvider { struct Badge: View { var body: some View { Circle() + .fill(Color.blue) + .frame(width: 40, height: 40) } } static var previews: some View { - VStack(spacing: 16) { - HStack { - Color.black - .frame(width: 44, height: 44) - .badge { - Badge() - } - - Color.black - .frame(width: 44, height: 44) - .badge(alignment: .bottomTrailing) { - Badge() - } - - Color.black - .frame(width: 44, height: 44) - .badge(alignment: .topLeading) { - Badge() - } - - Color.black - .frame(width: 44, height: 44) - .badge(alignment: .bottomLeading) { - Badge() - } - } + VStack(spacing: 44) { + Rectangle() + .badge(alignment: .topLeading) { + Badge() + } + .badge(alignment: .topTrailing) { + Badge() + } + .badge(alignment: .bottomLeading) { + Badge() + } + .badge(alignment: .bottomTrailing) { + Badge() + } + .shadow(color: .black, radius: 50, x: 0, y: 0) + .frame(width: 100, height: 100) HStack { Circle() - .frame(width: 44, height: 44) - .badge { + .badge(alignment: .topLeading) { Badge() } Circle() - .frame(width: 44, height: 44) - .badge(alignment: .bottomTrailing) { + .badge(alignment: .topTrailing) { Badge() } Circle() - .frame(width: 44, height: 44) - .badge(alignment: .topLeading) { - Badge() - } - - Circle() - .frame(width: 44, height: 44) .badge(alignment: .bottomLeading) { Badge() } - } - - HStack { - Color.black - .frame(width: 100, height: 100) - .badge(alignment: .bottomTrailing) { - Color.blue - .frame(width: 40, height: 40) - } Circle() - .frame(width: 100, height: 100) .badge(alignment: .bottomTrailing) { - Circle() - .fill(Color.blue) - .frame(width: 40, height: 40) + Badge() } } + .padding(.horizontal) } } } diff --git a/Sources/Turbocharger/Sources/ViewModifier/Hidden.swift b/Sources/Turbocharger/Sources/ViewModifier/Hidden.swift index 77de70a..53d5a57 100644 --- a/Sources/Turbocharger/Sources/ViewModifier/Hidden.swift +++ b/Sources/Turbocharger/Sources/ViewModifier/Hidden.swift @@ -4,6 +4,7 @@ import SwiftUI +/// A modifier that conditionally hides a view @frozen public struct HiddenModifier: ViewModifier { public var isHidden: Bool @@ -23,6 +24,8 @@ public struct HiddenModifier: ViewModifier { } extension View { + + /// A modifier that conditionally hides a view @inlinable public func hidden(_ isHidden: Bool) -> some View { modifier(HiddenModifier(isHidden: isHidden)) diff --git a/Sources/Turbocharger/Sources/ViewModifier/Mask.swift b/Sources/Turbocharger/Sources/ViewModifier/Mask.swift index c1cfde2..082567f 100644 --- a/Sources/Turbocharger/Sources/ViewModifier/Mask.swift +++ b/Sources/Turbocharger/Sources/ViewModifier/Mask.swift @@ -6,6 +6,8 @@ import SwiftUI @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension View { + + /// Masks this view using the inverted alpha channel of the given view. @inlinable public func invertedMask( alignment: Alignment = .center, @@ -13,6 +15,7 @@ extension View { ) -> some View { self.mask( Rectangle() + .scale(100) .ignoresSafeArea() .overlay( mask() diff --git a/Sources/Turbocharger/Sources/ViewModifier/SafeArea.swift b/Sources/Turbocharger/Sources/ViewModifier/SafeArea.swift index a449f55..0c7c08f 100644 --- a/Sources/Turbocharger/Sources/ViewModifier/SafeArea.swift +++ b/Sources/Turbocharger/Sources/ViewModifier/SafeArea.swift @@ -4,6 +4,8 @@ import SwiftUI +/// A modifier that adds additional safe area padding +/// to the edges of a view. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @frozen public struct SafeAreaPaddingModifier: ViewModifier { @@ -38,16 +40,23 @@ public struct SafeAreaPaddingModifier: ViewModifier { @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) extension View { + + /// A modifier that adds additional safe area padding + /// to the edges of a view. @inlinable public func safeAreaPadding(_ edgeInsets: EdgeInsets) -> some View { modifier(SafeAreaPaddingModifier(edgeInsets)) } + /// A modifier that adds additional safe area padding + /// to the edges of a view. @inlinable public func safeAreaPadding(_ length: CGFloat = 16) -> some View { modifier(SafeAreaPaddingModifier(length)) } + /// A modifier that adds additional safe area padding + /// to the edges of a view. @inlinable public func safeAreaPadding(_ edges: Edge.Set, _ length: CGFloat = 16) -> some View { modifier(SafeAreaPaddingModifier(edges, length))