Swift Talk App:第一步

作者: iOS_小久 | 来源:发表于2019-07-01 21:24 被阅读91次

我们开始使用SwiftUI构建一个Swift Talk应用程序。

我们通过构建一个简单的货币转换器来试验SwiftUI。今天,我们想进一步采用SwiftUI并开始研究Swift Talk应用程序。我们将通过将我们在过去几集中看到的各种组件(例如小型网络库和Resource包装器)整合在一起来获得基本设置和运行。

小编这里推荐一个群:691040931 里面有大量的书籍和面试资料哦有技术的来闲聊 没技术的来学习

添加包

我们创建一个新的Xcode项目,通过Xcode的File菜单,我们通过输入存储库URL并选择master分支来添加微型网络包。

我们还添加了一个包,它提供了两个库,代码可以由我们的后端和前端共享。第一个,Model包含两个 Codable结构。

另一个库ViewHelpers包含日期格式化程序。在服务器和客户端之间共享此代码非常有用,因为它可确保在网站和应用程序中呈现日期和持续时间的一致方式。

ContentView文件中,我们现在可以导入三个模块TinyNetworkingModelViewHelpers

除了Swift软件包之外,还有一些我们需要的东西。我们将Resource上周的文件复制到项目中,以及一些包含剧集和剧集集合的静态JSON数据。

收藏清单

我们现在要做的第一件事就是加载集合。我们创建一个文件Model.swift,在那里我们可以组织我们的数据。在那里,我们导入Model模块,它给我们两种类型:CollectionViewEpisodeView

这些类型的名称并不完美,它们在服务器上下文中更有意义,因为它们实际上是对我们数据的JSON视图,而不是UI视图。也许我们可以在将来重命名它们,但现在让我们继续它。

我们使用微型网络库来定义Endpoint可以加载剧集的方法:

import Foundation
import SwiftUI
import TinyNetworking
import Model

let allCollections = Endpoint<[CollectionView]>(json: .get, url: URL(string: "https://talk.objc.io/collections.json")!)

现在ContentView,我们可以创建一个Resource收集集合端点。我们必须创建资源ObjectBinding- 如果我们忘记这样做,我们将不会在资源更改时获得任何视图更新。

在正文视图中,如果已加载,我们会在列表中显示集合,否则我们会显示描述加载状态的文本:

import SwiftUI
import TinyNetworking
import Model
import ViewHelpers

struct ContentView : View {
    @ObjectBinding var collections = Resource(endpoint: allCollections)
    var body: some View {
        Group {
            if collections.value == nil {
                Text("Loading...")
            } else {
                List {
                    ForEach(collections.value!) { coll in
                        Text(coll.title)
                    }
                }
            }
        }
    }
}

这还没有编译,因为我们collections 尚未确定。上一次,我们通过提供数组元素上的标识属性的关键路径来实现。但是,我们实际上可以遵循 CollectionViewIdentifiable协议,它,因为它有一个可哈希箱的id性能:

extension CollectionView: Identifiable {}

我们运行应用程序,我们看到一个收集标题列表。

下一步是将列表视图拉出到一个单独的文件中。我们添加一个包含集合数组的简单属性:

import SwiftUI
import Model
import ViewHelpers

struct CollectionsList : View {
    let collections: [CollectionView]
    var body: some View {
        List {
            ForEach(collections) { coll in
                Text(coll.title)
            }
        }
    }
}

ContentView,我们就可以通过加载收藏到CollectionsList

struct ContentView : View {
    @ObjectBinding var collections = Resource(endpoint: allCollections)
    var body: some View {
        Group {
            if collections.value == nil {
                Text("Loading...")
            } else {
                CollectionsList(collections: collections.value!)
            }
        }
    }
}

我们收到编译器错误,因为CollectionsList视图的预览 缺少collections参数。如果我们对预览进行评论,我们会看到其余的代码都进行了编译,并且CollectionsList工作也是如此。为了使预览工作,我们需要传递一些静态数据。

为此,我们将JSON文件添加到项目中。我们编写了一个简单的函数,它从bundle中加载一个具有给定名称的JSON文件,并将其内容解码为泛型类型。我们可以强制解包任何可选项,因为此函数仅用于加载示例数据,在这种情况下崩溃无关紧要:

func sample<A: Codable>(name: String) -> A {
    let url = Bundle.main.url(forResource: name, withExtension: "json")!
    let data = try! Data(contentsOf: url)
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    return try! decoder.decode(A.self, from: data)
}

然后我们加载样本集以进行预览 CollectionsList

#if DEBUG
struct CollectionsList_Previews : PreviewProvider {
    static var previews: some View {
        CollectionsList(collections: sample(name: "collections"))
    }
}
#endif

我们打开画布,看到样本集被加载到列表视图中。

预览工作非常方便快速设置列表视图的样式。我们将标题包装在a中VStack,然后我们Text为标题添加另一个标题,以显示集合中的剧集数量。然后我们将其VStack与前沿对齐,然后 设置文本标签的样式。

最后,我们将集合的总持续时间添加到标题中,使用共享格式ViewHelpers

struct CollectionsList : View {
    let collections: [CollectionView]
    var body: some View {
        List {
            ForEach(collections) { coll in
                VStack(alignment: .leading) {
                    Text(coll.title)
                    Text("\(coll.episodes_count) episodes ᐧ \(TimeInterval(coll.total_duration).hoursAndMinutes)")
                        .font(.caption)
                        .color(.gray)
                }
            }
        }
    }
}

在某些时候,我们想要从列表中显示集合的详细视图,但在我们开始之前,我们必须设置导航视图。我们在列表中添加了一个导航标题修饰符,我们将列表的预览包装在一个NavigationView中,以便查看导航栏和标题:

