Alternative to Switch Statement in Swiftui Viewbuilder Block

Alternative to switch statement in SwiftUI ViewBuilder block?

⚠️ 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())
...
}
}

Using switch/enums in SwiftUI Views

switch in SwiftUI view builders is supported since Xcode 12:

enum Status {
case loggedIn, loggedOut, expired
}

struct SwiftUISwitchView: View {

@State var userStatus: Status = .loggedIn

var body: some View {
VStack {
switch self.userStatus {
case .loggedIn:
Text("Welcome!")
case .loggedOut:
Image(systemName: "person.fill")
case .expired:
Text("Session expired")
}

}
}
}

For Xcode 11 you can use the following workarounds:

a) Wrap it in a single Group block with an explicit return type - this is allowed if the switch statement is the only statement in the function builder block:

enum Status {
case loggedIn, loggedOut, expired
}

struct SwiftUISwitchView: View {

@State var userStatus: Status = .loggedIn

var body: some View {
VStack {
Group { () -> Text in
switch(self.userStatus) {
case .loggedIn:
return Text("Welcome!")
case .loggedOut:
return Text("Please log in")
case .expired:
return Text("Session expired")
}
}
}
}
}

struct SwitchUsageInSwiftUI_Previews: PreviewProvider {
static var previews: some View {
SwiftUISwitchView()
}
}

Alternative b) Create a separate function to compute the View based on the enum:

struct SwiftUISwitchView: View {

@State var userStatus: Status = .loggedIn

// if it's always the same View, you can use some View
func viewFor(status: Status) -> some View {
switch(status) {
case .loggedIn:
return Text("Welcome!")
case .loggedOut:
return Text("Please log in")
case .expired:
return Text("Session expired")
}
}

var body: some View {
VStack {
viewFor(status: userStatus)
}
}
}

If the returned view can have different types, you need to wrap it in an AnyView because some View requires the return type to be the same in all cases:

// if it's different types, you have to erase to AnyView
func viewForStatusDifferentViews(status: Status) -> AnyView {
switch(status) {
case .loggedIn:
return AnyView(Text("Welcome!"))
case .loggedOut:
return AnyView(Image(systemName: "person.fill"))
case .expired:
return AnyView(Text("Session expired"))
}
}

Alternative c) Create a separate View to compute the View by enum value:

// Alternative: A separate view
struct StatusView: View {

var status : Status

var body: some View {
switch(status) {
case .loggedIn:
return AnyView(Text("Welcome!"))
case .loggedOut:
return AnyView(Image(systemName: "person.fill"))
case .expired:
return AnyView(Text("Session expired"))
}
}

}

Runnable example code at Github: SwiftUIPlayground

SwiftUI Switch Statement Transition Behavior is not as expected

The difference likely has to do with how the SwiftUI result builder translates the if statement into buildIf, buildEither, etc vs. how the switch statements are translated. See: https://jasonzurita.com/swiftui-if-statement/

It looks like you can get the behavior to match the if statements if you explicitly define asymmetric transitions in the switch statement:

switch exampleStep {

case .stepOne:
Rectangle()
.foregroundColor(Color.green)
.frame(width: 100, height: 100)
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))

case .stepTwo:
Rectangle()
.foregroundColor(Color.red)
.frame(width: 100, height: 100)
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading)))

}

AsyncImage. Cannot use switch statement in Phase closure?

you can use a switch statement, use this, works for me:

case .failure(_):  // <-- here 

Here is the code I used to show that my answer works:

struct ContentView: View {

@State private var stringURL = "https://upload.wikimedia.org/wikipedia/commons/e/ef/Red-billed_gull%2C_Red_Zone%2C_Christchurch%2C_New_Zealand.jpg"

var body: some View {
AsyncImage(url: URL(string: stringURL)) { phase in
switch phase {
case .empty:
ProgressView()
case .success(let image):
image.resizable().scaledToFit()
case .failure(_): // <-- here
EmptyView()
@unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
}

}

SwiftUI when can i use if statement in result builder?

ToolbarContentBuilder does not allow if statements, it's designed to use ToolbarItemGroup, where you can use if like a regular ViewBuilder:

var body: some View {
NavigationView {
Text("Hello, world!")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
if foo { Button("foo") {} }
}
}
}
}


Related Topics



Leave a reply



Submit