データ項目の編集機能

データ一覧画面での編集機能は、データ項目の編集と、Kii Cloud へのアクセスの 2 つが大きな要素となります。

  • データ項目の編集 では、iOS SDK の API を使って画面遷移を行い、データ項目を入力します。Kii Cloud SDK 特有の機能は使用しないため、読み飛ばしても問題ありません。

  • Kii Cloud へのアクセス では、データ編集画面で取得したデータを元に Kii Cloud SDK の API を呼び出して、データを書き換えます。

ソースコードは以下のとおりです。

Swift

Objective-C

データ項目の編集

データ一覧画面では、収支のデータを編集できます。ナビゲーションバーの "Add" ボタンのタップではデータの追加を、一覧項目のタップではデータの更新と削除を実行できます。

以下はデータ項目の編集を行うためのストーリーボードです。

データ一覧画面からデータ編集画面への切り替え手段として、"addItem" セグエと "editItem" セグエが設定されています。"addItem" セグエは、ナビゲーションバーの "Add" ボタンに設定されています。"editItem" セグエはプログラムから使用します。

また、データ編集画面からの逆セグエ(Unwind Segue)として "doneEditItem" セグエが設定されています。

一覧画面(BalanceListViewController)と編集画面(EditItemViewController)との間では、項目の初期値や編集結果の受け渡しが必要です。この処理は、iOS アプリの画面遷移でよく使われる以下の手法に基づいて実装します。

  • 一覧画面から編集画面へのデータは、セグエの実行準備に使われる prepare(for:sender:) メソッドで、遷移先画面のインスタンス変数にセットします。

  • 編集画面から一覧画面へのデータは、編集画面のクラスに実装したプロトコル(DoneEditDelegate)を使って渡します。これにより、遷移先の画面から遷移元の画面への直接的な依存関係を持たずに結果を返せるため、クラス間の結合がよりシンプルになります。

図中の :num1::num4: の処理は以下のとおりです。

  1. BalanceListViewController クラスで "Add" ボタンがタップされたときは "addItem" セグエが実行されます。また、一覧の項目がタップされたときは、tableView(_:didSelectRowAt:) メソッドが呼び出され、その処理で "editItem" セグエが実行されます。セグエの実行により、prepare(for:sender:) メソッドが呼び出されます。

  2. prepare(for:sender:) メソッドで、編集画面の初期値をセットします。"addItem" セグエのときはデータ編集画面の初期値として空の値を、"editItem" セグエのときは初期値としてパラメーターの KiiObject に設定されている値を設定します。設定する項目は以下のとおりです。

    変数 内容
    mode "Add" ボタンによる追加操作(.Add)、一覧項目のタップによる編集操作(.Edit)の種別
    objectId 編集対象の KiiObject の ID、追加操作のときは nil
    name 収支の名目
    type エントリーのタイプ(収入であれば 1、支出であれば 2)
    amount 収支の額(画面表示された値の 100 倍、セント単位)
    delegate BalanceListViewController クラスの DoneEditDelegate プロトコル
  3. データ編集画面では、"Save" ボタンと "Delete" ボタンにアクションが設定されています。ユーザー操作に応じて、対応するアクションハンドラーが呼び出されます。

  4. 各アクションハンドラーでは Kii Cloud 上のデータを書き換えた後、DoneEditDelegate プロトコルのメソッドを呼び出して、編集結果を BalanceListViewController クラスの KiiObject 配列に反映します。

Kii Cloud へのアクセス

データ編集画面では Kii Cloud SDK の機能を使って KiiObject の追加、更新、削除を行います。"Save" ボタンでは KiiObject の追加または更新を、"Delete" ボタンでは KiiObject の削除をそれぞれ行います。

KiiObject の追加と更新

KiiObject の追加処理では、カレントユーザーのスコープの Bucket に KiiObject を作成します。KiiObject の作成方法は、Hello Kii の場合と同様です。

KiiObject の更新処理も追加処理と基本的に同じですが、保存の際に既存の KiiObject の ID を指定する点が異なります。

今回は、KiiObject のキーと値のペアとして、以下の内容を作成します。

  • name:収支の名目
  • type:エントリーのタイプ(収入であれば 1、支出であれば 2)
  • amount:収支の額(画面表示された値の 100 倍、セント単位)

