[mipass]入力画面はシンプルに

この記事は mipass というパスワード管理アプリを私がどうやって作っていったかを連載記事にしたものです。連載記事の目次はこちら

リスト画面ではこれだけの情報が表示されています。
この情報を入力する画面を別に用意します。

あっさりした画面ですが、色々とデフォルトの UI にはない処理が詰まっています。
そのちょっとした部分を1つ1つ解説していきます。

入力欄は TMP_InputField を使う

入力欄は全て TMP_InputField を使います。
カーソル初期位置が崩れたり、PC で ENTER を押すと次フィールドに……といった実装が難しいので、フルスクラッチの入力コントロールを作ることも考えましたが、最初から頑張りすぎると完成せず投げ出すと判断……。

TMP_InputField だけで物足りない部分は、他の GameObject と合わせます。(後述します)

全ての入力欄は ScrollView に貼り付けています。
これで、画面の大きさを変えても、スクロールさせる事が可能になり、全ての欄にアクセスすることができます。

入力欄削除ボタン

右にある✖ボタン。押すと入力欄を全て消してくれるボタンです。TMP_InputField にはない機能。
PC で入力していると Delete キーを押せばいいだけなので不要ですが、スマフォの入力だと 全選択 > カット って結構めんどくさいですよね。

ただ、ID やパスワード入力欄には✖ボタンをつけませんでした。
これは✖の誤タップにより情報が消えてしまうリスクを避けるためです。
「機能は常にあった方がいい」という考え方は危険です。ケースバイケースで考えましょう。

パスワード自動生成ボタン

パスワードを新しく作る時、2つの面倒が私たちを襲います。

  • 覚えるのが面倒
  • 考えるのが面倒

結果、似たようなパスワードにしてしまい、セキュリティに問題が発生します
「覚えるのが面倒」は、このアプリのコピペで補えるとして、「考えるのが面倒」には、パスワード自動生成が必要です。

なお、このボタンは「パスワード入力欄が空欄の時のみ表示」するようにしました。
既に設定されたパスワードを誤タップで変更してしまわないためですが、特定状況下で「パスワード生成ボタンは??」となる人が一定数出るのかもしれません……そこには目を瞑りました。

こういった迷いを全て解決するデザインもあるにはありますが、テレビのリモコンみたいにボタンがいっぱいで、どれを押していいかわからないというデザインは避けています。

仕事で業務アプリを作る場合、年齢が上になるほどボタン大量で、アイコンよりニホンゴ過多のテレビリモコンデザインが好きなようです。これはこれで、望まれて生産されている部分もあるんですね……。

私はなるべくボタンのないスマートな UI が好みですが、スマートな UI は少し間違うと独りよがりの意味不明な UI になりかねない事を肝に銘じています。

入力文字数に応じて、フォントサイズを変える

文字が枠内に収まるようフォントサイズを自在に変えられるのが TextMeshPro のいいところですが、TMP_InputField 初期状態ではその機能がオフになっているので、きちんと設定します。
(正直この機能は本当に便利)

なお、今回のアプリは「ユーザーは無理な使い方をしないだろう」という性善説に則り、登録文字最大数を特に決めていません。
印刷が必須などの要件がない限り、個人的にはユーザー任せが好みです。

パスワードの強度を視覚化する

パスワードが長く、様々な文字を使っているほど「パスワード強度は強く」なります。
強度が視覚化できるように、パスワードの下に強度バーを表示しました。
強度バーが長く、青に近いほど強度の強いパスワードになります。

強度の求め方は色々あると思いますが、目安なので簡単なロジックで十分だと思います。例えばこんなプログラムで点数をつけています。

    /// <summary>
    /// パスワード強度を計測
    /// </summary>
    /// <param name="val">パスワード</param>
    /// <returns>強度 0~100</returns>
    public static int GetPasswordStrong(string val)
    {
        string[] groups =
        {
            "0123456789",
            "abcefghjkmnopqrstvwxyz",
            "ABCDEFGHIJKMNPQRSTVWXYZ",
            "@!#$%&'-=^+*",
        };

        int type  = -1;
        int score = 0;

        int passwordLevel = 0;

        // パスワード強度ポイントを計算する
        //
        // 前のグループと違う文字 .. 英数5点、英大文字記号、6点
        // 同じグループが連続 .. 前回からー1点
        var used = new HashSet<string>();

        for (int i = 0; i < val.Length; i++)
        {
            string v = val[i].ToString();

            if (used.Contains(v) == true)
            {
                // 既に使った文字は1点
                passwordLevel += 1;
            }
            else
            {
                used.Add(v);

                for (int grp = 0; grp < groups.Length; grp++)
                {
                    if (groups[grp].IndexOf(v) >= 0)
                    {
                        if (type != grp)
                        {
                            type  = grp;
                            // 前回と違うグループは点数が5点、記号なら7点
                            score = grp == 3 ? 7 : 5;
                        }
                        else
                        {
                            // 同じグループが続くと、点数を減らしていく
                            if (--score < 1)
                            {
                                score = 1;
                            }
                        }

                        passwordLevel += score;
                    }
                }
            }
        }

        // 60 点以上を満点とする
        if (passwordLevel > 60)
        {
            passwordLevel = 60;
        }

        return passwordLevel * 100 / 60;
    }

