Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

toBアプリでLiquid Glassに挑んだ話 ── UITabBarのクラッシュからAppleミートアップでの学びまで

導入

こんにちは。技術本部 Sansan Engineering Unit Mobile Applicationグループの盛重です。2025年4月に新卒で入社し、SansanのiOSアプリ開発に携わっています。

WWDC25でAppleが発表した新しいデザイン言語「Liquid Glass」は、iOS 7のフラットなデザイン以来となる大規模なUI刷新です。

Liquid Glass適用後の撮影画面 ※画像内の名刺はダミーです。

Sansanアプリでは、エンジニア主導で、QA・デザイナー・PdMと連携しながらこの対応に取り組みました。対応の過程ではAppleの「Let's talk Liquid Glass」プログラムを通じたミートアップの機会も得ています。

Liquid Glass対応は、単にビルドして不具合を直すだけでは済みません。Sansanでは長年採用していたタブバーのカスタムレイアウトがクラッシュやHIG非準拠の顕在化を招き、デザインの再設計が必要になりました。また、HIGの理解が必要でありデザイナーに丸投げできない判断も多く、不具合箇所も多岐にわたったことで普段にはない量のPdM・デザイナーとの調整が発生しました。

本記事では、こうした課題に対してSansanがどのように判断し対応を進めたかを、以下の構成でまとめます。

  • Liquid Glassの概要と移行の選択肢
  • プロジェクトの進め方(QA・デザイナー・PdMとの並列実行体制)
  • タブバーデザインの再設計(クラッシュ対応から意思決定まで)
  • Appleミートアップで得たHIGの設計思想に関する知見
  • 無効化フラグを外したことで発生した問題と対応

今後Liquid Glass対応を控えている方の参考になれば幸いです。

Liquid Glassの概要

Liquid Glassとは

www.apple.com

Liquid Glassは、WWDC25で発表されたAppleにおける全プラットフォーム共通の新しいデザイン言語です。Appleはこれを過去最大規模のデザインアップデートと位置づけています。UIの根幹となる素材が刷新されるのはiOS 7のフラットデザイン以来であり、大きな転換点だと考えられます。

最大の特徴は、Lensingと呼ばれる光の表現にあります。従来のマテリアルは光を散乱させていたのに対し、Liquid Glassはリアルタイムに光を屈折・集束させます。Appleはこれを「デジタルメタマテリアル」と呼んでおり、物理世界のガラスの模倣ではなく、デジタルならではの新しい素材として設計されています。この素材はコンテンツそのものではなく、その上に浮かぶナビゲーションやコントロールの層に適用されます。タブバーやツールバーはスクロールや画面遷移に応じて動的に形を変え、UIとコンテンツの間に明確な階層を作ることで、コンテンツへの没入感を高めています。

移行の猶予期間と無効化フラグ

Appleは既存アプリの互換性を考慮し、Liquid Glassを無効化するフラグUIDesignRequiresCompatibility を提供しています。Info.plistに以下の設定を追加することで、Xcode 26でビルドした場合でも、従来(iOS 18以前)のデザインを維持することが可能です。

<key>UIDesignRequiresCompatibility</key>
<true/>

ただし、AppleはWWDC25の Platforms State of the Unionにおいて、 ” このオプションの提供は次のメジャーリリースで終了する予定です ” と発表しています。

Appleは例年、春頃にApp Storeへの提出要件を最新のXcodeおよびSDKへ引き上げる慣習があります。このため、無効化フラグはあくまで一時的な猶予措置です。2027年春以降にアプリをリリース・更新するには次期Xcodeでのビルドが必須となり、実質的にすべてのアプリがLiquid Glassへの完全な対応を迫られることになると考えられます。

Xcode 26で無効化フラグを設定せずにビルドすると、Liquid Glassが自動的に適用されます。その際、UIの表示崩れや、透過処理の競合によるクラッシュなど、アプリによって多種多様な影響が発生する可能性があります。

