forceios ネイティブアプリケーションの変更

学習の目的

この単元を完了すると、次のことができるようになります。

  • ユーザが Salesforce 取引先責任者を削除できるボタンを追加する。
  • 新しい REST 要求を iOS ネイティブテンプレートアプリケーションに追加する。
  • REST 応答を処理する。

forceios Swift アプリケーションのカスタマイズ

Swift アプリケーションを作成するために forceios が使用するテンプレートは、Salesforce 組織から取り込んだ取引先名のリストを表示します。このリストから、選択した取引先の詳細を表示することや、取引先の取引先責任者のリストを表示することができます。さらに、取引先責任者を選択してその詳細を表示することもできます。顧客がこのアプリケーションを使用してデータを操作することはできず、参照のみです。では、ユーザがその取引先責任者のレコードを削除できるボタンを [取引先責任者] 詳細ビューに追加して、forceios Swift アプリケーションを向上させましょう。ユーザがこのボタンをタップすると、コードは次のように反応します。
  1. 選択した取引先責任者レコードを削除する REST 要求を送信する。
  2. 顧客に削除を確定するよう求める。

この演習を行う場合は、「ネイティブ iOS」の最初の単元で作成した Mobile SDK ワークスペースを使用します。

ビューとモデル

Xcode プロジェクトの Classes フォルダを開くと、SwiftUI と Models という 2 つのサブフォルダが表示されます。この 2 つのフォルダは、その名が示すとおり、SwiftUI アプリケーションのビュー-モデルアーキテクチャを表しています。 

  • SwiftUI フォルダには、ビュー定義 (「UI 関連」ともいう) が格納されています。ほとんどの場合は、これらのファイルに SwiftUI のレイアウト設定とそのビジュアル属性が含まれます。SwiftUI のビューは通常、構造体として定義されます。
  • Models フォルダには、SwiftUI の ビューを動作させるデータ機能が格納されています。ビューは、Salesforce 取引先名の取得や取引先責任者レコードの削除などのデータタスクを実行するモデルをコールします。モデルには通常、機能を定義する中心的なクラス定義と、おそらくはデータ組織の構造体がいくつか含まれます。

このテンプレートアプリケーションの各モデルは、ペアのビューとファイル名のプレフィックスを共有します。たとえば、ContactDetailsModel.swift は ContactDetailsView.swift のモデルになります。

削除ボタンの追加

では、ビジュアル面の実装から始めましょう。[Delete Contact (取引先責任者の削除)] ボタンを設計してコーディングしてからテストして、完全に機能させます。 

テンプレートの ContactDetailView 構造体は、このファイルの他の場所で定義された項目を集約したリストです。ボタンとこの既存の List 要素を相互に独立した状態で維持するには、この両方をラップする VStack コンテナを作成します。このコンテナは、コードに要素が配置されるときに垂直方向に並べるように iOS に指示します。たとえば、[Delete (削除)] ボタンをシーンの下部に表示するには、VStack の List の後に配置します。 