数字、英小文字、英大文字、記号を同じグループとして、「なるべく一つ前の文字と別のグループを使った方が点数が高くなる」「以前使われた文字は点数を下げる」というものです。

なんちゃって判定ではありますが、そこそこパスワード強度を正しく評価してくれると思います。

パスワードの項目表示数の増減を行う

パスワードは最低1つ、最大4つまで指定できるようにしました。
(それ以上の情報は、メモ欄に記載するという運用方針です)

計5つの欄を最初から表示しっぱなしでもよかったんですが、ちょっと実装してみたかったのでボタンを押すと増減、増減に合わせてメモ欄のポジションもスクロールするようにしました。

この辺りに適したコンポーネントあるんでしょうか……? 知らないので、ゴリゴリコーディングです。
コルーチンでチャチャッと動かしています。

        float time = 0;

        float y_add    = SubAdd.GetY();
        float y_remove = SubRemove.GetY();
        float y_line   = MemoLine.GetY();

        while (time < 1)
        {
            yield return null;

            time += Time.deltaTime / 0.15f;
            if (time > 1)
            {
                time = 1;
            }

            SubAdd.SetY(y_add + (y - y_add) * time);
            SubRemove.SetY(y_remove + (y - y_remove) * time);

            float yl = y_line + ((y - 125) - y_line) * time;

            MemoLine.SetY(yl);
        }

なお、これに合わせてリスト画面のボタン数も変化します。
HorizontalLayoutGroup がとてもいい仕事をしてくれます。

削除ボタン

今回のアプリは、項目をまとめて消すという要件が発生しにくいです。
むしろ間違って消すと大損害ですので、削除の手続きは少し面倒にしておこうと考えました。

リスト一覧 > 入力画面に移動 > Remove > はい、いいえダイアログ

消すためにはこれだけの手順を踏み、Remove ボタンは押しづらい画面右上にあるので、間違いは起きにくいでしょう。
反面、消し方がわからないという意見が予想されますが、それを差し引いても「少しわかりづらく」する事が使っている人(データ)を護ることになると考えます。

こういうちょっとした部分の使い勝手は「何が正解か」とても悩みます。
出来るだけ多くのアプリを肌で感じて、自分の中に「いいデザイン」を積み重ねていきましょう。

ユーザーが「本当は」何を望み、「本当は」どうしたいのかを的確に言ってくれることは、まずありません。

「動かない ★1」は言ってくれますが、どういう状況で、なぜ動かなかったのかなんて報告はありません。
その後 OS 再起動で動いたけど「動かない ★1」のままでいいやメンドくさい。そういうユーザーだっています。

この絵の左上と右下の誤解は、言い得て真実です
語られない行間を上手に推察するのはとても難しいことです。

今回のアプリは「自分でも使う」つもりなので、なにを望まれているか推察しやすいのがいいですね。
実際「パスワード自動入力」なんかは使ってみて絶対必要だと感じ、つけたした機能です。

番外:タップしたらエフェクトを出す

スマフォゲームだと結構メジャーな演出ですよね。(なぜかはわかりませんが)
「フリーズしていない、反応してるんだよ」という事がわかりやすいので、表示しておいて損はないと思います。

using UnityEngine;
using UnityEngine.UI;

public class ClickEffect : MonoBehaviour
{
    Image        ClickImage;
    SimpleUIEase ClickEase;

    /// <summary>
    /// awake
    /// </summary>
    void Awake()
    {
        ClickImage = GetComponent<Image>();
        ClickEase  = GetComponent<SimpleUIEase>();

        ClickEase.SetValue(0);
    }

    /// <summary>
    /// update
    /// </summary>
    void Update()
    {
        if (ClickImage == null)
        {
            return;
        }

        if (Padd.GetKeyDown(ePad.Touch1) == true)
        {
            var tvector = Padd.GetTouchPos(0);

            var mousePosition = new Vector3(tvector.Position.x, tvector.Position.y, 0);
            var canvas     = this.GetComponentInParent<Canvas>();
            var canvasRect = canvas.GetComponent<RectTransform>();

            Vector2 localpoint;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, mousePosition, canvas.worldCamera, out localpoint);
            ClickImage.GetComponent<RectTransform>().anchoredPosition = localpoint;
            ClickEase.SetValue(1);
            ClickEase.Hide();
        }
    }
}

このコードを UI/Image の GameObject に貼り付けるだけですが、ちょっと自作ライブラリも混じってしまってます。

Padd(PadInput)

Padd.GetKeyDown や Padd.GetTouchPos は PadInput に同梱している Padd を GameObject に貼り付け、使用します。GameObject の1コンポーネントですが、いちいちコンポーネントを取得する必要なく、シングルトンに使用できます。

Input や InputSystem に置き換えてもいいでしょう。

SimpleUIEase

ポワンと光の玉が広がりながら消えるアニメーションは SimpleUIEase を使用しています。
単純なアニメーションであればかなり便利です。

開発経験者はこのように、やりたいことを簡単に出来るライブラリを自作して持っていることが多いです。
最初は、誰かが作ったものを使うところから始めましょう。
そのうち「あれしたいけど出来ない」「これしようとすると面倒」となっていき、自作への道が広がっていきます。

いざ自作すると、ありとあらゆる問題を想定するようになって、プログラムの実力が物凄い上がるよ!

返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA