SwiftUIとGraphQLを使ったInstagramクローン

今日から私たちは、あなた自身のソーシャルネットワークを構築するために多くのクールなツールを使用する方法を教えるブログ記事のシリーズを開始します:Instagramのようなアプリケーション。

技術的な節約はしないし、最新で最高のものを使うつもりだ:Parse、GraphQL、いくつかのNodeJS、そして特に(まだリリースされていない)最新のAppleのフレームワークSwiftUI。

完全に機能するようになるには数回の投稿が必要ですが、それが実現すれば、Back4appであなたのアイデアをほとんど労力をかけずに、いかにシンプルに立ち上げることができるかを実感していただけるでしょう。

iOS Instagramクローンプロジェクトをソースコード付きでダウンロードしてください。

さて、そろそろ…

手っ取り早く始めたいですか?

私たちのハブからこのアプリをクローンして、手間なく使い始めましょう!

はじめに

Back4appでホストされている場合、Parseがあなたの開発プロセスに大きなメリットをもたらすことはすでにご存知でしょう。

特にBack4appでホスティングされている場合、Parseの一連のツールは生産性を飛躍的に向上させるからです。もしこれらの投稿を見逃したのであれば、時間をかけて読むことを強くお勧めします。
GraphQLについてもっと読むことができるし、Web APIの記事で使い方についての完全な記事もある。
また、Cloud Code FunctionsのGraphQLとNodeJSを読むことで、NodeJSとの統合を見ることができます。

残るはSwiftUIだ。
Appleによれば、SwiftUIは「SwiftのパワーですべてのAppleプラットフォームでユーザーインターフェースを構築するための革新的で非常にシンプルな方法」だ。

私自身は、SwiftUIはそれ以上のものだと考えたい。

もしあなたが私のように、しばらくの間ソフトウェアを開発しているのであれば、おそらく開発者コミュニティが、Appleのドラッグ&ドロップUI構築インターフェースであるStoryboardsを使うことの利便性と、プログラムUIのパワーの間で常に分裂していることを知っているだろう。

SwiftUIは、初心者が学ぶのに十分なほど簡単でありながら、コード化されたUIの保守性を維持する。
リアルタイムレンダリングと組み合わせることで、開発者はコーディングしている内容のビジュアルアウトプットを簡単に見ることができ、コードを変更すればUIに反映され、UIをグラフィカルに変更すればコードに反映されるという2ウェイを実現します。

では、何が必要なのか?

この記事を書いている時点では、Appleはまだ新しいmacOS Catalina、XCode 11、iOS 13、その他いくつかの製品のベータテストを行っている。

macOS MojaveでXCode 11を使うことができますが、SwiftUIのプレビューをリアルタイムでレンダリングするにはCatalinaが必要です。

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

Catalinaがない?あなたのためのプレビューはありません!

Catalinaの公式のmacOSシェルとしてBashからZSHに変更することで、私の日々のワークフローに使っているツールやスクリプトの多くが壊れてしまうことがわかったので、私はしばらくMojaveにとどまることにしました。アップデートするかどうかはあなた次第だ。

また、Appを作成したBack4appアカウントが必要です。最初のAppの作成方法はBack4appのドキュメントをご覧ください。

最後に、プロジェクトにGraphQLを統合するためにApollo Clientを使用する。もしApollo Clientを使用した経験がないのであれば、Web認証の設定方法と使用方法について非常に詳しい記事を書いたので、そちらを参照してほしい。

では、始める前に必要なもの:

  • XCode 11がインストールされていること(プレビューを見たい場合はmacOS Catalinaがあること)
  • Back4appで作成した新しいアプリ
  • Apollo Clientのインストールと設定

私のアプリはInstagramに似ているため、Back4Gramと呼ぶ予定です。

最初のクラス

ParseがUserクラスを作成することで、新しいユーザーを登録したり、既存のユーザーを認証したり、Roleクラスを作成することで、同じような権限を持つユーザーをグループ化することができます。これは素晴らしいことで、少なくとも認証に関してはクラスを作成する必要がありません。

Userクラスには、自動的に作成される3つのプロパティがあり、そのうち2つは必須、1つはオプションです:

  • ユーザー名(必須)
  • パスワード(必須)
  • Eメール(オプション)

これだけで、我々のアプリはユーザーを登録・管理することができる。

ご存知のように、ユーザーのEメールを保存することに関するAppleのポリシーは非常に厳しいので、その情報を尋ねたり保存したりしないことをお勧めする。

