开始使用 AWS

构建 iOS 应用程序

使用 AWS Amplify 创建简单的 iOS 应用程序

模块 4:添加 GraphQL API 和数据库

在此模块中,您将使用 Amplify CLI 和库配置和添加 GraphQL API 到您的应用程序中。

简介

现在,我们已经创建并配置了带用户身份验证功能的应用程序。接下来,我们要在数据库中添加 API 以及“创建”、“读取”、“更新”、“删除”(CRUD) 操作。

在此模块中,您将使用 Amplify CLI 和库将 API 添加到您的应用程序中。您将创建的 API 是 GraphQL API,它利用 Amazon DynamoDB(NoSQL 数据库)支持的 AWS AppSync(托管 GraphQL 服务)。有关 GraphQL 的介绍,请访问此页面

您将构建的应用程序是备注记录应用程序,用户可使用它创建、删除和列出备注。本示例将让您了解如何构建很多常见类型的 CRUD+L(创建、读取、更新、删除和列出)应用程序。

您将学到的内容

  • 创建和部署 GraphQL API
  • 编写前端代码以与 API 互动

重要概念

API – 提供编程接口,以允许多个软件中介之间的通信和互动。

GraphQL – 基于应用程序类型化表示的查询语言和服务器端 API 实施。此 API 表示使用基于 GraphQL 类型系统的架构声明。(要了解有关 GraphQL 的更多信息,请访问此页面。)

 完成时间

20 分钟

 使用的服务

