Android / iOS モバイルアプリのテキスト入力文字数上限と文字カウントの設計について

この記事では、モバイルアプリでのテキスト入力において、以下のことについて解説します。

* UTF-8 の文字数を正しくカウントすることは難しい (別記事)
* 入力可能文字数の上限をどのように定義すべきか
* 入力中の文字カウントはどのように表示すべきか

# 前提

* アプリから利用する Web API の文字コードは UTF-8 とします

# 仕様としての文字カウントの難しさ

Unicode の文字列の文字カウントは特殊です。ユーザーにとっては見た目上の文字カウントと一致する書記素クラスタカウントが理想ですが、API の仕様や UI 表示を書記素クラスタカウントに統一するのも現実的ではありません。

| カウント方式 | 説明 |
| --- | --- |
| 書記素クラスタカウント | 見た目上の文字カウント |
| コードポイントカウント (UTF-8 文字数) | Unicode codepoint を 1 文字とカウントする |
| UTF-8 バイト数カウント | UTF-8 でのバイト数をカウントする |

Unicode の文字カウントについて詳しい解説は以下の記事を参照してください。

https://qiita.com/irgaly/items/5decdbf5ed89f9cf2c27

# 入力文字数の上限

入力文字数の上限は Web API とモバイルアプリの両方で合わせて決めることになります。

## コードポイントカウント (UTF-8 文字数)

入力文字数の上限はコードポイントカウントがお勧めです。

* モバイルアプリ、サーバーサイド、データベースいずれも、UTF-8 文字数ならカウントしやすい
* UTF-8 が最大 4 bytes であるため、最大データサイズ (bytes) = 最大文字数 x 4 (bytes) と計算できる

## 書記素クラスタカウント

入力文字数の上限に書記素クラスタカウントはお勧めしません。

* Kotlin, Swift など言語によって書記素クラスタカウントの実装が異なり、クライアントとサーバーとで仕様を合わせられない
* 書記素クラスタはデータサイズの上限が定まらない

## UTF-8 バイト数カウント

入力文字数の上限に UTF-8 バイト数カウントはお勧めしません。

* UTF-8 は 1 bytes ~ 4 bytes のいずれかであるため、上限バイト数を超えたときに最後の文字が表現できないことがある
* 上限となる文字数が曖昧で取り扱いにくい

# アプリ UI での文字カウント表示

Web API の仕様としてコードポイントカウントを採用することを前提とし、アプリの UI の文字カウント表示について確認します。

## 例: Twitter アプリ

例として Twitter Web の二つのテキスト入力欄を確認します。

### ツイート投稿画面

* 「ツイートする」ボタンの隣に文字数ではなくインジケーターで表示
* ツイートの文字カウントは Unicode に依存しない Twitter の独自ルールです

![tweet1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/1d0ca2bd-afed-d555-984e-b471c5d52aa6.png)

* のこり入力可能文字数が 10 以下となると入力可能文字数を表示

![tweet2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/8ab3fd5b-b2d5-73e0-f81a-87bf177af4ac.png)

* 入力可能文字数をオーバーすると、オーバーした文字数を表示

![tweet3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/ee5cb385-025c-cd92-8022-1aea31e148f2.png)

### プロフィール入力画面

* `{入力文字数} / {最大文字数}` が常に表示されています
* Twitter のプロフィール入力欄の文字カウントは Unicode コードポイントカウントです

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17931/a557de72-633e-88a4-7561-18fe787a7edd.png)

## 入力欄の種類

これらの例を踏まえると、テキスト入力欄は 2 種類に分けて考えると良さそうです。

| 種類 | 説明 |
| --- | --- |
| メッセージ入力欄 | Twitter のツイートやチャットアプリのチャットメッセージなど、文字数を意識させたくない入力欄 |
| フォーム入力欄 | プロフィール入力欄やエントリーフォームなど、入力文字数の上限を意識しながら記入する入力欄 |

## メッセージ入力欄

以下のようなものがメッセージ入力欄に該当します。

* Twitter のツイート入力欄
* LINE のメッセージ入力欄
* Discord のメッセージ入力欄
* Slack のメッセージ入力欄

Twitter だけは 140 字制限がありますが、それ以外のチャットアプリでは文字数の上限も文字カウントも表示されません。ユーザーには文字数上限を意識させない UI となっています。

メッセージ入力欄は以下のように UI を設計すると良いでしょう。

* メッセージ入力欄の文字数上限は大きめに定義し、普段使いではユーザーに文字数上限を意識させない
    * LINE のメッセージ入力文字数上限は 1 万文字らしいです。
* 入力文字数を超えて入力されたら、超えた文字数を UI に表示する

Unicode コードポイント数をカウントとして採用している場合、入力可能文字数を超えた文字数を表示するとそのカウントは書記素クラスタカウントとずれてしまうため、ユーザーは違和感を感じてしまいます。

ただし、入力可能文字数を大きめに定義していれば入力可能文字数を超えてしまうシチュエーションは稀です。ユーザーの感じる違和感は最小化されており許容範囲であると考えます。

## フォーム入力欄

プロフィールやエントリーフォームなどは、ユーザーも入力可能文字数の上限を意識しながら入力します。こちらは常に文字カウントを表示するべきです。

* フォーム入力欄の文字数上限は、その項目に必要な文字数を上限として定義する
* 入力文字カウントと入力上限文字カウントは常に表示する

フォーム入力欄では常に入力文字数を表示したほうがわかりやすいでしょう。ただし、Unicode の結合文字列や絵文字などを入力すると書記素クラスタカウントと文字カウントがずれてしまい、ユーザーは違和感を感じてしまいます。

この問題への対応としては以下のようなものが考えられます。

### フォーム入力欄に絵文字が多用されないのであれば許容する

文字カウントがずれるのは Unicode 結合文字列であり、異体字セレクタ文字や絵文字の入力時に発生する問題です。

対象のフォームが、異体字セレクタ文字や絵文字を頻繁に入力しないようなものであればあまり気にする必要はありません。ユーザーが違和感を覚える頻度が低いと見做して、文字カウントが書記素クラスタカウントとずれてしまうことを許容しましょう。

### どうしてもユーザーに違和感を感じさせたくない場合は書記素クラスタカウントを検討する

どうしてもユーザーにフォーム入力欄の文字カウントの違和感を感じさせたくないということであれば、UI 上の文字カウントでのみ書記素クラスタカウントを実装することを検討します。

以下の対応となります。

* Web API は UTF-8 文字カウントのままの仕様とし、書記素クラスタカウントでの入力可能文字数よりも大きめの文字数を上限として定義する
* アプリ UI でのみ書記素クラスタカウントの上限を定め、書記素クラスタカウントによる入力文字数と入力可能文字数を表示する
* 入力可能文字数を超えた入力は入力エラーとして扱う
* 入力可能文字数を超えていなくても Web API 側の UTF-8 文字数を超えていれば入力エラーとして扱う
    * この場合はユーザーにエラーの詳細を説明することが難しいです。Web API の UTF-8 文字数上限を可能な限り大きく定義することでこのエラーがほぼ発生しないように調整すべきです

# 参考: Twitter の文字カウント

以下の記事は Twitter のツイートの文字カウントについて詳細に解説されていてわかりやすいです。

https://news.mynavi.jp/article/20210907-1966765/

ただし、Twitter のツイートは Unicode やデータサイズは考慮しておらず Twitter の独自ルールによる文字カウントを採用しています。Web API やアプリの仕様として決めているというよりは、Twitter として文字をどのように認識するかを定めていることになります。あくまで参考程度にとどめておきましょう。