これはチュートリアルなので、ユーザーにその情報を入力してもらうことにします。

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

自動的に作成されるユーザークラスとロールクラス

まだ十分役に立っていないかのように、新しく作成されたAppにはすでにGraphQL QueriesとMutationが自動的に作成されています。これは自動的に作成されたクラスにも、時間をかけて作成したその他のクラスにも当てはまります。ドキュメントもご用意しています!

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

オートコンプリートがあることは言いましたか?そうです!

私の最初の変異は上記のようになり、新しいユーザーを作成するために私たちの特定の変異signUp()を使用します:

クエリは使用しているParseのバージョンによって異なります:

Parse 3.7.2:

mutation signUpUser($username: String!, $password: String!, $email: String) { 以下のようにします。
  ユーザー
    signUp(
      フィールド:{username: $username、password: $password、email:email: $email }.
    ){
      オブジェクトID
    }
  }
}

Parse 3.8:

mutation signUpUser($username: String!, $password: String!, $email: String) { {.
    signUp(
      フィールド:{username: $username、password: $password、email:email: $email }.
    ){
      オブジェクトID
    }
}

Parse 3.9:

mutation signUpUser($username: String!, $password: String!, $email: String) { {.
    signUp(
      フィールド:{username: $username、password: $password、email:email: $email }.
    ){
      id
    }
}

変数usernameとpasswordの型の最後に”!”をつけることで、必須変数にしていることに注意してください。

最後に、新しく作成されたユーザーのobjectIdを返します。

せっかくなので、すでに存在するユーザーをログインさせるためのGraphQLミューテーションもコーディングしてみましょう。
この場合、ユーザー名とパスワードの両方が必須で、そのユーザーのsessionTokenを取得します:

Parse 3.7.2

mutation logInUser($username: String!, $password: String!){次のようにします。
  users{
    logIn(ユーザー名: $ユーザー名, パスワード: $パスワード){ { logIn(username: $ユーザー名, password: $パスワード)
      セッショントークン
    }
  }
}

Parse 3.8

mutation logInUser($username: String!, $password: String!){ { logIn(username: $username, password: $password)
    logIn(username: $ユーザー名, password: $パスワード){。
      sessionToken
    }
}

Parse 3.9

mutation logInUser($username: String!, $password: String!){ { logIn(username: $username, password: $password)
    logIn(username: $ユーザー名, password: $パスワード){。
      sessionToken
    }
}

これはパラメータを必要とせず、ブール値を返すだけです:

Parse 3.7.2

突然変異 logOutUser{
  ユーザ
    ログアウト
  }
}

Parse 3.8

突然変異 logOutUser{
    ログアウト
}

構文Parse 3.9

突然変異 logOutUser{
    ログアウト
}

これらのファイルをUserMutations.graphqlという新しいファイルに追加し、前回の記事でダウンロード方法を学んだschema.graphqlファイルも保存する。

これらのファイルを安全に保管してください。

SwiftUIプロジェクト

SwiftUIプロジェクトを作成しよう。

XCode 11を起動し、”Welcome to XCode “ウィンドウから “Create a new XCode Project “をクリックする。
そのウィンドウが表示されない場合は、File(ファイル)> New(新規作成)> Project(プロジェクト)と進んでください。

一番上でiOS Appを選び、次にSingle View Appを選ぶ。良い名前を付け、言語としてSwiftを、ユーザーインターフェイスとしてSwiftUIを選択することを忘れないように:

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

ユーザーインターフェースとしてSwiftUIを選択することを忘れないでください。

そして、UserMutations.graphqlと schema.graphqlファイルをプロジェクトに追加します。
新しく生成されたAPI.swiftファイルをコンパイルし、プロジェクトに追加します。

この時点で、私はあなたが持っていることを期待しています:

  • XCode 11がインストールされ、実行されている
  • Apolloクライアントがインストールされ、XCodeプロジェクトに統合されている。
  • UserMutations.graphqlおよびschema.graphqlファイルがXCodeプロジェクトに追加されている。
  • API.swift生成ファイルをXCodeプロジェクトに追加する。

プロジェクトはこのようになるはずです:

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

すべてがXCode 11に統合されている

これでSwiftUIを始める準備ができました。

今すぐBack4AppにサインアップしてInstagramクローンアプリを作り始めましょう。

コントロールの決定

ユーザーをログインさせたり、ログアウトさせたりする前に、まずユーザーを作成する必要があります。そこで、最初にサインアップ画面をコーディングします。

通常、この画面には4つから5つのコントロールがあります:

  • この画面が何であるかをユーザに伝える何らかのテキスト(テキスト)
  • ユーザー名(テキスト入力コントロール)
  • パスワード(安全なテキスト入力コントロール)
  • パスワードの確認(オプション、プラットフォームによる、安全なテキスト入力コントロール)
  • Eメール(フォーマットされたテキスト入力コントロール)
  • サインアップボタン(ボタン)

ほとんどのモバイルプラットフォームでは、最後に入力された文字が表示され、ほとんどの場合、パスワードが正しく入力されているかどうかをユーザーが判断するのに十分だからです。

これらのコントロールは、上記の順序で、1つずつ下に縦に並んで表示され、それぞれ独自の色で表示されます。

実際にUIを構築する

先に述べたように、私はmacOS Catalinaを使用していないので、プレビューを有効にしませんが、私が持っていたコードと一緒にコンパイルして結果をお見せします。

File > New > Fileと進み、User InterfaceセクションでSwiftUI Viewを選択することで、新しいSwiftUI Viewを作成します。

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の中にあるものはすべて自動的に垂直にスタックされます:

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

VStackにSignUpViewを2回追加しました。

さて、なんとクールなことだろう!もっとコントロールが必要なら、どんどん追加していけばいい!

VStackと重複を削除して、ViewにSignUpView()を1つだけ追加して、SignUpViewに戻って変更を始めましょう。ContentView.swiftはこのようになるはずです:

struct ContentView:ビュー
    var body: あるView {。
        SignUpView()
    }
}

画面上部にTextを含むVStackが必要なことはすでに分かっているので、代わりにSignUpViewにVStackを追加し、”Hello World!”の文字列を “Sign Up “のような有用なものに変更してみよう。SignUpViewは以下のようになります:

struct SignUpView:ビュー
    var body: あるView {。
        VStack{
            テキスト("サインアップ")
        }
    }
}

そして、その下にどのコントロールを追加しなければならないかはすでに分かっているので、追加することができる:

  • ユーザー名用TextField
  • パスワード用 SecureField
  • 電子メール用テキストフィールド
  • サインアップ用ボタン

TextFieldsとSecureFieldは、古いUITextFieldとよく似ているが、ステートへのバインディングに依存しなければならないので、次のように宣言する:

Control(“プレースホルダ”, text:stateValueToBindTo)

そして、一番最初のステップは、バインドする状態を宣言することです:

状態 var username: String = ""
状態 var パスワード: 文字列 = ""
状態 var email:文字列 = ""

これで、コントロールを追加し始めることができる:

Text("Sign Up")
テキストフィールド("ユーザー名", text: $username)
SecureField("パスワード", text: $password)
TextField("Eメール(オプション)", text: $email)

最後に、次の構造を持つ Button を追加します:

Button(action:{
//トリガーされたときに実行するコード
}){
//コンテンツ
}

お気づきのように、これはUIButtonと同じように機能するが、その構造はより柔軟で、プログラムによってコンテンツを定義することができる。テキストや画像など、何でも追加できる。

古いtarget/action構造はなくなり、タップされたときの動作を扱う新しいaction:{}構造に置き換わっています。

私たちのボタンには、”Sign Up!”というテキストを追加します:

Button(action:{

}){
    テキスト("サインアップ!")
}

そして、最終的なコードは次のようになります:

struct SignUpView:ビュー
    状態 var username: String = ""
    状態 var password: String = ""
    状態 var email:文字列 = ""
    
    var body: あるビュー {.
        VStack{
            テキスト("サインアップ")
            テキストフィールド("ユーザー名", text: $username)
            SecureField("パスワード", text: $password)
            TextField("Eメール(オプション)", text: $email)
            Button(action:{
                
            }){
                テキスト("サインアップ!")
            }
        }
    }
}

プレビューはどうでしょう?

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)

