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
Saving Highscores with Nsuserdefaults
Drag a Cgrect Using Uipangesturerecognizer
Inline Kvo of a Property in Another View Controller
Remove \\U{E2} Characters from String
Swift Callkit Sometimes Can't Activate Loudspeaker After Received Call (Only Incoming Call)
Data Ranged Subscribe Strange Behavior
Displaying State of an Async API Call in Swiftui
How to Move Platform with Velocity
In Swift, Can One Use a String to Access a Struct Property
Why Strings Are Not Equal in My Case
Swift Lazy Stored Property Versus Regular Stored Property When Using Closure
How to Use Spritekit Archives with Skspritenode Subclasses
Name Convention for Unwrapped Value in Swift
What's the Rationale of Swift's Size Methods Taking 'Int'S
How to Make Prepareforsegue with Uicollectionview