Формируем View в SwiftUI, исходя из условий

от автора

Иногда нам нужно сформировать SwiftUI View, учитывая некоторые условия. Например, в приведенном коде мы определяем HomeView, который может содержать ProfileView, в случае если в LogInManager есть loggedInUser. Мы пытаемся это реализовать, используя стандартный оператор if:

struct HomeView: View {     @ObservedObject var loginManager: LoginManager      var body: some View {         VStack {             if let user = loginManager.loggedInUser {                 ProfileView(user: user)             }              ...         }     } }

К сожалению, этот код выдаст при компиляции ошибку:

Closure containing control flow statement cannot be used with function builder ViewBuilder.

Так как здесь используются не обычные замыкания, а function builders, мы не можем поместить в них произвольный код для формирования HStack или VStack. Так как же нам выйти из положения?

Один из способов — передать обработку таких optionals непосредственно в те view, которые мы формируем. Например, мы можем передавать в наш ProfileView не конкретное значение User, а сделать его optional:

struct ProfileView: View {     var user: User?      var body: some View {         guard let user = user else {             // We have to use 'AnyView' to perform type erasure here,             // in order to give our 'body' a single return type:             return AnyView(EmptyView())         }          return AnyView(VStack {             Text(user.name)             ...         })     } }

Этот код работает, но не особо красив. Нет никакого смысла создавать ProfileView для пользователя nil. Применим другой подход: используем map к нашему optional User, чтобы преобразовать его в ProfileView:

struct HomeView: View {     @ObservedObject var loginManager: LoginManager      var body: some View {         VStack {             loginManager.loggedInUser.map { user in                 ProfileView(user: user)             }             ...         }     } }

Так уже гораздо симпатичнее: нам не нужно вручную отдавать EmptyView, когда у User отсутствует значение. Также мы опять можем передавать в ProfileView конкретное значение, а не optional. А можно ли сделать ещё лучше?

Хорошая новость о @ViewBuilder состоит в том, что это не какая-то закрытая реализация в SwiftUI, а доступный атрибут, которым мы можем аннотировать свои собственные функции и замыкания.

Используя этот атрибут мы можем собрать view Unwrap, который принимает в качестве параметров optional значение и помеченную @ViewBuilder замыкание для преобразования не-nil значения во View:

struct Unwrap<Value, Content: View>: View {     private let value: Value?     private let contentProvider: (Value) -> Content      init(_ value: Value?,          @ViewBuilder content: @escaping (Value) -> Content) {         self.value = value         self.contentProvider = content     }      var body: some View {         value.map(contentProvider)     } }

Используя эту конструкцию, мы теперь можем полностью переработать всю структуру HomeView:

struct HomeView: View {     @ObservedObject var loginManager: LoginManager      var body: some View {         VStack {             Unwrap(loginManager.loggedInUser) { user in                 HStack {                     Text("Logged in as:")                     ProfileView(user: user)                 }             }             ...         }     } } 

ссылка на оригинал статьи https://habr.com/ru/post/495746/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *