基于 Amazon Amplify 构建自己的首个 iOS 应用程序

亚马逊云科技
Amazon Amplify
0
0
{"value":"### **摘要**\nAmazon Amplify 是一组位于云端的工具和[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)服务。本文的主要是介绍如何基于 Amazon Amplify 构建自己的首个 iOS 应用程序。首先,我们将构建一个简单的 iOS 应用程序。然后,使用 Amplify 命令行接口 (Amplify CLI) 初始化一个本地应用程序,同时添加用户身份验证、添加GraphQL API 和数据库以存储我们的数据,最后,更新我们的应用程序来存储图像文件。亚马逊云科技提供了100余种产品免费套餐。其中,计算资源Amazon EC2首年12个月免费,750小时/月;存储资源 [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) 首年12个月免费,5GB标准存储容量。\n[https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el](https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el)\n### **正文**\nAmazon Amplify 是一组专门构建的工具和功能,使前端 Web 和移动开发人员可以快速、轻松地在 Amazon 上构建全堆栈应用程序,随着使用案例的发展,可以灵活地利用广泛的 Amazon 服务。借助 Amplify,您可以在几分钟内配置 web 或移动应用程序后端并连接应用程序,可视化地构建 web 前端 UI,并在 Amazon 控制台外轻松管理应用程序内容。无需云专业知识,就可更快发布和扩展。\n现在很多技术方案都在上云,仿佛一切都可以云原生化。那么 iOS 应用程序开发是不是也可以在云端开发呢?今天就有机会体验了一把使用亚马逊的 Amazon Amplify 创建了一个 iOS 应用程序,实现了添加用户身份验证、添加 GraphQL API 和操作数据库等功能。接下来,我们就详细了解一下整个构建过程。\n#### **一、创建 iOS 应用程序**\nAmazon Amplify 提供基于 Git 的工作流,用于创建、管理、集成和部署 Web 和移动应用程序的[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)后端。Amplify CLI 提供了一个简单的基于文本的用户界面,用于预置和管理后端服务,比如用于应用程序的用户身份验证或 REST 或 GraphQL API。使用 Amplify 库可以轻松地将这些后端服务与应用程序中的几行代码集成。\n首先,我们创建一个新的 iOS 应用程序来记录旅行备注。其中,备注由标题、描述和图片组成。\n##### **1.1 创建 iOS 应用程序**\n启动 Xcode 并选择创建新的 Xcode 项目,具体操作如下图所示:\n\n![image.png](https://dev-media.amazoncloud.cn/d7f1c591b7eb4583a79d73d6648fed99_image.png)\n\n在 iOS 应用程序下,选择“单一视图应用程序”,然后单击“下一步”,具体操作如下图所示:\n\n![image.png](https://dev-media.amazoncloud.cn/cee3c047f0824df8a9a25956580414ef_image.png)\n\n键入项目的名称,例如 iOS Getting Started。确保将语言设置为“Swift”,如果可以设置用户界面的话,那就使用“SwiftUI”。其实,SwiftUI 是一种通过 Swift 编程语言在所有 Apple 平台上构建用户界面的简单方法。但是,本文使用的 XCode 版本没有该设置项,因此可以跳过,单击“下一步”,具体如下图所示:\n\n![image.png](https://dev-media.amazoncloud.cn/169b87badfb743e2b125e8060ebcfb43_image.png)\n\n最后,选择一个保存目录,然后单击“创建”,以创建项目,呈现效果如下图所示:\n\n![image.png](https://dev-media.amazoncloud.cn/53ea17a74f7249cbb3529ba4d7e59a60_image.png)\n\n##### **1.2 更新主视图**\n从 Xcode 左侧的文件列表中,打开 ContentView.swift,然后将代码替换为以下内容:\nObjective-C\n```\\n//\\n// ContentView.swift\\n// iOS Getting Started\\n//\\n// Created by lz on 2022/3/28.\\n// Copyright © 2022 lozen. All rights reserved.\\n//\\n\\nimport SwiftUI\\n\\n// singleton object to store user data\\nclass UserData : ObservableObject {\\n private init() {}\\n static let shared = UserData()\\n\\n @Published var notes : [Note] = []\\n @Published var isSignedIn : Bool = false\\n}\\n\\n// the data class to represents Notes\\nclass Note : Identifiable, ObservableObject {\\n var id : String\\n var name : String\\n var description : String?\\n var imageName : String?\\n @Published var image : Image?\\n\\n init(id: String, name: String, description: String? = nil, image: String? = nil ) {\\n self.id = id\\n self.name = name\\n self.description = description\\n self.imageName = image\\n }\\n}\\n\\n// a view to represent a single list item\\nstruct ListRow: View {\\n @ObservedObject var note : Note\\n var body: some View {\\n\\n return HStack(alignment: .center, spacing: 5.0) {\\n\\n // if there is an image, display it on the left\\n if (note.image != nil) {\\n note.image!\\n .resizable()\\n .frame(width: 50, height: 50)\\n }\\n\\n // the right part is a vertical stack with the title and description\\n VStack(alignment: .leading, spacing: 5.0) {\\n Text(note.name)\\n .bold()\\n\\n if ((note.description) != nil) {\\n Text(note.description!)\\n }\\n }\\n }\\n }\\n}\\n\\n// this is the main view of our app,\\n// it is made of a Table with one line per Note\\nstruct ContentView: View {\\n @ObservedObject private var userData: UserData = .shared\\n\\n var body: some View {\\n List {\\n ForEach(userData.notes) { note in\\n ListRow(note: note)\\n }\\n }\\n }\\n}\\n\\n// this is use to preview the UI in Xcode\\nstruct ContentView_Previews: PreviewProvider {\\n static var previews: some View {\\n\\n let _ = prepareTestData()\\n\\n return ContentView()\\n }\\n}\\n\\n// this is a test data set to preview the UI in Xcode\\nfunc prepareTestData() -> UserData {\\n let userData = UserData.shared\\n userData.isSignedIn = true\\n let desc = \\"this is a very long description that should fit on multiiple lines.\\\\nit even has a line break\\\\nor two.\\"\\n\\n let n1 = Note(id: \\"01\\", name: \\"Hello world\\", description: desc, image: \\"mic\\")\\n let n2 = Note(id: \\"02\\", name: \\"A new note\\", description: desc, image: \\"phone\\")\\n\\n n1.image = Image(systemName: n1.imageName!)\\n n2.image = Image(systemName: n2.imageName!)\\n\\n userData.notes = [ n1, n2 ]\\n\\n return userData\\n}\\n```\n上述代码都完成了如下工作:\n- 创建了一个备注类来存储备注的数据。针对 ImageName 和 Image 使用了两个不同的属性。稍后,我们将在“添加存储”部分中处理图像。\n- 创建了一个 UserData 类来保存特定用户的数据,在此阶段,只保存一个备注对象列表。\n- 主视图 ContentView 包含 ListRow 的列表\n- 每行都由 ListRow 呈现:包含图像和文本的水平堆栈。文本垂直堆栈备注名称和备注说明,以粗体显示。\n- 最后,调整了 ContentView_Previews 并添加了 prepareTestData() 以允许在画布中进行预览渲染。\n##### **1.3 构建和测试**\n检查画布以验证布局是否满足需要。如果看不到画布,可以使用编辑器菜单启用画布,然后单击画布。您可能还需要单击画布中的恢复按钮以生成布局预览。\n\n![image.png](https://dev-media.amazoncloud.cn/1d81d24b44224dc0b03c600265a454c2_image.png)\n\n请注意,预览数据是在代码中生成的,数据不会在运行时显示。如果一切正常,请在模拟器中构建并启动应用程序。单击产品菜单,然后选择运行或键入 。或者,您也可以单击工具栏中的播放 ▶️ 按钮。\n一段时间后,应用程序会在 iOS 模拟器中启动,初始屏幕为空。 预览数据不会在运行时呈现,它们仅用于 Xcode 内的预览,具体效果如下图所示:\n\n![image.png](https://dev-media.amazoncloud.cn/c9fc4ed182124d87961eedfdee8b138b_image.png)\n\n但是,这个过程中有个小插曲,由于我本机的XCode版本过低,导致代码编译不同,后来自己升级到11.5才解决。\n\n![image.png](https://dev-media.amazoncloud.cn/50bc85ba7ed146a7a232871883eb3cf9_image.png)\n\n#### **二、初始化 Amplify**\n上一步,我们已创建一个 iOS 应用程序,接下来继续开发并添加新功能。在应用程序中使用 Amazon Amplify,必须安装 Amplify 命令行,初始化 Amplify 项目目录,将项目配置为使用 Amplify 库,并在运行时初始化 Amplify 库。\n在正式开始前,我们先介绍两个概念。Amplify CLI – Amplify CLI 可使您直接从您的终端创建、管理和删除 Amazon 服务。Amplify 库 – 您可以使用 Amplify 库通过 Web 应用程序或移动应用程序使用 Amazon 服务。\n##### **2.1 安装 Amplify CLI**\nAmazon Amplify CLI 取决于 Node.js,要安装 Amazon Amplify CLI,请打开一个终端,然后输入以下命令:\nShell\n```\\n## Install Amplify CLI\\nnpm install -g @aws-amplify/cli\\n\\n## Verify installation and version\\namplify --version\\n##Scanning for plugins...\\n##Plugin scan successful\\n##4.27.2\\n```\n##### **2.2 初始化 Amplify 后端**\n在初始化 Amplify 后端模块之前,我们需要先创建对应的用户信息,在亚马逊服务管理后台添加对应的用户,并添加权限类型,具体如下图所示:\n\n![image.png](https://dev-media.amazoncloud.cn/32db4ab441594c5ba0496cd98562ddbe_image.png)\n\n用户名一般会默认生成,也可以自己修改并指定。另外,还要选择访问凭证类型,一种是访问密钥,为 API、CLI、SDK 和其他开发工具启用访问密钥 ID 和私有访问密钥;另一种是密码,启用密码,使得用户可以登录到亚马逊服务管理控制台。\n创建好用户后,点击“下一步”,选择“直接附加现有策略”,一直“下一步”,最后会提示创建用户成功。\n\n![image.png](https://dev-media.amazoncloud.cn/cfc30af72203473a869ea9eb3685f971_image.png)\n\n在执行过程中,可能会遇到如下报错:\nGroovy\n```\\n⠦ Initializing project in the cloud...An error occurred when creating the CloudFormation stack\\n✖ Root stack creation failed\\ninit failed\\nOptInRequired: The AWS Access Key Id needs a subscription for the service\\n at Request.extractError (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/protocol/query.js:50:29)\\n at Request.callListeners (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/sequential_executor.js:106:20)\\n at Request.emit (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/sequential_executor.js:78:10)\\n at Request.emit (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:686:14)\\n at Request.transition (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:22:10)\\n at AcceptorStateMachine.runTo (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/state_machine.js:14:12)\\n at /usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/state_machine.js:26:10\\n at Request.<anonymous> (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:38:9)\\n at Request.<anonymous> (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:688:12)\\n at Request.callListeners (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {\\n code: 'OptInRequired',\\n time: 2022-03-28T16:20:47.649Z,\\n requestId: '39858f24-3c22-4eaa-9a0e-398095a8ebc9',\\n statusCode: 403,\\n retryable: false,\\n retryDelay: 93.04199180846567\\n}\\n```\n很可能的原因就是信用卡信息错误,最常见的就是信用卡有效期填写有误,而我就是这个问题。\n接下来,我们开始正式创建后端基本结构,首先需要初始化 Amplify 项目目录并创建云后端。打开一个终端并将目录更改为第一步中的 iOS 项目。例如,如果您在文件夹 ~/job/ios/ 中创建项目,则可以键入:\nGroovy\n```\\ncd ~/job/ios/iOS Getting Started\\n```\n验证一下是否在正确的目录中,它应该如下所示:\nAssembly language\n```\\nliuzhen-40:iOS Getting Started lz\$ ls -al\\ntotal 16\\ndrwxr-xr-x 7 lz staff 224 Mar 28 11:06 .\\ndrwxr-xr-x 7 lz staff 224 Mar 28 10:05 ..\\n-rw-r--r--@ 1 lz staff 6148 Mar 28 22:09 .DS_Store\\ndrwxr-xr-x 9 lz staff 288 Mar 28 10:06 iOS Getting Started\\ndrwxr-xr-x@ 5 lz staff 160 Mar 28 10:05 iOS Getting Started.xcodeproj\\ndrwxr-xr-x 4 lz staff 128 Mar 28 10:05 iOS Getting StartedTests\\ndrwxr-xr-x 4 lz staff 128 Mar 28 10:05 iOS Getting StartedUITests\\n```\n初始化 Amplify 项目结构和配置文件,运行以下命令:\nSQL\n```\\nliuzhen-40:iOS Getting Started lz\$ amplify init\\nNote: It is recommended to run this command from the root of your app directory\\n? Enter a name for the project iOSGettingStarted\\n? Enter a name for the environment dev\\n? Choose your default editor: Visual Studio Code\\n? Choose the type of app that you're building ios\\nUsing default provider awscloudformation\\n\\nFor more information on AWS Profiles, see:\\nhttps://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html\\n\\n? Do you want to use an AWS profile? Yes\\n? Please choose the profile you want to use default\\nAdding backend environment dev to AWS Amplify Console app: dj56g6ku6ef6p\\n```\n##### **2.3 将 Amplify 库添加到项目中**\n在开始此步骤之前,请确保关闭 Xcode。然后命令行进入工程目录,使用 CocoaPods 软件包管理器初始化项目,请执行以下命令:\nNginx\n```\\n## Initialize the Amplify Project\\npod init\\n```\n完成此操作后,您应该看到一个名为 Podfile 的新建文件,此文件用于描述我们的项目所依赖的软件包。然后,更新 Podfile 以包含以下 Pod 并更新平台:\nBash\n```\\n# you need at least version 13.0 for this tutorial, more recent versions are valid too\\n\\nplatform :ios,'13.0'\\n\\ntarget 'iOS Getting Started' do\\n # Comment the next line if you don't want to use dynamic frameworks\\n use_frameworks!\\n # Pods for getting started\\n pod 'Amplify', '~> 1.0' # required amplify dependency\\n pod 'Amplify/Tools', '~> 1.0' # allows to call amplify CLI from within Xcode\\n\\nend\\n```\n将 Amplify 库下载并安装到您的项目中,执行如下命令:\nBash\n```\\npod install --repo-update\\n```\n完成此操作后,我们会发现生成了一个名为 iOS Getting Started.xcworkspace 的文件。之后,我们就需要使用这个文件,而不是 iOS Getting Started.xcodeproj 文件了。然后,在 Xcode 中打开工作区,并执行如下命令:\nBash\n```\\nxed .\\n```\n##### **2.4 在运行时初始化 Amplify**\n在运行时,Amplify 库需要由 CLI 生成的 Amplify 配置文件。将 Amplify 配置文件添加到项目中,使用 Finder 在项目目录的根目录下找到 awsconfiguration.json 和 amplifyconfiguration.json,并将其拖放到 Xcode 项目中。\n接下来,新建一个 swift 文本文件 Backend.swift,将其添加到我们的 Xcode 项目 (CTRL-N) 并添加以下代码:\nSwift\n```\\nimport UIKit\\nimport Amplify\\n\\nclass Backend {\\n static let shared = Backend()\\n static func initialize() -> Backend {\\n return .shared\\n }\\n private init() {\\n // initialize amplify\\n do {\\n try Amplify.configure()\\n print(\\"Initialized Amplify\\");\\n } catch {\\n print(\\"Could not initialize Amplify: \\\\(error)\\")\\n }\\n }\\n}\\n```\n应用程序启动后,初始化单例 Backend 对象。打开 AppDelegate.swift 文件并在 application(:didFinishLaunchingWithOptions:) 方法中添加 Backend.initialize();,如下所示:\nSwift\n```\\n应用程序启动后,初始化单例 Backend 对象。打开 AppDelegate.swift 文件并在 application(:didFinishLaunchingWithOptions:) 方法中添加 Backend.initialize();,如下所示:\\n```\n##### **2.5 验证设置**\n要验证一切是否都按预期运行,构建项目,单击 Product 菜单,然后选择 Build,或按 ⌘B。不出现错误即可。\n#### **三、添加身份验证**\n接下来,我们了解如何使用 Amplify CLI 和库对用户进行身份验证,以利用托管用户身份提供商 [Amazon Cognito](https://aws.amazon.com/cn/cognito/?trk=cndc-detail)。另外,知道如何使用 Cognito 托管用户界面展示整个用户身份验证流,从而使用户只需几行代码即可注册、登录和重置密码。使用“托管用户界面”意味着应用程序利用 Cognito 网页登录和注册用户界面流。应用程序的用户将重定向到 Cognito 托管的网页,并在登录后重定向回应用程序。当然,Cognito 和 Amplify 也支持本机 UI。\n##### **3.1 创建身份验证服务**\n创建身份验证服务,打开一个命令行终端,然后执行如下命令:\nBash\n```\\namplify add auth\\n```\n输出结果出现 successful 字样表示创建成功。\n##### **3.2 部署身份验证服务**\n现在,我们已在本地配置身份验证服务,我们可以将它部署到云。在终端中,在项目目录中执行以下命令:\nBash\n```\\namplify push\\n```\n##### **3.3 向项目添加 Amplify 身份验证库**\n在转至代码之前,将 Amplify 身份验证库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSCognitoAuthPlugin 添加行,或者复制并粘贴以下整个文件。\nBash\n```\\n# you need at least version 13.0 for this tutorial, more recent versions are valid too\\nplatform :ios, '13.0'\\n\\ntarget 'getting started' do\\n # Comment the next line if you don't want to use dynamic frameworks\\n use_frameworks!\\n\\n # Pods for getting started\\n pod 'Amplify', '~> 1.0' # required amplify dependency\\n pod 'Amplify/Tools', '~> 1.0' # allows to call amplify CLI from within Xcode\\n\\n pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~> 1.0' # support for Cognito user authentication\\n\\nend\\n```\n在终端中执行以下命令:\nBash\n```\\npod install\\n```\n##### **3.4 在运行时配置 Amplify 身份验证库**\n返回到 Xcode,打开 Backend.swift 文件,其完整代码如下所示:\nSwift\n```\\n// at the top of the file\\nimport AmplifyPlugins\\n\\nprivate init () {\\n // initialize amplify\\n do {\\n try Amplify.add(plugin: AWSCognitoAuthPlugin())\\n try Amplify.configure()\\n print(\\"Initialized Amplify\\")\\n } catch {\\n print(\\"Could not initialize Amplify: \\\\(error)\\")\\n }\\n}\\n```\n构建项目,保证不报错即可。\n##### **3.5 在运行时触发身份验证**\n其余代码更改会跟踪用户的状态,并在用户未登录时触发登录/注册用户界面。 添加登录和注销代码,在后端类中的任意位置,添加以下三种方法:\nSwift\n```\\n// signin with Cognito web user interface\\npublic func signIn() {\\n\\n _ = Amplify.Auth.signInWithWebUI(presentationAnchor: UIApplication.shared.windows.first!) { result in\\n switch result {\\n case .success(_):\\n print(\\"Sign in succeeded\\")\\n case .failure(let error):\\n print(\\"Sign in failed \\\\(error)\\")\\n }\\n }\\n}\\n\\n// signout\\npublic func signOut() {\\n\\n _ = Amplify.Auth.signOut() { (result) in\\n switch result {\\n case .success:\\n print(\\"Successfully signed out\\")\\n case .failure(let error):\\n print(\\"Sign out failed with error \\\\(error)\\")\\n }\\n }\\n}\\n\\n// change our internal state, this triggers an UI update on the main thread\\nfunc updateUserData(withSignInStatus status : Bool) {\\n DispatchQueue.main.async() {\\n let userData : UserData = .shared\\n userData.isSignedIn = status\\n }\\n}\\n```\n为了跟踪身份验证状态的变化,我们添加了代码以订阅由 Amplify 发送的身份验证事件。我们在 Backend.init() 方法中初始化该中心。在收到身份验证事件时,我们将调用 updateUserData() 方法。此方法可使 UserData 对象保持同步。UserData.isSignedIn 属性为 @Published,这意味着当值更改时,用户界面会自动刷新。 我们可以添加代码以在应用程序启动时检查以前的身份验证状态。当应用程序启动时,它会检查 Cognito 会话是否已存在,并相应地更新 UI。\n在 Backend.init() 中,请在 Amplify 初始化后添加以下代码:\nSwift\n```\\n// in private init() function\\n// listen to auth events.\\n// see https://github.com/aws-amplify/amplify-ios/blob/master/Amplify/Categories/Auth/Models/AuthEventName.swift\\n_ = Amplify.Hub.listen(to: .auth) { (payload) in\\n\\n switch payload.eventName {\\n\\n case HubPayload.EventName.Auth.signedIn:\\n print(\\"==HUB== User signed In, update UI\\")\\n self.updateUserData(withSignInStatus: true)\\n\\n case HubPayload.EventName.Auth.signedOut:\\n print(\\"==HUB== User signed Out, update UI\\")\\n self.updateUserData(withSignInStatus: false)\\n\\n case HubPayload.EventName.Auth.sessionExpired:\\n print(\\"==HUB== Session expired, show sign in UI\\")\\n self.updateUserData(withSignInStatus: false)\\n\\n default:\\n //print(\\"==HUB== \\\\(payload)\\")\\n break\\n }\\n}\\n \\n// let's check if user is signedIn or not\\n _ = Amplify.Auth.fetchAuthSession { (result) in\\n do {\\n let session = try result.get()\\n \\n// let's update UserData and the UI\\n self.updateUserData(withSignInStatus: session.isSignedIn)\\n \\n } catch {\\n print(\\"Fetch auth session failed with error - \\\\(error)\\")\\n }\\n} \\n```\n代码中的最后一个更改与用户界面相关,我们将 ZStack 添加到 ContentView。根据UserData.isSignedIn 的值,UI 将显示 SigninButton 或主列表视图。打开 ContentView.swift 并替换 ContentView 结构中的正文:\nSwift\n```\\nvar body: some View {\\n\\n ZStack {\\n if (userData.isSignedIn) {\\n NavigationView {\\n List {\\n ForEach(userData.notes) { note in\\n ListRow(note: note)\\n }\\n }\\n .navigationBarTitle(Text(\\"Notes\\"))\\n .navigationBarItems(leading: SignOutButton())\\n }\\n } else {\\n SignInButton()\\n }\\n }\\n}\\n```\n在同一文件中,添加 SignInButton 和 SignOutButton 视图:\nJavaScript\n```\\nstruct SignInButton: View {\\n var body: some View {\\n Button(action: { Backend.shared.signIn() }){\\n HStack {\\n Image(systemName: \\"person.fill\\")\\n .scaleEffect(1.5)\\n .padding()\\n Text(\\"Sign In\\")\\n .font(.largeTitle)\\n }\\n .padding()\\n .foregroundColor(.white)\\n .background(Color.green)\\n .cornerRadius(30)\\n }\\n }\\n}\\n\\nstruct SignOutButton : View {\\n var body: some View {\\n Button(action: { Backend.shared.signOut() }) {\\n Text(\\"Sign Out\\")\\n }\\n }\\n}\\n```\n最后,我们必须确保在 Cognito 托管用户界面提供的 Web 身份验证序列结束时启动我们的应用程序。我们将 gettingstarted URI 方案添加到应用程序的 Info.plist 文件中。在 Xcode 中,选择 Info.plist 文件,右键单击该文件,然后选择“打开为源代码”。\n另外,在顶部 <dict> 元素内添加以下 <key> 和 <array> 元素。\\nXML\\n```\\n<plist version=\\"1.0\\">\\n\\n <dict>\\n <!-- YOUR OTHER PLIST ENTRIES HERE -->\\n\\n <!-- ADD AN ENTRY TO CFBundleURLTypes for Cognito Auth -->\\n <!-- IF YOU DO NOT HAVE CFBundleURLTypes, YOU CAN COPY THE WHOLE BLOCK BELOW -->\\n <key>CFBundleURLTypes</key>\n <array>\\n <dict>\\n <key>CFBundleURLSchemes</key>\n <array>\\n <string>gettingstarted</string>\n </array>\\n </dict>\n </array>\\n\\n <!-- ... -->\\n </dict>\n```\\n然后,运行程序看看效果,整个注册流程如下图所示:\\n\\n![image.png](https://dev-media.amazoncloud.cn/f4fd107969ba44c8b7e4fcce42f4ef5f_image.png)\\n\\n![image.png](https://dev-media.amazoncloud.cn/396c116709b04fbb8e1926bc331d1b5a_image.png)\\n\\n#### **四、添加 API 和数据库**\\n现在,我们已经创建并配置了带用户身份验证功能的应用程序。接下来,我们要在数据库中添加 API 以及“创建”、“读取”、“更新”、“删除”(CRUD) 操作\\n##### **4.1 创建 GraphQL API 及数据库**\\n要创建 GraphQL API 及其数据库,打开一个命令行终端,然后从项目目录中执行如下命令:\\nBash\\n```\namplify add api\n```\\n初始化项目时选择的默认文本编辑器 (amplify init) 将使用预构建数据 schema 打开。删除此 schema,并使用我们的应用程序 GraphQL schema 替换:\\nBash\\n```\ntype NoteData\n@model\n@auth (rules: [ { allow: owner } ]) {\n id: ID!\n name: String!\n description: String\n image: String\n}\n```\\n##### **4.2 生成客户端代码**\\n根据我们刚刚创建的 GraphQL 数据模型定义,Amplify 会生成客户端代码(即 Swift 代码)以代表我们应用程序中的数据。要生成代码,在终端执行以下命令:\\nBash\\n```\namplify codegen models\n```\\n在 Finder 中找到新生成的文件,然后将其拖放到 Xcode 的项目中。\\n##### **4.3 部署 API 服务和数据库**\\n要部署我们刚刚创建的后端 API 和数据库,打开终端,然后执行如下命令:\\nBash\\n```\namplify push\n```\\n##### **4.4 将 API 客户端库添加到 Xcode 项目中**\\n在转至代码之前,将 Amplify API 库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSAPIPlugin 添加行,或者复制并粘贴以下整个文件。\\nBash\\n```\n# you need at least version 13.0 for this tutorial, more recent versions are valid too\nplatform :ios, '13.0'\n\ntarget 'getting started' do\n # Comment the next line if you don't want to use dynamic frameworks\n use_frameworks!\n\n # Pods for getting started\n pod 'Amplify', '~> 1.0' # required amplify dependency\n pod 'Amplify/Tools', '~> 1.0' # allows to call amplify CLI from within Xcode\n\n pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~> 1.0' # support for Cognito user authentication\n pod 'AmplifyPlugins/AWSAPIPlugin', '~> 1.0' # support for GraphQL API\n\nend\n```\\n然后,执行如下命令:\\nBash\\n```\npod install\n```\\n##### **4.5 在运行时初始化 Amplify 库**\\n返回到 Xcode,打开 Backend.swift,然后在私有 init() 方法的 Amplify 初始化序列中添加一行。完整代码块应如下所示:\\nSwift\\n```\n// initialize amplify\ndo {\n try Amplify.add(plugin: AWSCognitoAuthPlugin())\n try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))\n try Amplify.configure()\n print(\"Initialized Amplify\")\n} catch {\n print(\"Could not initialize Amplify: \\(error)\")\n}\n```\\n##### **4.6 在 GraphQL 数据模型和应用程序模型之间添加桥接**\\n我们的项目已经有一个数据模型来表示备注。因此,可以继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 ContentView.swift 并将此初始化程序添加到备注类中。\\nSwift\\n```\nconvenience init(from data: NoteData) {\n self.init(id: data.id, name: data.name, description: data.description, image: data.image)\n \n // store API object for easy retrieval later\n self._data = data\n}\n\nfileprivate var _data : NoteData?\n\n// access the privately stored NoteData or build one if we don't have one.\nvar data : NoteData {\n\n if (_data == nil) {\n _data = NoteData(id: self.id,\n name: self.name,\n description: self.description,\n image: self.imageName)\n }\n\n return _data!\n}\n```\\n##### **4.7 将 API CRUD 方法添加到后端类中**\\n我们添加 3 种方法来调用 API:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型(备注),以便从用户界面轻松交互。这些方法可透明地将 Note 转换为 GraphQL 的 NoteData 对象。\\n打开 Backend.swift 文件,然后在后端类末尾添加以下 snipet:\\nSwift\\n```\n// MARK: API Access\n\n func queryNotes() {\n\n _ = Amplify.API.query(request: .list(NoteData.self)) { event in\n switch event {\n case .success(let result):\n switch result {\n case .success(let notesData):\n print(\"Successfully retrieved list of Notes\")\n\n // convert an array of NoteData to an array of Note class instances\n for n in notesData {\n let note = Note.init(from: n)\n DispatchQueue.main.async() {\n UserData.shared.notes.append(note)\n }\n }\n\n case .failure(let error):\n print(\"Can not retrieve result : error \\(error.errorDescription)\")\n }\n case .failure(let error):\n print(\"Can not retrieve Notes : error \\(error)\")\n }\n }\n }\n\n func createNote(note: Note) {\n\n // use note.data to access the NoteData instance\n _ = Amplify.API.mutate(request: .create(note.data)) { event in\n switch event {\n case .success(let result):\n switch result {\n case .success(let data):\n print(\"Successfully created note: \\(data)\")\n case .failure(let error):\n print(\"Got failed result with \\(error.errorDescription)\")\n }\n case .failure(let error):\n print(\"Got failed event with error \\(error)\")\n }\n }\n }\n\n func deleteNote(note: Note) {\n\n // use note.data to access the NoteData instance\n _ = Amplify.API.mutate(request: .delete(note.data)) { event in\n switch event {\n case .success(let result):\n switch result {\n case .success(let data):\n print(\"Successfully deleted note: \\(data)\")\n case .failure(let error):\n print(\"Got failed result with \\(error.errorDescription)\")\n }\n case .failure(let error):\n print(\"Got failed event with error \\(error)\")\n }\n }\n }\n```\\n在同一 Backend.swift 文件中,更新 updateUserData(withSignInStatus:) 方法如下所示:\\nSwift\\n```\n// change our internal state, this triggers an UI update on the main thread\nfunc updateUserData(withSignInStatus status : Bool) {\n DispatchQueue.main.async() {\n let userData : UserData = .shared\n userData.isSignedIn = status\n\n // when user is signed in, query the database, otherwise empty our model\n if status {\n self.queryNotes()\n } else {\n userData.notes = []\n }\n }\n}\n```\\n现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。\\n##### **4.8 添加“Edit”按钮以添加备注**\\n现在,后端和数据模型已到位,最后一步是让用户创建新的 Note 然后将其删除。在 Xcode 中,打开 ContentView.swift,在 ContentView 结构中,添加绑定到用户界面的状态变量。\\nSwift\\n```\n// add at the begining of ContentView class\n@State var showCreateNote = false\n\n@State var name : String = \"New Note\"\n@State var description : String = \"This is a new note\"\n@State var image : String = \"image\"\n```\\n在文件中的任何位置,添加视图结构,以允许用户创建新的备注:\\nSwift\\n```struct AddNoteView: View {\n @Binding var isPresented: Bool\n var userData: UserData\n\n @State var name : String = \"New Note\"\n @State var description : String = \"This is a new note\"\n @State var image : String = \"image\"\n var body: some View {\n Form {\n\n Section(header: Text(\"TEXT\")) {\n TextField(\"Name\", text: \$name)\\n TextField(\\"Name\\", text: \$description)\n }\n\n Section(header: Text(\"PICTURE\")) {\n TextField(\"Name\", text: \$image)\\n }\\n\\n Section {\\n Button(action: {\\n self.isPresented = false\\n let noteData = NoteData(id : UUID().uuidString,\\n name: self.\$name.wrappedValue,\n description: self.\$description.wrappedValue)\\n let note = Note(from: noteData)\\n\\n // asynchronously store the note (and assume it will succeed)\\n Backend.shared.createNote(note: note)\\n\\n // add the new note in our userdata, this will refresh UI\\n self.userData.notes.append(note)\\n }) {\\n Text(\\"Create this note\\")\\n }\\n }\\n }\\n }\\n}\\n```\\n在导航栏上添加“+”按钮演示数据表以创建备注,返回 ContentView 结构,将 navigationBarItems(leading SignOutButton()) 替换为:\\nSwift\\n```\\n.navigationBarItems(leading: SignOutButton(),\\n trailing: Button(action: {\\n self.showCreateNote.toggle()\\n }) {\\n Image(systemName: \\"plus\\")\\n })\\n}.sheet(isPresented: \$showCreateNote) {\n AddNoteView(isPresented: self.\$showCreateNote, userData: self.userData)\\n```\\n##### **4.9 添加“滑动删除”行为**\\n最后,在 ContentView 中添加“轻扫以删除”行为:将 .onDelete { } 方法添加到 ForEach 结构中:\\nSwift\\n```\\nForEach(userData.notes) { note in\\n ListRow(note: note)\\n}.onDelete { indices in\\n indices.forEach {\\n // removing from user data will refresh UI\\n let note = self.userData.notes.remove(at: \$0)\n\n // asynchronously remove from database\n Backend.shared.deleteNote(note: note)\n }\n}\n```\\n##### **4.10 构建和测试**\\n首先,编译程序,保证不出现报错。然后,运行程序,整个完整流程如下图所示:\\n\\n![image.png](https://dev-media.amazoncloud.cn/fd53a0294584413db9de334787bf0852_image.png)\\n\\n#### **五、添加存储**\\n现在,我们的备注应用程序已在运行,接下来添加将图像与每个备注关联的功能。在本模块中,将使用 Amplify CLI 和库来创建利用 Amazon S3 的存储服务。最后,将更新 iOS 应用程序以启用图像上传、获取和渲染。\\n##### **5.1 创建存储服务**\\n要添加图像存储功能,我们将使用 Amplify 存储类别,执行如下命令:\\nBash\\n```\namplify add storage\n```\\n##### **5.2 部署存储服务**\\n要部署我们刚刚创建的存储服务,打开终端,然后执行以下命令:\\nBash\\n```\namplify push\n```\\n##### **5.3 向Xcode项目中添加 Amplify 存储库**\\n在转至代码之前,将 Amplify 存储库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSS3StoragePlugin 添加行,或者复制并粘贴以下整个文件。\\nBash\\n```\n# you need at least version 13.0 for this tutorial, more recent versions are valid too\nplatform :ios, '13.0'\n\ntarget 'getting started' do\n # Comment the next line if you don't want to use dynamic frameworks\n use_frameworks!\n\n # Pods for getting started\n pod 'Amplify', '~> 1.0' # required amplify dependency\n pod 'Amplify/Tools', '~> 1.0' # allows to call amplify CLI from within Xcode\n\n pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~> 1.0' # support for Cognito user authentication\n pod 'AmplifyPlugins/AWSAPIPlugin', '~> 1.0' # support for GraphQL API\n pod 'AmplifyPlugins/AWSS3StoragePlugin', '~> 1.0' # support for [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) storage\n\nend\n```\\n然后,执行如下命令:\\nBash\\n```\npod install\n```\\n##### **5.4 在运行时初始化 Amplify 存储插件**\\n返回 Xcode,打开 Backend.swift,然后在私有 init() 方法的 Amplify 初始化序列中添加一行。完整代码块应如下所示:\\nSwift\\n```\n// initialize amplify\ndo {\n try Amplify.add(plugin: AWSCognitoAuthPlugin())\n try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))\n try Amplify.add(plugin: AWSS3StoragePlugin())\n try Amplify.configure()\n print(\"Initialized Amplify\");\n} catch {\n print(\"Could not initialize Amplify: \\(error)\")\n}\n```\\n##### **5.5 将 Image CRUD 方法添加到后端类**\\n打开 Backend.Swift。在后端类中的任意位置,添加以下方法:\\nSwift\\n```\nfunc storeImage(name: String, image: Data) {\n\n// let options = StorageUploadDataRequest.Options(accessLevel: .private)\n let _ = Amplify.Storage.uploadData(key: name, data: image,// options: options,\n progressListener: { progress in\n // optionlly update a progress bar here\n }, resultListener: { event in\n switch event {\n case .success(let data):\n print(\"Image upload completed: \\(data)\")\n case .failure(let storageError):\n print(\"Image upload failed: \\(storageError.errorDescription). \\(storageError.recoverySuggestion)\")\n }\n })\n}\n\nfunc retrieveImage(name: String, completed: @escaping (Data) -> Void) {\n let _ = Amplify.Storage.downloadData(key: name,\n progressListener: { progress in\n // in case you want to monitor progress\n }, resultListener: { (event) in\n switch event {\n case let .success(data):\n print(\"Image \\(name) loaded\")\n completed(data)\n case let .failure(storageError):\n print(\"Can not download image: \\(storageError.errorDescription). \\(storageError.recoverySuggestion)\")\n }\n }\n )\n}\n\nfunc deleteImage(name: String) {\n let _ = Amplify.Storage.remove(key: name,\n resultListener: { (event) in\n switch event {\n case let .success(data):\n print(\"Image \\(data) deleted\")\n case let .failure(storageError):\n print(\"Can not delete image: \\(storageError.errorDescription). \\(storageError.recoverySuggestion)\")\n }\n }\n )\n}\n```\\n##### **5.6 API 检索数据时加载图像**\\n现在,后端函数已经可用,我们接下来在 API 调用返回结果时加载图像。添加此行为的中心位置是,应用程序通过 API 返回的 NoteData 构造备注 UI 时。打开 ContentView.swift 并更新备注的初始化程序(添加第 8 行至第 17 行):\\nSwift\\n```\n// add a publishable's object property\n@Published var image : Image?\n\n// update init's code\nconvenience init(from data: NoteData) {\n self.init(id: data.id, name: data.name, description: data.description, image: data.image)\n\n if let name = self.imageName {\n // asynchronously download the image\n Backend.shared.retrieveImage(name: name) { (data) in\n // update the UI on the main thread\n DispatchQueue.main.async() {\n let uim = UIImage(data: data)\n self.image = Image(uiImage: uim!)\n }\n }\n }\n // store API object for easy retrieval later\n self._data = data\n```\\n当备注实例中存在图像名称时,代码将调用 retrieveImage,它是一个异步函数。下载图像时需要调用一个函数,该函数创建一个图像 UI 对象并将其分配给备注实例。请注意,此分配会触发用户界面更新,因此会在应用程序的主线程中使用 DispatchQueue.main.async 进行更新。\\n##### **5.7 添加 UI 代码以捕获图像**\\n首先,我们添加通用代码以支持图像捕获。此代码可在许多应用程序中重复使用;它显示了一个图像选择器,允许用户从其图像库中选择图像。在 Xcode 中,创建新的 Swift 文件(⌘N,然后选择 Swift)。命名 CaptureImageView.swift 文件,然后添加此代码:\\nSwift\\n```\nimport Foundation\nimport UIKit\nimport SwiftUI\n\nstruct CaptureImageView {\n\n /// MARK: - Properties\n @Binding var isShown: Bool\n @Binding var image: UIImage?\n\n func makeCoordinator() -> Coordinator {\n return Coordinator(isShown: \$isShown, image: \$image)\n }\n}\n\nclass Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {\n @Binding var isCoordinatorShown: Bool\n @Binding var imageInCoordinator: UIImage?\n init(isShown: Binding<Bool>, image: Binding<UIImage?>) {\\n _isCoordinatorShown = isShown\\n _imageInCoordinator = image\\n }\\n func imagePickerController(_ picker: UIImagePickerController,\\n didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {\\n guard let unwrapImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }\\n imageInCoordinator = unwrapImage\\n isCoordinatorShown = false\\n }\\n func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {\\n isCoordinatorShown = false\\n }\\n}\\n\\nextension CaptureImageView: UIViewControllerRepresentable {\\n func makeUIViewController(context: UIViewControllerRepresentableContext<CaptureImageView>) -> UIImagePickerController {\\n let picker = UIImagePickerController()\\n picker.delegate = context.coordinator\\n\\n // picker.sourceType = .camera // on real devices, you can capture image from the camera\\n // see https://medium.com/better-programming/how-to-pick-an-image-from-camera-or-photo-library-in-swiftui-a596a0a2ece\\n\\n return picker\\n }\\n\\n func updateUIViewController(_ uiViewController: UIImagePickerController,\\n context: UIViewControllerRepresentableContext<CaptureImageView>) {\\n\\n }\\n}\\n```\\n##### **5.8 创建备注时存储图像**\\n创建备注时,我们从后端调用存储方法。打开 ContentView.swift 并修改 AddNoteView 以添加 ImagePicker 组件:\\nSwift\\n```\\n// at the start of the Content View struct \\n@State var image : UIImage? // replace the previous declaration of image\\n@State var showCaptureImageView = false\\n\\n// in the view, replace the existing PICTURE section\\nSection(header: Text(\\"PICTURE\\")) {\\n VStack {\\n Button(action: {\\n self.showCaptureImageView.toggle()\\n }) {\\n Text(\\"Choose photo\\")\\n }.sheet(isPresented: \$showCaptureImageView) {\\n CaptureImageView(isShown: self.\$showCaptureImageView, image: self.\$image)\\n }\\n if (image != nil ) {\\n HStack {\\n Spacer()\\n Image(uiImage: image!)\\n .resizable()\\n .frame(width: 250, height: 200)\\n .clipShape(Circle())\\n .overlay(Circle().stroke(Color.white, lineWidth: 4))\\n .shadow(radius: 10)\\n Spacer()\\n }\\n }\\n }\\n}\\n```\\n修改“创建备注”部分以存储图像和备注:\\nSwift\\n```\\nSection {\\n Button(action: {\\n self.isPresented = false\\n\\n let note = Note(id : UUID().uuidString,\\n name: self.\$name.wrappedValue,\\n description: self.\$description.wrappedValue)\\n\\n if let i = self.image {\\n note.imageName = UUID().uuidString\\n note.image = Image(uiImage: i)\\n\\n // asynchronously store the image (and assume it will work)\\n Backend.shared.storeImage(name: note.imageName!, image: (i.pngData())!)\\n }\\n\\n // asynchronously store the note (and assume it will succeed)\\n Backend.shared.createNote(note: note)\\n\\n // add the new note in our userdata, this will refresh UI\\n withAnimation { self.userData.notes.append(note) }\\n }) {\\n Text(\\"Create this note\\")\\n }\\n}\\n```\\n##### **5.9 构建和测试**\\n首先,编译程序,保证不出现报错。然后,运行程序,整个完整流程如下图所示:\\n\\n![image.png](https://dev-media.amazoncloud.cn/b833c3a7055b4181bd1b49920e4e29bb_image.png)\\n![image.png](https://dev-media.amazoncloud.cn/dae96849b7cf421d989a7f23ae8b75a1_image.png)\\n\\n### 结论\\n至此,我们已经体验了使用 Amazon Amplify 构建 iOS 应用程序的过程! 在应用程序中添加了身份验证,用户可以注册、登录和管理其账户。该应用程序还使用 Amazon DynamoDB 数据库配置了一个可扩展的 GraphQL API,用户可以创建和删除备注。另外,还使用 Amazon S3 添加了文件存储,从而使用户可以上传图像并在其应用程序中查看它们。但是,使用过程中也出现了很多小插曲,非常耽误时间,由于篇幅的限制,我简单说两点:一、Xcode 版本兼容性问题。最开始我使用低于11.5的版本,遇到了很多编译错误,最后升级到11.5才解决。因此,体验时要保证一个较高的 Xcode 版本,这一点应该在官方文档中说明;二、信用卡注册问题。服务中国客户,亚马逊应该入乡随俗,提供更加人性化的注册方式,可以考虑支付宝或者微信登陆注册,这样会比较符合大家的习惯。别的方面还比较顺利,感兴趣的话,小伙伴们就自己动手尝试一下吧!另外,亚马逊云科技专为开发者们打造了多种学习平台:\\n1. 入门资源中心:从0到1 轻松上手云服务,内容涵盖:成本管理,上手训练,开发资源。[https://aws.amazon.com/cn/getting-started/?nc1=h_ls&trk=32540c74-46f0-46dc-940d-621a1efeedd0&sc_channel=el](https://aws.amazon.com/cn/getting-started/?nc1=h_ls&trk=32540c74-46f0-46dc-940d-621a1efeedd0&sc_channel=el)\\n2. 架构中心:亚马逊云科技架构中心提供了云平台参考架构图表、经过审查的架构解决方案、Well-Architected 最佳实践、模式、图标等。[https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&trk=3fa608de-d954-4355-a20a-324daa58bbeb&sc_channel=el](https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&trk=3fa608de-d954-4355-a20a-324daa58bbeb&sc_channel=el)\\n3. 构建者库:了解亚马逊云科技如何构建和运营软件。[https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&cards-body.sort-order=desc&awsf.filter-content-category=*all&awsf.filter-content-type=*all&awsf.filter-content-level=*all&trk=835e6894-d909-4691-aee1-3831428c04bd&sc_channel=el](https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&cards-body.sort-order=desc&awsf.filter-content-category=*all&awsf.filter-content-type=*all&awsf.filter-content-level=*all&trk=835e6894-d909-4691-aee1-3831428c04bd&sc_channel=el)\\n4. 用于在亚马逊云科技平台上开发和管理应用程序的工具包:[https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&trk=972c69e1-55ec-43af-a503-d458708bb645&sc_channel=el](https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&trk=972c69e1-55ec-43af-a503-d458708bb645&sc_channel=el)\\n\\n【专属福利】\\n 福利一:100余种产品免费套餐。其中,计算资源 Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。\\n[https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el](https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el)\\n\\n 福利二:最新优惠大礼包,200\$数据与分析抵扣券,200\$机器学习抵扣券,200\$微服务与应用开发抵扣券。[https://www.amazonaws.cn/campaign/?sc_channel=el&sc_campaign=credit-acts-ldr&sc_country=cn&sc_geo=chna&sc_category=mult&sc_outcome=field&trkCampaign=request-credit-glb-ldr&trk=f45email&trk=02faebcb-3f61-4bcb-b68e-c63f3ae33c99&sc_channel=el](https://www.amazonaws.cn/campaign/?sc_channel=el&sc_campaign=credit-acts-ldr&sc_country=cn&sc_geo=chna&sc_category=mult&sc_outcome=field&trkCampaign=request-credit-glb-ldr&trk=f45email&trk=02faebcb-3f61-4bcb-b68e-c63f3ae33c99&sc_channel=el)\\n\\n福利三:解决方案 CloudFormation 一键部署模版库\\n[https://aws.amazon.com/cn/quickstart/?solutions-all.sort-by=item.additionalFields.sortDate&solutions-all.sort-order=desc&awsf.filter-tech-category=*all&awsf.filter-industry=*all&awsf.filter-content-type=*all&trk=afdbbdf0-610b-4421-ac0c-a6b31f902e4b&sc_channel=el](https://aws.amazon.com/cn/quickstart/?solutions-all.sort-by=item.additionalFields.sortDate&solutions-all.sort-order=desc&awsf.filter-tech-category=*all&awsf.filter-industry=*all&awsf.filter-content-type=*all&trk=afdbbdf0-610b-4421-ac0c-a6b31f902e4b&sc_channel=el)","render":"<h3><a id=\\"_0\\"></a><strong>摘要</strong></h3>\\n<p>Amazon Amplify 是一组位于云端的工具和无服务器服务。本文的主要是介绍如何基于 Amazon Amplify 构建自己的首个 iOS 应用程序。首先,我们将构建一个简单的 iOS 应用程序。然后,使用 Amplify 命令行接口 (Amplify CLI) 初始化一个本地应用程序,同时添加用户身份验证、添加GraphQL API 和数据库以存储我们的数据,最后,更新我们的应用程序来存储图像文件。亚马逊云科技提供了100余种产品免费套餐。其中,计算资源Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。<br />\\n<a href=\\"https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el</a></p>\\n<h3><a id=\\"_3\\"></a><strong>正文</strong></h3>\\n<p>Amazon Amplify 是一组专门构建的工具和功能,使前端 Web 和移动开发人员可以快速、轻松地在 Amazon 上构建全堆栈应用程序,随着使用案例的发展,可以灵活地利用广泛的 Amazon 服务。借助 Amplify,您可以在几分钟内配置 web 或移动应用程序后端并连接应用程序,可视化地构建 web 前端 UI,并在 Amazon 控制台外轻松管理应用程序内容。无需云专业知识,就可更快发布和扩展。<br />\\n现在很多技术方案都在上云,仿佛一切都可以云原生化。那么 iOS 应用程序开发是不是也可以在云端开发呢?今天就有机会体验了一把使用亚马逊的 Amazon Amplify 创建了一个 iOS 应用程序,实现了添加用户身份验证、添加 GraphQL API 和操作数据库等功能。接下来,我们就详细了解一下整个构建过程。</p>\n<h4><a id=\\"_iOS__6\\"></a><strong>一、创建 iOS 应用程序</strong></h4>\\n<p>Amazon Amplify 提供基于 Git 的工作流,用于创建、管理、集成和部署 Web 和移动应用程序的无服务器后端。Amplify CLI 提供了一个简单的基于文本的用户界面,用于预置和管理后端服务,比如用于应用程序的用户身份验证或 REST 或 GraphQL API。使用 Amplify 库可以轻松地将这些后端服务与应用程序中的几行代码集成。<br />\\n首先,我们创建一个新的 iOS 应用程序来记录旅行备注。其中,备注由标题、描述和图片组成。</p>\n<h5><a id=\\"11__iOS__9\\"></a><strong>1.1 创建 iOS 应用程序</strong></h5>\\n<p>启动 Xcode 并选择创建新的 Xcode 项目,具体操作如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/d7f1c591b7eb4583a79d73d6648fed99_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 iOS 应用程序下,选择“单一视图应用程序”,然后单击“下一步”,具体操作如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/cee3c047f0824df8a9a25956580414ef_image.png\\" alt=\\"image.png\\" /></p>\n<p>键入项目的名称,例如 iOS Getting Started。确保将语言设置为“Swift”,如果可以设置用户界面的话,那就使用“SwiftUI”。其实,SwiftUI 是一种通过 Swift 编程语言在所有 Apple 平台上构建用户界面的简单方法。但是,本文使用的 XCode 版本没有该设置项,因此可以跳过,单击“下一步”,具体如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/169b87badfb743e2b125e8060ebcfb43_image.png\\" alt=\\"image.png\\" /></p>\n<p>最后,选择一个保存目录,然后单击“创建”,以创建项目,呈现效果如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/53ea17a74f7249cbb3529ba4d7e59a60_image.png\\" alt=\\"image.png\\" /></p>\n<h5><a id=\\"12__26\\"></a><strong>1.2 更新主视图</strong></h5>\\n<p>从 Xcode 左侧的文件列表中,打开 ContentView.swift,然后将代码替换为以下内容:<br />\\nObjective-C</p>\n<pre><code class=\\"lang-\\">//\\n// ContentView.swift\\n// iOS Getting Started\\n//\\n// Created by lz on 2022/3/28.\\n// Copyright © 2022 lozen. All rights reserved.\\n//\\n\\nimport SwiftUI\\n\\n// singleton object to store user data\\nclass UserData : ObservableObject {\\n private init() {}\\n static let shared = UserData()\\n\\n @Published var notes : [Note] = []\\n @Published var isSignedIn : Bool = false\\n}\\n\\n// the data class to represents Notes\\nclass Note : Identifiable, ObservableObject {\\n var id : String\\n var name : String\\n var description : String?\\n var imageName : String?\\n @Published var image : Image?\\n\\n init(id: String, name: String, description: String? = nil, image: String? = nil ) {\\n self.id = id\\n self.name = name\\n self.description = description\\n self.imageName = image\\n }\\n}\\n\\n// a view to represent a single list item\\nstruct ListRow: View {\\n @ObservedObject var note : Note\\n var body: some View {\\n\\n return HStack(alignment: .center, spacing: 5.0) {\\n\\n // if there is an image, display it on the left\\n if (note.image != nil) {\\n note.image!\\n .resizable()\\n .frame(width: 50, height: 50)\\n }\\n\\n // the right part is a vertical stack with the title and description\\n VStack(alignment: .leading, spacing: 5.0) {\\n Text(note.name)\\n .bold()\\n\\n if ((note.description) != nil) {\\n Text(note.description!)\\n }\\n }\\n }\\n }\\n}\\n\\n// this is the main view of our app,\\n// it is made of a Table with one line per Note\\nstruct ContentView: View {\\n @ObservedObject private var userData: UserData = .shared\\n\\n var body: some View {\\n List {\\n ForEach(userData.notes) { note in\\n ListRow(note: note)\\n }\\n }\\n }\\n}\\n\\n// this is use to preview the UI in Xcode\\nstruct ContentView_Previews: PreviewProvider {\\n static var previews: some View {\\n\\n let _ = prepareTestData()\\n\\n return ContentView()\\n }\\n}\\n\\n// this is a test data set to preview the UI in Xcode\\nfunc prepareTestData() -&gt; UserData {\\n let userData = UserData.shared\\n userData.isSignedIn = true\\n let desc = &quot;this is a very long description that should fit on multiiple lines.\\\\nit even has a line break\\\\nor two.&quot;\\n\\n let n1 = Note(id: &quot;01&quot;, name: &quot;Hello world&quot;, description: desc, image: &quot;mic&quot;)\\n let n2 = Note(id: &quot;02&quot;, name: &quot;A new note&quot;, description: desc, image: &quot;phone&quot;)\\n\\n n1.image = Image(systemName: n1.imageName!)\\n n2.image = Image(systemName: n2.imageName!)\\n\\n userData.notes = [ n1, n2 ]\\n\\n return userData\\n}\\n</code></pre>\\n<p>上述代码都完成了如下工作:</p>\n<ul>\\n<li>创建了一个备注类来存储备注的数据。针对 ImageName 和 Image 使用了两个不同的属性。稍后,我们将在“添加存储”部分中处理图像。</li>\n<li>创建了一个 UserData 类来保存特定用户的数据,在此阶段,只保存一个备注对象列表。</li>\n<li>主视图 ContentView 包含 ListRow 的列表</li>\n<li>每行都由 ListRow 呈现:包含图像和文本的水平堆栈。文本垂直堆栈备注名称和备注说明,以粗体显示。</li>\n<li>最后,调整了 ContentView_Previews 并添加了 prepareTestData() 以允许在画布中进行预览渲染。</li>\n</ul>\\n<h5><a id=\\"13__139\\"></a><strong>1.3 构建和测试</strong></h5>\\n<p>检查画布以验证布局是否满足需要。如果看不到画布,可以使用编辑器菜单启用画布,然后单击画布。您可能还需要单击画布中的恢复按钮以生成布局预览。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/1d81d24b44224dc0b03c600265a454c2_image.png\\" alt=\\"image.png\\" /></p>\n<p>请注意,预览数据是在代码中生成的,数据不会在运行时显示。如果一切正常,请在模拟器中构建并启动应用程序。单击产品菜单,然后选择运行或键入 。或者,您也可以单击工具栏中的播放 ▶️ 按钮。<br />\\n一段时间后,应用程序会在 iOS 模拟器中启动,初始屏幕为空。 预览数据不会在运行时呈现,它们仅用于 Xcode 内的预览,具体效果如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/c9fc4ed182124d87961eedfdee8b138b_image.png\\" alt=\\"image.png\\" /></p>\n<p>但是,这个过程中有个小插曲,由于我本机的XCode版本过低,导致代码编译不同,后来自己升级到11.5才解决。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/50bc85ba7ed146a7a232871883eb3cf9_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"_Amplify_153\\"></a><strong>二、初始化 Amplify</strong></h4>\\n<p>上一步,我们已创建一个 iOS 应用程序,接下来继续开发并添加新功能。在应用程序中使用 Amazon Amplify,必须安装 Amplify 命令行,初始化 Amplify 项目目录,将项目配置为使用 Amplify 库,并在运行时初始化 Amplify 库。<br />\\n在正式开始前,我们先介绍两个概念。Amplify CLI – Amplify CLI 可使您直接从您的终端创建、管理和删除 Amazon 服务。Amplify 库 – 您可以使用 Amplify 库通过 Web 应用程序或移动应用程序使用 Amazon 服务。</p>\n<h5><a id=\\"21__Amplify_CLI_156\\"></a><strong>2.1 安装 Amplify CLI</strong></h5>\\n<p>Amazon Amplify CLI 取决于 Node.js,要安装 Amazon Amplify CLI,请打开一个终端,然后输入以下命令:<br />\\nShell</p>\n<pre><code class=\\"lang-\\">## Install Amplify CLI\\nnpm install -g @aws-amplify/cli\\n\\n## Verify installation and version\\namplify --version\\n##Scanning for plugins...\\n##Plugin scan successful\\n##4.27.2\\n</code></pre>\\n<h5><a id=\\"22__Amplify__169\\"></a><strong>2.2 初始化 Amplify 后端</strong></h5>\\n<p>在初始化 Amplify 后端模块之前,我们需要先创建对应的用户信息,在亚马逊服务管理后台添加对应的用户,并添加权限类型,具体如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/32db4ab441594c5ba0496cd98562ddbe_image.png\\" alt=\\"image.png\\" /></p>\n<p>用户名一般会默认生成,也可以自己修改并指定。另外,还要选择访问凭证类型,一种是访问密钥,为 API、CLI、SDK 和其他开发工具启用访问密钥 ID 和私有访问密钥;另一种是密码,启用密码,使得用户可以登录到亚马逊服务管理控制台。<br />\\n创建好用户后,点击“下一步”,选择“直接附加现有策略”,一直“下一步”,最后会提示创建用户成功。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/cfc30af72203473a869ea9eb3685f971_image.png\\" alt=\\"image.png\\" /></p>\n<p>在执行过程中,可能会遇到如下报错:<br />\\nGroovy</p>\n<pre><code class=\\"lang-\\">⠦ Initializing project in the cloud...An error occurred when creating the CloudFormation stack\\n✖ Root stack creation failed\\ninit failed\\nOptInRequired: The AWS Access Key Id needs a subscription for the service\\n at Request.extractError (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/protocol/query.js:50:29)\\n at Request.callListeners (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/sequential_executor.js:106:20)\\n at Request.emit (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/sequential_executor.js:78:10)\\n at Request.emit (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:686:14)\\n at Request.transition (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:22:10)\\n at AcceptorStateMachine.runTo (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/state_machine.js:14:12)\\n at /usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/state_machine.js:26:10\\n at Request.&lt;anonymous&gt; (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:38:9)\\n at Request.&lt;anonymous&gt; (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/request.js:688:12)\\n at Request.callListeners (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {\\n code: 'OptInRequired',\\n time: 2022-03-28T16:20:47.649Z,\\n requestId: '39858f24-3c22-4eaa-9a0e-398095a8ebc9',\\n statusCode: 403,\\n retryable: false,\\n retryDelay: 93.04199180846567\\n}\\n</code></pre>\\n<p>很可能的原因就是信用卡信息错误,最常见的就是信用卡有效期填写有误,而我就是这个问题。<br />\\n接下来,我们开始正式创建后端基本结构,首先需要初始化 Amplify 项目目录并创建云后端。打开一个终端并将目录更改为第一步中的 iOS 项目。例如,如果您在文件夹 ~/job/ios/ 中创建项目,则可以键入:<br />\\nGroovy</p>\n<pre><code class=\\"lang-\\">cd ~/job/ios/iOS Getting Started\\n</code></pre>\\n<p>验证一下是否在正确的目录中,它应该如下所示:<br />\\nAssembly language</p>\n<pre><code class=\\"lang-\\">liuzhen-40:iOS Getting Started lz\$ ls -al\\ntotal 16\\ndrwxr-xr-x 7 lz staff 224 Mar 28 11:06 .\\ndrwxr-xr-x 7 lz staff 224 Mar 28 10:05 ..\\n-rw-r--r--@ 1 lz staff 6148 Mar 28 22:09 .DS_Store\\ndrwxr-xr-x 9 lz staff 288 Mar 28 10:06 iOS Getting Started\\ndrwxr-xr-x@ 5 lz staff 160 Mar 28 10:05 iOS Getting Started.xcodeproj\\ndrwxr-xr-x 4 lz staff 128 Mar 28 10:05 iOS Getting StartedTests\\ndrwxr-xr-x 4 lz staff 128 Mar 28 10:05 iOS Getting StartedUITests\\n</code></pre>\\n<p>初始化 Amplify 项目结构和配置文件,运行以下命令:<br />\\nSQL</p>\n<pre><code class=\\"lang-\\">liuzhen-40:iOS Getting Started lz\$ amplify init\\nNote: It is recommended to run this command from the root of your app directory\\n? Enter a name for the project iOSGettingStarted\\n? Enter a name for the environment dev\\n? Choose your default editor: Visual Studio Code\\n? Choose the type of app that you're building ios\\nUsing default provider awscloudformation\\n\\nFor more information on AWS Profiles, see:\\nhttps://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html\\n\\n? Do you want to use an AWS profile? Yes\\n? Please choose the profile you want to use default\\nAdding backend environment dev to AWS Amplify Console app: dj56g6ku6ef6p\\n</code></pre>\\n<h5><a id=\\"23__Amplify__241\\"></a><strong>2.3 将 Amplify 库添加到项目中</strong></h5>\\n<p>在开始此步骤之前,请确保关闭 Xcode。然后命令行进入工程目录,使用 CocoaPods 软件包管理器初始化项目,请执行以下命令:<br />\\nNginx</p>\n<pre><code class=\\"lang-\\">## Initialize the Amplify Project\\npod init\\n</code></pre>\\n<p>完成此操作后,您应该看到一个名为 Podfile 的新建文件,此文件用于描述我们的项目所依赖的软件包。然后,更新 Podfile 以包含以下 Pod 并更新平台:<br />\\nBash</p>\n<pre><code class=\\"lang-\\"># you need at least version 13.0 for this tutorial, more recent versions are valid too\\n\\nplatform :ios,'13.0'\\n\\ntarget 'iOS Getting Started' do\\n # Comment the next line if you don't want to use dynamic frameworks\\n use_frameworks!\\n # Pods for getting started\\n pod 'Amplify', '~&gt; 1.0' # required amplify dependency\\n pod 'Amplify/Tools', '~&gt; 1.0' # allows to call amplify CLI from within Xcode\\n\\nend\\n</code></pre>\\n<p>将 Amplify 库下载并安装到您的项目中,执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">pod install --repo-update\\n</code></pre>\\n<p>完成此操作后,我们会发现生成了一个名为 iOS Getting Started.xcworkspace 的文件。之后,我们就需要使用这个文件,而不是 iOS Getting Started.xcodeproj 文件了。然后,在 Xcode 中打开工作区,并执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">xed .\\n</code></pre>\\n<h5><a id=\\"24__Amplify_274\\"></a><strong>2.4 在运行时初始化 Amplify</strong></h5>\\n<p>在运行时,Amplify 库需要由 CLI 生成的 Amplify 配置文件。将 Amplify 配置文件添加到项目中,使用 Finder 在项目目录的根目录下找到 awsconfiguration.json 和 amplifyconfiguration.json,并将其拖放到 Xcode 项目中。<br />\\n接下来,新建一个 swift 文本文件 Backend.swift,将其添加到我们的 Xcode 项目 (CTRL-N) 并添加以下代码:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">import UIKit\\nimport Amplify\\n\\nclass Backend {\\n static let shared = Backend()\\n static func initialize() -&gt; Backend {\\n return .shared\\n }\\n private init() {\\n // initialize amplify\\n do {\\n try Amplify.configure()\\n print(&quot;Initialized Amplify&quot;);\\n } catch {\\n print(&quot;Could not initialize Amplify: \\\\(error)&quot;)\\n }\\n }\\n}\\n</code></pre>\\n<p>应用程序启动后,初始化单例 Backend 对象。打开 AppDelegate.swift 文件并在 application(:didFinishLaunchingWithOptions:) 方法中添加 Backend.initialize();,如下所示:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">应用程序启动后,初始化单例 Backend 对象。打开 AppDelegate.swift 文件并在 application(:didFinishLaunchingWithOptions:) 方法中添加 Backend.initialize();,如下所示:\\n</code></pre>\\n<h5><a id=\\"25__303\\"></a><strong>2.5 验证设置</strong></h5>\\n<p>要验证一切是否都按预期运行,构建项目,单击 Product 菜单,然后选择 Build,或按 ⌘B。不出现错误即可。</p>\n<h4><a id=\\"_305\\"></a><strong>三、添加身份验证</strong></h4>\\n<p>接下来,我们了解如何使用 Amplify CLI 和库对用户进行身份验证,以利用托管用户身份提供商 Amazon Cognito。另外,知道如何使用 Cognito 托管用户界面展示整个用户身份验证流,从而使用户只需几行代码即可注册、登录和重置密码。使用“托管用户界面”意味着应用程序利用 Cognito 网页登录和注册用户界面流。应用程序的用户将重定向到 Cognito 托管的网页,并在登录后重定向回应用程序。当然,Cognito 和 Amplify 也支持本机 UI。</p>\n<h5><a id=\\"31__307\\"></a><strong>3.1 创建身份验证服务</strong></h5>\\n<p>创建身份验证服务,打开一个命令行终端,然后执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify add auth\\n</code></pre>\\n<p>输出结果出现 successful 字样表示创建成功。</p>\n<h5><a id=\\"32__314\\"></a><strong>3.2 部署身份验证服务</strong></h5>\\n<p>现在,我们已在本地配置身份验证服务,我们可以将它部署到云。在终端中,在项目目录中执行以下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify push\\n</code></pre>\\n<h5><a id=\\"33__Amplify__320\\"></a><strong>3.3 向项目添加 Amplify 身份验证库</strong></h5>\\n<p>在转至代码之前,将 Amplify 身份验证库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSCognitoAuthPlugin 添加行,或者复制并粘贴以下整个文件。<br />\\nBash</p>\n<pre><code class=\\"lang-\\"># you need at least version 13.0 for this tutorial, more recent versions are valid too\\nplatform :ios, '13.0'\\n\\ntarget 'getting started' do\\n # Comment the next line if you don't want to use dynamic frameworks\\n use_frameworks!\\n\\n # Pods for getting started\\n pod 'Amplify', '~&gt; 1.0' # required amplify dependency\\n pod 'Amplify/Tools', '~&gt; 1.0' # allows to call amplify CLI from within Xcode\\n\\n pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~&gt; 1.0' # support for Cognito user authentication\\n\\nend\\n</code></pre>\\n<p>在终端中执行以下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">pod install\\n</code></pre>\\n<h5><a id=\\"34__Amplify__344\\"></a><strong>3.4 在运行时配置 Amplify 身份验证库</strong></h5>\\n<p>返回到 Xcode,打开 Backend.swift 文件,其完整代码如下所示:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// at the top of the file\\nimport AmplifyPlugins\\n\\nprivate init () {\\n // initialize amplify\\n do {\\n try Amplify.add(plugin: AWSCognitoAuthPlugin())\\n try Amplify.configure()\\n print(&quot;Initialized Amplify&quot;)\\n } catch {\\n print(&quot;Could not initialize Amplify: \\\\(error)&quot;)\\n }\\n}\\n</code></pre>\\n<p>构建项目,保证不报错即可。</p>\n<h5><a id=\\"35__363\\"></a><strong>3.5 在运行时触发身份验证</strong></h5>\\n<p>其余代码更改会跟踪用户的状态,并在用户未登录时触发登录/注册用户界面。 添加登录和注销代码,在后端类中的任意位置,添加以下三种方法:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// signin with Cognito web user interface\\npublic func signIn() {\\n\\n _ = Amplify.Auth.signInWithWebUI(presentationAnchor: UIApplication.shared.windows.first!) { result in\\n switch result {\\n case .success(_):\\n print(&quot;Sign in succeeded&quot;)\\n case .failure(let error):\\n print(&quot;Sign in failed \\\\(error)&quot;)\\n }\\n }\\n}\\n\\n// signout\\npublic func signOut() {\\n\\n _ = Amplify.Auth.signOut() { (result) in\\n switch result {\\n case .success:\\n print(&quot;Successfully signed out&quot;)\\n case .failure(let error):\\n print(&quot;Sign out failed with error \\\\(error)&quot;)\\n }\\n }\\n}\\n\\n// change our internal state, this triggers an UI update on the main thread\\nfunc updateUserData(withSignInStatus status : Bool) {\\n DispatchQueue.main.async() {\\n let userData : UserData = .shared\\n userData.isSignedIn = status\\n }\\n}\\n</code></pre>\\n<p>为了跟踪身份验证状态的变化,我们添加了代码以订阅由 Amplify 发送的身份验证事件。我们在 Backend.init() 方法中初始化该中心。在收到身份验证事件时,我们将调用 updateUserData() 方法。此方法可使 UserData 对象保持同步。UserData.isSignedIn 属性为 @Published,这意味着当值更改时,用户界面会自动刷新。 我们可以添加代码以在应用程序启动时检查以前的身份验证状态。当应用程序启动时,它会检查 Cognito 会话是否已存在,并相应地更新 UI。<br />\\n在 Backend.init() 中,请在 Amplify 初始化后添加以下代码:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// in private init() function\\n// listen to auth events.\\n// see https://github.com/aws-amplify/amplify-ios/blob/master/Amplify/Categories/Auth/Models/AuthEventName.swift\\n_ = Amplify.Hub.listen(to: .auth) { (payload) in\\n\\n switch payload.eventName {\\n\\n case HubPayload.EventName.Auth.signedIn:\\n print(&quot;==HUB== User signed In, update UI&quot;)\\n self.updateUserData(withSignInStatus: true)\\n\\n case HubPayload.EventName.Auth.signedOut:\\n print(&quot;==HUB== User signed Out, update UI&quot;)\\n self.updateUserData(withSignInStatus: false)\\n\\n case HubPayload.EventName.Auth.sessionExpired:\\n print(&quot;==HUB== Session expired, show sign in UI&quot;)\\n self.updateUserData(withSignInStatus: false)\\n\\n default:\\n //print(&quot;==HUB== \\\\(payload)&quot;)\\n break\\n }\\n}\\n \\n// let's check if user is signedIn or not\\n _ = Amplify.Auth.fetchAuthSession { (result) in\\n do {\\n let session = try result.get()\\n \\n// let's update UserData and the UI\\n self.updateUserData(withSignInStatus: session.isSignedIn)\\n \\n } catch {\\n print(&quot;Fetch auth session failed with error - \\\\(error)&quot;)\\n }\\n} \\n</code></pre>\\n<p>代码中的最后一个更改与用户界面相关,我们将 ZStack 添加到 ContentView。根据UserData.isSignedIn 的值,UI 将显示 SigninButton 或主列表视图。打开 ContentView.swift 并替换 ContentView 结构中的正文:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">var body: some View {\\n\\n ZStack {\\n if (userData.isSignedIn) {\\n NavigationView {\\n List {\\n ForEach(userData.notes) { note in\\n ListRow(note: note)\\n }\\n }\\n .navigationBarTitle(Text(&quot;Notes&quot;))\\n .navigationBarItems(leading: SignOutButton())\\n }\\n } else {\\n SignInButton()\\n }\\n }\\n}\\n</code></pre>\\n<p>在同一文件中,添加 SignInButton 和 SignOutButton 视图:<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">struct SignInButton: View {\\n var body: some View {\\n Button(action: { Backend.shared.signIn() }){\\n HStack {\\n Image(systemName: &quot;person.fill&quot;)\\n .scaleEffect(1.5)\\n .padding()\\n Text(&quot;Sign In&quot;)\\n .font(.largeTitle)\\n }\\n .padding()\\n .foregroundColor(.white)\\n .background(Color.green)\\n .cornerRadius(30)\\n }\\n }\\n}\\n\\nstruct SignOutButton : View {\\n var body: some View {\\n Button(action: { Backend.shared.signOut() }) {\\n Text(&quot;Sign Out&quot;)\\n }\\n }\\n}\\n</code></pre>\\n<p>最后,我们必须确保在 Cognito 托管用户界面提供的 Web 身份验证序列结束时启动我们的应用程序。我们将 gettingstarted URI 方案添加到应用程序的 Info.plist 文件中。在 Xcode 中,选择 Info.plist 文件,右键单击该文件,然后选择“打开为源代码”。<br />\\n另外,在顶部 &lt;dict&gt; 元素内添加以下 &lt;key&gt; 和 &lt;array&gt; 元素。<br />\\nXML</p>\n<pre><code class=\\"lang-\\">&lt;plist version=&quot;1.0&quot;&gt;\\n\\n &lt;dict&gt;\\n &lt;!-- YOUR OTHER PLIST ENTRIES HERE --&gt;\\n\\n &lt;!-- ADD AN ENTRY TO CFBundleURLTypes for Cognito Auth --&gt;\\n &lt;!-- IF YOU DO NOT HAVE CFBundleURLTypes, YOU CAN COPY THE WHOLE BLOCK BELOW --&gt;\\n &lt;key&gt;CFBundleURLTypes&lt;/key&gt;\\n &lt;array&gt;\\n &lt;dict&gt;\\n &lt;key&gt;CFBundleURLSchemes&lt;/key&gt;\\n &lt;array&gt;\\n &lt;string&gt;gettingstarted&lt;/string&gt;\\n &lt;/array&gt;\\n &lt;/dict&gt;\\n &lt;/array&gt;\\n\\n &lt;!-- ... --&gt;\\n &lt;/dict&gt;\\n</code></pre>\\n<p>然后,运行程序看看效果,整个注册流程如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/f4fd107969ba44c8b7e4fcce42f4ef5f_image.png\\" alt=\\"image.png\\" /></p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/396c116709b04fbb8e1926bc331d1b5a_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"_API__524\\"></a><strong>四、添加 API 和数据库</strong></h4>\\n<p>现在,我们已经创建并配置了带用户身份验证功能的应用程序。接下来,我们要在数据库中添加 API 以及“创建”、“读取”、“更新”、“删除”(CRUD) 操作</p>\n<h5><a id=\\"41__GraphQL_API__526\\"></a><strong>4.1 创建 GraphQL API 及数据库</strong></h5>\\n<p>要创建 GraphQL API 及其数据库,打开一个命令行终端,然后从项目目录中执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify add api\\n</code></pre>\\n<p>初始化项目时选择的默认文本编辑器 (amplify init) 将使用预构建数据 schema 打开。删除此 schema,并使用我们的应用程序 GraphQL schema 替换:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">type NoteData\\n@model\\n@auth (rules: [ { allow: owner } ]) {\\n id: ID!\\n name: String!\\n description: String\\n image: String\\n}\\n</code></pre>\\n<h5><a id=\\"42__544\\"></a><strong>4.2 生成客户端代码</strong></h5>\\n<p>根据我们刚刚创建的 GraphQL 数据模型定义,Amplify 会生成客户端代码(即 Swift 代码)以代表我们应用程序中的数据。要生成代码,在终端执行以下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify codegen models\\n</code></pre>\\n<p>在 Finder 中找到新生成的文件,然后将其拖放到 Xcode 的项目中。</p>\n<h5><a id=\\"43__API__551\\"></a><strong>4.3 部署 API 服务和数据库</strong></h5>\\n<p>要部署我们刚刚创建的后端 API 和数据库,打开终端,然后执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify push\\n</code></pre>\\n<h5><a id=\\"44__API__Xcode__557\\"></a><strong>4.4 将 API 客户端库添加到 Xcode 项目中</strong></h5>\\n<p>在转至代码之前,将 Amplify API 库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSAPIPlugin 添加行,或者复制并粘贴以下整个文件。<br />\\nBash</p>\n<pre><code class=\\"lang-\\"># you need at least version 13.0 for this tutorial, more recent versions are valid too\\nplatform :ios, '13.0'\\n\\ntarget 'getting started' do\\n # Comment the next line if you don't want to use dynamic frameworks\\n use_frameworks!\\n\\n # Pods for getting started\\n pod 'Amplify', '~&gt; 1.0' # required amplify dependency\\n pod 'Amplify/Tools', '~&gt; 1.0' # allows to call amplify CLI from within Xcode\\n\\n pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~&gt; 1.0' # support for Cognito user authentication\\n pod 'AmplifyPlugins/AWSAPIPlugin', '~&gt; 1.0' # support for GraphQL API\\n\\nend\\n</code></pre>\\n<p>然后,执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">pod install\\n</code></pre>\\n<h5><a id=\\"45__Amplify__582\\"></a><strong>4.5 在运行时初始化 Amplify 库</strong></h5>\\n<p>返回到 Xcode,打开 Backend.swift,然后在私有 init() 方法的 Amplify 初始化序列中添加一行。完整代码块应如下所示:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// initialize amplify\\ndo {\\n try Amplify.add(plugin: AWSCognitoAuthPlugin())\\n try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))\\n try Amplify.configure()\\n print(&quot;Initialized Amplify&quot;)\\n} catch {\\n print(&quot;Could not initialize Amplify: \\\\(error)&quot;)\\n}\\n</code></pre>\\n<h5><a id=\\"46__GraphQL__596\\"></a><strong>4.6 在 GraphQL 数据模型和应用程序模型之间添加桥接</strong></h5>\\n<p>我们的项目已经有一个数据模型来表示备注。因此,可以继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 ContentView.swift 并将此初始化程序添加到备注类中。<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">convenience init(from data: NoteData) {\\n self.init(id: data.id, name: data.name, description: data.description, image: data.image)\\n \\n // store API object for easy retrieval later\\n self._data = data\\n}\\n\\nfileprivate var _data : NoteData?\\n\\n// access the privately stored NoteData or build one if we don't have one.\\nvar data : NoteData {\\n\\n if (_data == nil) {\\n _data = NoteData(id: self.id,\\n name: self.name,\\n description: self.description,\\n image: self.imageName)\\n }\\n\\n return _data!\\n}\\n</code></pre>\\n<h5><a id=\\"47__API_CRUD__622\\"></a><strong>4.7 将 API CRUD 方法添加到后端类中</strong></h5>\\n<p>我们添加 3 种方法来调用 API:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型(备注),以便从用户界面轻松交互。这些方法可透明地将 Note 转换为 GraphQL 的 NoteData 对象。<br />\\n打开 Backend.swift 文件,然后在后端类末尾添加以下 snipet:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// MARK: API Access\\n\\n func queryNotes() {\\n\\n _ = Amplify.API.query(request: .list(NoteData.self)) { event in\\n switch event {\\n case .success(let result):\\n switch result {\\n case .success(let notesData):\\n print(&quot;Successfully retrieved list of Notes&quot;)\\n\\n // convert an array of NoteData to an array of Note class instances\\n for n in notesData {\\n let note = Note.init(from: n)\\n DispatchQueue.main.async() {\\n UserData.shared.notes.append(note)\\n }\\n }\\n\\n case .failure(let error):\\n print(&quot;Can not retrieve result : error \\\\(error.errorDescription)&quot;)\\n }\\n case .failure(let error):\\n print(&quot;Can not retrieve Notes : error \\\\(error)&quot;)\\n }\\n }\\n }\\n\\n func createNote(note: Note) {\\n\\n // use note.data to access the NoteData instance\\n _ = Amplify.API.mutate(request: .create(note.data)) { event in\\n switch event {\\n case .success(let result):\\n switch result {\\n case .success(let data):\\n print(&quot;Successfully created note: \\\\(data)&quot;)\\n case .failure(let error):\\n print(&quot;Got failed result with \\\\(error.errorDescription)&quot;)\\n }\\n case .failure(let error):\\n print(&quot;Got failed event with error \\\\(error)&quot;)\\n }\\n }\\n }\\n\\n func deleteNote(note: Note) {\\n\\n // use note.data to access the NoteData instance\\n _ = Amplify.API.mutate(request: .delete(note.data)) { event in\\n switch event {\\n case .success(let result):\\n switch result {\\n case .success(let data):\\n print(&quot;Successfully deleted note: \\\\(data)&quot;)\\n case .failure(let error):\\n print(&quot;Got failed result with \\\\(error.errorDescription)&quot;)\\n }\\n case .failure(let error):\\n print(&quot;Got failed event with error \\\\(error)&quot;)\\n }\\n }\\n }\\n</code></pre>\\n<p>在同一 Backend.swift 文件中,更新 updateUserData(withSignInStatus:) 方法如下所示:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// change our internal state, this triggers an UI update on the main thread\\nfunc updateUserData(withSignInStatus status : Bool) {\\n DispatchQueue.main.async() {\\n let userData : UserData = .shared\\n userData.isSignedIn = status\\n\\n // when user is signed in, query the database, otherwise empty our model\\n if status {\\n self.queryNotes()\\n } else {\\n userData.notes = []\\n }\\n }\\n}\\n</code></pre>\\n<p>现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。</p>\n<h5><a id=\\"48_Edit_710\\"></a><strong>4.8 添加“Edit”按钮以添加备注</strong></h5>\\n<p>现在,后端和数据模型已到位,最后一步是让用户创建新的 Note 然后将其删除。在 Xcode 中,打开 ContentView.swift,在 ContentView 结构中,添加绑定到用户界面的状态变量。<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// add at the begining of ContentView class\\n@State var showCreateNote = false\\n\\n@State var name : String = &quot;New Note&quot;\\n@State var description : String = &quot;This is a new note&quot;\\n@State var image : String = &quot;image&quot;\\n</code></pre>\\n<p>在文件中的任何位置,添加视图结构,以允许用户创建新的备注:<br />\\nSwift</p>\n<pre><code class=\\"lang-struct\\"> @Binding var isPresented: Bool\\n var userData: UserData\\n\\n @State var name : String = &quot;New Note&quot;\\n @State var description : String = &quot;This is a new note&quot;\\n @State var image : String = &quot;image&quot;\\n var body: some View {\\n Form {\\n\\n Section(header: Text(&quot;TEXT&quot;)) {\\n TextField(&quot;Name&quot;, text: \$name)\\n TextField(&quot;Name&quot;, text: \$description)\\n }\\n\\n Section(header: Text(&quot;PICTURE&quot;)) {\\n TextField(&quot;Name&quot;, text: \$image)\\n }\\n\\n Section {\\n Button(action: {\\n self.isPresented = false\\n let noteData = NoteData(id : UUID().uuidString,\\n name: self.\$name.wrappedValue,\\n description: self.\$description.wrappedValue)\\n let note = Note(from: noteData)\\n\\n // asynchronously store the note (and assume it will succeed)\\n Backend.shared.createNote(note: note)\\n\\n // add the new note in our userdata, this will refresh UI\\n self.userData.notes.append(note)\\n }) {\\n Text(&quot;Create this note&quot;)\\n }\\n }\\n }\\n }\\n}\\n</code></pre>\\n<p>在导航栏上添加“+”按钮演示数据表以创建备注,返回 ContentView 结构,将 navigationBarItems(leading SignOutButton()) 替换为:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">.navigationBarItems(leading: SignOutButton(),\\n trailing: Button(action: {\\n self.showCreateNote.toggle()\\n }) {\\n Image(systemName: &quot;plus&quot;)\\n })\\n}.sheet(isPresented: \$showCreateNote) {\\n AddNoteView(isPresented: self.\$showCreateNote, userData: self.userData)\\n</code></pre>\\n<h5><a id=\\"49__775\\"></a><strong>4.9 添加“滑动删除”行为</strong></h5>\\n<p>最后,在 ContentView 中添加“轻扫以删除”行为:将 .onDelete { } 方法添加到 ForEach 结构中:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">ForEach(userData.notes) { note in\\n ListRow(note: note)\\n}.onDelete { indices in\\n indices.forEach {\\n // removing from user data will refresh UI\\n let note = self.userData.notes.remove(at: \$0)\\n\\n // asynchronously remove from database\\n Backend.shared.deleteNote(note: note)\\n }\\n}\\n</code></pre>\\n<h5><a id=\\"410__791\\"></a><strong>4.10 构建和测试</strong></h5>\\n<p>首先,编译程序,保证不出现报错。然后,运行程序,整个完整流程如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/fd53a0294584413db9de334787bf0852_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"_796\\"></a><strong>五、添加存储</strong></h4>\\n<p>现在,我们的备注应用程序已在运行,接下来添加将图像与每个备注关联的功能。在本模块中,将使用 Amplify CLI 和库来创建利用 Amazon S3 的存储服务。最后,将更新 iOS 应用程序以启用图像上传、获取和渲染。</p>\n<h5><a id=\\"51__798\\"></a><strong>5.1 创建存储服务</strong></h5>\\n<p>要添加图像存储功能,我们将使用 Amplify 存储类别,执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify add storage\\n</code></pre>\\n<h5><a id=\\"52__804\\"></a><strong>5.2 部署存储服务</strong></h5>\\n<p>要部署我们刚刚创建的存储服务,打开终端,然后执行以下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">amplify push\\n</code></pre>\\n<h5><a id=\\"53_Xcode_Amplify__810\\"></a><strong>5.3 向Xcode项目中添加 Amplify 存储库</strong></h5>\\n<p>在转至代码之前,将 Amplify 存储库添加到项目的依赖项中。打开 Podfile 文件,然后使用 AmplifyPlugins/AWSS3StoragePlugin 添加行,或者复制并粘贴以下整个文件。<br />\\nBash</p>\n<pre><code class=\\"lang-\\"># you need at least version 13.0 for this tutorial, more recent versions are valid too\\nplatform :ios, '13.0'\\n\\ntarget 'getting started' do\\n # Comment the next line if you don't want to use dynamic frameworks\\n use_frameworks!\\n\\n # Pods for getting started\\n pod 'Amplify', '~&gt; 1.0' # required amplify dependency\\n pod 'Amplify/Tools', '~&gt; 1.0' # allows to call amplify CLI from within Xcode\\n\\n pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~&gt; 1.0' # support for Cognito user authentication\\n pod 'AmplifyPlugins/AWSAPIPlugin', '~&gt; 1.0' # support for GraphQL API\\n pod 'AmplifyPlugins/AWSS3StoragePlugin', '~&gt; 1.0' # support for Amazon S3 storage\\n\\nend\\n</code></pre>\\n<p>然后,执行如下命令:<br />\\nBash</p>\n<pre><code class=\\"lang-\\">pod install\\n</code></pre>\\n<h5><a id=\\"54__Amplify__836\\"></a><strong>5.4 在运行时初始化 Amplify 存储插件</strong></h5>\\n<p>返回 Xcode,打开 Backend.swift,然后在私有 init() 方法的 Amplify 初始化序列中添加一行。完整代码块应如下所示:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// initialize amplify\\ndo {\\n try Amplify.add(plugin: AWSCognitoAuthPlugin())\\n try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels()))\\n try Amplify.add(plugin: AWSS3StoragePlugin())\\n try Amplify.configure()\\n print(&quot;Initialized Amplify&quot;);\\n} catch {\\n print(&quot;Could not initialize Amplify: \\\\(error)&quot;)\\n}\\n</code></pre>\\n<h5><a id=\\"55__Image_CRUD__851\\"></a><strong>5.5 将 Image CRUD 方法添加到后端类</strong></h5>\\n<p>打开 Backend.Swift。在后端类中的任意位置,添加以下方法:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">func storeImage(name: String, image: Data) {\\n\\n// let options = StorageUploadDataRequest.Options(accessLevel: .private)\\n let _ = Amplify.Storage.uploadData(key: name, data: image,// options: options,\\n progressListener: { progress in\\n // optionlly update a progress bar here\\n }, resultListener: { event in\\n switch event {\\n case .success(let data):\\n print(&quot;Image upload completed: \\\\(data)&quot;)\\n case .failure(let storageError):\\n print(&quot;Image upload failed: \\\\(storageError.errorDescription). \\\\(storageError.recoverySuggestion)&quot;)\\n }\\n })\\n}\\n\\nfunc retrieveImage(name: String, completed: @escaping (Data) -&gt; Void) {\\n let _ = Amplify.Storage.downloadData(key: name,\\n progressListener: { progress in\\n // in case you want to monitor progress\\n }, resultListener: { (event) in\\n switch event {\\n case let .success(data):\\n print(&quot;Image \\\\(name) loaded&quot;)\\n completed(data)\\n case let .failure(storageError):\\n print(&quot;Can not download image: \\\\(storageError.errorDescription). \\\\(storageError.recoverySuggestion)&quot;)\\n }\\n }\\n )\\n}\\n\\nfunc deleteImage(name: String) {\\n let _ = Amplify.Storage.remove(key: name,\\n resultListener: { (event) in\\n switch event {\\n case let .success(data):\\n print(&quot;Image \\\\(data) deleted&quot;)\\n case let .failure(storageError):\\n print(&quot;Can not delete image: \\\\(storageError.errorDescription). \\\\(storageError.recoverySuggestion)&quot;)\\n }\\n }\\n )\\n}\\n</code></pre>\\n<h5><a id=\\"56_API__900\\"></a><strong>5.6 API 检索数据时加载图像</strong></h5>\\n<p>现在,后端函数已经可用,我们接下来在 API 调用返回结果时加载图像。添加此行为的中心位置是,应用程序通过 API 返回的 NoteData 构造备注 UI 时。打开 ContentView.swift 并更新备注的初始化程序(添加第 8 行至第 17 行):<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// add a publishable's object property\\n@Published var image : Image?\\n\\n// update init's code\\nconvenience init(from data: NoteData) {\\n self.init(id: data.id, name: data.name, description: data.description, image: data.image)\\n\\n if let name = self.imageName {\\n // asynchronously download the image\\n Backend.shared.retrieveImage(name: name) { (data) in\\n // update the UI on the main thread\\n DispatchQueue.main.async() {\\n let uim = UIImage(data: data)\\n self.image = Image(uiImage: uim!)\\n }\\n }\\n }\\n // store API object for easy retrieval later\\n self._data = data\\n</code></pre>\\n<p>当备注实例中存在图像名称时,代码将调用 retrieveImage,它是一个异步函数。下载图像时需要调用一个函数,该函数创建一个图像 UI 对象并将其分配给备注实例。请注意,此分配会触发用户界面更新,因此会在应用程序的主线程中使用 DispatchQueue.main.async 进行更新。</p>\n<h5><a id=\\"57__UI__925\\"></a><strong>5.7 添加 UI 代码以捕获图像</strong></h5>\\n<p>首先,我们添加通用代码以支持图像捕获。此代码可在许多应用程序中重复使用;它显示了一个图像选择器,允许用户从其图像库中选择图像。在 Xcode 中,创建新的 Swift 文件(⌘N,然后选择 Swift)。命名 CaptureImageView.swift 文件,然后添加此代码:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">import Foundation\\nimport UIKit\\nimport SwiftUI\\n\\nstruct CaptureImageView {\\n\\n /// MARK: - Properties\\n @Binding var isShown: Bool\\n @Binding var image: UIImage?\\n\\n func makeCoordinator() -&gt; Coordinator {\\n return Coordinator(isShown: \$isShown, image: \$image)\\n }\\n}\\n\\nclass Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {\\n @Binding var isCoordinatorShown: Bool\\n @Binding var imageInCoordinator: UIImage?\\n init(isShown: Binding&lt;Bool&gt;, image: Binding&lt;UIImage?&gt;) {\\n _isCoordinatorShown = isShown\\n _imageInCoordinator = image\\n }\\n func imagePickerController(_ picker: UIImagePickerController,\\n didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {\\n guard let unwrapImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }\\n imageInCoordinator = unwrapImage\\n isCoordinatorShown = false\\n }\\n func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {\\n isCoordinatorShown = false\\n }\\n}\\n\\nextension CaptureImageView: UIViewControllerRepresentable {\\n func makeUIViewController(context: UIViewControllerRepresentableContext&lt;CaptureImageView&gt;) -&gt; UIImagePickerController {\\n let picker = UIImagePickerController()\\n picker.delegate = context.coordinator\\n\\n // picker.sourceType = .camera // on real devices, you can capture image from the camera\\n // see https://medium.com/better-programming/how-to-pick-an-image-from-camera-or-photo-library-in-swiftui-a596a0a2ece\\n\\n return picker\\n }\\n\\n func updateUIViewController(_ uiViewController: UIImagePickerController,\\n context: UIViewControllerRepresentableContext&lt;CaptureImageView&gt;) {\\n\\n }\\n}\\n</code></pre>\\n<h5><a id=\\"58__979\\"></a><strong>5.8 创建备注时存储图像</strong></h5>\\n<p>创建备注时,我们从后端调用存储方法。打开 ContentView.swift 并修改 AddNoteView 以添加 ImagePicker 组件:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">// at the start of the Content View struct \\n@State var image : UIImage? // replace the previous declaration of image\\n@State var showCaptureImageView = false\\n\\n// in the view, replace the existing PICTURE section\\nSection(header: Text(&quot;PICTURE&quot;)) {\\n VStack {\\n Button(action: {\\n self.showCaptureImageView.toggle()\\n }) {\\n Text(&quot;Choose photo&quot;)\\n }.sheet(isPresented: \$showCaptureImageView) {\\n CaptureImageView(isShown: self.\$showCaptureImageView, image: self.\$image)\\n }\\n if (image != nil ) {\\n HStack {\\n Spacer()\\n Image(uiImage: image!)\\n .resizable()\\n .frame(width: 250, height: 200)\\n .clipShape(Circle())\\n .overlay(Circle().stroke(Color.white, lineWidth: 4))\\n .shadow(radius: 10)\\n Spacer()\\n }\\n }\\n }\\n}\\n</code></pre>\\n<p>修改“创建备注”部分以存储图像和备注:<br />\\nSwift</p>\n<pre><code class=\\"lang-\\">Section {\\n Button(action: {\\n self.isPresented = false\\n\\n let note = Note(id : UUID().uuidString,\\n name: self.\$name.wrappedValue,\\n description: self.\$description.wrappedValue)\\n\\n if let i = self.image {\\n note.imageName = UUID().uuidString\\n note.image = Image(uiImage: i)\\n\\n // asynchronously store the image (and assume it will work)\\n Backend.shared.storeImage(name: note.imageName!, image: (i.pngData())!)\\n }\\n\\n // asynchronously store the note (and assume it will succeed)\\n Backend.shared.createNote(note: note)\\n\\n // add the new note in our userdata, this will refresh UI\\n withAnimation { self.userData.notes.append(note) }\\n }) {\\n Text(&quot;Create this note&quot;)\\n }\\n}\\n</code></pre>\\n<h5><a id=\\"59__1041\\"></a><strong>5.9 构建和测试</strong></h5>\\n<p>首先,编译程序,保证不出现报错。然后,运行程序,整个完整流程如下图所示:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/b833c3a7055b4181bd1b49920e4e29bb_image.png\\" alt=\\"image.png\\" /><br />\\n<img src=\\"https://dev-media.amazoncloud.cn/dae96849b7cf421d989a7f23ae8b75a1_image.png\\" alt=\\"image.png\\" /></p>\n<h3><a id=\\"_1047\\"></a>结论</h3>\\n<p>至此,我们已经体验了使用 Amazon Amplify 构建 iOS 应用程序的过程! 在应用程序中添加了身份验证,用户可以注册、登录和管理其账户。该应用程序还使用 Amazon DynamoDB 数据库配置了一个可扩展的 GraphQL API,用户可以创建和删除备注。另外,还使用 Amazon S3 添加了文件存储,从而使用户可以上传图像并在其应用程序中查看它们。但是,使用过程中也出现了很多小插曲,非常耽误时间,由于篇幅的限制,我简单说两点:一、Xcode 版本兼容性问题。最开始我使用低于11.5的版本,遇到了很多编译错误,最后升级到11.5才解决。因此,体验时要保证一个较高的 Xcode 版本,这一点应该在官方文档中说明;二、信用卡注册问题。服务中国客户,亚马逊应该入乡随俗,提供更加人性化的注册方式,可以考虑支付宝或者微信登陆注册,这样会比较符合大家的习惯。别的方面还比较顺利,感兴趣的话,小伙伴们就自己动手尝试一下吧!另外,亚马逊云科技专为开发者们打造了多种学习平台:</p>\n<ol>\\n<li>入门资源中心:从0到1 轻松上手云服务,内容涵盖:成本管理,上手训练,开发资源。<a href=\\"https://aws.amazon.com/cn/getting-started/?nc1=h_ls&amp;trk=32540c74-46f0-46dc-940d-621a1efeedd0&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/getting-started/?nc1=h_ls&amp;trk=32540c74-46f0-46dc-940d-621a1efeedd0&amp;sc_channel=el</a></li>\\n<li>架构中心:亚马逊云科技架构中心提供了云平台参考架构图表、经过审查的架构解决方案、Well-Architected 最佳实践、模式、图标等。<a href=\\"https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&amp;trk=3fa608de-d954-4355-a20a-324daa58bbeb&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&amp;trk=3fa608de-d954-4355-a20a-324daa58bbeb&amp;sc_channel=el</a></li>\\n<li>构建者库:了解亚马逊云科技如何构建和运营软件。<a href=\\"https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&amp;cards-body.sort-order=desc&amp;awsf.filter-content-category=*all&amp;awsf.filter-content-type=*all&amp;awsf.filter-content-level=*all&amp;trk=835e6894-d909-4691-aee1-3831428c04bd&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&amp;cards-body.sort-order=desc&amp;awsf.filter-content-category=*all&amp;awsf.filter-content-type=*all&amp;awsf.filter-content-level=*all&amp;trk=835e6894-d909-4691-aee1-3831428c04bd&amp;sc_channel=el</a></li>\\n<li>用于在亚马逊云科技平台上开发和管理应用程序的工具包:<a href=\\"https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&amp;trk=972c69e1-55ec-43af-a503-d458708bb645&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&amp;trk=972c69e1-55ec-43af-a503-d458708bb645&amp;sc_channel=el</a></li>\\n</ol>\n<p>【专属福利】<br />\\n福利一:100余种产品免费套餐。其中,计算资源 Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。<br />\\n<a href=\\"https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el</a></p>\\n<p>福利二:最新优惠大礼包,200数据与分析抵扣券,200机器学习抵扣券,200\$微服务与应用开发抵扣券。<a href=\\"https://www.amazonaws.cn/campaign/?sc_channel=el&amp;sc_campaign=credit-acts-ldr&amp;sc_country=cn&amp;sc_geo=chna&amp;sc_category=mult&amp;sc_outcome=field&amp;trkCampaign=request-credit-glb-ldr&amp;trk=f45email&amp;trk=02faebcb-3f61-4bcb-b68e-c63f3ae33c99&amp;sc_channel=el\\" target=\\"_blank\\">https://www.amazonaws.cn/campaign/?sc_channel=el&amp;sc_campaign=credit-acts-ldr&amp;sc_country=cn&amp;sc_geo=chna&amp;sc_category=mult&amp;sc_outcome=field&amp;trkCampaign=request-credit-glb-ldr&amp;trk=f45email&amp;trk=02faebcb-3f61-4bcb-b68e-c63f3ae33c99&amp;sc_channel=el</a></p>\\n<p>福利三:解决方案 CloudFormation 一键部署模版库<br />\\n<a href=\\"https://aws.amazon.com/cn/quickstart/?solutions-all.sort-by=item.additionalFields.sortDate&amp;solutions-all.sort-order=desc&amp;awsf.filter-tech-category=*all&amp;awsf.filter-industry=*all&amp;awsf.filter-content-type=*all&amp;trk=afdbbdf0-610b-4421-ac0c-a6b31f902e4b&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/quickstart/?solutions-all.sort-by=item.additionalFields.sortDate&amp;solutions-all.sort-order=desc&amp;awsf.filter-tech-category=*all&amp;awsf.filter-industry=*all&amp;awsf.filter-content-type=*all&amp;trk=afdbbdf0-610b-4421-ac0c-a6b31f902e4b&amp;sc_channel=el</a></p>\n"}
0
目录
关闭