モバイルアプリの操作方法を疑ってみる
Think different.[1]
はじめに
筆者は 2004 年からモバイルアプリの開発に携わってきました。20 年経過しようとしています。その間の変遷を経て、現在のモバイルアプリは、Apple[2] と Google[3] それぞれが提供するモバイル OS(iOS[4] と Android[5])向けに開発しています。モバイルアプリはこれまで、タッチスクリーンを主軸に大きく発展してきました。そして、2023 年 Apple から visionOS[6] が発表されました。空間コンピューティングという言葉で示されるように、visionOS では iOS での固定されていた画面単位という概念を大きく変えることでしょう。この記事では、visionOS が iOS から変えていく具体的な内容について踏み込めていません。変わっていくであろうこれからに備えるために筆者が必要だと感じていることを共有します。
まず、インタフェース[7]に関する筆者の考えと関連書籍を紹介します。その後、既成の操作方法を疑ってみて新しいインタフェースを作ってみます。
対象者
- モバイルアプリ開発に関わる人
- インタフェースに関心のある人
インタフェースとは何か
インタフェースとは何でしょうか?
何かと何かをつなぐ時に、その間を仲介するものをインタフェースと呼んでいます。何かと何かなので、モノとモノ、たとえば、電源プラグとコンセントの関係もインタフェースになります。インタフェースは仲介手続きを定義します。電池を例にすると、電池のソケットに対応していない電池は受け付けません。許可不許可を明確に区別します。プログラミングにおけるインタフェースもこの概念を表現しています。
モバイルアプリにおけるインタフェースは何でしょうか?
スマートフォンとスマートフォンを使う人の間にあるものはすべてモバイルアプリのインタフェースになります。今は主に画面が中心のため、画面に表示されるものを総称して UI と呼んだりします。UI は、User Interface の略称で、人(ユーザー)に対して何かをつなぐインタフェースです。UI は画面に限定されるものではないのですが、画面が主流のため、UI といったら画面を指すことがほとんどです。モバイルアプリの画面は、ユーザーに対するインタフェースになっています。ガラケー[8]の頃は出力しかできなかった画面が、スマートフォン以降搭載されたタッチスクリーンにより、出力と入力の両方の役割を担い、その大きさは年々大きくなっています。画面以外にも、たとえば、次のようにユーザーに対して機能を提供するインタフェースがあります。
- マイクとスピーカーによる音声の入出力
- 位置情報の送受信
- カメラによる映像の入力(顔認証含む)
- 指紋認証
- 電源ボタン
- マナーモードボタン
- 音量調整ボタン
- 振動で着信を伝える
モバイルアプリの画面のインタフェースの特徴として、さまざまな形と意図をもつ UI を自由に変更・配置できます。箇条書きで示したハードウェアに依存する固定化された役割ではなく、さまざまな表現と役割を担うことが可能になっています。UI と呼ぶと画面のレイアウトに限定されてしまっている傾向が強くなっている印象がありますが、UI は画面に限定されないです。
visionOS を見た時に今のインタフェースの主役である画面の役割が変わると思いました。もちろん、iOS アプリの延長として visionOS の空間に再配置可能ですが、折角固定化された画面がなくなるのであれば、変えたいという思いと使命感があります。
これまでのインタフェース
これまでのインタフェースの変遷を知るのに、モバイルアプリは、実にその多くを失ってしまいました。デバイスも OS もアプリも当時を知るのに紙媒体の方が永く残るのを目の当たりにしています。ここでは、これまでのインタフェースを知る手掛かりとなる本を紹介します。
2011 年頃の当時のインタフェースを知るには、同年に出版された『iPhoneアプリ設計の極意 思わずタップしたくなるアプリのデザイン』[9]という本が当時の制約の中でどのようにデザインしていくかに言及しています。スクリーンショットを見ると、今と比べると小さな画面の中で効果的なインタラクティブを追求する過程も見られます。副題が 『思わずタップしたくなるアプリのデザイン』 とあり、タップ中心の文化形成の起点にもなっています。この副題が集約された部分を引用します。
この場合の「デザイン」とは単に見た目だけでなく、アプリの機能、性能、ユーザーインターフェースまで含みます。「タップしたくなってしまう」アプリ、別の言い方をすれば「タップする価値のある」アプリは機能でも見た目でもユーザーを引きつけます。
タップしたくなるアプリの入口としてのアイコンについても詳細に触れています。2022 年に出版された『iOSアプリアイコン図鑑』[10]と合わせて読むと、当時から 10 年以上経過した歴史の変遷も体感でき、アイコンの見方が深まります。
2015 年にはそれまでのモバイルデザインのパターンをまとめた『モバイルデザインパターン ユーザーインタフェースのためのパターン集』[11]が出版されています。現在も配信中のアプリも事例として掲載されています。アップデートにより今ではもう見ることのできない当時のバージョンのものや、配信停止や対応 OS の関係でインストールや起動できなくなってしまったアプリのスクリーンショットを通してインタフェースのパターンを学ぶことができます。この本の序文からインタフェースの学習方法について引用します。
最善の学習方法は視覚的な例に触れることであると考えます。私は物書きであり、言葉が大好きです。望まれるなら、パターンとは何か、どのパターンを選ぶべきか、それぞれのパターンの違いは何かといった点についていくらでも言葉を書き連ねるでしょう。しかし、単にインタフェースをデザインしようとする人(言い換えるなら、1つのツールとしてパターンを学ぼうとおする人)にとって、言葉は明らかに不必要です。パターンを「感じ取る」ことができる程度の説明と、厳選されたさまざまな事例さえあれば、そのパターンを身につけて知識を強固なものにできるはずです。
2018 年『UI GRAPHICS』[12]では、デザイナ視点での知見が詰まっています。副題に 『成功事例と思想から学ぶ、これからのインターフェイスデザインとUX』 とあり、成功事例として当時のアプリのスクリーンショットが見られます。思想にも触れていて、この辺りは副題のとおり、これからのインタフェースを考える上でも有用な概念です。新しいインタフェースを考える上で重要な自己帰属感について触れた一節があるので引用します。
インターフェイス設計が、多様なセンサーをベースに、行為とグラフィック(対象)の関係性というインタラクションを設計することとなると、目的への効率性としての使いやすさの観点だけでなく、この自己帰属感をうまく設計しないことには、効率性以前の問題になってしまう。つまり、身体の延長感が得られず、自分の体験にならず、接することに不快感を覚えることになってしまう。
逆に自己帰属感がうまく設計できれば、身体の延長、一部になるかのような操作感を提供することができる。そして、インターフェイスで重要とされる、そのデバイスやインターフェイスそのものを意識させない透明性が得られる。結果的にユーザーは、目的のタスクやコンテンツに集中することができる。また、そのデバイスと接すること自体が、ユーザーの新しい体験をスムーズに広げる。自己帰属感の低いインタラクションは、いつまでたっても操作の対象としての意識がつきまとい、身体に近寄ってこない。これが不快感の原因である。
当時を振り返ったり、この辺りの書物に触れると iPhone SDK[13] の功績にはデザイナとプログラマの境界線を曖昧にしたというのが多いと感じています。それによって、相互に越境し合ってモバイルデザインが急速に発展していった印象を持っています。
これからのインタフェース
これからのインタフェースを考えるにあたって、2015 年に出版された『さよなら、インタフェース』[14]という本を紹介します。副題が 『脱「画面」の思考法』 とあり、タイトルでのインタフェースは画面を指していると思ってよいです。この本の中で印象的な部分を引用します。
本書の中で、筆者はユーザーインプットではなく、マシンインプットを考えろと言う。ユーザーが何かを入力するのではなく、マシンがさまざまな情報を自動的に取得し、それを元にユーザーの必要なものを提供するのがこれからのインターフェースだと。
『さよなら、インタフェース』の帯には 「時代は、NoUIへ。」 と掲げられています。これを見た時に、次に来るぞと思いました。2015 年でした。当時、Alexa[15] の登場もあって音声操作が主流になる可能性も感じていました。あれから 8 年経過しましたが、NoUI どころかアプリの画面数もユーザー入力も益々増加している印象です。その大きな要因として、モバイルアプリ市場の拡大と大規模開発化してしまった点にあります。モバイルアプリ開発の黎明期は一人で担当することが常でした。2023 年の現在は、チームで開発することが圧倒的に増えました。これからのモバイルアプリの進化のためのひとつの方法として、チームの規模を小さくして、極端には一人でモバイルアプリ開発を進める体制も有効と考えています。その限られたリソースから必然のアイデアとして、NoUI やマシンインプットを考えることが必要不可欠となり、進化を促進させる起爆剤になり得ます。
なお、Apple[16] と Google[17] からもインタフェースのガイドラインは提示されています。画面中心ですが、ユーザーインプットに関するガイドライン[18]も用意されています。
新しいインタフェースを作る
ここからは実演として既成のインタフェースを変えてみましょう。
今日において多くの人がディスプレイをタッチスクロールして情報を見ています。そのタッチスクロールのユーザー操作を取り除く UI の案を紹介します。
ユースケース
たとえば、あなたが電車に乗って、車内がそこそこ混んでいたとしましょう。入り口に留まり続けるのも同調圧力がそうさせません。車内の奥へ進み、右手で吊り革に掴まって立った状態です。徐にスマートフォンを取り出して、X(Twitter)[19] のアイコンをタップします。気になるポストがなくて、左手でもつスマートフォンを親指でスクロールして気になるポストを探しています。
片手でタッチスクロールするのは、大変です。実は、昔のスマートフォンは今よりも小さくて、その操作が今に比べると楽でした。徐々に大きく重くなるスマートフォンに人間の方が訓練されてあまり苦ではないという人もいます。しかし、マシンインプットで考えてみましょう。ユーザーが操作に慣れるのではなく、マシンの方に頑張ってもらう方法はないでしょうか? 片手でタッチスクロールせずにタッチスクロールと同等のマシンインプットを実現する方法はあるでしょうか? たとえば、片手でもつスマートフォンを傾けたら、その傾斜に応じてスクロールしてみたらどうでしょうか?
サンプルアプリ
タッチスクロールをセンサーを利用したスクロールに変えるサンプルアプリを用意しました。
下記 URL からソースコードを入手可能です。お手元の iPhone または Android デバイスにインストールすることで実際に UI の触り心地を確かめてみてください。
コードの解説
傾きの検知を利用できるようにする
iOS
Core Motion[20] を利用して iPhone の傾きを取得できます。次のように import
すれば使えるようになります。
import CoreMotion
Android
SensorManger[21] を利用して Android の傾きを取得できます。Manifest ファイルでの宣言が必要です。
<uses-feature android:name="android.hardware.sensor.accelerometer" />
傾きの検知を開始する
iOS
CMMotionManager
[22] の startAccelerometerUpdates
[23] を呼ぶことで傾きの検知を開始できます。クロージャに傾きが更新された時の処理を書きます。更新の頻度は accelerometerUpdateInterval
[24] で指定できます。
let motionManager = CMMotionManager()
// 更新の頻度を指定できます
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: .main) { [weak self] (data, error) in
// 傾きが更新された時の処理
}
Android
SensorManager
の registerListener
[25] に SensorEventListener
[26] を登録して、傾き更新の通知を受け取ることができるようになります。
val listener = object : SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
override fun onSensorChanged(event: SensorEvent) {
// 傾きが更新された時の処理
}
}
val sensorManager = context.getSystemService(SensorManager::class.java)
val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(listener, accelerometer, SensorManager.SENSOR_DELAY_GAME)
傾きの検知を終了する
iOS
CMMotionManager
の stopAccelerometerUpdates
[27] を呼ぶことで傾き検知を終了できます。deinit
などで呼ぶようにしましょう。
motionManager.stopAccelerometerUpdates()
Android
SensorManager
の unregisterListener
[28] を呼ぶことで傾きの検知を終了できます。onDispose
など呼ぶようにしましょう。
sensorManager.unregisterListener(listener)
ボタンと組み合わせてスクロールを制御する
無制限に傾きでスクロールするのは操作しづらいので、左下にボタンを配置します。ボタンをタップしている間はスクロールするように制御を変えます。ボタンをタッチした時の傾きを基準として、そこから手前に傾けると下に、奥に傾けると上にスクロールさせます。
iOS
UIKit[29] ベースでの実装サンプルになっています。当初 SwiftUI[30] での実装を試していたのですが、実現が難しそうなので採用を見送りました。 ScrollViewReader
[31] を利用すると ScrollViewProxy
を扱えるようになり ScrollView
をコードから操作できるようになっています。公開されている scrollTo
[32] は id
を渡して任意の位置へスクロールする想定になっています。今回は座標ベースの処理となるので採用を見送りました。
motionManager.startAccelerometerUpdates(to: .main) { [weak self] (data, error) in
guard let self = self,
let data = data else { return }
// ボタンをタッチした時の傾きを基準とする
// 初期値を nil にしておくことで、開始時の傾きだけ取得して更新し、
// 終了時に解放されるまでは更新しない
if (baseAccelerationY == nil) {
baseAccelerationY = data.acceleration.y
}
// 基準の軸から今の傾きの差分を求めて、任意の係数で補正します
// 補正する数字を大きくするほど、小さな傾きで大きく動くようになります
let calculatedOffsetY = (baseAccelerationY! - data.acceleration.y) * 300
// 今時点の UITableView の y 軸のオフセットに加算します
tableViewOffsetY += calculatedOffsetY
// スクロール範囲での y 座標の最大値を求めます
let maxY = tableView.contentSize.height - tableView.bounds.height + view.safeAreaInsets.bottom
// y 座標が範囲内に収まるように補正します
if (tableViewOffsetY < 0) { tableViewOffsetY = 0 }
if (maxY < tableViewOffsetY) { tableViewOffsetY = maxY }
// 更新された y 座標を UITableView に反映します
tableView.setContentOffset(CGPoint(x: 0, y: tableViewOffsetY), animated: true)
}
Android
Jetpack Compose[33] では、ボタンをタップしている間を pointerInput
[34] の detectTapGesture
[35] で実現できます。onPress
スコープの中で tryAwaitRelease
[36] するとタップ解除されるまで待つことができます。Jetpack Compose では他にもさまざまなクリック処理[37]が可能です。
Box(modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onPress = {
// タップされた時の処理
// タップ解除されるまで待つ
tryAwaitRelease()
// タップ解除された時の処理
}
)
}
傾きに合わせてスクロールの値を算出します。
val density = LocalDensity.current.density
override fun onSensorChanged(event: SensorEvent) {
// TYPE_ACCELEROMETER を指定すると、
// SensorEvent.values[1] から、 y 軸方向の加速力(重量を含む)を取得できます
//
// https://developer.android.com/guide/topics/sensors/sensors_motion
// ボタンをタッチした時の傾きを基準とする
if (baseY == null) baseY = event.values[1]
// 基準の軸から今の傾きの差分を求めて、任意の係数で補正します
scrollOffset = (event.values[1] - (baseY ?: 0f)) * 100 * density
}
上記で算出した値に応じて UI に反映します。
// 傾きから算出した scrollOffset が変化したらスクロール処理する
LaunchedEffect(scrollOffset) {
scrollState.animateScrollBy(
value = scrollOffset,
animationSpec = tween(durationMillis = 200, easing = LinearEasing),
)
}
おわりに
今回紹介した案は、当たり前になっているけどユーザー操作量が多く、それをなくす、もしくは減らすことはできないかというユースケースに取り組みました。この方法がベストだとかそういうことを言いたいのではなく、当たり前になっている UI も変えることが可能であり、それをプログラマはすぐに試すことができるということを伝えたかったです。
スマートフォンには他にもセンサーが搭載されていて、Apple と Google はさまざまな機能を開放しています。
現代では、チーム開発が主流となっています。UI/UX はデザイナが考えます。スマートフォンの特性を活かして、マシンにユーザーを従わせるのではなく、ユーザーにマシンが従うようにシステムを設計・構築するのはプログラマの役目だと考えています。大規模なチーム開発では、インタフェースを設計・構築する役割分担があり、何かを試行錯誤する機会を得るのは難しいです。
そこでぜひ、小規模で、可能なら一人でモバイルアプリのインタフェースの設計と構築をしてみてほしいです。そうすることで NoUI やマシンインプットの観点で再設計せざるを得ない機会が増えると考えています。そして来るべき visionOS の時代に備えたインタフェースを一緒に模索していきたいです。そのための基礎体力として、既成のモバイルアプリのインタフェースにおいても十分に改良の余地があり、拡張または再構築していく体験が重要になります。もしモバイルアプリを触っていて、操作しづらいなあ、面倒だなと感じることがあれば、そこには新しいインタフェースが必要です。一緒に新しいインタフェースを作りましょう。
ヒント
- スマートフォンに搭載されているセンサーにはどんなものがあるか調べてみよう
- SDK が提供している API にはどんなものがあるか調べてみよう
- 面倒だったのに慣れてしまった操作としてどんなものがあるか思い出してみよう
- もし画面がなかったら(あるいは目が見えない人に)どんな表現で情報を伝えることができるか考えてみよう
YUMEMI.grow
YUMEMI.grow Mobile #8 にて、この内容を元に発表しました。
ゆめみ大技林
ゆめみ大技林 '23 (2)に、この記事を掲載しています。技術書店マーケットで購入が可能です。無料配布になります。
紙版との差分
- 注釈 29 が抜けていて採番し直したため、それ以降の数字にズレがあります https://github.com/ykws/zenn-content/commit/018442e69615fbcf320e29683f94a7d760f42b5a
-
https://ja.wikipedia.org/wiki/Think_different 1997 年の Apple Computer の広告キャンペーンのスローガン。 ↩︎
-
https://developer.apple.com/jp/visionos/ https://developer.apple.com/documentation/visionOS ↩︎
-
インタフェースの表記は、本記事では左記に統一します。書籍の名前や引用はその表記に従うため、表記のぶれがあります。 ↩︎
-
ガラパゴス・ケータイの略称。スマートフォン登場後、スマートフォン登場前の携帯電話と区別するためにそう呼んでいます。フィーチャーフォンと呼ばれることもあります。ガラケーでは、画面にタッチして入力できなくて、物理的なハードキーが付属しており、ボタンで入力していました。 ↩︎
-
JOSH CLARK, Tapworthy DESIGNING GREAT iPHONE APPS, O'REILLY, 2011, [深津貴之 監訳, 武舎広幸・武舎るみ 訳, iPhoneアプリ設計の極意--思わずタップしたくなるアプリのデザイン, オライリー・ジャパン, 2011] ↩︎
-
マイケル・フラルップ, iOSアプリアイコン図鑑, ホビージャパン, 2022 ↩︎
-
Theresa Neil, Mobile Design Pattern Gallery Second Edition UI Patterns for Smartphone Apps, O'REILLY, 2015, [深津貴之 監訳, 牧野聡 訳, オライリー・ジャパン, 2015] ↩︎
-
安藤剛・水野勝仁・萩原俊矢・ドミニク・チェン・菅俊一・鹿野護・有馬トモユキ・渡邊恵太・須齋佑紀/津﨑将氏, 【新版】UI GRAPHICS 成功事例と思想から学ぶ、これからのインターフェイスデザインと UX, BNN, 2018 / 2015 年刊行の同書籍の改訂版として出版されました ↩︎
-
当初は iPad もなく、 iPhone のみをターゲットとしていたため、 Apple から提供される Software Development Kit をそう呼んでいました。 ↩︎
-
Golden Krishna, THE BEST INTERFACE IS NO INTERFACE, BNN, 2015, [武舎広幸 監訳, 武舎るみ 訳, さよなら、インタフェース 脱「画面」の思考法, BNN, 2015] ↩︎
-
https://www.amazon.co.jp/meet-alexa/b?ie=UTF8&node=5485773051 ↩︎
-
https://developer.apple.com/jp/design/human-interface-guidelines/ https://developer.apple.com/jp/design/tips/ https://developer.apple.com/jp/design/human-interface-guidelines/designing-for-visionos visionOS 向けのデザインのガイドもあります。 ↩︎
-
https://m3.material.io/ Material Design is an adaptable system of guidelines, components, and tools that support the best practices of user interface design. と説明があり、ユーザーインタフェース設計のベストプラクティスと銘打たれています。 ↩︎
-
https://developer.apple.com/jp/design/human-interface-guidelines/inputs ↩︎
-
https://about.twitter.com/ja 執筆時点で、ブランド名は X に変わりましたが、ドメイン含めて Twitter の名前が残っている状態です。 ↩︎
-
https://developer.android.com/guide/topics/sensors/sensors_overview ↩︎
-
https://developer.apple.com/documentation/coremotion/cmmotionmanager ↩︎
-
https://developer.apple.com/documentation/coremotion/cmmotionmanager/1616148-startaccelerometerupdates ↩︎
-
https://developer.apple.com/documentation/coremotion/cmmotionmanager/1616135-accelerometerupdateinterval ↩︎
-
https://developer.android.com/reference/android/hardware/SensorManager#registerListener(android.hardware.SensorEventListener, android.hardware.Sensor, int) ↩︎
-
https://developer.android.com/reference/android/hardware/SensorEventListener ↩︎
-
https://developer.apple.com/documentation/coremotion/cmmotionmanager/1616138-stopaccelerometerupdates ↩︎
-
https://developer.android.com/reference/android/hardware/SensorManager#unregisterListener(android.hardware.SensorEventListener) ↩︎
-
https://developer.apple.com/documentation/swiftui/scrollviewreader ↩︎
-
https://developer.apple.com/documentation/swiftui/scrollviewproxy/scrollto(_:anchor:) ↩︎
-
https://developer.android.com/jetpack/compose/touch-input/pointer-input ↩︎
-
https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/package-summary#(androidx.compose.ui.input.pointer.PointerInputScope).detectTapGestures(kotlin.Function1,kotlin.Function1,kotlin.coroutines.SuspendFunction2,kotlin.Function1) ↩︎
-
https://developer.android.com/reference/kotlin/androidx/compose/foundation/gestures/PressGestureScope ↩︎
-
https://star-zero.medium.com/composeの様々なクリック処理-dd66f19992c5 Jetpack Compose のさまざまなクリック処理について紹介されています。 ↩︎
Discussion