SwiftUI'nin DSL'sini ne etkinleştirir?


89

Apple'ın yeni gibi görünüyor SwiftUIçerçeve bir kullanır sözdizimi yeni bir tür etkin bir şekilde başlığın oluşturur ancak başka sözdizimi vardır:

var body: some View {
    VStack(alignment: .leading) {
        Text("Hello, World") // No comma, no separator ?!
        Text("Hello World!")
    }
}

Bu sözdiziminin gerçekte ne olduğunu anlamaya çalışırken , VStackburada kullanılan başlatıcının () -> Content ikinci parametre olarak türün bir kapanışını aldığını, buna Contentuyan genel bir parametrenin kapanış yoluyla çıkarıldığını öğrendim View. Hangi türden çıkarıldığını bulmak Contentiçin, işlevselliğini koruyarak kodu biraz değiştirdim:

var body: some View {
    let test = VStack(alignment: .leading) {
        Text("Hello, World")
        Text("Hello World!")
    }

    return test
}

Bununla, testtipte kendini gösterir VStack<TupleView<(Text, Text)>>, yani Contenttiptedir TupleView<Text, Text>. Aranıyor TupleView, bunun bir sarıcı tipi kaynak var buldum SwiftUIsadece yerleştirilmeli tuple geçirerek başlatılabilir kendisi.

Soru

Şimdi merak ediyorum, dünyada Textbu örnekteki iki örnek bir TupleView<(Text, Text)>. Bu saldırıya uğradı SwiftUIve bu nedenle normal Swift sözdizimi geçersiz mi? tip TupleViewolmak SwiftUIbu varsayımı destekler. Yoksa bu geçerli Swift sözdizimi mi? Varsa dışarıda nasıl kullanılır SwiftUI?


3
developer.apple.com/documentation/swiftui/vstack/3278367-init bir "özel öznitelik" @ViewBuilder geliştirici.apple.com/ documentation/swiftui/viewbuilder olduğunu gösterir .
Martin R.

Yanıtlar:


111

Martin'in dediği gibi , VStack's belgelerine bakarsanız init(alignment:spacing:content:), content:parametrenin özniteliğe sahip olduğunu görebilirsiniz @ViewBuilder:

init(alignment: HorizontalAlignment = .center, spacing: Length? = nil,
     @ViewBuilder content: () -> Content)

Bu öznitelik ViewBuilder, üretilen arayüze bakarsanız şöyle görünen türü ifade eder :

@_functionBuilder public struct ViewBuilder {

    /// Builds an empty view from an block containing no statements, `{ }`.
    public static func buildBlock() -> EmptyView

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
    /// through unmodified.
    public static func buildBlock(_ content: Content) -> Content 
      where Content : View
}

Bu @_functionBuilderözellik, burada Swift evrimine değinilen ve SwiftUI'de kullanılmasına olanak tanıyan Xcode 11 ile birlikte gelen Swift sürümü için özel olarak uygulanan " işlev oluşturucular " adlı resmi olmayan bir özelliğin bir parçasıdır .

Bir türün işaretlenmesi @_functionBuilder, bunun işlevler, hesaplanan özellikler ve bu durumda işlev türünün parametreleri gibi çeşitli bildirimlerde özel bir öznitelik olarak kullanılmasına izin verir. Bu tür açıklamalı bildirimler, kod bloklarını dönüştürmek için işlev oluşturucuyu kullanır:

  • Açıklamalı fonksiyonlar için, dönüştürülen kod bloğu gerçeklemedir.
  • Açıklamalı hesaplanmış özellikler için, dönüştürülen kod bloğu alıcıdır.
  • İşlev türünün açıklamalı parametreleri için, dönüştürülen kod bloğu, kendisine iletilen herhangi bir kapatma ifadesidir (varsa).

Bir işlev oluşturucunun kodu dönüştürme yolu , bir dizi ifadeyi alan ve bunları tek bir değerde birleştiren gibi oluşturucu yöntemlerin uygulanmasıyla tanımlanır buildBlock.

Örneğin, birden çok görünümü tek bir görüntüde birleştirerek 1 ila 10 uyumlu parametreyi ViewBuilderuygular :buildBlockViewTupleView

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {

    /// Passes a single view written as a child view (e..g, `{ Text("Hello") }`)
    /// through unmodified.
    public static func buildBlock<Content>(_ content: Content)
       -> Content where Content : View

    public static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) 
      -> TupleView<(C0, C1)> where C0 : View, C1 : View

    public static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2)
      -> TupleView<(C0, C1, C2)> where C0 : View, C1 : View, C2 : View

    // ...
}

Bu, VStackbaşlatıcıya geçirilen bir kapanış içindeki bir dizi görünüm ifadesinin buildBlockaynı sayıda argümanı alan bir çağrıya dönüştürülmesine izin verir . Örneğin:

