I'm trying to combine the View1 and View2
using a @viewBuilder
named SeparateViewPanels
. But it throws the following error. Why is that? I can see that the error is about a wrong type I used. As I see it basically says I can't use the type of view
when it expects a type of content
.
Error: Cannot convert return expression of type 'TupleView<(View1, View2)>' to return type '(toolBar: Content, view2D: Content)'
but even if I replace the content
with anyView
etc, it still throws more errors. So it was not the solution. How to solve this? I'm trying out the @viewbuilder
functionality so I can't completely eliminate @viewbuilder
import AppKit
import Combine
import SwiftUI
@main
struct demo44App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct View1: View {
var body: some View {
Text("Hello, World!")
}
}
struct View2: View {
var body: some View {
Text("Hello, World!")
}
}
struct ContentView: View {
var body: some View {
SeparateViewPanels {
View1()
View2()
}
}
}
struct SeparateViewPanels<Content: View>: View {
@State var propertiesWindowWidth: Double = 100
let contents: () -> (toolBar: Content, view2D: Content)
init(@ViewBuilder contents: @escaping () -> (toolBar: Content, view2D: Content)) {
self.contents = contents
}
var body: some View {
GeometryReader { geo in
VStack(alignment: .center) {
HStack (alignment: .center, spacing: 0) {
Rectangle()
.fill(.blue)
.opacity(0.7)
.frame(width: 100, height: geo.size.height)
.overlay {
contents().toolBar
}
Rectangle()
.fill(.gray)
.opacity(0.7)
.frame(width: 100, height: geo.size.height)
.overlay {
contents().view2D
}
}
}
}
}
}
toolBar
and view2D
are views of different types, so SeparateViewPanels
should have 2 type parameters - Toolbar
and View2D
. Then, change the return type of the content
closure to TupleView<(Toolbar, View2D)>
as the error message suggests.
You can get the separate views from a TupleView
by using the value.0
and view.1
properties.
struct SeparateViewPanels<ToolBar: View, View2D: View>: View {
@State var propertiesWindowWidth: Double = 100
let contents: () -> TupleView<(ToolBar, View2D)>
init(@ViewBuilder contents: @escaping () -> TupleView<(ToolBar, View2D)>) {
self.contents = contents
}
var body: some View {
GeometryReader { geo in
VStack(alignment: .center) {
HStack (alignment: .center, spacing: 0) {
let tuple = contents()
Rectangle()
.fill(.blue)
.opacity(0.7)
.frame(width: 100, height: geo.size.height)
.overlay {
tuple.value.0
}
Rectangle()
.fill(.gray)
.opacity(0.7)
.frame(width: 100, height: geo.size.height)
.overlay {
tuple.value.1
}
}
}
}
}
}
Also consider having separate initialiser parameters for the two views. I think it is clearer for the user of SeparateViewPanels
to see that you expect exactly two views.
let toolbar: () -> ToolBar
let view2D: () -> View2D
init(@ViewBuilder toolbar: @escaping () -> ToolBar, @ViewBuilder view2D: @escaping () -> View2D) {
self.toolbar = toolbar
self.view2D = view2D
}
var body: some View {
GeometryReader { geo in
VStack(alignment: .center) {
HStack (alignment: .center, spacing: 0) {
Rectangle()
.fill(.blue)
.opacity(0.7)
.frame(width: 100, height: geo.size.height)
.overlay {
toolbar()
}
Rectangle()
.fill(.gray)
.opacity(0.7)
.frame(width: 100, height: geo.size.height)
.overlay {
view2D()
}
}
}
}
}
The caller would do:
SeparateViewPanels {
View1()
} view2D: {
View2()
}