そのため、iOS 26対応にあたって開発者は以下のいずれかの戦略を選択することになります。

  1. Liquid Glass対応を行う:新デザインに最適化し、フラグなしで動作させる
  2. 無効化フラグを設定する:iOS 26環境でも旧デザインを維持する(Xcode 27までに対応が必須)

SansanではすでにiOS 26対応を済ませており、その際は無効化フラグを設定していました。今回はそのフラグを外し、Liquid Glass対応を進めました。

Sansanにおける対応方針とプロジェクトの進め方

Sansanでは複数のプロダクトでiOSアプリを提供しているため、まずはプロダクト横断でLiquid Glass対応のリスクとインパクトを整理する定例を設け、対応方針を揃えました。そのうえでSansanアプリでは、エンジニアが技術課題として主導する形でプロジェクトを立ち上げました。

Liquid Glass対応は、新しいSDKでビルドして初めて不具合が顕在化するため、機能数が多いSansanアプリにおいて、事前の見積もり段階で影響範囲の全容を把握することが困難です。また、判明した不具合箇所に対して「対応すべきか否か」を個別に判断していくプロセスになるため、調査の完了を待ってから着手する構成では対応期間が長くなります。そのため、事前調査・デザイン・実装・QAを可能な限り並列で進める方針を取りました。

事前調査

影響範囲の把握にあたり、QAが全画面を調査し従来のアプリとの差分をすべて起票する体制を取りました。QAメンバーにとってLiquid Glass固有の変更かどうかの判断が難しいため、「差分があればすべて起票する」というシンプルなルールにしています。

一方で、Liquid Glassは標準コンポーネントやアラートなど広範囲に影響するため、全ての変更内容が起票されると工数が膨れます。そのため、ナビゲーションバーのアイコン周囲の枠追加、トグルやアラートのデザイン変更といった既知の変更点は事前にQAメンバーへ共有し、調査中も都度「これは起票不要」と連携することで不要な起票を抑えました。

デザイン

デザイナーがスムーズに作業に入れるよう、他社事例やApple公式ドキュメント、WWDCセッション動画を一元化したプロジェクトページを作成し、プロジェクトの進行を加速させる工夫をしました。デザイン観点での対応方針としては、標準コンポーネントの見た目の変化はOS準拠として許容し、カスタムUIについてはユーザー体験を損なう箇所に限ってデザイナーと協議して対応する方針としました。

調査・デザイン・実装・QAの並列実行

2週間にわたる全画面調査の結果、差分は最終的に240件起票されました。これらをエンジニアとデザイナーで対応方針を振り分けたところ、対応が必要と判断したのは95件でした。起票を待って一括で着手するのではなく、振り分けが済んだものから順次、調査・デザイン・実装・QAを並行して進めることで、短い期間で対応を進められる体制を整えました。

タブバーデザインの再設計

Liquid Glass対応の中で最も議論を要したのが、タブバーのデザインです。

従来の実装

Sansanのアプリでは、タブバー中央にカメラボタンを配置するカスタムレイアウトを採用していました。名刺取り込みはSansanの中核機能であり、どの画面からでもすぐにカメラを起動できるこの導線は、コンバージョンに直結する重要な設計判断です。

実装としては、UITabBarの上にカスタムのUIButtonを重ねて配置し、タップ時にカメラのフルスクリーンモーダルを表示する形をとっています。標準のUITabBarItemではなく独自のボタンを使っているため、UITabBarの内部構造に依存したコードが存在していました。

タブバーの見た目の変化

Liquid Glass適用で発生したクラッシュ

前述の通り、SansanのタブバーはUITabBarの内部構造に依存したカスタム実装を含んでいました。これがLiquid Glassの導入で即座に問題を引き起こし、iOS 26の端末でアプリを起動した直後、Index out of rangeでクラッシュが発生しました。XcodeのDebug View Hierarchyで調査したところ、iOS 26でビュー階層が大きく変わっていることがわかりました。

