直接从您的 SwiftUI 视图中使用 GraphQL。Graphaello 是一个代码生成命令行工具,它允许您在 SwiftUI 视图中使用属性包装器,以使用来自 GraphQL 的数据。
Graphaello 的主要功能包括
如果您正在为其他平台寻找类似的东西,Graphaello 的灵感很大程度上来自于 Relay。
代码片段很酷,但这在真实项目中看起来如何?这里有一些示例应用程序,您可以查看一下。
国家/地区 | 音乐 | CovidUI |
---|---|---|
![]() |
![]() |
![]() |
简单的 Hello World 应用程序,显示有关许多不同国家/地区的信息 | 更复杂的应用程序,使用分页和许多可重用组件 | 集成测试,显示来自我自己的 GraphQL API 的数据:CovidQL |
使用 Countries API | 使用 GraphBrains | 使用 CovidQL |
仓库 | 仓库 | 仓库 |
本 Readme 旨在记录关于 Graphaello 的一切,从 CLI 和 API。但是,对于初学者来说,这不是最佳资源。我写了一篇教程,其中我深入探讨了 Graphaello 的优势以及如何使用它构建一个简单的应用程序来浏览电影。这篇文章甚至面向完全不熟悉 GraphQL 的人。因此,如果您有兴趣,请务必查看这里。
让我们言归正传,直接看我们使用 Graphaello 的 View 的第一个示例
// Define a Cell
struct CharacterCell: View {
// Use the GraphQL Property Wrapper
@GraphQL(StarWars.Person.name)
var name: String?
@GraphQL(StarWars.Person.homeworld.name)
var home: String?
var body: some View {
HStack {
name.map { Text($0).bold() }
Spacer()
home.map { Text($0) }
}
}
}
此代码告诉 Graphaello 执行以下操作
Person
类型的可重用定义,专为您的 View 而设计fragment CharacterCell_Person on Person {
name
homeworld {
name
}
}
let person: CharacterCell.Person = ...
let view = CharacterCell(person: person)
而且我有没有提到这一切都是类型安全的?!?!
@GraphQL(StarWars.Person.name)
var name: String? // works
@GraphQL(StarWars.Person.name)
var name: Bool // doesn't work
还有: 如果它是标量,那么您甚至不需要指定类型!!
@GraphQL(StarWars.Person.name)
var name // Swift knows it's a String?
请记住,Graphaello 仍处于早期阶段,因此尚未准备好用于生产环境。使用时请您自行承担风险。
可以通过 Homebrew 安装 Graphaello
brew tap nerdsupremacist/tap
brew install graphaello
或者,如果您是其中之一,您可以直接从源代码安装它。您说了算!
git clone https://github.com/nerdsupremacist/Graphaello.git
cd Graphaello
sudo make install
我们将从两个方面介绍如何使用 Graphaello。
几乎所有示例都将引用星球大战 API:https://swapi-graphql.netlify.com
您可以非常轻松地直接从您的 SwiftUI 视图中使用来自 GraphQL API 的信息
例如,此 CharacterCell
显示一个单元格,其中包含人物的姓名和家园世界
struct CharacterCell: View {
@GraphQL(StarWars.Person.name)
var name: String?
@GraphQL(StarWars.Person.homeworld.name)
var home: String?
var body: some View {
HStack {
name.map { Text($0).bold() }
Spacer()
home.map { Text($0) }
}
}
}
// Initializer is automatically created by Graphaello
let view = CharacterCell(person: person)
如果您的视图有一个带有自己数据的子视图,则您的视图不需要知道它的具体细节,而只需要知道它需要填充它
struct CharacterDetail: View {
@GraphQL(StarWars.Person._fragment)
var headerCell: CharacterCell.Person
@GraphQL(StarWars.Person.eyes)
var eyes: String?
var body: some View {
VStack {
CharacterCell(person: headerCell)
eyes.map { Text($0) }
}
}
}
let view = CharacterDetail(person: person)
您可以直接访问 API 的任何查询字段
struct FilmView {
// .film refers to a field in the query
@GraphQL(StarWars.film.title)
var title: String?
var body: String {
title.map { Text($0) }
}
}
let client = ApolloClient(url: ...)
let api = StarWars(client: client)
let view = api.filmView(id: ...)
所有 mutation 都可以直接从 {API_NAME}.Mutation
使用。在此示例中,我们使用的是 TODO 应用程序,因为星球大战 API 不支持 mutation
struct TodoCell: View {
// _nonNull() is equivalent to !
@GraphQL(Todos.Todo.id._nonNull())
var id: String
// _withDefault(FOO) is equivalent to ?? FOO
@GraphQL(Todos.Todo.title._withDefault(""))
var title: String
@GraphQL(Todos.Todo.completed._withDefault(false))
var completed: Bool
@GraphQL(Todos.Mutation.toggle.completed._withDefault(false))
var toggle: Toggle // Define a type name for your mutation
var body: some View {
HStack {
Text(title)
Spacer()
Button(completed ? "Mark as not done" : "Mark as done") {
toggle.commit(id: self.id) { completed in
self.completed = completed
}
}
ActivityIndicator().animated(toggle.isLoading)
}
}
}
如果您的 API 支持 Connections,您可以开箱即用地在您的应用程序中包含分页
struct CharacterList: View {
@GraphQL(StarWars.allPeople._nonNull())
var characters: Paging<CharacterCell.Person>
var body: some View {
List {
ForEach(characters.values) { character in
CharacterCell(person: character)
}
characters.hasMore ? Button("Load More") {
self.characters.loadMore()
}.disabled(characters.isLoading) : nil
}
}
}
或者您甚至可以使用附带的 PagingView
,当您接近列表末尾时,项目将自动加载
struct CharacterList: View {
@GraphQL(StarWars.allPeople._nonNull())
var characters: Paging<CharacterCell.Person>
var body: some View {
List {
PagingView(characters) { character in
CharacterCell(person: character)
}
}
}
}
每当您使用带有参数的字段时,这些参数都会传播给使用您的视图的人。但是您也可以从 @GraphQL
注解中预先填充它们。您可以
默认
struct FilmView {
@GraphQL(StarWars.film.title)
var title: String?
var body: String {
title.map { Text($0) }
}
}
...
let first = api.filmView(id: ...)
let second = api.filmView() // uses the default from the API
强制它们
struct FilmView {
@GraphQL(StarWars.film(id: .argument).title)
var title: String?
var body: String {
title.map { Text($0) }
}
}
...
let view = api.filmView(id: ...) // id is required
硬编码它们
struct MyFavoriteFilmView {
@GraphQL(StarWars.film(id: .value("...")).title)
var title: String?
var body: String {
title.map { Text($0) }
}
}
...
let view = api.filmView() // id is not available as an argument
覆盖默认值
struct FilmView {
@GraphQL(StarWars.film(id: .argument(default: "...")).title)
var title: String?
var body: String {
title.map { Text($0) }
}
}
...
let first = api.filmView(id: ...)
let second = api.filmView() // uses the default set by the View
Graphaello 的路径上还有其他操作可用
compactMap { $0 }
) ?? y
)!
)flatMap { $0 }
Graphaello 工具非常简单,只有三个命令
将生成所有 swift 代码并将其插入到您的项目中。
参数
Project:指向项目。如果未提供,将选择您当前工作目录中的第一个项目。Apollo:引用它应该使用的 Apollo CLI。可以是“binary”(如果您已通过 npm 安装)或“derivedData”(它将查找您项目的构建文件夹。仅在构建阶段使用此选项)。如果未提供,则默认为 binary。跳过格式化标志:如果您的项目非常大,格式化生成的代码可能需要很长时间。在原型设计期间,您可能希望跳过格式化。
将 Graphaello 注入到您的项目中。此步骤是可选的,但建议执行:运行时它将
codegen
(可选)您可以使用标志跳过可选步骤
将 API 添加到您的项目。只需提供 GraphQL 端点的 URL,它将被添加到您的项目中。
参数
API 名称:您可以更改 API 的调用名称。如果未提供,它将是主机名的 UpperCamelCase 版本
欢迎并鼓励贡献!
Graphaello 与服务器端的 GraphZahl 结合使用效果最佳。GraphZahl 使您能够在 Swift 中以声明方式实现您的 GraphQL 服务器,而无需任何样板代码。
目前这是一个研究项目。有关其工作原理的更多详细信息将在稍后发布。
Graphaello 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。
本项目是在慕尼黑工业大学应用软件工程系主任的监督下完成的。该系拥有使用和维护此工具的永久权利。