これ以外にも、KiiObject が Kii Cloud に保存されるタイミングで、作成日時 _created などの既定フィールドが自動的に作成されます。

以下に作成処理のコードを示します。本質的ではない部分は省略しています。

  • @IBAction func saveClicked(_ sender: Any) {
      // Read input values.
      let amount = Int(Double(self.amountText.text!)! * 100)
      var name = self.nameText.text
      let type = self.type
    
      // Create a KiiObject instance.
      let bucket = KiiUser.current()?.bucket(withName: BalanceItem.Bucket)
      var object: KiiObject
      if self.mode == .Add {
        object = bucket!.createObject()
      } else {
        object = bucket!.createObject(withID: self.objectId!)
      }
      object.setObject(NSNumber(value: amount), forKey: BalanceItem.FieldAmount)
      object.setObject(name, forKey: BalanceItem.FieldName)
      object.setObject(NSNumber(value: type!.rawValue), forKey: BalanceItem.FieldType)
    
      // Call the Kii Cloud API for saving the KiiObject on Kii Cloud.
      object.save { (object, error) in
        if error != nil {
          let alert = KiiAlert.create(title: "Error", message: description)
          self.present(alert, animated: true, completion: nil)
          return
        }
    
        // Return to the data listing screen.
        self.closeKeyboard()
        if self.mode == EditItemViewMode.Add {
          self.doneEditDelegate.addItem(object: object);
        } else {
          self.doneEditDelegate.updateItem(object: object);
        }
        self.performSegue(withIdentifier: "doneEditItem", sender: nil)
      }
    }
  • - (IBAction)saveClicked:(id)sender {
      // Read input values.
      double amount = (int)([self.amountText.text doubleValue] * 100);
      NSString *name = self.nameText.text;
      int type = self.type;
    
      // Create a KiiObject instance.
      KiiBucket *bucket = [[KiiUser currentUser] bucketWithName:BalanceItemBucket];
      KiiObject *object;
      if (self.mode == EditItemViewModeAdd) {
        object = [bucket createObject];
      } else {
        object = [bucket createObjectWithID:self.objectId];
      }
      [object setObject:[NSNumber numberWithInt:amount] forKey:BalanceItemFieldAmount];
      [object setObject:name forKey:BalanceItemFieldName];
      [object setObject:[NSNumber numberWithInt:type] forKey:BalanceItemFieldType];
    
      // Call the Kii Cloud API for saving the KiiObject on Kii Cloud.
      [object saveWithBlock:^(KiiObject *object, NSError *error) {
        if (error != nil) {
          UIAlertController *alert = [KiiAlert createWithTitle:@"Error" andMessage:error.description];
          [self presentViewController:alert animated:YES completion:nil];
          return;
        }
    
        // Return to the data listing screen.
        [self closeKeyboard];
        if (self.mode == EditItemViewModeAdd) {
          [self.doneEditDelegate addItem:object];
        } else {
          [self.doneEditDelegate updateItem:object];
        }
        [self performSegueWithIdentifier:@"doneEditItem" sender:nil];
      }];
    }

self.mode.Add のときは追加処理、.Edit のときは編集処理(更新または削除)を表します。

Kii Balance で KiiObject の追加を行う際は、Kii Cloud 側で KiiObject の ID を自動発行するため、KiiBucketcreateObject() メソッドでは ID を指定せずに処理を行います。KiiObject の更新を行う際は、メソッドの引数に更新対象の KiiObject の ID(self.objectId)を指定して処理を行います。

save() メソッドを呼び出して保存が成功したときは、DoneEditDelegate プロトコルのメソッドを呼び出して BalanceListViewController クラスで管理している KiiObject の配列を更新します。

KiiObject の更新

ここで、save() メソッドを実行する KiiObject のインスタンスは、一覧表示のために BalanceListViewController で管理しているインスタンスとは異なりますが、更新処理は正しく実行されます。更新処理の際、REST API の URL を構成する ID や対象 Bucket の文字列が同じであれば、Kii Cloud 上の KiiObject を正しく更新することができます。

Kii Cloud での更新が成功すると、DoneEditDelegate プロトコルの updateItem() メソッドによって、BalanceListViewControllerKiiObject インスタンスを新しいものに差し替えます。