従来(〜iOS 18)のUITabBarでは、各タブに対応するUITabBarButtonがサブビューとしてフラットに並んでいました。

UITabBar
├── _UIBarBackground
├── UITabBarButton  ← index 0
├── UITabBarButton  ← index 1
├── UITabBarButton  ← index 2
├── UITabBarButton  ← index 3
├── UITabBarButton  ← index 4
└── UIButton        ← カスタムカメラボタン

ところがiOS 26では、Liquid Glassの導入に伴い内部構造が大きく変更されました。

UITabBar
├── _UITabBarPlatterView
│   ├── subviews[0]選択中タブのコンテナ(ここに5つのUITabButtonが格納されている)
│   ├── subviews[1]  ← Liquid Lens
│   └── subviews[2]非選択タブのコンテナ(ここにも同じく5つのUITabButtonが格納されている)
└── UIButton          ← カスタムカメラボタン

UITabBarButtonが直下に並ぶ前提でtabBar.subviewsをフィルタリングしていたコードが、_UITabBarPlatterViewひとつしか見つけられず、期待したインデックスでのアクセスに失敗してクラッシュしていました。

これはUITabBarの内部構造というプライベートな実装詳細に依存していたことが、Liquid Glassという大きなUI刷新のタイミングで一気に表面化した形です。

Liquid Lensの挙動と新たな課題

上記のクラッシュを回避するためのリファクタをした後、次に直面したのはLiquid Lensの挙動に関する問題です。

iOS 26のタブバーには、選択中のタブを示すガラス状のマテリアルが新たに導入されました。WWDC25 Session 219: Meet Liquid Glassでは「transparent liquid lens」と表現されており、本記事ではこれをLiquid Lensと呼びます。Liquid Lensは、タブ切り替え時に滑らかにスライドするアニメーションを伴います。

ところがSansanのカメラボタンをタップすると、Liquid Lensがカメラボタンの方向へ動き始めた直後にフルスクリーンモーダルが表示され、Lensが元のタブに戻ってしまうという不自然な挙動が発生しました。

youtube.com

原因は、カメラボタンの裏側にダミーのUIViewControllerUITabBarController.viewControllersに登録し、tabBarController(_:shouldSelect:)return falseして遷移をキャンセルする実装にあります。iOS 18以前ではタブ選択時に目に見えるアニメーションがなかったため問題になりませんでしたが、Liquid Glassの導入により、この「選択しかけてキャンセル」というパターンがユーザーに見える形で露出しました。

さらにこの問題を掘り下げる中で、そもそもタブバー中央にカスタムボタンを配置するレイアウト自体がAppleのHuman Interface Guidelinesに沿っていないことを改めて認識しました。これまでは実害がなかったために暗黙的に許容されていましたが、Liquid Glassの導入によってHIG非準拠がデザインの不整合として顕在化した形です。

再設計の選択肢と意思決定

クラッシュの修正とLiquid Lensの挙動把握を経て、タブバーをどのような形に再設計するかの検討を行いました。5つの案を洗い出し、4つの観点で比較評価を行い最終的な決定を行いました。

概要 標準コンポーネント適合 HIG準拠 実装工数 ユーザー学習コスト
案1: 現レイアウト+Liquid Glass適用 既存カスタムレイアウトのままLGを有効化 🔺 M なし
案2: フルカスタムタブバー UITabBarを使わず完全に自前描画 L S
案3: TabRoleの活用 TabRole(.search)でカメラボタンを末尾に配置 🔺 🔺 M M
案4: カメラボタンの配置変更 カメラボタンをタブバー外に移動し標準構成に ⭕️ ⭕️ M L
案5: 現レイアウト+タブ表示維持 既存レイアウトを維持しつつ正規タブとして実装 ⭕️ ⭕️ M S

それぞれの観点を選んだ背景には、今回の対応を通じて得た具体的な教訓があります。

