Альтернатива переключению оператора в блоке SwiftUI ViewBuilder?
Я пытался скопировать свое приложение, используя 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 ответов
Спасибо за ответы, ребята. Я нашел решение на форумах разработчиков 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())
...
}
}
Обновление: SwiftUI 2 теперь включает поддержку операторов switch в построителях функций, https://github.com/apple/swift/pull/30174
Добавляя к ответу Николая, в котором переключатель компилируется, но не работает с переходами, вот версия его примера, которая поддерживает переходы.
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")
...
}
}
Обратите внимание id(...)
который был добавлен к каждому AnyView. Это позволяет SwiftUI идентифицировать представление в его иерархии представлений, что позволяет ему правильно применять анимацию перехода.
Похоже, вам не нужно извлекать оператор switch в отдельную функцию, если вы укажете тип возвращаемого значения ViewBuilder
. Например:
Group { () -> Text in
switch status {
case .on:
return Text("On")
case .off:
return Text("Off")
}
}
Примечание. Вы также можете вернуть произвольные типы представлений, если заключите их в
AnyView
и укажите это как возвращаемый тип.
Вы можете использовать enum с
@ViewBuilder
следующим образом ...
Declear enum
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"
}
}
}
Теперь в файле просмотра
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()
}
}
}
Если вы хотите использовать тот же случай с 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()
}
}
}
Вы должны обернуть свой код в представление, например VStack
, или же Group
:
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)
}
Предоставление
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()
}
}
}
Можно делать с оболочкой 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()
}
}
}
}
Чтобы не использовать AnyView(). Я буду использовать кучу операторов if и реализовать протоколы Equatable и CustomStringConvertible в моем Enum для получения связанных значений:
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)
}
}
}