SwiftUI では、ボタンコンストラクタに action 引数が必要です。顧客がボタンをタップすると、このアクションが実行されます。ここでは単に、顧客のタップを確認するメッセージを Xcode コンソールに送信します。

  1. Xcode プロジェクトエクスプローラで、[Classes (クラス)] > [SwiftUI] > [ContactDetailsView.swift] を開きます。
  2. ContactDetailView 構造体までスクロールします。この構造体は、次の文字列を含む行で始まります。 
    struct ContactDetailView: View {
  3. var body: some View の定義で、List 宣言を VStack でラップします。List の前の return キーワードを削除します。
  4. var body: some View { 
        VStack(alignment: .center, spacing: 3) {
            List {
                FieldView(label: "First Name", value: contact.FirstName)
                FieldView(label: "Last Name", value: contact.LastName)
                FieldView(label: "Email", value: contact.Email)
                FieldView(label: "Phone Number", value: contact.PhoneNumber)
                AddressView(contact: contact)
            }
        }
    }
  5. List ブロックの下にある VStack の閉じ中括弧の直前に Button ブロックを追加します。
    var body: some View { 
        VStack(alignment: .center, spacing: 3) {
            List {
                FieldView(label: "First Name", value: contact.FirstName)
                FieldView(label: "Last Name", value: contact.LastName)
                FieldView(label: "Email", value: contact.Email)
                FieldView(label: "Phone Number", value: contact.PhoneNumber)
                AddressView(contact: contact)
            }                  
            Button(action:{
                print("Delete Contact button tapped.")})
            {
                Text("Delete Contact")
                .bold()
                .font(.title)
                .padding()
                .foregroundColor(Color.white)
                .background(Color.gray)
            }
        }
    }
この時点でアプリケーションを実行すると、ビューの下部にボタンが表示されますが、何の役目も果たしません。タップすると、Xcode に情報メッセージが表示されるだけです。

たとえば、顧客が凸凹道を走行するバスの中でアプリケーションを利用中、誤って [Delete Contact (取引先責任者の削除)] ボタンをタップしてしまうおそれがあります。そこで顧客のデータを保護するために、取引先責任者を削除する前に確認を求めることにします。では、Salesforce 組織の取引先責任者レコードを本当に削除してもよいか顧客に念を押すアクションシートを追加しましょう。アクションシートとは、複数のアクションボタンが設定されたアラートのようなものです。この場合は、[OK] と [キャンセル] の 2 つがあれば事足ります。顧客が [キャンセル] ボタンをクリックすると、アプリケーションが削除要求を破棄します。
  1. ContactDetailView 定義の先頭に、deleteWarning という非公開の状態変数を追加して、false に設定します。この変数によってアクションシートの表示が制御されます。
    struct ContactDetailView: View {
        @State private var deleteWarning = false
  2. 定義した Button アクションで、deleteWarningtrue に設定します。顧客がボタンをタップすると、この割り当てが実行されます。 
    Button(action:{
            self.deleteWarning = true
            print("Delete Contact button tapped.")})
  3. VStack ブロックの閉じ中括弧の後に、アクションシート定義を追加します。
    .actionSheet(isPresented: $deleteWarning) {
        ActionSheet(title: Text("Deleting Contact"),
            message: Text("This action deletes this contact in your org."),
            buttons: [
                .cancel {},
                .default(Text("OK")) {
                    // TO DO
                }
            ]
        )
    }

この時点までのビュー定義は次のようになります。

var body: some View {
    VStack(alignment: .center, spacing: 3) {
        List {
            FieldView(label: "First Name", value: contact.FirstName)
            FieldView(label: "Last Name", value: contact.LastName)
            FieldView(label: "Email", value: contact.Email)
            FieldView(label: "Phone Number", value: contact.PhoneNumber)
            AddressView(contact: contact)
        }
        Button(action:{
            self.deleteWarning = true
            print("Delete Contact button tapped.")})
        {
            Text("Delete Contact")
            .bold()
            .font(.title)
            .padding()
            .foregroundColor(Color.white)
            .background(Color.gray)
        }
    }
    .actionSheet(isPresented: $deleteWarning) {
        ActionSheet(title: Text("Deleting Contact"),
            message: Text("This action deletes this contact in your org."),
            buttons: [
                .cancel {},
                .default(Text("OK")) {
                    // TODO! 
                }
            ]
        )
    }
}
相当量のコードを追加しました。ここで設定をテストしてみましょう。
  1. [Run (実行)] をクリックし、アプリケーションでエラーを確認します。ここまでの作業にエラーがなければ、iPhone Simulator が起動し、数秒後に Salesforce のログイン画面が表示されます。
  2. 開発者組織にログインして、データへのアクセスを承認します。
  3. [取引先] ビューで、任意の取引先名をクリックして取引先責任者リストを表示します。
  4. 任意の取引先責任者の名前をクリックすると、詳細が表示されます。現在開いているのが ContactDetails ビューです。
  5. [Delete Contact (取引先責任者の削除)] ボタンをクリックします。このボタンが表示されていない場合は、コードを再確認します。
  6. ボタンが正しく設定されていることを確認するには、Xcode デバッグコンソールに「Delete Contact button tapped」という行があるかどうかを確認します。
  7. 色を変えたい場合は、ボタンの foregroundColor プロパティと background プロパティを変更します。変更を確認するために、アプリケーションを終了して再起動します。
メモ

アプリケーションの構築中や実行中、Xcode に次の警告が表示されることがあります (ID は異なります)。この警告は無視して構いません。 

[LayoutConstraints] Unable to simultaneously satisfy constraints.
                                                                                                    Probably at least one of the constraints in the following list is one you don't want. 
                                                                                                    Try this: 
                                                                                                        (1) look at each constraint and try to figure out which you don't expect; 
                                                                                                        (2) find the code that added the unwanted constraint or constraints and fix it. 
                                                                                                        (
                                                                                                            "<NSLayoutConstraint:0x600003c4ecb0 UIView:0x7fca9dd27330.width == - 16   (active)>"
                                                                                                        )
                                                                                                        Will attempt to recover by breaking constraint 
                                                                                                            <NSLayoutConstraint:0x600003c4ecb0 UIView:0x7fca9dd27330.width == - 16   (active)>

詳細は、スタックオーバーフローに関するディスカッションを参照してください。

次に、このボタンによって実際に取引先責任者レコードが削除されるようにします。 

削除要求の Salesforce への送信

取引先責任者レコードを削除するには、コードをモデルソースファイルに追加します。Classes/Models/ContactDetailModel.swift ファイルを参照すると、ContactDetailModel クラスのコードが最小限であることがわかります。

class ContactDetailModel: ObservableObject{
    @Published var contact: Contact
    init(with contact: Contact){
        self.contact = contact
    }
}

self.contact プロパティの Id メンバーで、現在表示しているレコードを特定できます。

REST 要求を作成するには、RestClient.shared.requestForDelete(withObjectType:objectId:apiVersion:) メソッドをコールします。この要求は、成功した場合にはコール元に何ら重要な情報が返されないという点で、Salesforce API の中でも珍しいものです。アプリケーションが解析やリリースするデータパッケージを受信しないため、この REST 応答はシンプルな完了クロージャで処理できます。 

  1. Xcode プロジェクトエクスプローラで、[Classes (クラス)] > [SwiftUI] > [ContactDetailsModel.swift] を開きます。
  2. ContactDetailModel クラスの既存のコードで、deleteContact という新しい空の func を定義します。この新しいメソッドは Contact 種別の contact 引数を取り、void を返します。
  3. class ContactDetailModel: ObservableObject{
        @Published var contact: Contact
        init(with contact: Contact){
            self.contact = contact
        }
        func deleteContact(contact: Contact) -> Void {
        }
    }
  4. 新しい関数の最上部で、REST API メソッドをコールし、requestRestRequest オブジェクトに定義します。
    func deleteContact(contact: Contact) -> Void {
        let request = RestClient.shared.requestForDelete(withObjectType: "Contact", 
                                                               objectId: contact.Id, 
                                                             apiVersion: nil)
    }
  5. Salesforce に要求を送信するには、RestClient.shared.send(request:_:) 関数をコールします。1 つ目の引数は、先ほど作成したフォーマット済みの要求です。2 つ目の引数は、完了クロージャのスタブです。 
    func deleteContact(contact: Contact) -> Void {
        let request = RestClient.shared.requestForDelete(withObjectType: "Contact", 
                                                               objectId: contact.Id, 
                                                             apiVersion: nil)
        RestClient.shared.send(request: request) {result in
        }
    }
  6. 完了クロージャに .success(_).failure(_) の 2 つのケースを処理する switch ブロックを入力します。この簡単な演習では、それぞれの結果の状況メッセージを Xcode デバッグコンソールに出力します。
  7. func deleteContact(contact: Contact) -> Void {
        let request = RestClient.shared.requestForDelete(withObjectType: "Contact", 
                                                               objectId: contact.Id, 
                                                             apiVersion: nil)
        RestClient.shared.send(request: request) {result in
            switch result {
                case .success(_):
                    print("Contact deleted.")
                case .failure(_):
                    print("Your attempt to delete this contact could not be completed. This is possibly because it is associated with cases or other dependencies.")
            }
        }
    }
これでモデルクラスは完了です。Salesforce レコードを削除する Mobile SDK 機能のコードを完成させました。あと 1 つ残っている作業は、新しい deleteContact(_:) メソッドをコールすることです。このコールをどこで実行するか想像つきますか? アクションシートを思い出してください。
  1. ContactDetailsView.swift ファイルを開きます。
  2. .actionSheet 定義までスクロールします。アクションシートの buttons 配列で、.default ボタン ([OK] ボタン) に空のクロージャがあります。
  3. .default クロージャにコールを追加します。最初に、ローカルの contact 変数を渡す ContactDetailModel のインスタンスを作成し、次にモデルインスタンスで deleteContact(_:) をコールします。
  4. .actionSheet(isPresented: $deleteWarning) {
        ActionSheet(title: Text("Deleting Contact"),
            message: Text("This action deletes this contact in your org."),
            buttons: [
                .cancel {},
                .default(Text("OK")) {
                    let model = ContactDetailModel(with: contact)
                    model.deleteContact(contact: contact) 
                }
            ]
        )
    }
この成果品はまだ本番品質ではありません。顧客が取引先責任者リストビューに戻って取引先責任者が表示されていないことを確認するまで、正常に削除されたかどうかがわかりません。また、取引先責任者がビューから消えていないことに気付いた場合に、何がいけなかったのか見当がつきません。この点を修正可能と思われる 1 つの方法が、RestClient.shared.send(request:_:) ステートメントを Combine パブリッシャーに置き換えて、削除コールの結果を公開することです。続いて、ContactDetailView の ビュー UI またはアラートボックスで結果を顧客に表示します。 

実際に試してみましょう。

iOS シミュレータでコードをビルドして実行する準備ができました。Developer Edition データベース内のデフォルトの取引先責任者を削除しようとすると、エラー応答が返されます。これらのエラーは、Developer Edition 組織にあらかじめパッケージされた各取引先責任者が他のレコードの親であるために発生します。テストの準備をするには、Developer Edition 組織にログインし、他のレコードを所有していないテスト取引先責任者を 1 件以上作成します。

無料で学習を続けましょう!
続けるにはアカウントにサインアップしてください。
サインアップすると次のような機能が利用できるようになります。
  • 各自のキャリア目標に合わせてパーソナライズされたおすすめが表示される
  • ハンズオン Challenge やテストでスキルを練習できる
  • 進捗状況を追跡して上司と共有できる
  • メンターやキャリアチャンスと繋がることができる