SwiftUI 02-构建列表和导航(Building List

2019-06-13  本文已影响0人  字节码

本章Demo 链接
Blog 链接

简介

此示例是记录学习SwiftUI的过程,原文出自
SwiftUI Essentials Building Lists and Navigation

SwiftUI 01-创建和组合视图 (Creating and Combining Views) 中创建了landmark的详情页,本节我们做landmark的列表页。

我们将创建可以显示任何landmark信息的视图,并动态生成一个滚动列表,用户可以点击该列表查看landmark的详细视图。要微调UI,我们将使用Xcode的画布以不同的设备大小呈现多个预览。

下载项目文件以开始构建此项目,并按照以下步骤操作。

第一节 了解示例中的数据

在第一个教程中, 我们直接把数据写死在视图的代码中。现在我们将学习把数据传递给自定义视图以供其显示。
首先下载入门项目并熟悉示例数据。

2f8f9d15-348e-4c7f-b53d-be31a5d8c457.png

我们将在本教程的其余部分以及随后的所有内容中使用此示例数据。

我们将在此以及以下每个教程中创建多个view的类。

第2节 创建行视图

我们将在本教程中构建的第一个视图是用于显示每个地标的详细信息的行。 此行视图将信息存储在其显示的地标的属性中,以便一个视图可以显示任何地标。 稍后,您将多个行组合成一个地标列表。

6f1c3da5-34c7-4c27-ba77-270ed2a29272.png
import SwiftUI

struct LandmarkRow : View {
    var landmark: Landmark
    
    
    var body: some View {
        Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/)
    }
}

#if DEBUG
struct LandmarkRow_Previews : PreviewProvider {
    static var previews: some View {
        LandmarkRow()
    }
}
#endif

添加landmark属性时,预览将停止工作,因为LandmarkRow类型在初始化期间需要传入一个Landmark的模型。

Snip20190609_41.png

要修复预览,我们需要修改预览中初始化LandmarkRow()的代码。

预览显示文本Hello World。

修复后,您可以为行构建布局。

Snip20190609_42.png

第3节 自定义行预览

Xcode的画布自动识别并显示当前编辑器中符合PreviewProvider协议的任何类型。 预览提供程序返回一个或多个视图,其中包含用于配置大小和设备的选项。

我们可以从预览提供程序自定义返回的内容,以准确呈现对我们最有帮助的预览。

de4301fa-bf9c-45ac-b7f2-bbb990ccb41b.png

修改后预览立即更改以显示第二个model的数据而不是第一个。

我们可以在previews中使用group返回多行cell预览。

Snip20190609_43.png Snip20190609_44.png

Group是用于对视图内容进行分组的容器。 Xcode将组的子视图渲染为画布中的单独预览。

import SwiftUI

struct LandmarkRow : View {
    var landmark: Landmark
    
    
    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

#if DEBUG
struct LandmarkRow_Previews : PreviewProvider {
    static var previews: some View {
        Group {
            LandmarkRow(landmark: landmarkData[0])
            LandmarkRow(landmark: landmarkData[1])
        }
        .previewLayout(.fixed(width: 300, height: 70))
    }
}
#endif

视图的子项继承视图的上下文设置,例如预览配置。

我们在预览提供程序中编写的代码仅更改Xcode在画布中显示的内容。 由于#if DEBUG指令,编译器会删除代码,因此它在release下不会打包到应用程序中。

第4节 创建landmark列表

使用SwiftUIList类型时,可以显示特定于平台的视图列表。 列表的元素可以是静态的,就像我们目前创建的堆栈的子视图一样,或者是动态生成的。 您甚至可以混合静态和动态生成的视图。

c91b6546-1230-43a2-8867-2f0e445edb99.png

现在我们预览显示以适合iOS的列表样式呈现的两个landmark,这个List不就是UITableView吗?

Snip20190609_45.png

第5节 使列表动态化

我们可以直接从集合中生成行,而不是单独指定列表的元素。

我们可以通过传递数据集合以及为集合中的每个元素提供视图的闭包来创建显示集合元素的列表。 该列表使用提供的闭包将集合中的每个元素转换为子视图。

3f2f8071-97e1-481e-92a2-efb18be01ec7-1.png

列表使用可识别的数据。 我们可以通过以下两种方式之一来识别数据:通过调用identified(by:)方法,使用唯一标识每个元素的属性的键路径,或者使您的数据类型遵守Identifiable协议。

这为landmarkData数组中的每个元素创建一个LandmarkRow

接下来,我们将通过向Landmark类型添加Identifiable协议来简化List代码。

由于Landmark类型已经具有Identifiable协议所需的id属性,因此没有其他工作要做。

import SwiftUI

struct LandmarkList : View {
    var body: some View {
        List(landmarkData) { landmark in
            LandmarkRow(landmark: landmark)
        }
    }
}

#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}
#endif

从现在开始,我们将能够直接使用Landmark元素的集合。

第6节 在列表和详细信息之间设置导航

列表正确呈现,但我们无法点击某个landmark以查看它的详细信息页面。

通过将导航功能嵌入到NavigationView中,然后将每行嵌套在NavigationButton中以设置到目标视图的转换,可以将导航功能添加到列表中。

c6f21df9-1d6d-42d4-b21b-0a2588f4cd97.png Snip20190609_48.png Snip20190609_49.png

第7节 将数据传递到子视图

LandmarkDetail视图中是使用写死的数据展示UI 的。 就像LandmarkRow一样,我们需要在LandmarkDetail中添加一个Landmark类型的模型以作为其数据源显示view。

从子视图开始,我们将转换CircleImageMapViewLandmarkDetail以显示传入的数据,而不是将数据写死在代码中。

cb49732f-cb3e-4e77-a2e6-84f7f7618502.png
import SwiftUI

struct CircleImage : View {
    
