Skip to content

Commit

Permalink
Avoid AnyView in the Counter code (#168)
Browse files Browse the repository at this point in the history
It's not needed there as `ViewBuilder` is smarter now.

The macOS SwiftUI SDK even with Xcode 12 still doesn't have the `@ViewBuilder` attribute on the `View` protocol, so we have to build for iOS on CI to make the native buld pass.

Resolves #167, which wasn't caused by protocol conformance problems, but by infinite recursion in the `_ConditionalContent` implementation of `body`.

Adds assorted formatting fixes, some are apparently caused by the newer `swiftformat` version.
  • Loading branch information
MaxDesiatov authored Jul 16, 2020
1 parent 31d90fd commit 8f23ac9
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 48 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ jobs:
run: |
set -ex
sudo xcode-select --switch /Applications/Xcode_12_beta.app/Contents/Developer/
xcodebuild -version
cd "TokamakDemo Native"
xcodebuild -scheme macOS
xcodebuild -scheme iOS -destination 'generic/platform=iOS' \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ struct Counter: View {
let limit: Int

var body: some View {
count < limit ?
AnyView(
VStack {
Button("Increment") { count += 1 }
Text("\(count)")
}
) : AnyView(
VStack { Text("Limit exceeded") }
)
if count < limit {
VStack {
Button("Increment") { count += 1 }
Text("\(count)")
}
.onAppear { print("Counter.VStack onAppear") }
.onDisappear { print("Counter.VStack onDisappear") }
} else {
VStack { Text("Limit exceeded") }
}
}
}
```
Expand Down Expand Up @@ -125,20 +126,20 @@ separate modules for platform-specific renderers. Currently, the only available
is `TokamakDOM`, but we intend to provide other renderers in the future, such as `TokamakHTML`
for static websites and server-side rendering. Tokamak users only need to import a renderer module
they would like to use, while `TokamakCore` is hidden as an "internal" `Tokamak` package target.
Unfortunately, Swift does not allow us to specify that certain symbols in `TokamakCore` are private
Unfortunately, Swift does not allow us to specify that certain symbols in `TokamakCore` are private
to a package, but they need to stay `public` for renderer modules to get access to them. Thus, the
current workaround is to mark those symbols with underscores in their names to indicate this. It
can be formulated as these "rules":

1. If a symbol is restricted to a module and has no `public` access control, no need for an underscore.
2. If a symbol is part of a public renderer module API (e.g. `TokamakDOM`), no need for an underscore,
users may use those symbols directly, and it is re-exported from `TokamakCore` by the renderer module
2. If a symbol is part of a public renderer module API (e.g. `TokamakDOM`), no need for an underscore,
users may use those symbols directly, and it is re-exported from `TokamakCore` by the renderer module
via `public typealias`.
3. If a function or a type have `public` on them only by necessity to make them available in `TokamakDOM`,
but unavailable to users (or not intended for public use), underscore is needed to indicate that.

The benefit of separate modules is that they allow us to provide separate renderers for different platforms.
Users can pick and choose what they want to use, e.g. purely static websites would use only `TokamakHTML`,
Users can pick and choose what they want to use, e.g. purely static websites would use only `TokamakHTML`,
single-page apps would use `TokamakDOM`, maybe in conjuction with `TokamakHTML` for pre-rendering. As we'd
like to try to implement a native renderer for Android at some point, probably in a separate `TokamakAndroid`
module, Android apps would use `TokamakAndroid` with no need to be aware of any of the web modules.
Expand Down Expand Up @@ -193,7 +194,7 @@ unacceptable behavior to conduct@tokamak.dev.

- Thanks to the [Swift community](https://swift.org/community/) for
building one of the best programming languages available!
- Thanks to everyone who developed [React](https://reactjs.org/) with its [reconciler/renderer
- Thanks to everyone who developed [React](https://reactjs.org/) with its [reconciler/renderer
architecture](https://reactjs.org/docs/codebase-overview.html#renderers) that inspired Tokamak
in the first place.
- Thanks to the designers of [the SwiftUI API](https://developer.apple.com/documentation/swiftui)
Expand All @@ -204,13 +205,13 @@ unacceptable behavior to conduct@tokamak.dev.
[ReSwift](https://github.com/ReSwift/ReSwift), [Katana
UI](https://github.com/BendingSpoons/katana-ui-swift) and
[Komponents](https://github.com/freshOS/Komponents) for inspiration!
SwiftUI is a trademark owned by Apple Inc. Software maintained as a part of the Tokamak project

SwiftUI is a trademark owned by Apple Inc. Software maintained as a part of the Tokamak project
is not affiliated with Apple Inc.

## License

Tokamak is available under the Apache 2.0 license.
Tokamak is available under the Apache 2.0 license.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down
2 changes: 1 addition & 1 deletion Sources/TokamakCore/Views/View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
public protocol View {
associatedtype Body: View

var body: Self.Body { get }
@ViewBuilder var body: Self.Body { get }
}

extension Never: View {
Expand Down
21 changes: 12 additions & 9 deletions Sources/TokamakCore/Views/ViewBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,28 @@ public struct _ConditionalContent<TrueContent, FalseContent>: View

let storage: Storage

@ViewBuilder
public var body: some View {
public var body: Never {
neverBody("_ConditionContent")
}
}

extension _ConditionalContent: GroupView {
public var children: [AnyView] {
switch storage {
case let .trueContent(view):
view
return [AnyView(view)]
case let .falseContent(view):
view
return [AnyView(view)]
}
}
}

// FIXME: Remove type erasure when https://github.com/swiftwasm/swift/issues/1379
// is resolved
extension Optional: View where Wrapped: View {
public var body: AnyView {
public var body: some View {
if let view = self {
return AnyView(view)
view
} else {
return AnyView(EmptyView())
EmptyView()
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/TokamakDOM/Views/SecureField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ extension SecureField: ViewDeferredToRenderer where Label == Text {
], listeners: [
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
"input": { event in
if let newValue = event.target.object?.value.string {
proxy.textBinding.wrappedValue = newValue
}
},
if let newValue = event.target.object?.value.string {
proxy.textBinding.wrappedValue = newValue
}
},
]))
}
}
8 changes: 4 additions & 4 deletions Sources/TokamakDOM/Views/TextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ extension TextField: ViewDeferredToRenderer where Label == Text {
"blur": { _ in proxy.onEditingChanged(false) },
"keypress": { event in if event.key == "Enter" { proxy.onCommit() } },
"input": { event in
if let newValue = event.target.object?.value.string {
proxy.textBinding.wrappedValue = newValue
}
},
if let newValue = event.target.object?.value.string {
proxy.textBinding.wrappedValue = newValue
}
},
]))
}
}
21 changes: 10 additions & 11 deletions Sources/TokamakDemo/Counter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,15 @@ public struct Counter: View {
let limit: Int

public var body: some View {
count < limit ?
AnyView(
VStack {
Button("Increment") { count += 1 }
Text("\(count)")
}
.onAppear { print("Counter.VStack onAppear") }
.onDisappear { print("Counter.VStack onDisappear") }
) : AnyView(
VStack { Text("Limit exceeded") }
)
if count < limit {
VStack {
Button("Increment") { count += 1 }
Text("\(count)")
}
.onAppear { print("Counter.VStack onAppear") }
.onDisappear { print("Counter.VStack onDisappear") }
} else {
VStack { Text("Limit exceeded") }
}
}
}
2 changes: 1 addition & 1 deletion Sources/TokamakDemo/OutlineGroupDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct File: Identifiable {
let children: [File]?
}

@available(OSX 10.16, *)
@available(OSX 10.16, iOS 14, *)
struct OutlineGroupDemo: View {
let fs: [File] = [
.init(id: 0, name: "Users", children: [
Expand Down

0 comments on commit 8f23ac9

Please sign in to comment.