[Unity]iosでキーボードから文字入力をする&キャンセル処理

# 概要
環境
`Unity 2019.4.5f1` 
`Xcode 11.6`
`ios 13.5.1`

InputField公式リファレンス
https://docs.unity3d.com/2019.1/Documentation/ScriptReference/UI.InputField.html


# InputField
InputFieldはPCだとキーボードから入力可能だが、スマホへBuildするとスマホのキーボードが出てきて入力が可能になる。

実装は簡単で、Create/UIから選択するだけでいろいろ整えてくれたオブジェクトを出してくれる。
こちらでやるべき部分は下のコード部分。

```c#:InputFieldManager.cs
using UnityEngine.UI;

public class InputFieldManager : MonoBehaviour {

  private InputField inputField;
  public string resultText;   // 入力されたテキストを格納

  void Start(){
    inputField = this.gameObject.GetComponent<InputField>();
    InitInputField ();
  }

  // フィールドの初期化
  private void InitInputField () {
    inputField.text = "";
    inputText = "";
  }

  // OnValueCangeで呼び出す関数
  public void ChangeText(){
    // 入力したテキストをstringに格納する
    resultText = inputField.text;
  }

  // OnEndEditで呼び出す関数
  public void FinishEditText(){
    // 入力が終わった後にどこかに渡すとか
    
    // 入力が終わったので初期化
    InitInputField ();
  }
}
```

スクリプトを書いたら、適当なオブジェクト(InputFieldやInputPanelなど)にアタッチして、インスペクターからOnValueChangedとOnEndEditのそれぞれにどの関数を呼び出すか指定する。

ちなみに呼び出される順番はOnValueChanged(inputField.textが変わるたびに) → OnEndEdit

# キャンセル処理
LINEなどを使ってる時を想像して欲しい。
何かを入力していたが、あれ？と思って一旦中断してキーボードを消し、確認した後で再入力ということがあると思う。

CPと挙動が異なる点として、PCはOnEndEditが呼ばれたりinputField.isFocused == falseの場合でも、InputFieldにあるテキストは消えないが、
iosの場合、InputFieldではキーボードを消してしまうとInputFieldにあるテキストも消えてしまう。

そこで入力をキャンセルした時にInputFieldにあるテキストを保持したい。
テキストの保持は以下のコードでできる。
注意点として、TouchScreenKeyboard.isSupported=true(Unity Editorは=false)環境のみでTouchScreenKeyboard.Statusは動作する。`#if UNITY_IOS`などで囲んでおくといいかも。

```c#:InputFieldManager.cs
bool isCancel = false;

// OnValueCangeで呼び出す関数
public void ChangeText(){
  if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled) {
    // Cancleを押した時
    isCancel = true;
  } else if (inputField.isFocused && !isCancel){
    // 他のところをタップした時
    inputText = inputField.text;
    Debug.Log ("inputText: " + inputText);
   }
}

// OnEndEditで呼び出す関数
public void FinishEditText(){
  if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Done) {
    // 入力完了時何かに渡す

    // フィールドの初期化
    InitInputField ();
    Debug.Log ("Done");
  } else if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled || inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.LostFocus) {
    // 入力キャンセル時もしくは
    inputField.text = inputText;
    isCancel = false;
    Debug.Log ("Canseled");
  } else { 
    // 他の部分をタップした場合
    inputField.text = inputText;
    isCancel = false;
    Debug.Log ("Canseled");
  }
}
```

キーボードとInputField以外の部分をタップすると`inputField.isFocused == false`となり、キーボードが消えてしまう。なぜか、

```
inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.LostFocus 
```
は呼ばれないようなのでFinishEditTextではelseにもキャンセルされたと認識して同じ処理をしている。
また、`inputField.isFocused == false`を利用して他のところがタップされた時はテキストは更新されないようにしている。


他にはまった点としては元々はChangeTextでも`resultText = inputField.text`という処理を行っていたが、入力した後にCancelボタンを押すとなぜか`inputField.text = ""`という挙動になった。どうやらデフォルトっぽい挙動だが、邪魔だったのでChangeTextでキャンセルが押された場合にはフラグを変更させるだけにさせた。

フラグを作った理由としてはCancelを押してキーボードを閉じた場合、`inputField.isFocused == true`となりつつ`inputField.text = ""`が入ってしまうので、他の部分をタップした時と区別するために行っている。


# 位置調整
下からキーボードが出てくるため何もしなければキーボードで見えなくなる部分が出てくる。
今回はゴリ押しでキーボードの高さを調べて、重なってしまうUIの位置を上にずらすことで対応した。

動かすUIは一括りのGameObjectに入れてlocalPositionで変更するのが楽。今回の場合は、InputPanelの上にさらにまとめるGameObjectがあったのでそれを移動させている。

InputFieldのみの移動で大丈夫なら、InputPanelをlocalPosition.y変更すればいい。

Start時に初期位置を保存しておき、`inputField.isFocused`の時一回だけ移動させるようにしてる。
キャンセルか何かでキーボードが消えた時に初期位置を代入してやると元に戻る。

```c#
private void StartInputText () {
  if (inputField.isFocused && isOnceInput) {
    isOnceInput = false;
    // y軸をいい感じの値にする
    parentRect.localPosition += new Vector3 (0, 940f, 0);
  }
}
```

# 最終的なコード

```c#:InputFieldManager.cs
using UnityEngine;
using UnityEngine.UI;

public class InputFieldManager : MonoBehaviour {
  private InputField inputField;
  public string resultText;   // 入力されたテキストを格納

  private RectTransform parentRect;
  private Vector3 defaultParentPos;  // 初期位置
  private bool isOnceInput = true; // 入力時のfooter・bodyの位置移動フラグ
  
  private bool isCancel = false;  // cancelボタンが押されたか


  void Start(){
    inputField = this.gameObject.GetComponent<InputField>();
    parentRect = this.transform.parent.GetComponent<RectTransform> ();
    defaultParentPos = parentRect.localPosition;
    InitInputField ();
  }

  void Update(){
    StartInputText ()
  }

  // 入力開始時
  private void StartInputText () {
    if (inputField.isFocused && isOnceInput) {
      isOnceInput = false;
      // y軸をいい感じの値にする
      parentRect.localPosition += new Vector3 (0, 940f, 0);
    }
  }

  // キーボードによって上にずれたUIの位置を戻す
  public void ResetKeybord () {
    isOnceInput = true;
    parentRect.localPosition = defaultParentPos;
    isCancel = false;
  }

  // フィールドの初期化
  private void InitInputField () {
    inputField.text = "";
    inputText = "";
    ResetKeybord ();
  }

  // OnValueCangeで呼び出す関数
  public void ChangeText(){
    if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled) {
      // Cancleを押した時
      isCancel = true;
    } else if (inputField.isFocused && !isCancel){
      // 他のところをタップした時
      inputText = inputField.text;
      Debug.Log ("inputText: " + inputText);
     }
  }

  // OnEndEditで呼び出す関数
  public void FinishEditText(){
    if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Done) {
      // 入力完了時何かに渡す

      // フィールドの初期化
      InitInputField ();
      Debug.Log ("Done");
    } else if (inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.Canceled || inputField.touchScreenKeyboard.status == TouchScreenKeyboard.Status.LostFocus) {
      // 入力キャンセル時もしくは
      inputField.text = inputText;
      ResetKeybord ();
      Debug.Log ("Canseled");
    } else { 
      // 他の部分をタップした場合
      inputField.text = inputText;
      ResetKeybord ();
      Debug.Log ("Canseled");
    }
  }
}

```