struct CollectionsList : View {
    let collections: [CollectionView]
    var body: some View {
        List {
            // ...
        }.navigationBarTitle(Text("Collections"))
    }
}

#if DEBUG
struct CollectionsList_Previews : PreviewProvider {
    static var previews: some View {
        NavigationView {
            CollectionsList(collections: sample("collections"))
        }
    }
}
#endif

我们还需要在导航视图中包含集合列表ContentView,以便在运行应用程序时查看标题:

struct ContentView : View {
    @ObjectBinding var collections = Resource(endpoint: allCollections)
    var body: some View {
        Group {
            if collections.value == nil {
                Text("Loading...")
            } else {
                NavigationView {
                    CollectionsList(collections: collections.value!)
                }
            }
        }
    }
}

收集细节

在导航视图到位后,我们可以轻松地为单个集合添加详细视图。在那里,我们可以通过创建另一个Endpoint包装的艺术品来加载该集合的艺术作品Resource

我们为我们创建一个新的SwiftUI视图文件,CollectionDetails然后导入我们需要的模块:

import SwiftUI
import TinyNetworking
import Model

我们为CollectionView这个视图中的呈现定义了一个属性,并且我们添加了一个VStack包含大字体集合标题的标签。我们还调用lineLimit修饰符 nil以获得多行标签:

struct CollectionDetails : View {
    let collection: CollectionView
    var body: some View {
        VStack {
            Text(collection.title)
                .font(.largeTitle)
                .lineLimit(nil)
            Text(collection.description)
        }
    }
}

在集合列表中,我们将每个列表项包装在一个以CollectionsDetails视图为目标的导航按钮中:

struct CollectionsList : View {
    let collections: [CollectionView]
    var body: some View {
        List {
            ForEach(collections) { coll in
                NavigationButton(destination: CollectionDetails(collection: coll)) {
                    VStack(alignment: .leading) {
                        Text(coll.title)
                        Text("\(coll.episodes_count) episodes ᐧ \(TimeInterval(coll.total_duration).hoursAndMinutes)")
                            .font(.caption)
                            .color(.gray)
                    }
                }
            }
        }.navigationBarTitle(Text("Collections"))
    }
}

和之前的集合列表一样,我们现在需要提供样本数据以预览集合详细信息视图。将sample函数移动到我们的模型文件中,并将样本集合拉出到全局变量,并在列表和详细信息视图的预览中使用此变量是有意义的:

let allCollections = Endpoint<[CollectionView]>(json: .get, url: URL(string: "https://talk.objc.io/collections.json")!)

let sampleCollections: [CollectionView] = sample(name: "collections")

func sample<A: Codable>(name: String) -> A {
    // ... }
#if DEBUG
struct CollectionsList_Previews : PreviewProvider {
    static var previews: some View {
        NavigationView {
            CollectionsList(collections: sampleCollections)
        }
    }
}
#endif
#if DEBUG
struct CollectionDetails_Previews : PreviewProvider {
    static var previews: some View {
        CollectionDetails(collection: sampleCollections[0])
    }
}
#endif

我们在模拟器中运行应用程序,我们尝试通过选择其中一个集合来打开详细视图。然后我们注意到集合的描述标签仍然仅限于显示单行文本。我们添加lineLimit(nil)修饰符来修复:

struct CollectionDetails : View {
    let collection: CollectionView
    var body: some View {
        VStack {
            Text(collection.title)
                .font(.largeTitle)
                .lineLimit(nil)
            Text(collection.description)
                .lineLimit(nil)
        }
    }
}

但这对预览没有影响。这似乎是一个错误,因为在模拟器中,标签现在正确地跨越所需的行数。

加载艺术品

要加载集合的图稿,我们将图像资源添加到集合详细信息视图中。

struct CollectionDetails : View {
    let collection: CollectionView
    let image: Resource<UIImage>
    // ... }

然后我们定义一个初始化器,我们可以在其中从collection参数构造该资源。我们首先创建一个Endpoint带有集合的图片URL,以及一个解析闭包,它UIImage从加载的数据构造一个:

struct ImageError: Error {}

struct CollectionDetails : View {
    let collection: CollectionView
    let image: Resource<UIImage>
    init(collection: CollectionView) {
        self.collection = collection
        let endpoint = Endpoint<UIImage>(.get, url: collection.artwork.png, expectedStatusCode: expected200to300) { data in
            guard let d = data, let i = UIImage(data: d) else {
                return .failure(ImageError())
            }
            return .success(i)
        }
        self.image = Resource(endpoint: endpoint)
    }
    // ... }

这些代码大部分是可重用的,所以我们稍后会把它拉出来以便能够加载其他图像。

现在我们可以包含图像,如果它已加载。我们必须制作图像resizable并将其纵横比设置为.fit,因为我们希望图像可以缩放并适合视图。如果我们不使用任何修饰符,那么SwiftUI将始终使用图像的原始大小。

我们现在记得图像资源需要 ObjectBinding。没有它,代码仍然可以编译,一切似乎都有效,但是在加载图像时,视图永远不会被重新渲染:

@ObjectBinding var image: Resource<UIImage>

加载图像在预览中不起作用,因此我们必须在模拟器中运行应用程序来测试它。在那里,我们看到该系列的艺术作品出现在详细视图中。


扫码进交流群 有技术的来闲聊 没技术的来学习

691040931

原文转载地址:https://talk.objc.io/episodes/S01E158-the-swift-talk-app-first-steps

相关文章

网友评论

    本文标题:Swift Talk App:第一步

    本文链接:https://www.haomeiwen.com/subject/sfgncctx.html