なお、BalanceListViewController に格納されている KiiObject インスタンスを取得し、そのフィールドを書き換えてから save() メソッドを呼び出す実装でも問題ありません。ただし、この実装では、エラー発生時にクライアント側の KiiObject を巻き戻す処理(保存しておいた変更前の値に戻す処理など)が必要になります。

更新機能の設計

Kii Cloud での更新処理には、以下の 3 通りがあります。今回は、部分アップデート(更新チェックなし)の方式を実行しています。

  • フルアップデート(更新チェックなし)

    クライアントから送信したキーと値のペアで、サーバーの KiiObject を完全に置き換える更新です。それまでにサーバーに保存されていた値は残りません。

    更新の際、他のクライアントから書き換えられたかどうかをチェックしません。

  • 部分アップデート(更新チェックなし)

    クライアントから送信したキーと値のペアと、サーバーのキーと値のペアがマージされる更新です。結果として、サーバーに保存されていたキーのうち、クライアントが送信していないものは存在し続けます。

    更新の際、他のクライアントから書き換えられたかどうかをチェックしません。

  • フルアップデート(更新チェックあり)

    クライアントから送信したキーと値のペアで、サーバーの KiiObject を完全に置き換える更新です。それまでにサーバーに保存されていた値は残りません。

    更新の際、前回その KiiObject がダウンロードされてから更新されるまでの間に、他のクライアントが KiiObject を更新しているとエラーになります。

これらの動作イメージは、KiiObject の更新 で詳細を説明しています。

Kii Balance では、クライアントから送信する KiiObject のキーのセットは、サーバー上のキーと同じであるため、フルアップデートと部分アップデートのどちらを選んでも問題ありません。

また、今回の更新対象はユーザースコープであるため、上書きによる事故は起きないものとして更新チェックなしとしています。もし、グループスコープのように複数のユーザーが同時に書き込むような場合には、更新チェックありを採用した方が、より安全といえます。

実際のモバイルアプリを設計する際は、差分更新かどうか、同時アクセスが発生するかどうかなどの条件を考慮して更新方法を検討してください。

KiiObject の削除

KiiObject の削除処理では、指定された ID の KiiObject を削除します。

以下に削除処理を示します。本質的ではない部分は省略しています。

  • func deleteItem() {
      // Create a KiiObject instance with its ID.
      let bucket = KiiUser.current()?.bucket(withName: BalanceItem.Bucket)
      let object = bucket!.createObject(withID: self.objectId!)
    
      // Call the Kii Cloud API for deleting the KiiObject on Kii Cloud.
      object.delete { (object, error) in
        if error != nil {
          let description = (error! as NSError).userInfo["description"] as! String
          let alert = KiiAlert.create(title: "Error", message: description)
          self.present(alert, animated: true, completion: nil)
          return
        }
    
        // Return to the data listing screen.
        self.doneEditDelegate.deleteItem(object: object);
        self.performSegue(withIdentifier: "doneEditItem", sender: nil)
      }
    }
  • - (void) deleteItem {
      // Create a KiiObject instance with its ID.
      KiiBucket *bucket = [[KiiUser currentUser] bucketWithName:BalanceItemBucket];
      KiiObject *object = [bucket createObjectWithID:self.objectId];
    
      // Call the Kii Cloud API for deleting the KiiObject on Kii Cloud.
      [object deleteWithBlock:^(KiiObject *object, NSError *error) {
        if (error != nil) {
          UIAlertController *alert = [KiiAlert createWithTitle:@"Error" andMessage:error.description];
          [self presentViewController:alert animated:YES completion:nil];
          return;
        }
    
        // Return to the data listing screen.
        [self.doneEditDelegate deleteItem:object];
        [self performSegueWithIdentifier:@"doneEditItem" sender:nil];
      }];
    }

削除処理も更新の場合と同様です。ID を指定して削除用の KiiObject インスタンスを新しく生成し、delete(_:) メソッドによって Kii Cloud 上の KiiObject を削除します。

削除の完了後、DoneEditDelegate プロトコルを使って BalanceListViewController クラスが持つ KiiObject の配列から KiiObject を削除します。


iOS のチュートリアルは以上で完了です。さらに解析を進める場合は、クラス構成 の情報をヒントに、ソースコードを読み解いてください。


次は...

最後に、より実用的なモバイルアプリを設計するため、データ設計のヒントや実装上の注意点を説明します。

アプリケーション設計の最適化 に移動してください。