struct ContentView : View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Hello, World")
      Text("Hello World!")
    }
  }
}

bir çağrıya dönüşür buildBlock(_:_:):

struct ContentView : View {
  var body: some View {
    VStack(alignment: .leading) {
      ViewBuilder.buildBlock(Text("Hello, World"), Text("Hello World!"))
    }
  }
}

sonuçlanan opak sonuç türü some View karşılanmaktaydı TupleView<(Text, Text)>.

ViewBuilderYalnızca buildBlocken fazla 10 parametreyi tanımladığını unutmayın , bu nedenle 11 alt görünüm tanımlamaya çalışırsak:

  var body: some View {
    // error: Static member 'leading' cannot be used on instance of
    // type 'HorizontalAlignment'
    VStack(alignment: .leading) {
      Text("Hello, World")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
      Text("Hello World!")
    }
  }

Bu kod bloğunu işleyecek bir oluşturucu yöntemi olmadığından bir derleyici hatası alıyoruz (bu özellik hala devam eden bir çalışma olduğundan, etrafındaki hata mesajlarının o kadar yararlı olmayacağını unutmayın).

Gerçekte, insanların bu kısıtlamaya sık sık maruz kalacağına inanmıyorum, örneğin yukarıdaki örnek, ForEachbunun yerine görünüm kullanılarak daha iyi sunulacaktır :

  var body: some View {
    VStack(alignment: .leading) {
      ForEach(0 ..< 20) { i in
        Text("Hello world \(i)")
      }
    }
  }

Bununla birlikte, 10'dan fazla statik olarak tanımlanmış görünüme ihtiyacınız varsa, Groupgörünümü kullanarak bu kısıtlamayı kolayca çözebilirsiniz :

  var body: some View {
    VStack(alignment: .leading) {
      Group {
        Text("Hello world")
        // ...
        // up to 10 views
      }
      Group {
        Text("Hello world")
        // ...
        // up to 10 more views
      }
      // ...
    }

ViewBuilder ayrıca aşağıdaki gibi diğer işlev oluşturucu yöntemlerini de uygular:

extension ViewBuilder {
    /// Provides support for "if" statements in multi-statement closures, producing
    /// ConditionalContent for the "then" branch.
    public static func buildEither<TrueContent, FalseContent>(first: TrueContent)
      -> ConditionalContent<TrueContent, FalseContent>
           where TrueContent : View, FalseContent : View

    /// Provides support for "if-else" statements in multi-statement closures, 
    /// producing ConditionalContent for the "else" branch.
    public static func buildEither<TrueContent, FalseContent>(second: FalseContent)
      -> ConditionalContent<TrueContent, FalseContent>
           where TrueContent : View, FalseContent : View
}

Bu, ona if ifadelerini işleme yeteneği verir:

  var body: some View {
    VStack(alignment: .leading) {
      if .random() {
        Text("Hello World!")
      } else {
        Text("Goodbye World!")
      }
      Text("Something else")
    }
  }

hangisine dönüşür:

  var body: some View {
    VStack(alignment: .leading) {
      ViewBuilder.buildBlock(
        .random() ? ViewBuilder.buildEither(first: Text("Hello World!"))
                  : ViewBuilder.buildEither(second: Text("Goodbye World!")),
        Text("Something else")
      )
    }
  }

(gereksiz 1 argüman yayarak ViewBuilder.buildBlocknetlik çağrısı yapar ).


4
ViewBuilderyalnızca buildBlocken fazla 10 parametre tanımlar - bu, var body: some View11'den fazla alt görünümün olamayacağı anlamına mı gelir?
LinusGeffarth

1
@LinusGeffarth Gerçekte insanların bu kısıtlamaya o kadar sık ​​maruz kalacağını sanmıyorum, çünkü ForEachbunun yerine görünüm gibi bir şey kullanmak isteyecekler . Ancak Groupgörünümü bu kısıtlamayı çözmek için kullanabilirsiniz , bunu göstermek için cevabımı düzenledim.
Hamish

3
@MandisaW - görünümleri kendi görünümlerinize göre gruplayabilir ve yeniden kullanabilirsiniz. Bununla ilgili bir sorun görmüyorum. Şu anda WWDC'deyim ve SwiftUI laboratuvarındaki mühendislerden biriyle konuştum - şu anda Swift'in bir sınırlaması olduğunu söyledi ve mantıklı bir sayı olarak 10 ile gittiler. Swift'e değişken jenerikler eklendiğinde, istediğimiz kadar çok "alt görüntülemeye" sahip olabileceğiz.
Losiowaty

1
Belki daha ilginç, her iki yöntemin de amacı nedir? Görünüşe göre her ikisini de uygulamanız gerekiyor ve her ikisi de aynı dönüş türüne sahip, neden her biri söz konusu türü döndürmüyor?
Gusutafu


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.