標準コンポーネント適合は、前述のクラッシュが直接的な動機です。UITabBarの内部構造に依存したコードはiOS 26で即座に破綻しました。標準的な使い方に近いほど、今後のOSアップデートに対する耐性が高まると考えました。

HIG準拠は、Liquid Glassによって改めて重要性を認識した観点です。これまで開発において実害がなかったHIG非準拠が、Liquid Lensの挙動という形で目に見えるUIの破綻として顕在化しました。HIGに沿うことは、プラットフォームの進化に追従し続けるための一つの指針であると考えました。

実装工数は、iOS 26のリリースまでの限られた期間という現実的な制約です。Liquid Glass対応はタブバーだけではなくアプリ全体に及ぶため、タブバーの再設計だけに過剰な工数をかけることは現実的ではありません。

ユーザー学習コストは、ビジネスへの直接的な影響を考慮した観点です。タブバー中央のカメラボタンは名刺取り込みの主要導線であり、このボタン経由のコンバージョンはビジネス上の重要指標です。長年この配置で操作を学習してきたユーザーへの影響は無視できません。

総合的に見ると、案4と案5が有力候補として上がりました。しかし、案4のカメラボタンの配置変更によるユーザー学習コストが大きく、コンバージョンへの影響も無視できません。そのため、最終的に案5の既存のレイアウトを維持しつつ、正規のタブとしてカメラタブを実装する選択肢を採用しました。理想と現実のバランスの中で、現時点で最善と判断した選択です。

最終的には、HIGに則り撮影ボタンを正規のタブバーのタブボタンとして扱うことで、撮影画面は以下のような見た目に変化しました。

適用前(従来):カメラ起動時はタブバーが非表示
適用後:タブバーを常時表示

Appleとのミートアップで得た知見

Sansanでは、Liquid Glass対応の過程でAppleの「Let's talk Liquid Glass」プログラムを通じてミートアップの機会を得ました。開発・デザイン・PMが参加し、開発中に生じた疑問をAppleのデザインコンサルタントに直接確認することができました。

なお、Appleは個社対応において「OK/NG」という明確な回答は出さないスタンスです。そのため、許可を求めるのではなく、こちら側の設計意図と解釈を提示し、それがLiquid Glassの設計哲学やHIGの思想と矛盾していないかをすり合わせる形で進めました。

ここでは、そこで得られた知見のうち、他の開発者にも参考になるポイントを共有します。

スキャンタブの設計はHIGの思想と矛盾しない

最も重要な確認事項であった「名刺スキャン機能をタブとして配置することがHIGの思想と矛盾しないか」について、Appleからは「スキャンタブを押した後も常にタブバーが表示され続け、撮影空間に移動する仕様であれば全く問題ない」という回答を得ました。

ここで重要なのは、スキャン機能が「タブを押して即座に完了するアクション」ではなく、「撮影→確認→編集→登録」という一連の作業を伴うセクションであるという点です。つまり、スキャンタブは「撮影して終わり」の単発アクションではなく、ユーザーが滞在して作業を行う「場所」として定義できるかどうかが判断基準となります。

この解釈を補強する先例として、Apple純正の翻訳アプリではカメラ翻訳がタブとして配置されています。これは、カメラ機能を単発のアクションとしてではなく、「カメラを介した翻訳空間」へのナビゲーションとして定義されているためです。Sansanのスキャンタブも同様に、「カメラを介した名刺撮影空間」へのナビゲーションとして位置づけています。

タブバーの設計原則とアンチパターン

Appleからは、タブバーの根本的な設計原則と、それに反するアンチパターンが共有されました。

大前提として、タブは背後にあるコンテンツの「世界(コンテキスト)」を切り替えるものであり、別の何かを起動する単なるトリガーであってはならないという考え方です。この原則を踏まえ、以下のアンチパターンが示されました。

アンチパターン1:ユーザーの意図しないタブ遷移

あるタブで作業しているのに、プログラム的に別のタブに遷移させることはNGです。タブの遷移は基本的にユーザーが行うものであり、「いつの間にか違うタブに移っている」状態を作るべきではありません。