次に、いくつかの素敵な機能を追加しましょう:

パディングを追加して、これらの要素の間にスペースを確保します。
backgroundColorとforegroundColorプロパティを設定して、私たちのブランドと一貫性のあるコントロールを用意します。
cornerRadiusを設定して、角を丸くします。
そして、フォントとfontWeightを設定することで、見栄えを良くします。
出来上がり!

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

素敵でしょう?
この時点で私たちのコードを把握しておいてください:

struct SignUpView:ビュー
    状態 var username: String = ""
    状態 var password: String = ""
    状態 var email:文字列 = ""
    
    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)

    
    var body: あるビュー {.
        VStack{
            テキスト("サインアップ")
                .font(.largeTitle)
                .foregroundColor(lightBlueColor)
                .fontWeight(.semibold)
                .padding(.bottom, 20)
            テキストフィールド("ユーザー名", text: $username)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            SecureField("Password", text: $password)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            テキストフィールド("Eメール(オプション)", text: $email)
                .padding()
                .background(lightGreyColor)
                .cornerRadius(5.0)
                .padding(.bottom, 20)
            Button(action:{
                
            }){
                テキスト("サインアップ!")
                 .font(.headline)
                 .foregroundColor(.white)
                 .padding()
                 .frame(width: 220, height: 60)
                 .background(lightBlueColor)
                 .cornerRadius(15.0)
            }
        }.padding()
    }
}

