从10月8日至10日加入我们,在纽约市了解有关 GraphQL联邦和API平台工程的最新技巧、趋势和新闻。加入2024年纽约市的GraphQL峰会
文档
免费开始

9. 完成详情视图


在本节中,您将编写一个第二 ,该查询请求有关单个 的详细信息,并使用这些数据在一个 DetailView

为了在详情页上显示更多信息,您有几个选择

  • 您可以在 LaunchList 查询中请求您想要为每个 launch显示的所有详细信息,然后将检索到的对象传递到 DetailViewController
  • 您可以为 不同的 查询提供 launch 的标识符,然后请求您想要显示的所有详细信息。

如果请求的列表与详情页之间的大小没有实质性差异,第一个选项可能看起来更容易。

但是,记得 GraphQL 的一大优点是您可以为显示在页面上的数据 query 精确。 如此,如果您不打算显示额外的信息,则可以通过在需要数据之前不请求数据来节省带宽、执行时间和电池寿命。

这尤其适用于当您的详情视图的查询比列表视图大得多时。传递标识符并根据该标识符获取数据被视为最佳实践。尽管在这种情况下数据量没有太大差异,但您将建立一个查询来帮助根据 ID 获取详细信息,这样您就会知道如何在未来这样做。

创建详情查询

创建一个新空文件并将其命名为 LaunchDetails.graphql。在文件中,您将添加要显示在详细视图中的详细信息。首先,您希望返回到 您的沙盒并确保您的 查询正常工作!

在“探索者”标签中,首先在中部 区域点击“新建标签页”按钮:

the new tab button

将添加一个新标签页,而其中没有任何内容

The UI after adding a new tab

在左侧列中,点击“文档”下方的“”一词,以便显示可能的查询列表:

The list of possible queries

通过单击旁边的按钮选择 launch 查询。沙盒探索器将自动为您设置查询:

What the launch query will look like immediately after adding it

首先,将 名称从“查询”更改为“”,这将反映在标签页名称中,并有助于区分您正在处理的查询:

The renamed query

让我们看看这里添加了什么

  • 同样,我们添加了一个 操作,但这次它有一个传入的参数。这是沙盒探索器自动添加的,因为非空 launchId 没有默认值。
  • 参数名称前面加有 $,类型紧随其后。注意,这里的 ID 类型有一个感叹号,表示它不能为空。
  • 在这个 操作 中,我们调用 launch 查询。 id 是查询期望的 参数,而 $launchId 是我们上面传入的参数的名称。
  • 再次,这里留有空白供您添加要获取的详细信息的 ,这里是一个 Launch
  • 最后,在底部,“”部分已展开,并添加了一个键为 "launchId" 的字典。在运行时,这将用于填充 $launchId 参数中的空白。

注意:GraphQL对null的可空性的假设与Swift不同。在Swift中,如果你没有用问号或感叹号标注属性的类型,那么该属性是非可空的。

GraphQL中,如果你没有用感叹号标注字段的类型,那么该字段被认为是可空的。这是因为GraphQL的字段默认是可空的。

在编辑Swift和GraphQL文件之间切换时,请记住这个区别。

现在在沙盒资源管理器中,首先使用复选框或输入来添加你在LaunchList查询中已请求的属性。有一点不同:由于补丁将显示在更大的ImageView中,所以使用LARGE来设置任务补丁的大小:

(沙盒资源管理器)
query LaunchDetails($id:ID!) {
launch(id: $id) {
id
site
mission {
name
missionPatch(size:LARGE)
}
}
}

接下来,在左侧侧边栏中查看其他哪些字段可用。选中rocket将为请求有关火箭的详细信息添加一组括号,并钻入rocket属性,显示Rocket类型上的可用字段:

The available properties for Rocket

点击按钮来勾选nametype。然后,点击左侧侧边栏中Rocket类型旁边的后退按钮返回到Launch

The back button

最后,在Launch上勾选isBooked属性。您的最终查询应如下所示:

(沙盒资源管理器)
query LaunchDetails($launchId: ID!) {
launch(id: $launchId) {
id
site
mission {
name
missionPatch(size: LARGE)
}
rocket {
name
type
}
isBooked
}
}

操作部分底部,更新变量部分以传入launch的ID。在这种情况下,它需要是一个包含数字的字符串:

(沙盒资源管理器)
{ "id": "25" }

这告诉SandBox Explorer以值$launchId填充该变量,然后运行查询。按下大播放按钮,你应该会为ID号为25的launch得到一些结果:

Detail request returning JSON

