在SwiftUI ViewBuilder块中切换语句的替代方法?

时间:2019-06-24 12:20:11

标签: swift swiftui

我一直在尝试使用SwiftUI复制我的应用程序。它有一个RootViewController,根据枚举值,它显示了一个不同的子视图控制器。与在SwiftUI中一样,我们使用视图而不是视图控制器,我的代码如下所示:

struct RootView : View {
   @State var containedView: ContainedView = .home

   var body: some View {
      // custom header goes here
      switch containedView {
         case .home: HomeView()
         case .categories: CategoriesView()
         ...
      }
   }
}

不幸的是,我收到警告:

  

包含封闭流的控制流语句不能与函数生成器ViewBuilder 一起使用。

那么,是否有其他选择可以切换,以便我可以复制此行为?

8 个答案:

答案 0 :(得分:16)

更新:SwiftUI 2现在在函数构建器https://github.com/apple/swift/pull/30174

中包括对switch语句的支持。

在尼古拉的答案中,它编译了开关,但无法处理转换,这是他的示例的一个版本,它确实支持转换。

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")
     ... 
  }
}

请注意已添加到每个AnyView的id(...)。这样,SwiftUI可以在其视图层次结构中识别该视图,从而可以正确地应用过渡动画。

答案 1 :(得分:6)

谢谢大家的回答。我在 Apple开发论坛中找到了一种解决方案。 基尔·吉拉德回答。解决方案是按照Lu_,Linus和Mo的建议在函数中提取开关,但是我们必须将视图包装在AnyView中才能起作用–像这样:

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

答案 2 :(得分:3)

您必须将代码包装在View中,例如VStackGroup

var body: some View {
   Group {
       switch containedView {
          case .home: HomeView()
          case .categories: CategoriesView()
          ...
       }
   }
}

或者,添加返回值应该可以:

var body: some View {
    switch containedView {
        case .home: return HomeView()
        case .categories: return CategoriesView()
        ...
    }
}

但是,解决此问题的最佳实践方法是创建一个返回视图的方法:

func nextView(for containedView: YourViewEnum) -> some View {
    switch containedView {
        case .home: return HomeView()
        case .categories: return CategoriesView()
        ...
    }
}

var body: some View {
    nextView(for: containedView)
}

答案 3 :(得分:2)

如果您指定ViewBuilder的返回类型,则似乎不需要将switch语句提取到单独的函数中。例如:

Group { () -> Text in
    switch status {
    case .on:
        return Text("On")
    case .off:
        return Text("Off")
    }
}
  

注意:如果将任意视图类型包装在AnyView中,并将其指定为返回类型,则还可以返回任意视图类型。

答案 4 :(得分:1)

您可以使用包装视图

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()
            }
        }
    }
}

答案 5 :(得分:0)

不使用AnyView()。我将使用一堆if语句,并在我的Enum中实现Equatable和CustomStringConvertible协议以检索关联的值:

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)
        }
    }
}

然后我将使用一个结构分开我的观点:

struct ErrorContentView: View {
    var error: Error

    var body: some View {
        VStack {
            Image("error")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 100)
            Text(error.localizedDescription)
        }
    }
}

答案 6 :(得分:0)

您可以将 enum 与 sed 一起使用,如下...

清除枚举

@ViewBuilder

现在在查看文件中

enum Destination: CaseIterable, Identifiable {
  case restaurants
  case profile
  
  var id: String { return title }
  
  var title: String {
    switch self {
    case .restaurants: return "Restaurants"
    case .profile: return "Profile"
    }
  }
  
}

如果你想在 NavigationLink 中使用相同的 case ... 你可以使用它如下

struct ContentView: View {

   @State private var selectedDestination: Destination? = .restaurants

    var body: some View {
        NavigationView {
          view(for: selectedDestination)
        }
     }

  @ViewBuilder
  func view(for destination: Destination?) -> some View {
    switch destination {
    case .some(.restaurants):
      CategoriesView()
    case .some(.profile):
      ProfileView()
    default:
      EmptyView()
    }
  }
}

答案 7 :(得分:0)

default 中提供 switch 语句为我解决了这个问题:

struct RootView : View {
   @State var containedView: ContainedView = .home

   var body: some View {
      // custom header goes here
      switch containedView {
         case .home: HomeView()
         case .categories: CategoriesView()
         ...
         default: EmptyView()
      }
   }
}