Low Effort Graphaello Logo

Twitter: @nerdsupremacist Twitter: @nerdsupremacist

Graphaello

直接从您的 SwiftUI 视图中使用 GraphQL。Graphaello 是一个代码生成命令行工具,它允许您在 SwiftUI 视图中使用属性包装器,以使用来自 GraphQL 的数据。

Graphaello 的主要功能包括

如果您正在为其他平台寻找类似的东西,Graphaello 的灵感很大程度上来自于 Relay

示例

代码片段很酷,但这在真实项目中看起来如何?这里有一些示例应用程序,您可以查看一下。

国家/地区 音乐 CovidUI
简单的 Hello World 应用程序,显示有关许多不同国家/地区的信息 更复杂的应用程序,使用分页和许多可重用组件 集成测试,显示来自我自己的 GraphQL API 的数据:CovidQL
使用 Countries API 使用 GraphBrains 使用 CovidQL
仓库 仓库 仓库

教程

本 Readme 旨在记录关于 Graphaello 的一切,从 CLI 和 API。但是,对于初学者来说,这不是最佳资源。我写了一篇教程,其中我深入探讨了 Graphaello 的优势以及如何使用它构建一个简单的应用程序来浏览电影。这篇文章甚至面向完全不熟悉 GraphQL 的人。因此,如果您有兴趣,请务必查看这里

TLDR?

让我们言归正传,直接看我们使用 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 执行以下操作

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

可以通过 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

所有 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 的路径上还有其他操作可用

命令行工具

Graphaello 工具非常简单,只有三个命令

Codegen

将生成所有 swift 代码并将其插入到您的项目中。

参数

Project:指向项目。如果未提供,将选择您当前工作目录中的第一个项目。Apollo:引用它应该使用的 Apollo CLI。可以是“binary”(如果您已通过 npm 安装)或“derivedData”(它将查找您项目的构建文件夹。仅在构建阶段使用此选项)。如果未提供,则默认为 binary。跳过格式化标志:如果您的项目非常大,格式化生成的代码可能需要很长时间。在原型设计期间,您可能希望跳过格式化。

Init

将 Graphaello 注入到您的项目中。此步骤是可选的,但建议执行:运行时它将

您可以使用标志跳过可选步骤

Add

将 API 添加到您的项目。只需提供 GraphQL 端点的 URL,它将被添加到您的项目中。

参数

API 名称:您可以更改 API 的调用名称。如果未提供,它将是主机名的 UpperCamelCase 版本

贡献

欢迎并鼓励贡献!

相关工作

Graphaello 与服务器端的 GraphZahl 结合使用效果最佳。GraphZahl 使您能够在 Swift 中以声明方式实现您的 GraphQL 服务器,而无需任何样板代码。

了解

目前这是一个研究项目。有关其工作原理的更多详细信息将在稍后发布。

许可证

Graphaello 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。

本项目是在慕尼黑工业大学应用软件工程系主任的监督下完成的。该系拥有使用和维护此工具的永久权利。