というわけで、ピカピカの画面がほぼできあがりました。
では

いくつかの機能

UIの準備ができたので、次はApolloコールをGraphQLメソッドに追加します。

ほとんどすべてのViewでこれらの呼び出しを使用するつもりなので、すべてのViewがアクセスできる場所にApolloクライアントを作成する必要があります。その場所とは、AppDelegate.swiftです。

その中で、Apolloクライアントを以下のように宣言します。

インポートUIKit

とコードブロックの開始部分の下に、Apolloクライアントを宣言します。

UIApplicationMain
class AppDelegate:UIResponder, UIApplicationDelegate {...
...

前回の記事で紹介したように、今後はすべてのViewでGraphQLクエリーとミューテーションを実行できるようになります。
最初にApolloフレームワークをインポートし、次のようにクライアントをインスタンス化する必要があります:

インポートApollo

// Apolloクライアントの初期化。
// 詳しくはこちら: https://www.back4app.com/docs/ios/swift-graphql
let apollo:アポロクライアント = {
    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、
            構成: 構成
        )
    )
}()

文字列YourAppIdHereと YourClientKeyHereを現在の値に置き換えるだけで、次に進むことができます。

おそらくお察しの通り、サインアップボタンをクリックしたときにサインアップのためのミューテーションを実行する必要があります:

            // 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 { 以下のようにします。
                            print ("User created with ObjectId: " + objId)
                        }
                        // ただし、GraphQLエラーの場合はそのメッセージを表示する
                        else if let errors = graphQLResult.errors { // グラフQLエラー。
                            // GraphQLエラー
                            print(errors)
                            
                        }
                    // 失敗した場合、そのメッセージを表示する
                    case .failure(let error):
                      // ネットワークエラーまたはレスポンスフォーマットエラー
                      print(error)
                }
            }

これでうまくいくが、これらのprint()メソッドはコンソールにしか表示されない。ユーザーにアラートを表示する必要があるので、次のような構造のアラートに変更してみましょう:

Alert(タイトル:Text(“タイトル”), message:Text(“Message”), dismissButton: .default(Text(“TextOfButton”)))

実行時にタイトルとメッセージを変更する必要があるため、これらの値を処理する Struct を設定する必要があります:

メッセージ構造体
    var alertTitle:文字列 = ""
    var alertText:文字列 = ""
}

var myMessage = Message()

また、Viewがアラートを表示するタイミングを知ることができるように、Stateを作成します:

ステート private var showingAlert = false

そして、Buttonアクションの完全なコードは次のようになります:

Button(action:{
            // TextFieldsから取得したパラメータを渡して、SignUpUserの変異を実行します。
            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 { // グラフQLエラー。
                            // 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)
                }
            }
           }){
               テキスト("サインアップ!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width: 220, height: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
           .alert(isPresented: $showingAlert) { { アラート(タイトル: $showingAlert)
                アラート(title:Text(myMessage.alertTitle), message:テキスト(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 Clientを使用してGraphQL変異を実行する完全に動作するSwiftUIビューを作りました。なんと素晴らしいことでしょう!

Instagramクローンアプリの開発を続けましょう!次のステップはログインとログアウトで、投稿はすでにオーブンに入っています!

ご期待ください!

参考

今すぐBack4AppにサインアップしてInstagramクローンアプリの開発を始めましょう。


Leave a reply

Your email address will not be published.