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

Nik*_*nov 14 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()
         ...
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我收到警告:

包含控制流语句的闭包不能与function builder一起使用ViewBuilder

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

Nik*_*nov 31

谢谢大家的回答。我在Apple开发论坛上找到了一种解决方案。基尔·吉拉德Kiel Gillard)回答了。解决方案是按照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())
     ... 
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,AnyView() 会擦除类型,因此会阻止 SwiftUI 的某些性能优化发挥作用。本文更详细地介绍了原因:https://www.objc.io/blog/2019/11/05/static-types-in-swiftui/ (5认同)
  • ***注意***在将视图添加到层​​次结构或从层次结构中删除视图时插入的过渡动画似乎不适用于switch。即使明确指定。他们确实使用IF语句。 (2认同)
  • 谢谢你!但是,我想补充一点,您甚至不需要辅助函数。只需使用 AnyView 进行包装即可! (2认同)

ops*_*psb 18

更新:SwiftUI 2 现在支持函数构建器中的 switch 语句,https://github.com/apple/swift/pull/30174


添加到 Nikolai 的答案中,该答案使开关编译但不使用转换,这是他的示例的一个版本,它支持转换。

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")
     ... 
  }
}
Run Code Online (Sandbox Code Playgroud)

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


Ava*_*rio 13

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

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

注意:如果您将它们包装起来AnyView并将其指定为返回类型,您也可以返回任意视图类型。


Wah*_*don 11

您可以使用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"
    }
  }
  
}
Run Code Online (Sandbox Code Playgroud)

然后,在视图文件中:

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()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您想使用与NavigationLink相同的情况,可以按如下方式使用:

struct ContentView: View {
  
  @State private var selectedDestination: Destination? = .restaurants
  
  var body: some View {
    NavigationView {

      List(Destination.allCases,
           selection: $selectedDestination) { item in
        NavigationLink(destination: view(for: selectedDestination),
                       tag: item,
                       selection: $selectedDestination) {
          Text(item.title).tag(item)
        }
      }
        
    }
  }
  
  @ViewBuilder
  func view(for destination: Destination?) -> some View {
    switch destination {
    case .some(.restaurants):
      CategoriesView()
    case .some(.profile):
      ProfileView()
    default:
      EmptyView()
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Lin*_*rth 5

您必须将代码包装在视图中,例如VStack, 或Group

var body: some View {
   Group {
       switch containedView {
          case .home: HomeView()
          case .categories: CategoriesView()
          ...
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

或者,添加返回值应该有效:

var body: some View {
    switch containedView {
        case .home: return HomeView()
        case .categories: return CategoriesView()
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var body: some View {
   Group {
       switch containedView {
          case .home: HomeView()
          case .categories: CategoriesView()
          ...
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 我尝试使用 VStack,但没有成功。组也不行。 (4认同)
  • 这些方法都不适合我编译(当然,您希望它们能够编译)。 (2认同)