使用 SwiftUI 和 GraphQL 克隆 Instagram

今天,我们将开始一系列博文,教你如何使用大量酷炫工具来创建自己的社交网络:一个类似 Instagram 的应用程序。

我们不会节省技术成本,我们将使用最新、最棒的工具:Parse、GraphQL、一些 NodeJS,尤其是(尚未发布的)Apple最新框架 SwiftUI。

这将需要几篇文章才能完全发挥作用,但当我们做到这一点时,你就会意识到在 Back4app,只需很少的努力就能让你的想法变得如此简单。

为了更好地学习,请下载带源代码的iOS Instagram 克隆项目

看来是时候…

您想快速上手?

从我们的中枢克隆这个应用程序,并开始使用它,没有任何麻烦!

一些介绍

您可能已经知道 Parse 为您的开发过程带来的巨大好处,尤其是在 Back4app 托管的情况下,因为我们的工具集可以极大地提高工作效率。

我们最近关于GraphQL的文章向你展示了如果使用GraphQL,随着时间的推移维护 API 的开发是多么容易。如果您错过了这些文章,我强烈建议您花点时间阅读一下,因为它们将大大提高您的理解能力。
您可以阅读有关 GraphQL的更多信息,我们在Web API 一文中也有一篇关于如何使用GraphQL的完整文章。
您还可以在阅读GraphQL 和 NodeJS 的云代码函数中看到与NodeJS 的一些集成。

剩下的就是 SwiftUI 了。
根据 Apple 的说法,SwiftUI 是 “利用 Swift 的强大功能在所有 Apple 平台上构建用户界面的一种创新、异常简单的方法”。

我个人认为,SwiftUI 的功能远不止于此。

如果你像我一样开发软件有一段时间了,你可能会知道开发者社区一直在使用 Storyboards(Apple的拖放式用户界面构建界面)的便利性和程序化用户界面的强大功能之间徘徊。

SwiftUI 的出现带来了这两个世界的最佳选择:它既便于初学者学习,又保持了编码用户界面的可维护性。
将其与实时渲染相结合,开发人员就能轻松地看到他们正在编码的可视化输出,并使其具有双向性,这样,如果您更改代码,它就会反映到用户界面上,而从图形上更改用户界面,它也会反映到代码上。

那么,你需要什么呢?

在撰写这篇文章时,Apple仍在测试新的 macOS Catalina、XCode 11、iOS 13 和其他一些即将上架的产品。

虽然你可以在 macOS Mojave 中使用 XCode 11,但你需要使用 Catalina 才能实时呈现 SwiftUI 的预览效果。

screen-shot-2019-08-26-at-09-07-25

没有 Catalina?就没有预览!

这是你必须做出的决定,因为我发现将 Bash 更改为 ZSH 作为 Catalina 中的 macOS 官方 shell 会破坏我在日常工作流程中使用的许多工具和脚本,因此我决定暂时使用 Mojave。你可以自行决定是否更新。

此外,您还需要一个已创建应用程序的 Back4app 账户。您可以在Back4app 文档中了解如何创建第一个应用程序。

最后,我们将使用Apollo Client将 GraphQL 集成到我们的项目中。如果您没有使用经验,我已经写过一篇非常详细的文章,介绍如何配置和使用Web Authentication

因此,在我们开始之前,你需要

  • 已安装 XCode 11(如果想看预览版,请安装 macOS Catalina)
  • 在 Back4app 中创建新应用程序
  • 安装并配置好 Apollo 客户端

我的应用程序将被命名为Back4Gram,因为它很像 Instagram。

我们的第一课

我经常跟你们讲 Parse 是如何创建用户类(User class)和角色类(Role class)的,前者可以注册新用户并对现有用户进行身份验证,后者可以对具有类似权限的用户进行分组。这很好,因为至少在身份验证时,我们不需要创建任何类。

用户类有三个自动创建的属性,其中两个是必须的,一个是可选的:

  • 用户名(必填)
  • 密码(必填)
  • 电子邮件(可选)

