モダリティを考慮したiOSアプリのナビゲーションの再設計

こんにちは。メルペイのiOSエンジニアの@kenmazです。
この記事は、Merpay Advent Calendar 2023 の19日目の記事です。

概要

iOSアプリ開発において、お客さまにより良い体験を提供する上でナビゲーションの設計は非常に重要なトピックです。特にメルペイのように「決済」「申し込み」「登録」といった自己完結型のタスクを提供する画面が多いアプリでは、iOSのモーダル表示を活用した設計手法である「モダリティ」を意識することが Apple Human Interface Guideline において推奨されています。これにより、お客さまを迷わせることのない使いやすいアプリを構築でき、またコードの保守性も向上します。

本記事では、メルペイiOSチームが既存機能のリライトプロジェクトを進める中で発見した既存の画面設計の問題点を、モダリティの設計手法に基づいて解決した事例をご紹介します。

背景

メルペイでは現在、メルカリで採用しているSwiftUIベースのアーキテクチャと最新のデザインシステムライブラリを使って、メルペイが提供する全ての画面を書き換えるプロジェクトを進めています。プロジェクト自体の詳細については先日開催されたMerpay & Mercoin Tech Fest 2023での発表の書き起こし記事をご覧ください。

このリライトプロジェクトでは、単にコードを書き換えるだけではなく、同時に既存の機能の見直しや、設計上の問題点なども可能な限り同時に改善しながら進めています。その中で見つかったのが、今回のテーマであるナビゲーションの設計上の問題です。

メルペイのUIとモダリティ

冒頭でも述べた通り、メルペイでは「決済」「チャージ」「登録」「申し込み」といったような自己完結型のタスクを提供する機能が多いのが特徴です。対照的に、メルカリでは商品の検索や閲覧など「情報探索」が体験の中心にあり、そこに「購入」「出品」といった自己完結型タスクが付随する構造になっています。

WWDC2022の Explore navigation design for iOS というビデオでは、iOSでは自己完結型のタスクを提供する画面は「モーダル表示」の使用を推奨しています。モーダル表示とは、現在表示しているコンテンツやタブバーなどを意図的に覆い隠すように画面下からせり上がって画面を表示する方法のことです。これにより、元々表示していたコンテンツの情報階層を一時的に切り離し、特定のタスクに焦点を絞ることで、お客さまに「今自分が何をやっているか」をわかりやすく伝えることができます。このようなアプリの設計手法のことを「モダリティ」と呼びます。

また上記ビデオでは、モーダルで表示するにふさわしい自己完結型タスクとして、

  1. イベントの作成やリマインダーの設定などのシンプルなタスク
  2. 複雑なステップを伴うマルチステップのタスク
  3. 動画の再生などのフルスクリーンコンテンツの表示

の3種類が挙げられています。

メルペイはまさに上記1および2の機能を多く提供しており、そのような機能にはモーダル表示を適用するのが好ましいことがわかります。

課題事例:銀行口座接続

さて、メルペイのリライトプロジェクトを進める中で、モダリティの設計手法に反している画面がいくつか見つかりました。その一つが「銀行口座接続」機能です。ここからは既存の銀行口座接続機能のナビゲーション設計の問題点とその解決策について紹介します。

銀行口座接続機能とは、お客さまの銀行口座をメルペイのアカウントに登録するための機能です。銀行口座を登録することで、メルペイでのお支払いに使える残高をお客さまの銀行口座からチャージできます。

銀行口座機能はメルカリアプリのさまざまな箇所から呼び出されます。例として、残高チャージ画面から銀行口座接続機能を呼び出すナビゲーション(改善前のもの)を示します。

銀行口座接続フロー(改善前)
銀行口座接続フロー(改善前)

上図は、銀行口座が一つも登録されていない状態でメルペイ残高にチャージしようとする際のナビゲーションを示しています。大まかな流れは以下の通りです(説明を簡単にするため、いくつかの画面は省略しています)

  1. 支払い画面でチャージボタンをタップすると、チャージ画面がモーダルで表示
  2. チャージ画面でチャージ方法を選択すると、チャージ方法画面がプッシュ遷移で表示
  3. 「お支払い用銀行口座を登録する」をタップすると、モーダル画面が閉じ、支払い画面に戻る
  4. 銀行口座接続のイントロダクション画面がプッシュ遷移で表示
  5. 「次に進む」ボタンをタップすると、銀行の選択画面がプッシュ遷移で表示
  6. 接続したい銀行を選択すると、口座情報の入力画面がモーダル表示
  7. 口座情報を入力し「銀行サイトへ」ボタンをタップすると、各銀行のwebサイトにアクセスし、認証が完了したら登録完了画面にプッシュ遷移
  8. 登録完了画面の「OK」ボタンをタップするとモーダル画面が閉じ、支払い画面に戻る

