SwiftUI, если позволить внутри View - PullRequest
1 голос
/ 13 февраля 2020

Мне нужен оператор if let внутри View.

@ObservedObject var person: Person?
 var body: some View {
      if person != nil {
        // this works
      }
      if let p = person {
        // Compiler error
      }
    }

Замыкание, содержащее оператор потока управления, нельзя использовать с конструктором функций 'ViewBuilder'

Ответы [ 3 ]

2 голосов
/ 13 февраля 2020

Да, нотация, запрошенная в снимке кода, не разрешена, по крайней мере, на данный момент, но ожидаемого результата можно достичь с помощью следующего очень простого подхода - извлечение потока управления в функцию :

var person: Person? // actually @ObservedObject does not allowed optional
var body: some View {
    VStack {
         if person != nil {
           // same as before
         }
         personViewIfExists() // << just extract it in helper function
    }
}

private func personViewIfExists() -> some View { // generates view conditionally
    if let p = person {
      return ExistedPersonView(person: p) // << just for demo
    }
}

при некоторых условиях также может потребоваться следующий вариант функции (хотя он дает тот же результат)

private func personViewIfExists() -> some View {
    if let p = person {
      return AnyView(ExistedPersonView(person: p))
    }
    return AnyView(EmptyView())
}
1 голос
/ 13 февраля 2020

Или вы можете создать свой собственный IfLet View Builder:

import SwiftUI

struct IfLet<Value, Content, NilContent>: View where Content: View, NilContent: View {

    let value: Value?
    let contentBuilder: (Value) -> Content
    let nilContentBuilder: () -> NilContent

    init(_ optionalValue: Value?, @ViewBuilder whenPresent contentBuilder: @escaping (Value) -> Content, @ViewBuilder whenNil nilContentBuilder: @escaping () -> NilContent) {
        self.value = optionalValue
        self.contentBuilder = contentBuilder
        self.nilContentBuilder = nilContentBuilder
    }

    var body: some View {
        Group {
            if value != nil {
                contentBuilder(value!)
            } else {
                nilContentBuilder()
            }
        }
    }
}

extension IfLet where NilContent == EmptyView {

    init(_ optionalValue: Value?, @ViewBuilder whenPresent contentBuilder: @escaping (Value) -> Content) {
        self.init(optionalValue, whenPresent: contentBuilder, whenNil: { EmptyView() })
    }
}

Используя это, вы можете теперь сделать следующее:

var body: some View {
    IfLet(pieceOfData) { realData in
        // realData is no longer optional
    }
}

Хотите ответить, если ваш опциональный nil?

var body: some View {
    IfLet(pieceOfData, whenPresent: { realData in
        // realData is no longer optional
        DataView(realData)
    }, whenNil: {
        EmptyDataView()
    })
}
0 голосов
/ 29 апреля 2020

Более простой подход - просто использовать .map()

Он покажет UserProfile, если user имеет значение, и ничего, если user равно nil:

var user: User? 
var body: some View {
  user.map { UserProfile(user: $0) }
}

Если вы хотите предоставить представление по умолчанию для значения nil, вы можете просто использовать оператор объединения nil:

var user: User? 
var body: some View {
  user.map { UserProfile(user: $0) } ?? UserProfile(user: .default) 
}

Однако для этого требуется, чтобы два представления были одного типа, и на практике, если они того же типа, что вам лучше просто написать UserProfile(user: $0 ?? .default).

Интересен случай, когда два вида не относятся к одному и тому же типу. Вы можете просто стереть их тип, заключив их в AnyView, но на этом этапе это становится немного громоздким и трудным для чтения:

var user: User? 
var body: some View {
  user.map { AnyView(UserProfile(user: $0)) } ?? AnyView(Text("Not logged in"))
}

Так что в этом случае мой предпочтительный подход заключается в использовании custom IfLet struct (то же имя, что и у Procrastin8, но с другой реализацией и синтаксисом), которая позволяет мне писать что-то вроде этого, что я нахожу очень ясным в намерениях и легко набираемым.

var user: User? 
var body: some View {
  IfLet(user) { UserProfile(user: $0) } 
    .else { Text("Not logged in") }
}

Код для моего IfLet компонента следующий.

struct IfLet<Wrapped, IfContent> : View where IfContent: View {
  let optionalValue: Wrapped?
  let contentBuilder: (Wrapped) -> IfContent

  init(_ optionalValue: Wrapped?, @ViewBuilder contentBuilder: @escaping (Wrapped) -> IfContent) {
    self.optionalValue = optionalValue
    self.contentBuilder = contentBuilder
  }

  var body: some View {
    optionalValue.map { contentBuilder($0) }
  }

  func `else`<ElseContent:View>(@ViewBuilder contentBuilder: @escaping () -> ElseContent) -> some View {
    if optionalValue == nil {
      return AnyView(contentBuilder())
    } else {
      return AnyView(self)
    }
  }
}

Наслаждайтесь!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...