这些就足够我们的应用程序注册和管理用户了。

你可能知道,Apple公司对存储用户电子邮件的政策非常严格,所以我建议不要询问或保留这些信息,否则可能会未获批准。

由于这只是一个教程,我将要求用户输入这些信息,让你看看它是如何工作的。

screen-shot-2019-08-26-at-09-30-18

自动创建用户和角色类

如果这还不够有用,新创建的应用程序已经为我们自动创建了 GraphQL 查询和突变。这不仅适用于我们自动创建的类,也适用于您长期创建的任何其他类。我们还为您提供了相关文档!

screen-shot-2019-08-26-at-09-39-46

我告诉过你我们还有自动完成功能吗?是的!

因此,我的第一个突变将如上所述,并使用我们特定的突变 signUp() 来创建一个新用户:

查询将取决于你使用的 Parse 版本:

Parse 3.7.2:

突变 signUpUser($username: 字符串!, $password: 字符串!, $email: 字符串) {
  用户 {
    signUp(
      fields:{ username: $username, password: $password, email:$email }
    ){
      对象 ID
    }
  }
}

Parse 3.8:

突变 signUpUser($username: 字符串!, $password: 字符串!, $email: 字符串) {
    signUp(
      fields:{ username: $username, password: $password, email:$email }
    ){
      对象 ID
    }
}

Parse 3.9:

突变 signUpUser($username: 字符串!, $password: 字符串!, $email: 字符串) {
    signUp(
      fields:{ username: $username, password: $password, email:$email }
    ){
      id
    }
}

请注意,我在变量 username 和 password 的类型后面加上了”!”,使它们成为强制性变量,而 email 则是可选变量,在其类型后面没有加上”!”。

最后,我将返回新创建用户的 objectId。

既然到了这里,我们也来编写登录已存在用户的 GraphQL 变量代码。
在这种情况下,用户名和密码都是必须的,我将为用户获取 sessionToken:

Parse 3.7.2

突变 logInUser($username: String!, $password: String!){
  用户{
    登录(用户名:$username,密码:$password){
      会话令牌
    }
  }
}

Parse 3.8

突变 logInUser($username: String!, $password: String!){
    logIn(username: $username, password: $password){
      会话令牌
    }
}

Parse 3.9

突变 logInUser($username: String!, $password: String!){
    logIn(username: $username, password: $password){
      会话令牌
    }
}

最后但并非最不重要的一点是,用户注销的突变不需要任何参数,只返回一个布尔值:

Parse 3.7.2

变异 logOutUser{
  用户{
    注销
  }
}

Parse 3.8

变异 logOutUser{
    logOut
}

Parse 3.9

变异 logOutUser{
    logOut
}

将所有这些内容添加到一个名为UserMutations.graphql 的新文件中,同时保存你在上一篇文章中学过如何下载的schema.graphql文件。

请妥善保存这些文件,因为我们将把它们添加到我们的…

SwiftUI 项目

让我们创建 SwiftUI 项目。

启动 XCode 11,在 “欢迎使用 XCode “窗口中点击 “创建一个新的 XCode 项目”。
如果你没有看到该窗口,也可以进入 “文件”>”新建”>”项目”。

在顶部选择 “iOS 应用程序”,然后选择 “单视图应用程序”。给它起个好名字,记得选择 Swift 作为语言,SwiftUI 作为用户界面:

screen-shot-2019-08-26-at-10-15-21

别忘了选择 SwiftUI 作为用户界面

现在,安装Apollo Client,就像在Web api 文章中学到的那样,然后将UserMutations.graphqlschema.graphql文件添加到项目中。
编译并将新生成的API.swift文件添加到项目中。

此时,我希望你具备以下条件

  • 安装并运行 XCode 11
  • Apollo客户端已安装并集成到 XCode 项目中
  • 在 XCode 项目中添加 UserMutations.graphql 和 schema.graphql 文件
  • 在 XCode 项目中添加 API.swift 生成文件