一見何の問題もないように見えますが、いくつかの課題が存在します。それらの課題を解決した改善後のフローを以下に示します。

銀行口座接続フロー(改善後)
銀行口座接続フロー(改善後)

どのような課題があり、どのように解決したのかを詳しく見ていきましょう。

課題

課題1:銀行口座接続フローの一部画面が非モーダルで表示されている

上述の通り、銀行口座接続のような「複雑なステップを伴うマルチステップの自己完結型タスク」はモーダル表示することが推奨されています。しかし上のナビゲーション図を見ると「イントロダクション」画面と「銀行の選択」画面はモーダル表示ではなく、支払い画面からプッシュ遷移で表示されています。

先に述べた通り、モーダル表示のメリットのひとつは「タブバーなどを意図的に覆い隠すように画面下からせり上がって画面を表示」することにあります。あえてタブバーを隠すことによって「いまは銀行口座接続の作業が進行中ですよ」ということを表現し、現在のタスクへの集中をお客さまに促すことができます。

しかし「イントロダクション」画面や「銀行の選択」画面はモーダル表示ではないので、下部のタブバーは表示されたままで、操作することも可能です。銀行口座接続の処理中に、誤ってタブバーを操作してしまい、意図せずタスクから離脱させてしまう危険性もあります。

理想的には、これら二つの画面を含め銀行口座接続タスクの画面全体(水色の枠で囲まれた部分)はモーダル表示にすべきでしょう。

課題2:残高チャージの中断

今回示した例は、銀行口座が未登録の状態で残高チャージを行う際のナビゲーションを示しています。つまり本来行いたかったタスクは「残高チャージ」なのですが、銀行口座が未登録だったため、まずサブタスクとして「銀行口座接続」タスクに誘導している状況です。

理想的にはサブタスクである「銀行口座接続」タスクが完了したら、本来のタスクである「残高チャージ」タスクに制御を戻したいところですが、実際はそうはなっていません。

現状の銀行口座接続フローはモーダル表示されることを想定しておらず、銀行口座接続フローを表示する際は、まず全てのモーダルを閉じた後に非モーダルとして表示することを前提として設計されてしまっています。そのため、チャージ画面が閉じられてしまい、本来の目的である「残高チャージ」タスクが中断されてしまっているのです。

理想的には、チャージ方法画面で「お支払い用銀行口座を登録する」をタップした際は、チャージ画面を閉じるのではなく、表示したままにしておくべきです。その上にさらに銀行口座接続フローをモーダル表示し、接続が完了したら単にモーダルを閉じて元のチャージ方法画面に制御を戻せばよいのです。「残高チャージ」タスクを中断すべきではありません。

課題3:コードの再利用性

現状のナビゲーションの設計はコードにも問題を引き起こします。メルペイiOSでは銀行口座接続フローのようなアプリ内のさまざまな箇所から呼び出される画面に対して、以下のようなインタフェースを用意しています。

public enum MerpayScene {
    case connectBank(completion: (Result) -> Void, ..)
    case ...
}

public protocol MerpaySceneRouterProtocol {
    func viewController(scene: MerpayScene) -> UIViewController
}

// Caller
let vc = sceneRouter.viewController(scene: .connectBank(...))
navigationController.pushViewController(vc, animated: true)

各画面は MerpayScene のenum値として定義されており、それを MerpaySceneRouter に渡すことで対応するViewControllerを取得できます。上記例では、MerpayScene.connectBankを指定することで、銀行口座接続フローのエントリーポイントとなる画面のViewControllerを取得しています。

ただし、このように取得したViewControllerを pushViewController(_:animated:) で遷移させると、銀行口座接続のようなマルチステップで構成されるタスクの場合、そのタスクが完了した後の処理の実装が面倒になるという問題があります。

