Skip to content

Commit

Permalink
1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
nathantannar4 committed Jan 16, 2024
1 parent b4201ba commit 989cb78
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 58 deletions.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let package = Package(
.macCatalyst(.v13),
.tvOS(.v13),
.watchOS(.v6),
// .visionOS(.v1)
.visionOS(.v1)
],
products: [
.library(
Expand All @@ -19,7 +19,7 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/nathantannar4/Engine", from: "1.0.0"),
.package(url: "https://github.com/nathantannar4/Engine", from: "1.3.0"),
],
targets: [
.target(
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ let package = Package(
)
```

### Xcode Cloud / Github Actions / Fastlane / CI

[Engine](https://github.com/nathantannar4/Engine) includes a Swift macro, which requires user validation to enable or the build will fail. When configuring your CI, pass the flag `-skipMacroValidation` to `xcodebuild` to fix this.

## Introduction to Turbocharger

`Turbocharger` was started with the two goals. 1) To expand the standard API that SwiftUI provides to what many would commonly desired or need; and 2) To demonstrate how to use [Engine](https://github.com/nathantannar4/Engine) to make reusable components that are backwards compatible.
Expand Down
25 changes: 11 additions & 14 deletions Sources/Turbocharger/Sources/View/CALayerRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ public protocol CALayerRepresentable: View where Body == Never {
/// system calls the ``CALayerRepresentable/updateCALayer(_:context:)``
/// method.
///
@MainActor func makeCALayer(_ layer: CALayerType, context: Context)
@MainActor(unsafe) func makeCALayer(_ layer: CALayerType, context: Context)

/// Updates the layer with new information.
///
/// > Note: This protocol implementation is optional
///
@MainActor func updateCALayer(_ layer: CALayerType, context: Context)
@MainActor(unsafe) func updateCALayer(_ layer: CALayerType, context: Context)

associatedtype Coordinator = Void

@MainActor func makeCoordinator() -> Coordinator
@MainActor(unsafe) func makeCoordinator() -> Coordinator

/// Cleans up the layer in anticipation of it's removal.
@MainActor static func dismantleCALayer(_ layer: CALayerType, coordinator: Coordinator)
@MainActor(unsafe) static func dismantleCALayer(_ layer: CALayerType, coordinator: Coordinator)

typealias Context = CALayerRepresentableContext<Self>
}
Expand Down Expand Up @@ -143,13 +143,10 @@ private struct CALayerRepresentableBody<

deinit {
if let layer {
let coordinator = coordinator
Task { @MainActor in
Representable.dismantleCALayer(
layer,
coordinator: coordinator
)
}
Representable.dismantleCALayer(
layer,
coordinator: coordinator
)
}
}
}
Expand All @@ -161,10 +158,10 @@ private struct CALayerRepresentableBody<
@available(watchOS, unavailable)
struct GradientLayer: CALayerRepresentable {
func makeCALayer(_ layer: CAGradientLayer, context: Context) {
#if os(iOS) || os(tvOS)
layer.colors = [UIColor.green.cgColor, UIColor.blue.cgColor]
#else
#if os(macOS)
layer.colors = [NSColor.green.cgColor, NSColor.blue.cgColor]
#else
layer.colors = [UIColor.green.cgColor, UIColor.blue.cgColor]
#endif
}

Expand Down
36 changes: 20 additions & 16 deletions Sources/Turbocharger/Sources/View/LabeledView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ import SwiftUI
import Engine

/// The style for ``LabeledView``
@available(iOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 13.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 9.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(visionOS, introduced: 1.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
public protocol LabeledViewStyle: ViewStyle where Configuration == LabeledViewStyleConfiguration {
associatedtype Configuration = Configuration
}

/// The configuration parameters for ``LabeledView``
@available(iOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContentStyleConfiguration with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 13.0, message: "Please use the built in LabeledContentStyleConfiguration with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContentStyleConfiguration with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 9.0, message: "Please use the built in LabeledContentStyleConfiguration with LabeledContent")
@available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(visionOS, introduced: 1.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@frozen
public struct LabeledViewStyleConfiguration {
/// A type-erased label of a ``LabeledView``
Expand All @@ -31,10 +33,11 @@ public struct LabeledViewStyleConfiguration {
}

/// A backwards compatible port of `LabeledContent`
@available(iOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 13.0, message: "Please use the built in LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 9.0, message: "Please use the built in LabeledContent")
@available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(visionOS, introduced: 1.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
public struct LabeledView<Label: View, Content: View>: View {
var label: Label
var content: Content
Expand Down Expand Up @@ -73,10 +76,11 @@ private struct LabeledViewBody: ViewStyledView {
}

extension View {
@available(iOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 13.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 16.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 9.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
@available(visionOS, introduced: 1.0, deprecated: 100000.0, message: "Please use the built in LabeledContentStyle with LabeledContent")
public func labeledViewStyle<Style: LabeledViewStyle>(_ style: Style) -> some View {
styledViewStyle(LabeledViewBody.self, style: style)
}
Expand Down
168 changes: 168 additions & 0 deletions Sources/Turbocharger/Sources/View/PlatformViewRepresentable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//
// Copyright (c) Nathan Tannar
//

import SwiftUI
import Engine

#if !os(watchOS)

/// A protocol for defining a `NSViewRepresentable`/`UIViewRepresentable`
/// that has a backwards compatible `sizeThatFits`
public protocol PlatformViewRepresentable: DynamicProperty, View where Body == Never {

#if os(macOS)
associatedtype PlatformView: NSView
#else
associatedtype PlatformView: UIView
#endif

@MainActor(unsafe) func makeView(context: Context) -> PlatformView
@MainActor(unsafe) func updateView(_ view: PlatformView, context: Context)
@MainActor(unsafe) func sizeThatFits(_ proposal: ProposedSize, view: PlatformView) -> CGSize?
@MainActor(unsafe) static func dismantleView(_ view: PlatformView, coordinator: Coordinator)

associatedtype Coordinator = Void
@MainActor(unsafe) func makeCoordinator() -> Coordinator

typealias Context = _PlatformViewRepresentableBody<Self>.Context
}

extension PlatformViewRepresentable {
public var body: Never {
bodyError()
}

private var content: _PlatformViewRepresentableBody<Self> {
_PlatformViewRepresentableBody(representable: self)
}

public static func _makeView(
view: _GraphValue<Self>,
inputs: _ViewInputs
) -> _ViewOutputs {
_PlatformViewRepresentableBody<Self>._makeView(view: view[\.content], inputs: inputs)
}

public static func _makeViewList(
view: _GraphValue<Self>,
inputs: _ViewListInputs
) -> _ViewListOutputs {
_PlatformViewRepresentableBody<Self>._makeViewList(view: view[\.content], inputs: inputs)
}

@available(iOS 14.0, macOS 11.0, tvOS 14.0, *)
public static func _viewListCount(
inputs: _ViewListCountInputs
) -> Int? {
_PlatformViewRepresentableBody<Self>._viewListCount(inputs: inputs)
}
}

#if os(macOS)
public struct _PlatformViewRepresentableBody<
Representable: PlatformViewRepresentable
>: NSViewRepresentable {

var representable: Representable

public func makeNSView(
context: Context
) -> Representable.PlatformView {
representable.makeView(context: context)
}

public func updateNSView(
_ nsView: Representable.PlatformView,
context: Context
) {
representable.updateView(nsView, context: context)
}

@available(macOS 13.0, iOS 16.0, tvOS 16.0, *)
public func sizeThatFits(
_ proposal: ProposedViewSize,
nsView: Representable.PlatformView,
context: Context
) -> CGSize? {
representable.sizeThatFits(ProposedSize(proposal), view: nsView)
}

public func _overrideSizeThatFits(
_ size: inout CGSize,
in proposedSize: _ProposedSize,
nsView: Representable.PlatformView
) {
if #available(macOS 13.0, iOS 16.0, tvOS 16.0, *) {
// Already handled
} else if let sizeThatFits = representable.sizeThatFits(ProposedSize(proposedSize), view: nsView) {
size = sizeThatFits
}
}

public static func dismantleNSView(
_ nsView: Representable.PlatformView,
coordinator: Coordinator
) {
Representable.dismantleView(nsView, coordinator: coordinator)
}

public func makeCoordinator() -> Representable.Coordinator {
representable.makeCoordinator()
}
}
#else
public struct _PlatformViewRepresentableBody<
Representable: PlatformViewRepresentable
>: UIViewRepresentable {

var representable: Representable

public func makeUIView(
context: Context
) -> Representable.PlatformView {
representable.makeView(context: context)
}

public func updateUIView(
_ uiView: Representable.PlatformView,
context: Context
) {
representable.updateView(uiView, context: context)
}

@available(iOS 16.0, tvOS 16.0, watchOS 9.0, *)
public func sizeThatFits(
_ proposal: ProposedViewSize,
uiView: Representable.PlatformView,
context: Context
) -> CGSize? {
representable.sizeThatFits(ProposedSize(proposal), view: uiView)
}

public func _overrideSizeThatFits(
_ size: inout CGSize,
in proposedSize: _ProposedSize,
uiView: Representable.PlatformView
) {
if #available(iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
// Already handled
} else if let sizeThatFits = representable.sizeThatFits(ProposedSize(proposedSize), view: uiView) {
size = sizeThatFits
}
}

public static func dismantleUIView(
_ uiView: Representable.PlatformView,
coordinator: Coordinator
) {
Representable.dismantleView(uiView, coordinator: coordinator)
}

public func makeCoordinator() -> Representable.Coordinator {
representable.makeCoordinator()
}
}
#endif

#endif // !os(watchOS)
2 changes: 1 addition & 1 deletion Sources/Turbocharger/Sources/View/ProposedSize.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public struct ProposedSize: Equatable {
}

public func toCoreGraphics() -> CGSize {
#if os(iOS) || os(tvOS)
#if os(iOS) || os(tvOS) || os(visionOS)
return CGSize(width: width ?? UIView.noIntrinsicMetric, height: height ?? UIView.noIntrinsicMetric)
#elseif os(watchOS)
return CGSize(width: width ?? -1, height: height ?? -1)
Expand Down
3 changes: 1 addition & 2 deletions Sources/Turbocharger/Sources/View/ProposedSizeObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ public struct ProposedSizeObserver: ViewModifier {
GeometryReader { proxy in
Color.clear
.hidden()
.onAppear { size = ProposedSize(size: proxy.size) }
.onAppearAndChange(of: proxy.size) { size = ProposedSize(size: $0) }
.onDisappear { size = .unspecified }
.onChange(of: proxy.size) { size = ProposedSize(size: $0) }
}
)
}
Expand Down
53 changes: 53 additions & 0 deletions Sources/Turbocharger/Sources/ViewModifier/OnAppearAndChange.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Copyright (c) Nathan Tannar
//

import SwiftUI
import Engine

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@frozen
public struct OnAppearAndChangeModifier<
Value: Equatable
>: VersionedViewModifier {

@usableFromInline
var value: Value

@usableFromInline
var action: (Value) -> Void

@inlinable
public init(value: Value, action: @escaping (Value) -> Void) {
self.value = value
self.action = action
}

@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *)
public func v5Body(content: Content) -> some View {
content
.onChange(of: value, initial: true) { _, newValue in
action(newValue)
}
}

#if !os(visionOS)
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public func v2Body(content: Content) -> some View {
content
.onAppear { action(value) }
.onChange(of: value, perform: action)
}
#endif
}

extension View {

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public func onAppearAndChange<V: Equatable>(
of value: V,
perform action: @escaping (V) -> Void
) -> some View {
modifier(OnAppearAndChangeModifier(value: value, action: action))
}
}
Loading

0 comments on commit 989cb78

Please sign in to comment.