Sansanのアプリでも、名刺スキャン後に自動で名刺一覧タブに遷移させる設計が指摘を受けました。

アンチパターン2:タブを押した瞬間に単一アクションが完了する設計

タブを押した瞬間にモーダルやシートが起動する「タブ=アクションのトリガー」という使い方もNGです。アクション結果を別のタブで見せたい場合は、プッシュまたはモーダルで表示するのが適切とのことでした。なお、ビジネス的観点からアクションをタブバーに配置したい判断は理解できるものの、ユーザーの期待を裏切るリスクにも注意が必要との助言がありました。

フルスクリーンモーダルとLiquid Glassの関係

Sansanアプリでは、名刺情報の設定画面をフルスクリーンモーダルで表示しています。この際のLiquid Glassの適用について確認したところ、シートの表示サイズに応じてマテリアルが切り替わるのがデフォルトの挙動とのことでした。ハーフモーダルのときはLiquid Glassで透過感を出し、全画面展開のときは元のコンテキストを感じさせる必要が薄れるため標準マテリアルに切り替わります。マップアプリのボトムシートがこの挙動の好例です。これはSwiftUIのシートのデフォルト挙動でもあり、シート表示を多用しているアプリでは参考になるはずです。

検索タブの設計思想

iOS 26のタブバーでは、右端に独立した検索アイテムを配置できるTabRole(.search)が追加されました。

developer.apple.com

Appleのファーストパーティアプリではこの位置は必ず検索に割り当てられていますが、APIレベルでは別のアイコンを設定することも技術的には可能です。この点について確認したところ、Appleの意図は「プラットフォーム全体のラーニングカーブを下げるため、あの位置は検索専用として使ってほしい」というものでした。あえて検索として活用したアプリの中には、実装した翌日からトラフィックが2倍になった事例もあるとのことです。

検索UIの配置に関しては、「下部に何があるか」で判断するというガイダンスも共有されました。タブバーがないアプリは届きやすい下部に検索を配置し(設定アプリがこのパターン)、タブバーやツールバーで下部が込み合っている場合は上部に配置するのが基本原則です。

無効化フラグを外したことで発生した問題と対応

ここでは、Sansanアプリで発生した不具合の一部を紹介します。

UISearchBarの高さが変わる

UISearchBarの高さの変化

Liquid Glassによって検索窓の大きさや色、検索をキャンセルするボタンなどの見た目が変わりました。

検索窓そのもののレイアウトなどはOSが制御しているため実装の変更は不要ですが、検索窓を配置している画面全体のレイアウトや背景色を検索窓に合わせて微調整する必要がありました。

具体例を挙げると、画像のようにLiquid Glassを有効にすると検索窓が少し大きくなります。わずかな差ですが、検索窓の周辺が詰まって見える画面がいくつか生まれるため余白やUI同士の制約を調整する変更を加えています。

UIBarButtonItemのデザイン崩れ

UIBarButtonItemのデザイン崩れ
Liquid Glassでは、画像のように「完了」ボタンや戻るボタンなどが丸もしくは楕円に縁取られるようになります。

その副作用として、ボタンに余計な制約設定をしていると横長なボタンが生まれることがあります。例えばユーザーのアイコンや名刺など画像をプレビューする画面で、QLPreviewControllerのナビゲーションのボタンが長くなっていました。

いずれもナビゲーションのボタンが無いことを明示する、ボタンのタイトルに余計な空白文字を設定してないかなど、以前の実装を見直すことで解決できました。

override public func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

+     // ナビゲーションのボタンが何もないことを明示する
+     navigationItem.rightBarButtonItem = nil
+     navigationItem.rightBarButtonItems = []
     hideToolbarsInSubviews(forView: view)
}
 <navigationItem key="navigationItem" title="手入力" id="NAB-IQ-QEE">