タスクがモーダルとして表示されるのであれば、以下のように呼び出し側は単に present(_:animated:completion:) で対象画面をモーダル表示し、タスクが完了したら呼び出された側で dismiss(animated:completion:) を呼べば、呼び出し元の画面にスムーズに戻ることができます。また、completion引数を指定することで、タスクの実行結果に応じて呼び出し元で処理を分岐させる、といったことも容易に実現できます。

let vc = sceneRouter.viewController(
    scene: MerpayScene.connectBank(
       completion: { success in
          if success {
             ...
          } else {
             ...
          }
       }
    )
)
present(vc, animated: true)

一方、タスクをプッシュ遷移で表示している場合は、やや制御が難しくなります。モーダルのように dismiss(animated:completion:) を呼び出すだけ、とはいかずに、たとえば呼び出し元のViewControllerをメモリに保持しておき、popToViewController(_:animated:) で呼び出し元の画面に戻すなど、やや特殊な実装が必要になる場合があります。

またタスクの実行結果に応じて呼び出し元でなんらかの処理を行いたい場合、 dismiss(animated:completion:) とは違って、popToViewController(_:animated:)popViewController(animated:) には、呼び出し元の画面への遷移が完了したことをフックするための completion 引数などは用意されていないので、呼び出し元の viewWillAppear に追加の処理を仕込んで検知する、といったような余計なハックが必要になることもあります。

銀行口座接続のような自己完結型のタスクは素直にモーダル表示することを前提とし、呼び出す側としては単に present(_:animated:completion:) で表示、タスクが完了したら dismiss(animated:completion:) で呼び出し元に制御が戻ってくる設計にすることで、理解しやすく再利用しやすいコードを保つことができます。

モダリティを考慮した再設計

これらの課題を解決する方法は、銀行口座接続フロー全体をモーダル表示を前提としたものに再設計することです。再設計を行い改善したナビゲーションは、先に示した 銀行口座接続フロー(改善後) の通りです。

上図の通り、改善後のナビゲーションでは銀行口座接続フロー全体がモーダル表示となっていることがわかります。銀行口座の登録が完了したらチャージ方法画面に制御が戻ってくるので、「残高チャージ」タスクが中断されることはありません。あとは登録した口座を選択して、残高チャージを実行するだけです。非常にシームレスな体験を実現できました。

注)上記改善は2024年初旬にリリース予定です

余談:モーダル on モーダル

ところで、冒頭で紹介したWWDCのビデオでは「モーダルの上に表示するモーダルは乱雑で 複雑に感じるため、制限すべし」といった説明がありました。上記の改善後のナビゲーションはまさに「残高チャージ」モーダルの上に「銀行口座接続フロー」モーダルを表示している状態にあたります。このような設計は避けるべきなのでしょうか?

しかし、同ビデオの中ではさらに「サブビューの一貫性と 集中力を高めるために複数のモダリティタスクが必要な場合もあります」という説明もありました。ビデオ内で例として示されていたのは旅行の行程を編集するモーダルの画面から、iOS標準の写真選択画面をモーダル表示で呼び出すような事例でした。そのようなケースでは全く違和感は感じません。

個人的には、銀行口座接続や写真の選択といった、十分に自己完結的で独立したタスクであれば許容可能であると考えます。プロダクトチーム内で慎重に判断して導入することをお勧めします。

まとめ

以上、メルペイiOSチームで既存機能のリライトプロジェクトを進める中で発見した既存の画面設計の問題点を、モダリティの設計手法に基づいて再検討し、改善した事例をご紹介しました。

なお、私の同僚の @kris も冒頭で紹介したWWDCのビデオからインスピレーションを受けて、メルカードのUIに取り組んでいます。その内容はMerpay Tech Openness Month 2023のブログ記事として公開されているので、興味のある方はそちらも合わせてご参照ください。

メルペイには数多くの機能があり、全ての画面についてリライトプロジェクトが完了するのはもう少し時間がかかりそうです。ただのリファクタリングプロジェクトとして終わらせるのではなく、本記事で紹介したような改善ポイントを見つけ、可能な限り改善し、プロダクト全体の品質向上に貢献できるように、iOSチーム一丸となって改善に取り組んでいきたいと考えています。

明日の記事は@komatsuさんです。引き続きお楽しみください。

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加