您的项目应与此相似:

screen-shot-2019-08-26-at-10-32-00

所有内容均已集成至 XCode 11

现在我们可以开始使用 SwiftUI 了。

立即注册 Back4App开始创建您的 Instagram 克隆应用程序。

决定我们的控件

在登录或注销用户之前,我们必须先创建该用户。因此,我们首先要编写登录界面的代码。

通常,我们可以在该屏幕中设置 4 到 5 个控件:

  • 某种类型的文本,告诉用户这是什么屏幕(文本)
  • 用户名(文本输入控件)
  • 密码(安全文本输入控件)
  • 确认密码(可选,取决于平台,安全文本输入控件)
  • 电子邮件(格式化文本输入控件)
  • 注册按钮(按钮)

由于这将是一个移动应用程序,我决定不使用确认密码文本输入控件,因为大多数移动平台都允许用户查看最后输入的字符,在大多数情况下,这足以让用户确定密码是否输入正确。

这些控件将按照上面的顺序垂直排列,一个在另一个下面,每个控件都有自己的颜色。

实际构建用户界面

正如我前面提到的,我没有使用 macOS Catalina,因此不会启用预览功能,但我会按照我的代码编译并向你展示编译结果。

通过 “文件”>”新建”>”文件”,然后在 “用户界面 “部分选择 “SwiftUI 视图”,创建一个新的 SwiftUI 视图。

screen-shot-2019-08-26-at-11-08-58

将该文件命名为 SignUpView.swift,然后它就会集成到你的项目中。

screen-shot-2019-08-26-at-11-14-13

您会发现,新创建的视图中已经有一个文本控件,其中包含 “Hello World!”字符串。

screen-shot-2019-08-26-at-11-18-31

Swift 的美妙之处在于,你可以创建多个简单的视图,其中只包含该视图所需的控件,然后将多个视图合并为一个更复杂的视图。这就是最佳的面向对象。

我们决定让控件垂直排列,SwiftUI 为此提供了一个名为 VStack(垂直堆叠)的特定控件:VStack 中的所有内容都将自动垂直堆叠,因此,为了进行测试,让我们将 VStack 控件添加到ContentView.swift中,并在其中添加两次 SignUpView:

screen-shot-2019-08-26-at-11-39-39

在 VStack 中添加两次 SignUpView:它显示了两次垂直对齐的 SignUpView。

现在,这多酷啊!如果我们需要更多控件,可以继续添加!

让我们移除 VStack 和重复的控件,在该视图中只添加一个 SignUpView(),然后回到我们的 SignUpView 并开始更改它,因为我们已经看到了测试结果。现在,您的 ContentView 应该与下面相似:

struct ContentView:View {
    var body: some View {
        SignUpView()
    }
}

我们已经知道我们需要在屏幕顶部使用带文本的 VStack,因此我们只需将 VStack 添加到 SignUpView 中,并将 “Hello World!” 字符串改为有用的字符串,如 “Sign Up”。我们的 SignUpView 应该是这样的:

struct SignUpView:View {
    var body: some View {
        VStack{
            Text("Sign Up")
        }
    }
}

由于我们已经知道必须在下面添加哪些控件,因此可以添加

  • 一个用户名文本字段
  • 一个密码安全字段
  • 电子邮件文本字段
  • 用于注册的按钮

TextFields 和 SecureField 都与旧的 UITextField 非常相似,但必须依赖于与状态的绑定,因此我们要将它们声明为

Control(“Placeholder”, text:stateValueToBindTo)

第一步是声明要绑定的状态:

@State var username: String = ""
@State var password: String = ""
@State var email:字符串 = ""

有了这些,我们就可以开始添加控件了:

Text("Sign Up")
TextField("用户名", text: $username)
SecureField("Password", text: $password)
TextField("电子邮件(可选)", text: $email)

最后,我们可以添加结构如下的 Button:

Button(action:{
//触发时运行的代码
}){
//内容
}

