接上一篇持续对 SwiftUI 的教程进行一些解读。 原文|地址

教程 2 – Building Lists and Navigation

Section 4 – Step 2: 静态List

var body: some View {
    List {
        LandmarkRow(landmark: landmarkData[0])
        LandmarkRow(landmark: landmarkData[1])
    }
}

这儿的ListHStack或许VStack之类的容器很类似,接受一个 view builder 并选用 View DSL 的方法列举了两个LandmarkRow。这种方法构建了对应着UITableView的静态 cell 的安排方法。

public init(content: () -> Content)

我们可以运行 app,并运用 Xcode 的 View Hierarchy 工具来观察 UI,结果可能会让你觉得很眼熟:

关于SwiftUI的开始探究,想入门SwiftUI运用的不容错失!(二)

实践上在屏幕上制造的UpdateCoalesingTableView是一个UITableView的子类,而两个 cellListCoreCellHost也是UITableViewCell的子类。关于List来说,SwiftUI 底层直接运用了成熟的UITableView的一套结束逻辑,而并非从头进行制造。比较起来,像是Text或许Image这样的单一ViewUIKit层则悉数一致由DisplayList.ViewUpdater.Platform.CGDrawingView这个UIView的子类进行制造。

不过在运用 SwiftUI 时,我们首先需求做的就是跳出 UIKit 的思维方法,不该该去关心背后的制造和结束。运用UITableView来表达List或许只是权宜之计,或许在未来也会被另外更高效的制造方法替代。因为 SwiftUI 层只是View描绘的数据抽象,因此和 React 的 Virtual DOM 以及 Flutter 的 Widget 相同,背后的具体制造方法是彻底解耦合,而且可以进行替换的。这为往后 SwiftUI 更进一步留出了满意的可能性。

Section 5 – Step 2: 动态ListIdentifiable

List(landmarkData.identified(by: .id)) { landmark in
    LandmarkRow(landmark: landmark)
}

除了静态方法以外,List当然也可以接受动态方法的输入,这时运用的初始化方法和上面静态的情况不相同:

public struct List<Selection, Content> where Selection : SelectionManager, Content : View {
    public init<Data, RowContent>(
        _ data: Data, action: @escaping (Data.Element.IdentifiedValue) -> Void,
        rowContent: @escaping (Data.Element.IdentifiedValue) -> RowContent) 
    where 
        Content == ForEach<Data, Button<HStack<RowContent>>>, 
        Data : RandomAccessCollection, 
        RowContent : View, 
        Data.Element : Identifiable
    //...
}

这个初始化方法的约束比较多,我们一行行来看:

  • Content == ForEach<Data, Button<HStack<RowContent>>>因为这个函数签名中并没有呈现ContentContent仅只List<Selection, Content>的类型声明中有定义,所以在这与其说是一个约束,不如说是一个用来反向承认List实践类型的描绘。现在让我们先将留心力放在更重要的当地,稍后会再多讲一些这个。
  • Data : RandomAccessCollection这基本上等同于要求第一个输入参数是Array
  • RowContent : View关于构建每一行的rowContent来说,需求返回是View是很正常的工作。留心rowContent其实也是被@ViewBuilder标记的,因此你也可以把LandmarkRow的内容翻开写进去。不过一般我们会更期望尽可能拆小 UI 部件,而不是把东西堆在一起。
  • Data.Element : Identifiable要求Data.Element(也就是数组元素的类型) 上存在一个可以辨别出某个实例的满意Hashable的 id。这个要求将在数据变更时快速定位到改动的数据所对应的 cell,并进行 UI 改写。

关于List以及其他一些常见的基础View,有一个比较幽默的实际。在下面的代码中,我们期望List的初始化方法生成的是某个类型的View

var body: some View {
    List {
        //...
    }
}

但是你看遍List 的文档,甚至是 Cmd + Click 到 SwiftUI 的 interface 中查找View相关的内容,都找不到List : View之类的声明。

难道是因为 SwiftUI 做了什么四肢,让原本没有满意View的类型都可以“充当”一个View吗?当然不是这样…假设你在运行时暂定 app 并用 lldb 打印一下List的类型信息,可以看到下面的下面的信息:

(lldb) type lookup List
...
struct List<Selection, Content> : SwiftUI._UnaryView where ...

进一步,_UnaryView的声明是:

protocol _UnaryView : View where Self.Body : _UnaryView {
}

SwiftUI 内部的一元视图_UnaryView协议虽然是满意View的,但它被躲藏起来了,而满意它的List虽然是 public 的,但是却可以把这个协议链的信息也作为内部信息躲藏起来。这是 Swift 内部结构的特权,第三方的开发者无法这样在在两个 public 的声明之间刺进一个私有声明。

最后,SwiftUI 中当时 (Xcode 11 beta 1) 只有对应UITableViewList,而没有UICollectionView对应的像是Grid这样的类型。现在想要结束类似效果的话,只能嵌套运用VStackHStack。这是比较乖僻的,因为技能层面上应该和 table view 没有太多差异,大概是因为工期不太够?信赖往后应该会弥补上Grid

教程 3 – Handling User Input

