Skip to content

Commit

Permalink
0.1.13
Browse files Browse the repository at this point in the history
  • Loading branch information
nathantannar4 committed Oct 24, 2023
1 parent f429892 commit 4ca197b
Show file tree
Hide file tree
Showing 24 changed files with 1,576 additions and 187 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ DerivedData/
.netrc
build.sh
Sources/EngineCore
Sources/EnginePrivate
Sources/EnginePrivate
Sources/EngineCore.xcframework.zip
6 changes: 4 additions & 2 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,10 @@
PRODUCT_BUNDLE_IDENTIFIER = nathantannar.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down Expand Up @@ -386,9 +387,10 @@
PRODUCT_BUNDLE_IDENTIFIER = nathantannar.Example;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
Expand Down
4 changes: 2 additions & 2 deletions Example/Example/UserInterfaceIdiomExamples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ struct IdiomText: UserInterfaceIdiomContent {
Text("TV")
}

var carBody: some View {
Text("Car")
var visionBody: some View {
Text("Vision")
}
}

Expand Down
3 changes: 3 additions & 0 deletions Example/Example/ViewStyleExamples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,9 @@ struct DoubleLabelStyle: LabeledViewStyle {
configuration.label
.border(Color.yellow, width: 2)

configuration.label
.border(Color.red, width: 2)

LabeledView {
configuration.content
} label: {
Expand Down
7 changes: 4 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ let package = Package(
.macOS(.v10_15),
.macCatalyst(.v13),
.tvOS(.v13),
.watchOS(.v6)
.watchOS(.v6),
// .visionOS(.v1),
],
products: [
.library(
Expand All @@ -25,8 +26,8 @@ let package = Package(
targets: [
.binaryTarget(
name: "EngineCore",
url: "https://github.com/nathantannar4/Engine/releases/download/0.1.11/EngineCore.xcframework.zip",
checksum: "fab9275d8e2eec39eb498a084b0818f3328078c90805b966816319ef4ad5d85d"
url: "https://github.com/nathantannar4/Engine/releases/download/0.1.13/EngineCore.xcframework.zip",
checksum: "803b5f45aa79fbaa03d42967434358ec6b609d1d5cbedc6f75d413326d6bc0f9"
),
.target(
name: "Engine",
Expand Down
105 changes: 95 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ For some sample code to get started with `Engine`, build and run the included "E

### Custom View Styles

```
```swift
public protocol ViewStyle {
associatedtype Configuration
associatedtype Body: View
Expand All @@ -86,7 +86,7 @@ A view style makes developing reusable components easier. This can be especially

You can use the `ViewStyle` APIs to make components that share common behavior and/or styling, such as font/colors, while allowing for complete customization of the appearance and layout. For example, this `StepperView` is a component that defaults to `Stepper` but allows for a different custom styling to be used.

```
```swift
// 1. Define the style
protocol StepperViewStyle: ViewStyle where Configuration == StepperViewStyleConfiguration { }

Expand Down Expand Up @@ -210,9 +210,84 @@ struct StepperViewBody: ViewStyledView {

[Open Examples](https://github.com/nathantannar4/Engine/blob/main/Example/Example/ViewStyleExamples.swift)

### Variadic Views
### View Input

```swift
public protocol ViewAlias: View where Body == Never {
associatedtype DefaultBody: View = EmptyView
@MainActor @ViewBuilder var defaultBody: DefaultBody { get }
}

extension View {

/// Statically type-erases `Source` to be resolved by the ``ViewAlias``.
@inlinable
public func viewAlias<
Alias: ViewAlias,
Source: View
>(
_ : Alias.Type,
@ViewBuilder source: () -> Source
) -> some View
}
```

A ``ViewAlias`` is can be defined statically by one of its ancestors. Because ``ViewAlias`` is guaranteed to be static it can be used for type-erasure without the performance impacts associated with `AnyView`.

### View Output

```swift
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol ViewOutputKey {
associatedtype Content: View = AnyView
typealias Value = ViewOutputList<Content>
static func reduce(value: inout Value, nextValue: () -> Value)
}

extension View {

/// A modifier that writes a `Source` view to a ``ViewOutputKey``
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@inlinable
public func viewOutput<
Key: ViewOutputKey,
Source: View
>(
_ : Key.Type,
@ViewBuilder source: () -> Source
) -> some View where Key.Content == Source
```

A `ViewOutputKey` allows for a descendent view to return one or more views to a parent view.

```swift
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol ViewOutputAlias: View where Body == Never {
associatedtype Content: View = AnyView
associatedtype DefaultBody: View = EmptyView
@MainActor @ViewBuilder var defaultBody: DefaultBody { get }
}

extension View {

/// Statically defines the `Source` to be resolved by the ``ViewOutputAlias``.
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@inlinable
public func viewOutputAlias<
Alias: ViewOutputAlias,
Source: View
>(
_ : Alias.Type,
@ViewBuilder source: () -> Source
) -> some View where Alias.Content == Source
}
```

A `ViewOutputAlias` is a more streamlined variant of `ViewOutputKey` that only supports returning a single view from a descendent.

### Variadic Views

```swift
@frozen
public struct VariadicViewAdapter<Source: View, Content: View>: View {

Expand All @@ -232,7 +307,7 @@ A variadic view allows many possibilities with SwiftUI to be unlocked, as it per

You can use `VariadicViewAdapter` to write components like a custom picker view.

```
```swift
enum Fruit: Hashable, CaseIterable {
case apple
case orange
Expand Down Expand Up @@ -284,11 +359,11 @@ struct PickerView<Selection: Hashable, Content: View>: View {

### Availability

```
```swift
public protocol VersionedView: View where Body == Never {
associatedtype V5Body: View = V4Body

@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)
@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, xrOS 1.0, *)
@ViewBuilder var v5Body: V5Body { get }

associatedtype V4Body: View = V3Body
Expand Down Expand Up @@ -320,7 +395,7 @@ Supporting multiple release versions for SwiftUI can be tricky. If a modifier or

You can use `VersionedViewModifier` to help adopt newer SwiftUI APIs with less friction. Such as adopting a new view type like `Grid`, while still supporting older iOS versions with a custom grid view; or using new view modifiers which due to the required `if #available(...)` checks can force you to refactor your code.

```
```swift
struct ContentView: VersionedView {
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
var v4Body: some View {
Expand All @@ -336,11 +411,21 @@ struct ContentView: VersionedView {
}
}

struct UnderlineIfAvailableModifier: VersionedViewModifier {
struct UnderlineModifier: VersionedViewModifier {
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
func v4Body(content: Content) -> some View {
content.underline()
}

// Add support for a semi-equivalent version for iOS 13-15
func v1Body(content: Content) -> some View {
content
.background(
Rectangle()
.frame(height: 1)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
)
}
}

struct UnderlineButtonStyle: ButtonStyle {
Expand All @@ -366,7 +451,7 @@ struct ContentView: View {

### Static Conditionals

```
```swift
public protocol StaticCondition {
static var value: Bool { get }
}
Expand Down Expand Up @@ -410,7 +495,7 @@ Should you ever have a modifier or view that is conditional upon a static flag,

You can use `StaticConditionalContent` to gate features or content to Debug or Testflight builds without impacting your production build performance.

```
```swift
struct IsDebug: StaticCondition {
static var value: Bool {
#if DEBUG
Expand Down
24 changes: 11 additions & 13 deletions Sources/Engine/Sources/AnyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,25 @@ import EngineCore

extension AnyView {

/// Creates a type-erased view from a type-erased value if that value is also a view
/// Creates a type-erased view from a type-erased value if that value is also a `View`
@_disfavoredOverload
public init?(_ content: Any) {
func project<T>(_ value: T) -> AnyView? {
let conformance = ViewProtocolDescriptor.conformance(of: T.self)!
var visitor = AnyViewVisitor(input: value)
conformance.visit(visitor: &visitor)
return visitor.output
}
guard let view = _openExistential(content, do: project) else {
guard let view = AnyView(visiting: content) else {
return nil
}
self = view
}
}

private struct AnyViewVisitor<Input>: ViewVisitor {
var input: Input
var output: AnyView!
// MARK: - Previews

struct AnyView_Previews: PreviewProvider {
static var previews: some View {
VStack {
AnyView(Optional<String>.none as Any)

mutating func visit<Content>(type: Content.Type) where Content: View {
output = AnyView(unsafeBitCast(input, to: Content.self))
let content: Any = Text("Hello, World")
AnyView(content)
}
}
}
48 changes: 48 additions & 0 deletions Sources/Engine/Sources/ConditionalContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ extension ConditionalContent: View where TrueContent: View, FalseContent: View {
}
}

extension ConditionalContent where TrueContent: View, FalseContent == EmptyView {
@inlinable
public init(
if condition: Bool,
@ViewBuilder then: () -> TrueContent
) {
self.init(if: condition, then: then, else: { EmptyView() })
}
}

extension ConditionalContent: Equatable where TrueContent: Equatable, FalseContent: Equatable {
public static func == (lhs: ConditionalContent<TrueContent, FalseContent>, rhs: ConditionalContent<TrueContent, FalseContent>) -> Bool {
switch (lhs.storage, rhs.storage) {
Expand All @@ -65,3 +75,41 @@ extension ConditionalContent: Equatable where TrueContent: Equatable, FalseConte
}
}
}

// MARK: - Previews

struct ConditionalContent_Previews: PreviewProvider {
struct Preview: View {
@State var condition = true

var body: some View {
VStack {
Toggle(
isOn: $condition.animation(.default),
label: { EmptyView() }
)
.labelsHidden()

ConditionalContent(if: condition) {
VStack {
content
}
} else: {
HStack {
content
}
}
}
}

@ViewBuilder
var content: some View {
Text("Hello")
Text("World")
}
}

static var previews: some View {
Preview()
}
}
Loading

0 comments on commit 4ca197b

Please sign in to comment.