iOS 15からハーフモーダルっぽい表示が標準APIを使ってできるようになりました。この記事ではその「ハーフモーダル」について紹介しているCustomize and resize sheets in UIKitという動画を紹介してみます。
概要
従来の場合
今までモーダルは全画面表示でした(スクショは.pageSheetの場合)。
iOS 15 〜
iOS 15では「medium」モードにすると画面の半分だけを覆うシートが作成できるようになりました。
landscapeやiPadのときの表示はこのようになります。
detentsとは
まず最初にdetentsというものを理解する必要があります。detentsとはシートが自然に止まる高さのことを表し、.medium()と.large()が用意されています。
コードで書くとこんなかんじです。この指定により、シートの覆い具合を調整することができます。まずviewControllerのsheetPresentationControllerを取得します。このシートに対しdetentsを設定してあげると、サイズの指定ができます。デフォルトでは.large()のみが設定された状態だと解釈されます。設定とその結果の対応は以下の通りです。
-
.large()のみ- フルサイズのシートが表示される(リサイズ不可)
-
.medium()と.large()の両方- mediumとlargeでリサイズ可能なシートが表示される
-
.medium()のみ- ハーフサイズのシートが表示される(リサイズ不可)
detentsを用いた実装例
従来の場合
例えば写真ピッカーは次のような実装をして表示していたと思います。


detentsでサイズ指定
iOS 15から、例えば以下のコード、表示ができます。
-
PHPickerViewControllerのsheetPresentationControllerを取得してdetentsに.medium()と.large()を設定- リサイズ可能なシートを表示する
- ピッカーの
didFinishPickingデリゲートからdismiss行を削除- 写真を選択し終わってもシートが完全に閉じないようにする
これにより、シートを半分にした状態で写真を選択することも、ドラッグしてフルサイズにしたシートから写真を選択することもできます。
スクロール制御
ただし、このままだと上にスクロールした時にシート自体もスクロールしてフルサイズになってしまい、「スクロールすると、シートは半分のままで、ピッカー内の写真一覧だけがスクロールされる」状態にならなくなってしまいます。バーをドラッグした時にシートのサイズが変わるようにするにはprefersScrollingExpandsWhenScrolledToEdgeをfalseにする必要があります。

選択時のサイズ指定
次の改善ポイントは写真選択時のアクションです。.large()の状態で写真を選択しても、選択された写真が表示されるエリアは見れないので、ちゃんと写真が選択されたのかその場でわかりません。そこで、写真が選択された時はシートが.medium()の大きさになるよう変更してみます。デリゲートメソッド内でselectedDetentIdentifierを.mediumに設定します。

リサイズ時にアニメーションをつける
写真選択時に.large()から.medium()に変更できたのはいいものの、このままだとアニメーションがないのでいきなりサイズが変わってしまいます。次のようにanimateChangesブロックで囲むと、自然なアニメーションをつけることができます。

dimmingを消す

選択された写真エリアが薄暗いので、dimmingを解除してみます。smallestUndimmedDetentIdentifierの設定をいじることで解除が可能です。このプロパティはデフォルトnilで、すべてのdetentsでdimmingがかかっています。dimmingを解除したい場合はdimmingさせたくない最小のdetentsを設定します。

このプロパティは視覚的に「薄暗さ」を取り除くだけではなく、シート外のコンテンツにアクセスできるような「ノンモーダル状態」を作ることができます。ので、.medium()の時に写真選択エリアに触れて操作することもできます。
キーボードとの併用

.medium()の時にキーボードを表示すると、自動で.large()サイズになります。キーボードが消えると.medium()サイズに戻ります。
視覚的なカスタマイズ
detentsを操作する以外のところでもいろいろできます。
landscape時のシートサイズ
iOS 15からは、シートの下端だけが画面の縁についている状態で表示できるprefersEdgeAttachedInCompactHeightが追加されました。これをtrueにするとSafe Areaと同じ幅のシートを表示できます。

presentedViewControllersのpreferredContentSizeに沿った幅のシートにしたい場合は、widthFollowsPreferredContentSizeWhenEdgeAttachedをtrueに設定します。preferredContentSizeを設定することで、幅をさらにカスタマイズすることもできます。

grabberの表示非表示
prefersGrabberVisibleの設定で、grabberの表示非表示を操作することができます。

シートのリサイズが可能であることがわかりづらい場合にはgrabberを表示すると役に立ちます。
シートのradius変更
iPadでポップオーバーを表示する場合
写真ピッカーをポップオーバーにするにはまずmodalPresentationStyleに.popoverを設定します。次に、先程のようにsheetPresentationControllerを取得するのではなく、popoverPresentationControllerを取得します。ポップオーバーのsourceをbarButtonItemに設定し、adaptiveSheetPresentationControllerというポップオーバーの新しいプロパティを取得します。このプロパティはポップオーバーがcompactサイズのクラスに適応するシートのインスタンスを返してくれます。

これで写真ボタンをタップするとポップオーバーに写真ピッカーが表示され、領域のサイズを小さくすると、.medium()サイズのシートが表示されるようになりました。


ただし、このままだと.large()状態で写真を選択した時に自動で.medium()サイズになってくれません。popoverPresentationControllerのadaptiveSheetPresentationControllerを取得して先程と同じように設定する必要があります。

まとめ
- iOS 15からハーフモーダルっぽいUIが標準APIを使って作れるようになったよ
- ちょこちょこカスタマイズできるよ