Section 3 – Step 2:@StateBinding

@State var showFavoritesOnly = true
var body: some View {
    NavigationView {
        List {
            Toggle(isOn: $showFavoritesOnly) {
                Text("Favorites only")
            }
    //...
            if !self.showFavoritesOnly || landmark.isFavorite {

这儿呈现了两个曾经在 Swift 里没有的特性:@State$showFavoritesOnly

假设你 Cmd + Click 点到State的定义里边,可以看到它其实是一个特别的struct

@propertyWrapper public struct State<Value> : DynamicViewProperty, BindingConvertible {
    /// Initialize with the provided initial value.
    public init(initialValue value: Value)
    /// The current state value.
    public var value: Value { get nonmutating set }
    /// Returns a binding referencing the state value.
    public var binding: Binding<Value> { get }
    /// Produces the binding referencing this state value
    public var delegateValue: Binding<Value> { get }
}

@propertyWrapper标示和上一篇中提到的@_functionBuilder类似,它修饰的struct可以变成一个新的修饰符并效果在其他代码上,来改动这些代码默许的行为。这儿@propertyWrapper修饰的State被用做了@State修饰符,并用来修饰View中的showFavoritesOnly变量。

@_functionBuilder担任依照规则“从头结构”函数的效果不同,@propertyWrapper的修饰符最终会效果在特色上,将特色“包裹”起来,以达到控制某个特色的读写行为的意图。假设将这部分代码“翻开”,它实践上是这个样子的:

// @State var showFavoritesOnly = true
   var showFavoritesOnly = State(initialValue: true)
var body: some View {
    NavigationView {
        List {
//          Toggle(isOn: $showFavoritesOnly) {
            Toggle(isOn: showFavoritesOnly.binding) {
                Text("Favorites only")
            }
    //...
//          if !self.showFavoritesOnly || landmark.isFavorite {
            if !self.showFavoritesOnly.value || landmark.isFavorite {

我把改动之前的部分注释了一下,而且在后面一行写上了翻开后的结果。可以看到@State只是声明Statestruct 的一种简写方法罢了。State里对具体要怎么读写特色的规则进行了定义。关于读取,非常简略,运用showFavoritesOnly.value就能拿到State存储的实践值。而原代码中$showFavoritesOnly的写法也只不过是showFavoritesOnly.binding的简化。binding将创建一个showFavoritesOnly的引证,并将它传递给Toggle。再次侧重,这个binding是一个引证类型,所以Toggle中对它的批改,会直接反应到当时 View 的showFavoritesOnly去设置它的value。而State的 value didSet 将触发body的改写,然后结束 State -> View 的绑定。

在 Xcode 11 beta 1 中,Swift 中运用的修饰符名字是@propertyDelegate,不过在 WWDC 上 Apple 提到这个特性时把它叫做了@propertyWrapper。依据可靠消息,在未来正式版中应该也会叫做@propertyWrapper,所以我们在看各种资料的时分最好也主张一个简略的映射关系。

假设你想要了解更多关于@propertyWrapper的细节,可以看看相关的提案和论坛谈论。比较有意思的细节是 Apple 在将相应的 PR merge 进了 master 今后又把这个提案的打回了“批改”的状态,而非直接接受。除了@propertyWrapper的称谓批改以外,应该还会有一些其他的细节批改,但是现已公开的行为形式上应该不会太大改动了。

SwiftUI 中还有几个常见的@开头的修饰,比方@Binding@Environment@EnvironmentObject等,原理上和@State都相同,只不过它们所对应的 struct 中定义读写方法有差异。它们一起构成了 SwiftUI 数据流的最基本的单元。关于 SwiftUI 的数据流,假设翻开的话满意一整篇文章了。在这儿仍是非常主张看一看Session 226 – Data Flow Through SwiftUI的相关内容。

教程 7 – Working with UI Controls

Section 4 – Step 2: 关于View的生命周期

ProfileEditor(profile: $draftProfile)
    .onDisappear {
        self.draftProfile = self.profile
    }

在 UIKit 开发时,我们经常会触摸一些像是viewDidLoadviewWillAppear这样的生命周期的方法,并在里边进行一些配备。SwiftUI 里也有一部分这类生命周期的方法,比方.onAppear.onDisappear,它们也被“一致”在了 modifier 这面大旗下。

但是相关于 UIKit 来说,SwiftUI 中能 hook 的生命周期方法比较少,而且相对要通用一些。自身在生命周期中做操作这种方法就和声明式的编程理念有些相悖,看上去就像是加上了一些指令式的 hack。我个人比较等待ViewCombine能再深度结合一些,把像是self.draftProfile = self.profile这类依赖生命周期的操作也用绑定的方法搞定。

比较于.onAppear.onDisappear,更通用的事件照应 hook 是.onReceive(_:perform:),它定义了一个可以照应方针Publisher的任意的View,一旦订阅的Publisher发出新的事件时,onReceive就将被调用。因为我们可以自行定义这些 publisher,所以它是完备的,这在把现有的 UIKit View 转换到 SwiftUI View 时会非常有用。

iOS相关资料下载