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/
Добавить комментарий