diff --git a/Sources/Engine/Sources/ConditionalShape.swift b/Sources/Engine/Sources/ConditionalShape.swift index a0d3e99..107ebde 100644 --- a/Sources/Engine/Sources/ConditionalShape.swift +++ b/Sources/Engine/Sources/ConditionalShape.swift @@ -49,6 +49,7 @@ extension ConditionalShape: Shape { // MARK: - Previews +@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) struct ConditionalShape_Previews: PreviewProvider { static var previews: some View { Preview() diff --git a/Sources/Engine/Sources/HostingController.swift b/Sources/Engine/Sources/HostingController.swift index 2f2a9f9..0323dc0 100644 --- a/Sources/Engine/Sources/HostingController.swift +++ b/Sources/Engine/Sources/HostingController.swift @@ -45,6 +45,28 @@ open class HostingController< } } + #if os(iOS) || os(tvOS) + @available(iOS 16.0, tvOS 16.0, *) + public var allowUIKitAnimationsForNextUpdate: Bool { + get { + guard let view else { return false } + let result = try? swift_getFieldValue("allowUIKitAnimationsForNextUpdate", Bool.self, view) + return result ?? false + } + set { + guard let view else { return } + try? swift_setFieldValue("allowUIKitAnimationsForNextUpdate", newValue, view) + } + } + + @available(iOS 16.0, tvOS 16.0, *) + public var automaticallyAllowUIKitAnimationsForNextUpdate: Bool { + get { shouldAutomaticallyAllowUIKitAnimationsForNextUpdate } + set { shouldAutomaticallyAllowUIKitAnimationsForNextUpdate = newValue } + } + private var shouldAutomaticallyAllowUIKitAnimationsForNextUpdate: Bool = true + #endif + public init(content: Content) { super.init(rootView: content) } @@ -59,6 +81,15 @@ open class HostingController< public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + open override func viewWillLayoutSubviews() { + if #available(iOS 16.0, tvOS 16.0, *), shouldAutomaticallyAllowUIKitAnimationsForNextUpdate, + UIView.inheritedAnimationDuration > 0 || view.layer.animationKeys()?.isEmpty == false + { + allowUIKitAnimationsForNextUpdate = true + } + super.viewWillLayoutSubviews() + } } #endif // !os(watchOS) diff --git a/Sources/Engine/Sources/ShapeAdapter.swift b/Sources/Engine/Sources/ShapeAdapter.swift index 3bd1d21..bed3d43 100644 --- a/Sources/Engine/Sources/ShapeAdapter.swift +++ b/Sources/Engine/Sources/ShapeAdapter.swift @@ -37,6 +37,7 @@ public struct ShapeAdapter: Shape { // MARK: - Previews +@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) struct ShapeAdapter_Previews: PreviewProvider { static var previews: some View { Preview() diff --git a/Sources/Engine/Sources/ViewInputConditionalContent.swift b/Sources/Engine/Sources/ViewInputConditionalContent.swift index fed7713..40805a1 100644 --- a/Sources/Engine/Sources/ViewInputConditionalContent.swift +++ b/Sources/Engine/Sources/ViewInputConditionalContent.swift @@ -28,6 +28,16 @@ public struct ViewInputConditionalContent< self.falseContent = otherwise() } + @inlinable + public init( + _ : Condition, + @ViewBuilder then: () -> TrueContent, + @ViewBuilder otherwise: () -> FalseContent + ) { + self.trueContent = then() + self.falseContent = otherwise() + } + public var body: Never { bodyError() } @@ -67,6 +77,13 @@ extension ViewInputConditionalContent where FalseContent == EmptyView { ) { self.init(Condition.self, then: then, otherwise: { EmptyView() }) } + + public init( + _ : Condition, + @ViewBuilder then: () -> TrueContent + ) { + self.init(Condition.self, then: then, otherwise: { EmptyView() }) + } } // MARK: - Previews diff --git a/Sources/Engine/Sources/ViewInputConditionalModifier.swift b/Sources/Engine/Sources/ViewInputConditionalModifier.swift index c57aafa..1be190d 100644 --- a/Sources/Engine/Sources/ViewInputConditionalModifier.swift +++ b/Sources/Engine/Sources/ViewInputConditionalModifier.swift @@ -28,6 +28,16 @@ public struct ViewInputConditionalModifier< self.falseModifier = otherwise() } + @inlinable + public init( + _ : Condition, + @ViewModifierBuilder then: () -> TrueModifier, + @ViewModifierBuilder otherwise: () -> FalseModifier + ) { + self.trueModifier = then() + self.falseModifier = otherwise() + } + public func body(content: Content) -> Never { bodyError() } @@ -70,6 +80,13 @@ extension ViewInputConditionalModifier where FalseModifier == EmptyModifier { ) { self.init(Condition.self, then: then, otherwise: { EmptyModifier() }) } + + public init( + _ : Condition, + @ViewModifierBuilder then: () -> TrueModifier + ) { + self.init(Condition.self, then: then, otherwise: { EmptyModifier() }) + } } // MARK: - Previews diff --git a/Sources/Engine/Sources/ViewInputs.swift b/Sources/Engine/Sources/ViewInputs.swift index 70126c3..e9fcfe3 100644 --- a/Sources/Engine/Sources/ViewInputs.swift +++ b/Sources/Engine/Sources/ViewInputs.swift @@ -8,9 +8,30 @@ import os.log @frozen public struct ViewInputs { - + + public struct Options: OptionSet { + public var rawValue: UInt32 + + public init(rawValue: UInt32) { self.rawValue = rawValue } + + public static func flag(_ index: Int) -> Options { + Options(rawValue: 1 << index) + } + + public static let isAxisHorizontal = Options(rawValue: 1 << 3) + } + public var _graphInputs: _GraphInputs + public var options: Options { + do { + let rawValue = try swift_getFieldValue("options", UInt32.self, _graphInputs) + return Options(rawValue: rawValue) + } catch { + return Options(rawValue: 0) + } + } + init(inputs: _GraphInputs) { self._graphInputs = inputs } @@ -66,6 +87,7 @@ public struct _ViewInputsLogModifier: ViewInputsModifier { #if DEBUG let log: String = { var message = "\n=== ViewInputs ===\n" + dump(inputs.options, to: &message) var ptr = inputs._graphInputs.customInputs.elements while let p = ptr { dump(p, to: &message) diff --git a/Sources/Engine/Sources/ViewStackAxisReader.swift b/Sources/Engine/Sources/ViewStackAxisReader.swift new file mode 100644 index 0000000..327dabb --- /dev/null +++ b/Sources/Engine/Sources/ViewStackAxisReader.swift @@ -0,0 +1,131 @@ +// +// Copyright (c) Nathan Tannar +// + +import SwiftUI + +/// A `View` that statically depends if its parent is a `VStack` or `HStack`. +/// +/// > Tip: You to make views that behave like `Divider` and `Spacer` +/// +@frozen +public struct ViewStackAxisReader: View { + + @usableFromInline + var vertical: Content + + @usableFromInline + var horizontal: Content + + @inlinable + public init( + @ViewBuilder content: (Axis) -> Content + ) { + self.vertical = content(.vertical) + self.horizontal = content(.horizontal) + } + + public var body: some View { + ViewInputConditionalContent(IsAxisHorizontal.self) { + horizontal + } otherwise: { + vertical + } + } +} + +private struct IsAxisHorizontal: ViewInputsCondition { + static func evaluate(_ inputs: ViewInputs) -> Bool { + inputs.options.contains(.isAxisHorizontal) + } +} + +// MARK: - Previews + +private struct CustomDivider: View { + + var scale: CGFloat = 1 + + @Environment(\.pixelLength) var pixelLength + + var body: some View { + ViewStackAxisReader { axis in + Capsule(style: .circular) + .frame( + width: axis == .horizontal ? scale * pixelLength : nil, + height: axis == .vertical ? scale * pixelLength : nil + ) + } + } +} + +@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) +struct ViewStackAxisReader_Previews: PreviewProvider { + static var previews: some View { + VStack(spacing: 12) { + HStack { + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + + CustomDivider(scale: 3) + + CustomDivider() + + Divider() + .overlay(Color.black) + } + + VStack { + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + + CustomDivider() + + Divider() + } + + LazyHStack { + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + } + + LazyVStack { + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + } + + ZStack { + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + } + + if #available(iOS 16.0, macOS 13.0, *) { + Grid(verticalSpacing: 8) { + CustomDivider() + + Divider() + + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + + GridRow { + ViewStackAxisReader { axis in + Text(axis == .horizontal ? "Horizontal" : "Vertical") + } + + CustomDivider() + + Divider() + } + } + } + } + .fixedSize() + } +}