正如你可能注意到的,它的工作原理与 UIButton 大同小异,但其结构要灵活得多,因为我们可以通过编程定义其内容,几乎可以在其中添加任何东西:文本、图片。你说什么都行。

旧的 target/action 结构已不复存在,取而代之的是一个新的 action:{} 结构,用于处理点击时的行为。

我们的按钮将包含一个文本,上面写着 “注册!”,让我们添加它:

Button(action:{

}){
    Text("Sign Up!")
}

最后的代码应该是这样的

struct SignUpView:View {
    @State var username: String = ""
    @State var password: String = ""
    @State var email:String = ""
    
    var body: some View {
        VStack{
            文本("注册")
            TextField("Username", text: $username)
            SecureField("Password", text: $password)
            TextField("电子邮件(可选)", text: $email)
            Button(action:{
                
            }){
                Text("Sign Up!")
            }
        }
    }
}

预览效果如何?

screen-shot-2019-08-26-at-16-11-48

看起来很不错,但通过设置一些可视化属性,我们可以改进很多。
如果说 SwiftUI 有什么超棒的地方,那就是你可以在代码中添加可视化属性,并实时查看变化!
让我们尝试一下!

首先,让我们声明要使用的颜色

let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)
    let lightBlueColor = Color(red: 36.0/255.0, green: 158.0/255.0, blue: 235.0/255.0, opacity: 1.0)

然后,让我们添加一些漂亮的功能:

一些填充,这样我们就可以在这些元素之间留出一些空间。
设置背景和前景色属性,这样我们的控件就能与我们的品牌保持一致。
设置角半径(cornerRadius),这样我们就能拥有圆角。
设置字体和 fontWeight,让效果更漂亮。
就是这样!

screen-shot-2019-08-26-at-16-30-55

好看吗?
现在,你就可以跟踪我们的代码了:

struct SignUpView:View {
    @State var username: String = ""
    @State var password: String = ""
    @State var email:String = ""
    