    var image: Image
    
    var body: some View {
        image
            // 给图片添加圆角
            .clipShape(Circle())
            // 给圆角添加边框
            .overlay(Circle().stroke(Color.gray, lineWidth: 4))
            // 添加半径为10的阴影
            .shadow(radius: 10)
    }
}

#if DEBUG
struct CircleImage_Previews : PreviewProvider {
    static var previews: some View {
        CircleImage(image: Image("turtlerock"))
    }
}
#endif

这是使用SwiftUI构建视图时的常见模式。 自定义视图通常会包装并封装特定视图的一系列修饰符。


import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    var coordinate: CLLocationCoordinate2D

    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {

        let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}

struct MapView_Preview: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    var coordinate: CLLocationCoordinate2D

    func makeUIView(context: Context) -> MKMapView {
        MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context) {
        let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
        let region = MKCoordinateRegion(center: coordinate, span: span)
        view.setRegion(region, animated: true)
    }
}

struct MapView_Preview: PreviewProvider {
    static var previews: some View {
        MapView(coordinate: landmarkData[0].locationCoordinate)
    }
}

当我们在模拟器中独立运行而不是预览时,我们的应用程序将以SceneDelegate中定义的根视图开始。

import SwiftUI

struct LandmarkList : View {
    var body: some View {
        // 设置导航容器
        NavigationView {
            // 初始化一个类型TableView的view
            List(landmarkData) { landmark in
                // 点击cell时,将当前`landmark`传递到目标`LandmarkDetail`中。
                NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
                    
                    LandmarkRow(landmark: landmark)
                }
            }
            // 显示当前列表页的导航标题
            .navigationBarTitle(Text("Landmarks"))
        }
        
    }
}

#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}
#endif

第8节 动态生成预览

接下来,我们将向LandmarkList_Previews预览提供程序添加代码,以显示不同设备大小的列表视图的预览。 默认情况下,预览会以活动方案中设备的大小进行渲染。 我们可以通过调用previewDevice(_ :)方法来更改预览设备。

475e4ac3-b605-4309-a294-e9d2efa6f1ab.png
#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
    static var previews: some View {
        LandmarkList()
            // 以iPhone SE 设备的大小预览画布
            .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
        
    }
}
#endif

我们可以提供Xcode方案菜单中显示的任何设备的名称。

Snip20190609_50.png Snip20190609_51.png

ForEach以与列表相同的方式对集合进行操作,这意味着我们可以在任何可以使用子视图的位置使用它,例如在堆栈,列表,组等中。 当数据元素是简单的值类型 - 就像在这里使用的字符串一样 - 我们可以使用\ .self作为标识符的关键路径。

Snip20190609_52.png

测试

可选项:
1.Group
2.ForEath
3.UITableView

答案:2

可选项:
1.func map(_:)
2.func sorted(by:)
3.func identified(by:)

答案:3

可选项:
1.NavigationButton
2.UITableViewDelegate
3.NavigationView

答案: 1

可选项:

  1. Change the simulator selected in the active scheme.
  2. Make a different choice in Canvas Settings in Xcode’s preferences.
  3. Specify one or more devices using the previewDevice(_:)method.
  4. Connect your development device and click the Device Preview button.

答案:2

上一篇 下一篇

猜你喜欢

热点阅读