现在你已经确认它工作正常,请复制查询(可以通过选择所有文本或像之前一样从昆虫菜单中选择“操作”选项),并将其粘贴到你的LaunchDetails.graphql文件中。从终端运行代码生成以生成新查询的代码。

执行查询

现在让我们添加代码来运行此查询以检索数据。

打开DetailViewModel.swift并添加这个import

DetailViewModel.swift
import RocketReserverAPI

接下来,让我们更新init()方法并添加一些variables来存储我们的Launch数据:

DetailViewModel.swift
let launchID: RocketReserverAPI.ID
@Published var launch: LaunchDetailsQuery.Data.Launch?
@Published var isShowingLogin = false
@Published var appAlert: AppAlert?
init(launchID: RocketReserverAPI.ID) {
self.launchID = launchID
}

接下来需要运行查询,因此在 loadLaunchDetails 方法中将 TODO 替换为此代码:

DetailViewModel.swift
func loadLaunchDetails() {
guard launchID != launch?.id else {
return
}
Network.shared.apollo.fetch(query: LaunchDetailsQuery(launchId: launchID)) { [weak self] result in
guard let self = self else {
return
}
switch result {
case .success(let graphQLResult):
if let launch = graphQLResult.data?.launch {
self.launch = launch
}
if let errors = graphQLResult.errors {
self.appAlert = .errors(errors: errors)
}
case .failure(let error):
self.appAlert = .errors(errors: [error])
}
}
}

现在我们已经启动了查询,需要更新 UI 代码来使用新的数据。

更新 UI 代码

首先,转到 DetailView.swift,并添加以下 import声明:

DetailView.swift
import RocketReserverAPI
import SDWebImageSwiftUI

接下来,需要更新 init() 方法,以使用 launchID 来初始化 DetailViewModel

DetailView.swift
init(launchID: RocketReserverAPI.ID) {
_viewModel = StateObject(wrappedValue: DetailViewModel(launchID: launchID))
}

几乎完成了!让我们更新 body 视图变量,使用从 DetailViewModel 得到的 launch 数据,并调用 loadLaunchDetails 方法:

DetailView.swift
var body: some View {
VStack {
if let launch = viewModel.launch {
HStack(spacing: 10) {
if let missionPatch = launch.mission?.missionPatch {
WebImage(url: URL(string: missionPatch))
.resizable()
.placeholder(placeholderImg)
.indicator(.activity)
.scaledToFit()
.frame(width: 165, height: 165)
} else {
placeholderImg
.resizable()
.scaledToFit()
.frame(width: 165, height: 165)
}
VStack(alignment: .leading, spacing: 4) {
if let missionName = launch.mission?.name {
Text(missionName)
.font(.system(size: 24, weight: .bold))
}
if let rocketName = launch.rocket?.name {
Text("🚀 \(rocketName)")
.font(.system(size: 18))
}
if let launchSite = launch.site {
Text(launchSite)
.font(.system(size: 14))
}
}
Spacer()
}
if launch.isBooked {
cancelTripButton()
} else {
bookTripButton()
}
}
Spacer()
}
.padding(10)
.navigationTitle(viewModel.launch?.mission?.name ?? "")
.navigationBarTitleDisplayMode(.inline)
.task {
viewModel.loadLaunchDetails()
}
.sheet(isPresented: $viewModel.isShowingLogin) {
LoginView(isPresented: $viewModel.isShowingLogin)
}
.appAlert($viewModel.appAlert)
}

另外,还需要更新 DetailView 的预览代码,如下所示:

DetailView.swift
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(launchID: "110")
}
}

现在,我们需要将 DetailView 连接到我们的 LaunchListView。让我们转到 LaunchListView.swift 并更新我们的 List,如下所示:

LaunchListView.swift
ForEach(0..<viewModel.launches.count, id: \.self) { index in
NavigationLink(destination: DetailView(launchId: viewModel.launches[index].id)) {
LaunchRow(launch: viewModel.launches[index])
}
}

这将允许我们点击列表中的任何 LaunchRow,并加载对应的 DetailView

测试 DetailView

现在,一切都已连接,构建并运行应用程序,当你点击任何 launch 时,你应该看到一个相应的 DetailView,如下所示:

Completed DetailView

你可能已经注意到详细信息视图包括一个 Book Now! 按钮,但Currently there is no way to book a seat. To fix this, let's 学习如何通过 mutations 在你的图中对对象进行更改,包括身份验证

上一页
8. 分页结果
下一页
10. 编写你的第一个mutation
评价文章评价在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,商号为Apollo GraphQL。

隐私政策

公司