    let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)
    let lightBlueColor = Color(红色:36.0/255.0,绿色:158.0/255.0,蓝色:235.0/255.0,不透明度:1.0)

    
    var body: some View {
        VStack{
            文本("注册)
                .font(.largeTitle)
                .foregroundColor(lightBlueColor)
                .fontWeight(.semibold)
                .padding(.bottom, 20)
            TextField("Username", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            SecureField("Password", text: $password)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            TextField("电子邮件(可选)", text: $email)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            Button(action:{
                
            }){
                Text("Sign Up!")
                 .font(.headline)
                 .foregroundColor(.white)
                 .padding()
                 .frame(width: 220, height: 60)
                 .background(lightBlueColor)
                 .cornerRadius(15.0)
            }
        .padding()
    }
}

这样,我们漂亮闪亮的屏幕就差不多准备好了。
怎么样…

一些功能

现在我们的用户界面已经准备就绪,是时候在 GraphQL 方法中添加 Apollo 调用了。

由于我们将在几乎所有的视图中使用这些调用,所以我们应该在所有视图都能访问它的地方创建一个 Apollo 客户端。这个地方就是AppDelegate.swift

在其中的

导入 UIKit

和代码块的开头

@UIApplicationMain
类 AppDelegate:UIResponder, UIApplicationDelegate {
...

正如我们在上一篇文章中所展示的,从现在起所有的 View 都可以执行 GraphQL 查询和突变。
首先,您必须导入 Apollo 框架,然后像这样实例化您的客户端:

导入 Apollo

// Apollo 客户端初始化。
更多信息请点击:https://www.back4app.com/docs/ios/swift-graphql
让 apollo:ApolloClient = {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = [
        "X-Parse-Application-Id":"YourAppIdHere"、
        "X-Parse-Client-Key":"YourClientKeyHere
    ]
    
    let url = URL(string: "https://parseapi.back4app.com/graphql")!
    
    return ApolloClient(
        networkTransport:HTTPNetworkTransport(
            url: url、
            configuration: 配置
        )
    )
}()

只需将字符串YourAppIdHereYourClientKeyHere替换为当前值,我们就可以继续了。

您可能已经猜到,我们必须在点击注册按钮时执行注册突变,因此,让我们在操作块中调用它:

            // 执行 SignUpUser 突变,传递我们刚刚从 TextFields 中获得的参数
            apollo.perform(mutation.SignUpUserMutation)SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in
                // 让我们切换结果,以便将成功与错误区分开来
                switch result {
                    // 在成功的情况下
                    case .success(let graphQLResult):
                        // 我们尝试Parse结果
                        if let objId = graphQLResult.data.users.signUp.objectId {
                            print ("User created with ObjectId: " + objId)
                        }
                        // 但如果出现任何 GraphQL 错误,我们会显示该消息
                        else if let errors = graphQLResult.errors {
                            // GraphQL 错误
                            print(errors)
                            
                        }
                    // 如果失败,我们将显示该消息
                    case .failure(let error):
                      // 网络或响应格式错误
                      print(error)
                }
            }

这样就可以了,但这些 print() 方法只能打印到控制台。我们需要向用户发出警报,因此我们将其改为警报,其结构如下:

Alert(title:Text(“Title”), message:Text(“Message”), dismissButton: .default(Text(“TextOfButton”))

由于我们需要在运行时更改标题和消息,我们必须设置一个 Struct 来处理这些值,因为自变量不能在运行时更改:

struct Message {
    var alertTitle:字符串 = ""
    var alertText:字符串 = ""
}

var myMessage = Message()

同时创建一个状态,以便 View 知道何时显示警报:

@State private var showingAlert = false

按钮操作的完整代码应如下所示:

Button(action:{
            // 执行 SignUpUser 变量,传递我们刚刚从 TextFields 获取的参数
            apollo.perform(mutation:SignUpUserMutation(username: self.username, password: self.password, email: self.email)){ result in
                // 让我们切换结果,以便将成功与错误区分开来
                switch result {
                    // 在成功的情况下
                    case .success(let graphQLResult):
                        // 我们尝试Parse结果
                        if let objId = graphQLResult.data.users.signUp.objectId {
                            myMessage.alertTitle = "耶!"
                            myMessage.alertText = "用户已注册!"
                            
                            self.showingAlert = true

                            print ("User created with ObjectId: " + objId)
                        }
                        // 但如果出现任何 GraphQL 错误,我们会显示该消息
                        else if let errors = graphQLResult.errors {
                            // GraphQL 错误

                            myMessage.alertTitle = "哎呀!"
                            myMessage.alertText = "我们有一个 GraphQL 错误:" + errors.description
                            self.showingAlert = true

                            print(errors)
                        }
                    // 如果出现故障,我们将显示该信息
                    case .failure(let error):
                        // 网络或响应格式错误
                        myMessage.alertTitle = "哎呀!"
                        myMessage.alertText = "我们出错了:" + error.localizedDescription
                        self.showingAlert = true
                        
                        print(error)
                }
            }
           }){
               Text("Sign Up!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width: 220, height: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
           .alert(isPresented: $showingAlert) {
                警报(title:Text(myMessage.alertTitle), message:Text(myMessage.alertText), dismissButton: .default(Text("OK"))
           }

看起来很有希望,是吧?

是时候了

编译并运行。尝试注册一个新用户,你应该会得到…

screen-shot-2019-08-27-at-15-40-59

看看用户是否已在 Parse 控制面板中创建!

screen-shot-2019-08-27-at-15-42-30

结论

您已经创建了一个可以使用 Apollo 客户端执行 GraphQL mutations 的完整 SwiftUI 视图。这真是太棒了!

我们将继续开发 Instagram 克隆应用程序!下一步是登录和退出,文章已经出炉!

敬请期待!

参考资料

现在就注册Back4App开始创建您的Instagram克隆应用程序。


Leave a reply

Your email address will not be published.