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