Alternative to switch statement in SwiftUI ViewBuilder block?

前端 未结 6 1473
耶瑟儿~
耶瑟儿~ 2020-12-02 12:21

⚠️ 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

相关标签:
6条回答
  • 2020-12-02 12:34

    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)
    }
    
    0 讨论(0)
  • 2020-12-02 12:40

    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.

    0 讨论(0)
  • 2020-12-02 12:41

    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.

    0 讨论(0)
  • 2020-12-02 12:43

    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()
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-02 12:51

    ⚠️ 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())
         ... 
      }
    }
    
    0 讨论(0)
  • 2020-12-02 12:52

    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)
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题