-    <barButtonItem key="backBarButtonItem" title=" " id="RL8-NT-phH"/>
+    <barButtonItem key="backBarButtonItem" title="" id="RL8-NT-phH"/>

UIButtonの背景クリップによる表示崩れ

UIButtonの背景クリップによる表示崩れ
Liquid Glassにするとボタンの一部やボタンとセットにしているテキストが見切れてしまう場合があります。

OSによってボタンの背景が丸くクリップされるようになったことが原因で、見切れてしまうボタンに対しては手動で余白の調整が必要になります。

// cornerStyleをfixedで固定し、わずかなcontentInsetsを追加することで、アイコンの欠けを防ぐ。
if #available(iOS 26.0, *) {
    configuration.contentInsets = NSDirectionalEdgeInsets(
        top: 2,
        leading: 2,
        bottom: 2,
        trailing: 2
    )
    configuration.cornerStyle = .fixed
} else {
    configuration.contentInsets = .zero
}

参考:Can't make buttons rectangular! | Apple Developer Forums

ナビゲーションバーのアイコンの変化

ナビゲーションバーのアイコンの変化

Liquid Glass適用前のナビゲーションバーやツールバーでは、ボタンに独自のアイコンを使用していました。Liquid Glass適用後、UIButtonと同様に戻るボタンにも背景がクリップされるようになり、さらにそれまで灰色だったオリジナルの戻るアイコンがOSによって黒色に塗り替えられるようになりました。

これに対して、以下の2点の対応を行いました。まず、背景がくり抜かれることで独自アイコンが少し右にずれて見えるようになったため、独自アイコンをやめてOS標準のSF Symbolsアイコンに置き換えました。

次に、アイコンが黒色にレンダリングされる件については、WWDCセッション(Build a UIKit app with the new design)でもlabelColorを使用するよう明言されていることから、従来の灰色を維持することをやめ、OSのデフォルトカラーに従う方針としました。

// Before: カスタムアイコンをleftBarButtonItemに設定
- let backButton = UIBarButtonItem(
-     image: UIImage(named: "navigation_back"),
-     style: .plain,
-     target: self,
-     action: #selector(didTapBack)
- )
- navigationItem.leftBarButtonItem = backButton

// After: 標準のbackBarButtonItemを使用(SF Symbolsが自動適用される)
+ navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)

まとめ

本記事では、SansanアプリのLiquid Glass対応について、プロジェクトの進め方から具体的な技術課題、Appleミートアップで得た知見までをまとめました。

今回の対応で特に有効だったのは、調査・デザイン・実装・QAを並列で進める体制です。Liquid Glass対応では、新しいSDKでビルドして初めて影響範囲が分かることが多く、調査から実装までを直列に進めると待ち時間が発生しやすくなります。そのため、起票されたものから順に切り分け、着手できるものを並行して進めたことで、限られた期間の中でも対応を前に進めることができました。加えて、Appleミートアップで「スキャンタブがHIGの思想と矛盾しない」という確認が取れたことも、タブバーの再設計における意思決定を後押ししました。

また今回の対応は、これまで表面化していなかった実装や設計上の前提を見直す機会にもなりました。UITabBarの内部構造への依存やHIG非準拠のUIは、普段は大きな問題にならなくても、Liquid Glassのような大規模なUI刷新のタイミングでクラッシュや不自然な挙動として一気に表面化します。そのため、OSの進化に継続的に追従するには、日頃から標準コンポーネントに寄せた設計を意識しておくことが重要だと改めて感じました。

これからLiquid Glass対応に取り組む方にとって、本記事が少しでも参考になれば幸いです。

Sansan技術本部ではカジュアル面談を実施しています

Sansan技術本部では中途の方向けにカジュアル面談を実施しています。Sansan技術本部での働き方、仕事の魅力について、現役エンジニアの視点からお話しします。「実際に働く人の話を直接聞きたい」「どんな人が働いているのかを事前に知っておきたい」とお考えの方は、ぜひエントリーをご検討ください。

© Sansan, Inc.