实施

  • 要创建 GraphQL API 及其后备数据库,请打开一个终端,然后从项目目录中执行此命令

    amplify add api
    
    ? Please select from one of the below mentioned services: select GraphQL and press enter
    ? Provide API name: select the default, press enter
    ? Choose the default authorization type for the API: use the arrow key to select Amazon Cognito User Pool and press enter
    ? Do you want to configure advanced settings for the GraphQL API: select the default No, I am done and press enter
    ? Do you have an annotated GraphQL schema? keep the default N and press enter
    ? What best describes your project: choose any model, we are going to replace it with our own anyway. Press enter
    ? Do you want to edit the schema now? type Y and press enter

    初始化项目时选择的默认文本编辑器 (amplify init) 将使用预构建数据 schema 打开。

    删除此 schema,并使用我们的应用程序 GraphQL schema 替换

    type NoteData
    @model
    @auth (rules: [ { allow: owner } ]) {
        id: ID!
        name: String!
        description: String
        image: String
    }

    数据模型由一个类 NoteData 和 4 个属性组成:ID 和名称是必填项,描述和图像是选填字符串。

    @model 转换器指示我们要创建数据库来存储数据。

    @auth 转换器添加了身份验证规则,以允许访问这些数据。对于此项目,我们希望仅 NoteData 的拥有者有访问它们的权限。

    完成后,请不要忘记保存

  • 根据我们刚刚创建的 GraphQL 数据模型定义,Amplify 会生成客户端代码(即 Swift 代码)以代表我们应用程序中的数据。

    要生成代码,请在终端执行以下命令:

    amplify codegen models

    此操作将在 amplify/generated/models 目录创建 Swift 文件。您可以通过以下方式看到这些内容:

    ➜  iOS Getting Started git:(master) ✗ ls -al amplify/generated/models
    total 24
    drwxr-xr-x  5 stormacq  admin  160 Jul  9 14:20 .
    drwxr-xr-x  3 stormacq  admin   96 Jul  9 14:20 ..
    -rw-r--r--  1 stormacq  admin  380 Jul  9 14:20 AmplifyModels.swift
    -rw-r--r--  1 stormacq  admin  822 Jul  9 14:20 NoteData+Schema.swift
    -rw-r--r--  1 stormacq  admin  445 Jul  9 14:20 NoteData.swift

    在 Xcode 项目中导入这些文件:在查找器中找到它们,然后将其拖放到 Xcode 中的项目。

  • 要部署我们刚刚创建的后端 API 和数据库,请转至您的终端,然后执行命令

    amplify push
    # press Y when asked to continue
    
    ? Are you sure you want to continue? accept the default Y and press enter
    ? Do you want to generate code for your newly created GraphQL API type N and press enter

    几分钟后,您应该会看到一条成功消息:

    ✔ All resources are updated in the cloud
    
    GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
  • 在转至代码之前,请将 Amplify API 库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSAPIPlugin 添加行,或者复制并粘贴以下整个文件。

    # you need at least version 13.0 for this tutorial, more recent versions are valid too
    platform :ios, '13.0'
    
    target 'getting started' do
      # Comment the next line if you don't want to use dynamic frameworks
      use_frameworks!
    
      # Pods for getting started
      pod 'Amplify', '~> 1.0'                             # required amplify dependency
      pod 'Amplify/Tools', '~> 1.0'                       # allows to call amplify CLI from within Xcode
    
      pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~> 1.0' # support for Cognito user authentication
      pod 'AmplifyPlugins/AWSAPIPlugin', '~> 1.0'         # support for GraphQL API
    
    end

    在终端执行以下命令:

    pod install

    该命令需要一些时间才能完成。您将看到(实际版本号可能有所不同):

    Analyzing dependencies
    Downloading dependencies
    Installing AmplifyPlugins 1.0.4
    Installing AppSyncRealTimeClient (1.1.6)
    Installing ReachabilitySwift (5.0.0)
    Installing Starscream (3.0.6)
    Generating Pods project
    Integrating client project
    Pod installation complete! There are 4 dependencies from the Podfile and 11 total pods installed.
  • 返回到 Xcode,打开 Backend.swift,然后在私有 init() 方法的 Amplify 初始化序列中添加一行。完整代码块应如下所示:

    // initialize amplify
    do {
       try Amplify.add(plugin: AWSCognitoAuthPlugin())
       try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))
       try Amplify.configure()
       print("Initialized Amplify")
    } catch {
       print("Could not initialize Amplify: \(error)")
    }
  • 我们的项目已经有一个数据模型来表示备注。因此,我决定继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 ContentView.swift 并将此初始化程序添加到备注类中。

    convenience init(from data: NoteData) {
        self.init(id: data.id, name: data.name, description: data.description, image: data.image)
     
        // store API object for easy retrieval later
        self._data = data
    }
    
    fileprivate var _data : NoteData?
    
    // access the privately stored NoteData or build one if we don't have one.
    var data : NoteData {
    
        if (_data == nil) {
            _data = NoteData(id: self.id,
                                name: self.name,
                                description: self.description,
                                image: self.imageName)
        }
    
        return _data!
    }
  • 我们添加 3 种方法来调用 API:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型(备注),以便从用户界面轻松交互。这些方法可透明地将 Note 转换为 GraphQL 的 NoteData 对象。

    打开 Backend.swift 文件,然后在后端类末尾添加以下 snipet:

        // MARK: API Access
    
        func queryNotes() {
    
            _ = Amplify.API.query(request: .list(NoteData.self)) { event in
                switch event {
                case .success(let result):
                    switch result {
                    case .success(let notesData):
                        print("Successfully retrieved list of Notes")
    
                        // convert an array of NoteData to an array of Note class instances
                        for n in notesData {
                            let note = Note.init(from: n)
                            DispatchQueue.main.async() {
                                UserData.shared.notes.append(note)
                            }
                        }
    
                    case .failure(let error):
                        print("Can not retrieve result : error  \(error.errorDescription)")
                    }
                case .failure(let error):
                    print("Can not retrieve Notes : error \(error)")
                }
            }
        }
    
        func createNote(note: Note) {
    
            // use note.data to access the NoteData instance
            _ = Amplify.API.mutate(request: .create(note.data)) { event in
                switch event {
                case .success(let result):
                    switch result {
                    case .success(let data):
                        print("Successfully created note: \(data)")
                    case .failure(let error):
                        print("Got failed result with \(error.errorDescription)")
                    }
                case .failure(let error):
                    print("Got failed event with error \(error)")
                }
            }
        }
    
        func deleteNote(note: Note) {
    
            // use note.data to access the NoteData instance
            _ = Amplify.API.mutate(request: .delete(note.data)) { event in
                switch event {
                case .success(let result):
                    switch result {
                    case .success(let data):
                        print("Successfully deleted note: \(data)")
                    case .failure(let error):
                        print("Got failed result with \(error.errorDescription)")
                    }
                case .failure(let error):
                    print("Got failed event with error \(error)")
                }
            }
        }

    在同一 Backend.swift 文件中,更新 updateUserData(withSignInStatus:) 方法如下所示:

    // change our internal state, this triggers an UI update on the main thread
    func updateUserData(withSignInStatus status : Bool) {
        DispatchQueue.main.async() {
            let userData : UserData = .shared
            userData.isSignedIn = status
    
            // when user is signed in, query the database, otherwise empty our model
            if status {
                self.queryNotes()
            } else {
                userData.notes = []
            }
        }
    }

    现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。

  • 现在,后端和数据模型已到位,本节的最后一步是让用户创建新的 Note 然后将其删除。

    在 Xcode 中,打开 ContentView.swift

    a.在 ContentView 结构中,添加绑定到用户界面的状态变量。

    // add at the begining of ContentView class
    @State var showCreateNote = false
    
    @State var name : String        = "New Note"
    @State var description : String = "This is a new note"
    @State var image : String       = "image"

    b.在文件中的任何位置,添加视图结构,以允许用户创建新的备注:

    struct AddNoteView: View {
        @Binding var isPresented: Bool
        var userData: UserData
    
        @State var name : String        = "New Note"
        @State var description : String = "This is a new note"
        @State var image : String       = "image"
        var body: some View {
            Form {
    
                Section(header: Text("TEXT")) {
                    TextField("Name", text: $name)
                    TextField("Name", text: $description)
                }
    
                Section(header: Text("PICTURE")) {
                    TextField("Name", text: $image)
                }
    
                Section {
                    Button(action: {
                        self.isPresented = false
                        let noteData = NoteData(id : UUID().uuidString,
                                                name: self.$name.wrappedValue,
                                                description: self.$description.wrappedValue)
                        let note = Note(from: noteData)
    
                        // asynchronously store the note (and assume it will succeed)
                        Backend.shared.createNote(note: note)
    
                        // add the new note in our userdata, this will refresh UI
                        self.userData.notes.append(note)
                    }) {
                        Text("Create this note")
                    }
                }
            }
        }
    }

    c.在导航栏上添加“+”按钮演示数据表以创建备注

    返回 ContentView 结构,将 navigationBarItems(leading SignOutButton()) 替换为:

        .navigationBarItems(leading: SignOutButton(),
                            trailing: Button(action: {
            self.showCreateNote.toggle()
        }) {
            Image(systemName: "plus")
        })
    }.sheet(isPresented: $showCreateNote) {
        AddNoteView(isPresented: self.$showCreateNote, userData: self.userData)
  • 最后,在 ContentView 中添加“轻扫以删除”行为: .onDelete { } 方法添加到 ForEach 结构中:

    ForEach(userData.notes) { note in
        ListRow(note: note)
    }.onDelete { indices in
        indices.forEach {
            // removing from user data will refresh UI
            let note = self.userData.notes.remove(at: $0)
    
            // asynchronously remove from database
            Backend.shared.deleteNote(note: note)
        }
    }
  • 要验证一切是否都按预期运行,请构建并运行项目。单击产品菜单,然后选择运行或键入 ⌘R。应该不会出现错误。

    假设您仍然登录,该应用程序一开始会提供一个空列表。它现在有一个“+”按钮可用于添加备注。点击“+”符号,点击“创建此备注”,备注将出现在列表中。

    您可以向下拉来关闭 AddNoteView。请注意,在 iOS 模拟器上,无法再次点击“+”,您需要先“拉取以刷新”列表。

    您可以通过向左滑动一行来删除 Note。

    以下是完整流程。

结论

您现已成功创建 iOS 应用程序! 您使用 AWS Amplify 添加了一个 GraphQL API,并在您的应用程序中配置了创建、读取和删除功能。

在下一模块中,我们将添加 UI 和行为以管理图片。

此模块有帮助吗?