⚠️ 23 June 2020 Edit: From Xcode 12, both switch and if let statements will be supported in the ViewBuilder!
I’ve been trying to replicate an app of
You must wrap your code in a View, such as VStack
, or Group
:
var body: some View {
Group {
switch containedView {
case .home: HomeView()
case .categories: CategoriesView()
...
}
}
}
or, adding return values should work:
var body: some View {
switch containedView {
case .home: return HomeView()
case .categories: return CategoriesView()
...
}
}
The best-practice way to solve this issue, however, would be to create a method that returns a view:
func nextView(for containedView: YourViewEnum) -> some AnyView {
switch containedView {
case .home: return HomeView()
case .categories: return CategoriesView()
...
}
}
var body: some View {
nextView(for: containedView)
}
It looks like you don't need to extract the switch statement into a separate function if you specify the return type of a ViewBuilder
. For example:
Group { () -> Text in
switch status {
case .on:
return Text("On")
case .off:
return Text("Off")
}
}
Note: You can also return arbitrary view types if you wrap them in
AnyView
and specify that as the return type.
Update: SwiftUI 2 now includes support for switch statements in function builders, https://github.com/apple/swift/pull/30174
Adding to Nikolai's answer, which got the switch compiling but not working with transitions, here's a version of his example that does support transitions.
struct RootView: View {
@State var containedViewType: ContainedViewType = .home
var body: some View {
VStack {
// custom header goes here
containedView()
}
}
func containedView() -> some View {
switch containedViewType {
case .home: return AnyView(HomeView()).id("HomeView")
case .categories: return AnyView(CategoriesView()).id("CategoriesView")
...
}
}
Note the id(...)
that has been added to each AnyView. This allows SwiftUI to identify the view within it's view hierarchy allowing it to apply the transition animations correctly.
You can do with a wrapper View
struct MakeView: View {
let make: () -> AnyView
var body: some View {
make()
}
}
struct UseMakeView: View {
let animal: Animal = .cat
var body: some View {
MakeView {
switch self.animal {
case .cat:
return Text("cat").erase()
case .dog:
return Text("dog").erase()
case .mouse:
return Text("mouse").erase()
}
}
}
}
⚠️ 23 June 2020 Edit: From Xcode 12, both switch and if let statements will be supported in the ViewBuilder!
Thanks for the answers, guys. I’ve found a solution on Apple’s Dev Forums.
It’s answered by Kiel Gillard. The solution is to extract the switch in a function as Lu_, Linus and Mo suggested, but we have to wrap the views in AnyView
for it to work – like this:
struct RootView: View {
@State var containedViewType: ContainedViewType = .home
var body: some View {
VStack {
// custom header goes here
containedView()
}
}
func containedView() -> AnyView {
switch containedViewType {
case .home: return AnyView(HomeView())
case .categories: return AnyView(CategoriesView())
...
}
}
For not using AnyView(). I will use a bunch of if statements and implement the protocols Equatable and CustomStringConvertible in my Enum for retrieving my associated values:
var body: some View {
ZStack {
Color("background1")
.edgesIgnoringSafeArea(.all)
.onAppear { self.viewModel.send(event: .onAppear) }
// You can use viewModel.state == .loading as well if your don't have
// associated values
if viewModel.state.description == "loading" {
LoadingContentView()
} else if viewModel.state.description == "idle" {
IdleContentView()
} else if viewModel.state.description == "loaded" {
LoadedContentView(list: viewModel.state.value as! [AnimeItem])
} else if viewModel.state.description == "error" {
ErrorContentView(error: viewModel.state.value as! Error)
}
}
}
And I will separate my views using a struct:
struct ErrorContentView: View {
var error: Error
var body: some View {
VStack {
Image("error")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100)
Text(error.localizedDescription)
}
}
}