WWDC Quick Look 💓 By SwiftGGTeam
Customize and resize sheets in UIKit

Customize and resize sheets in UIKit

元の動画を見る

ハイライト

UIKit シートに iOS 15 が導入されましたUISheetPresentationController、中型/大型のダブルハイト ドッキング (戻り止め)、非モーダル インタラクション、カスタムの丸いコーナーとグラバーをサポートし、ハーフスクリーン カード スタイル UI をシステム レベルのネイティブ機能にします。

主な内容

以前は、iOS で半画面のドラッグ可能なカードを作成したいときは、基本的にカスタム トランジション アニメーションのセットを自分で作成する必要がありました。システムに組み込まれているシートの高さは全画面のみです。ユーザーが写真を選択したり設定を変更したりすると、インターフェイス全体が覆われ、操作が完了した後にユーザーはインターフェイス全体を再度開く必要があります。

iOS 15 では、カスタマイズ API の完全なセットが Sheet に追加されます。中心となるアイデアは、シートの状態が「全画面」の 1 つだけではなくなり、複数の「戻り止め」を切り替えることができるということです。システムは、中 (画面の約半分) と大 (全画面) の 2 つのドッキング ポイントを事前に定義します。シートで両方の高さをサポートすることも、一方だけを維持することもできます。

さらに重要なのは、薄暗くなったビューを削除できることです。これは、シートが空中に浮かんでいても、ユーザーはその背後にあるコンテンツを操作できることを意味します。たとえば、画像セレクターは半画面モードで開きます。ユーザーが写真をクリックすると、その後ろのポストカードに写真が直接表示されます。シートは開いたままになり、いつでも別の画像に変更できます。

シート コントローラーの取得

01:36

シートをカスタマイズする最初のステップは、UISheetPresentationController例。入手方法とpopoverPresentationController同じ: 存在する前にターゲットのビュー コントローラーを読み取りますsheetPresentationController財産。

if let sheet = viewController.sheetPresentationController {
    // Customize the sheet
}
present(viewController, animated: true)

キーポイント:

  • sheetPresentationControllerだけでmodalPresentationStyleのために.formSheetまたは.pageSheetnil 以外を返します
  • デフォルトは.pageSheetなので、ほとんどの場合は直接読んでください。

ストップを設定する

02:21

detentsプロパティは、シートがサポートする高さを決定します。デフォルトは[.large()], そのため、設定されていない場合は全画面表示になります。

if let sheet = picker.sheetPresentationController {
    sheet.detents = [.medium(), .large()]
}
present(picker, animated: true)

3 つの一般的な構成:

  • [.large()]- デフォルトの動作、全画面シート
  • [.medium(), .large()]— ドラッグして半画面と全画面を切り替えます
  • [.medium()]— 半画面を修正し、全画面にドラッグできない

ノンモーダル画像セレクター

03:27

実際のシナリオを見てみましょう。PHPickerViewController画像を選択します。iOS 14の書き方は全画面ピッカーを表示して選択後に閉じるというものです。iOS 15 は半画面に永続的に変更できます。

func showImagePicker() {
    let picker = PHPickerViewController()
    picker.delegate = self
    if let sheet = picker.sheetPresentationController {
        sheet.detents = [.medium(), .large()]
    }
    present(picker, animated: true)
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    // assign result to imageView.image
    // dismiss は呼び出さない
}

キーポイント:

  • detentsに設定[.medium(), .large()]、ピッカーは半画面モードで開きます
  • 取り除くdismiss(animated:)の場合、ピッカーは開いたままになり、ユーザーは画像の選択を続けることができます。
  • ユーザーは引き続きシートを全画面にドラッグして、さらに多くの写真を閲覧できます

自動スクロール拡張を無効にする

05:34

デフォルトの動作では、スクロール ビューが上にスクロールし、下にスクロールし続けると、シートが自動的に大きく拡大されます。画像ピッカーのようなシナリオでは、スクロールがシートの高さではなくコンテンツのみに影響を与えるようにしたい場合があります。

if let sheet = picker.sheetPresentationController {
    sheet.detents = [.medium(), .large()]
    sheet.prefersScrollingExpandsWhenScrolledToEdge = false
}

キーポイント:

  • prefersScrollingExpandsWhenScrolledToEdgeデフォルトは true
  • falseに設定した後は、シートの上部にあるバーをドラッグすることによってのみ高さを変更できます

プログラムによる停止の切り替え

06:12

ユーザーが写真を選択した後、シートを自動的に中間の高さに縮小します。これにより、フィードバックが提供されるだけでなく、その背後にあるコンテンツも表示されます。

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    // assign result to imageView.image
    if let sheet = picker.sheetPresentationController {
        sheet.animateChanges {
            sheet.selectedDetentIdentifier = .medium
        }
    }
}

キーポイント:

  • selectedDetentIdentifier制御電流停止
  • 必ず使用してくださいanimateChangesラップしない場合、スイッチにはアニメーションがありません。
  • animateChangesスタック内の他のシートのリンクされたアニメーションは同時に処理されます。

背景マスクを削除する

07:12

薄暗いビューを削除することが、ノンモーダル インタラクションを実現する鍵となります。

if let sheet = picker.sheetPresentationController {
    sheet.detents = [.medium(), .large()]
    sheet.prefersScrollingExpandsWhenScrolledToEdge = false
    sheet.smallestUndimmedDetentIdentifier = .medium
}

キーポイント:

  • smallestUndimmedDetentIdentifier中程度に設定すると、中程度の高さではマスクが表示されなくなります。
  • ユーザーがドラッグするとマスクがフェードインします
  • マスクを削除すると、ユーザーはシートの内側と外側のコンテンツを同時に操作できるようになります。

横画面とビジュアルカスタマイズ

08:44

iOS 13 のシートは、横向きモードではデフォルトで全画面表示になります。iOS 15 では、下部スナップの別の外観が提供されます。

if let sheet = fontPicker.sheetPresentationController {
    sheet.prefersEdgeAttachedInCompactHeight = true
    sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}

グラバーインジケーターバーを表示:

if let sheet = fontPicker.sheetPresentationController {
    sheet.prefersGrabberVisible = true
}

カスタムの丸い角:

if let sheet = fontPicker.sheetPresentationController {
    sheet.preferredCornerRadius = 20.0
}

キーポイント:

  • prefersEdgeAttachedInCompactHeight全画面ではなく、横画面の下部をシートで吸着させる
  • widthFollowsPreferredContentSizeWhenEdgeAttached幅をそれに従うようにするpreferredContentSize
  • システムは、積み重ねられたシートの角の丸みの一貫性を自動的に維持します。

ポップオーバーはカスタマイズされたシートに適応します

10:05

iPad でポップオーバーを使用すると、iPhone では自動的にカスタマイズされたシートになります。

func showImagePicker(_ sender: UIBarButtonItem) {
    let picker = PHPickerViewController()
    picker.delegate = self
    picker.modalPresentationStyle = .popover
    if let popover = picker.popoverPresentationController {
        popover.barButtonItem = sender

        let sheet = popover.adaptiveSheetPresentationController
        sheet.detents = [.medium(), .large()]
        sheet.prefersScrollingExpandsWhenScrolledToEdge = false
        sheet.smallestUndimmedDetentIdentifier = .medium
    }
    present(picker, animated: true)
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    if let sheet = picker.popoverPresentationController?.adaptiveSheetPresentationController {
        sheet.animateChanges {
            sheet.selectedDetentIdentifier = .medium
        }
    }
}

キーポイント:

  • adaptiveSheetPresentationControllerコンパクトサイズクラスのポップオーバーのアダプティブシートを入手
  • 設定方法は通常のシートと全く同じです
  • デリゲートメソッドも渡しますpopoverPresentationController?.adaptiveSheetPresentationControllerアクセス

##詳細

ディテント機構の詳しい説明

UISheetPresentationController.Detentこれは iOS 15 で導入された新しいタイプで、Sheet の自然なドッキング高さを表します。現在、システムには 2 つの事前定義値が用意されています。

  • .medium()— 全画面の高さの約 50%
  • .large()— 画面全体の高さ

detentsプロパティは配列であり、順序は機能に影響しません。selectedDetentIdentifier現在アクティブなストップを表し、実行時にコードを通じて切り替えることができます。

アニメーションとインタラクションの詳細

animateChangesが重要な方法です。これはクロージャを受け入れ、クロージャ内の Sheet プロパティを変更するとスムーズなアニメーションが生成されます。このメソッドは、基になるシートのズームリバウンドを含む、シート スタック全体のアニメーションを調整します。

キーボード回避機能も組み込まれています。シートが中程度の高さのときに、内側のテキスト ボックスにフォーカスが当たってキーボードがポップアップすると、シートはキーボードを避けるために自動的に上方に拡張され、キーボードが格納された後は自動的に元に戻ります。

完全な構成例

上記の機能をすべて組み合わせると、一般的な非モーダル イメージ セレクターの構成は次のようになります。

func showImagePicker() {
    let picker = PHPickerViewController()
    picker.delegate = self

    if let sheet = picker.sheetPresentationController {
        // 半画面と全画面をサポートする
        sheet.detents = [.medium(), .large()]
        // スクロールしても自動的に展開しない
        sheet.prefersScrollingExpandsWhenScrolledToEdge = false
        // 半画面時は遮蔽を表示せず、背後のコンテンツとの操作を許可する
        sheet.smallestUndimmedDetentIdentifier = .medium
        // grabber を表示する
        sheet.prefersGrabberVisible = true
        // 角丸をカスタマイズする
        sheet.preferredCornerRadius = 20.0
    }

    present(picker, animated: true)
}

func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    // 選択された画像を処理する
    if let result = results.first {
        result.itemProvider.loadObject(ofClass: UIImage.self) { image, error in
            DispatchQueue.main.async {
                self.imageView.image = image as? UIImage
            }
        }
    }

    // 選択後に自動で半画面へ戻す
    if let sheet = picker.sheetPresentationController {
        sheet.animateChanges {
            sheet.selectedDetentIdentifier = .medium
        }
    }
}

キーポイント:

  • detents利用可能な高さを定義する
  • prefersScrollingExpandsWhenScrolledToEdgeスクロール動作を制御する
  • smallestUndimmedDetentIdentifier制御マスク範囲
  • animateChangesプロパティの変更をラップしてアニメーション化する
  • 呼ばれていないdismiss、シートを永続的に保持します

重要ポイント

  1. 半画面常駐ツールパネルを作成
  • 対処方法: 描画アプリやメモ取りアプリでは、ツールバーを中程度の戻り止めシートにして、ユーザーがツールを選択したときにメイン キャンバスが常に表示されるようにします。
  • 実行する価値がある理由: 以前は、ツールバーは下部の固定スペースを占有するか、全画面表示されてクリエイティブ フローを中断していました。ハーフスクリーンシートはスペースをとらず、中断もありません
  • 開始方法: を使用しますdetents = [.medium()] + smallestUndimmedDetentIdentifier = .medium非モーダルツールパネルの実装
  1. 折りたたみ可能な詳細ページを作成
  • 対処法: 地図アプリでは、位置の詳細を表示するために中/大の二重戻り止めを使用します。デフォルトでは、基本情報は半分の画面に表示されます。引き上げると完全な内容が表示されます。
  • 実行する価値がある理由: System Map App はこの対話モデルを検証しました。使用UISheetPresentationControllerカスタムトランジションを使用せずに直接再現可能
  • 開始方法:detents = [.medium(), .large()] + prefersGrabberVisible = true、ユーザーはドラッグして切り替えることができます
  1. クロスデバイスに適応するポップアップ ウィンドウを作成する
  • 対処法: iPad でポップオーバーを使用すると、iPhone では自動的にカスタマイズされたシートになります。 1 つのコード セットで両方のデバイスに適応できます。
  • 実行する価値がある理由:adaptiveSheetPresentationControllerポップオーバーのコンパクトな適応動作をカスタマイズ可能にし、2 セットのロジックを作成する必要性を排除します
  • 開始方法: 設定modalPresentationStyle = .popover、次に設定しますpopover.adaptiveSheetPresentationController
  1. リアルタイム プレビュー用の構成パネル
  • 対処方法: 写真編集アプリやフィルターアプリでは、パラメータを調整するためのパネルが半画面シートで開き、調整中にメイン画像がリアルタイムで変化します。
  • 実行する価値がある理由: ディミングを削除すると、ユーザーはパネルとメイン画像を同時に見ることができるため、パラメーターを調整するエクスペリエンスがより直感的になります。
  • 開始方法:smallestUndimmedDetentIdentifier = .medium+ 無視されず、パラメーターの変更は背後のビューに直接反映されます。
  1. 既存のカスタム カードを置き換える
  • 何をすべきか: 現在使用している場合UIViewControllerトランジションをカスタマイズしてハーフスクリーン カードを実装し、UISheetPresentationController
  • 実行する価値がある理由: システムによって実装されるジェスチャ、アニメーション、およびキーボード回避がより完全になり、コード量が数百行から十数行に削減されました。
  • 開始方法: カスタム遷移コードを削除し、現在の状態で設定します。sheetPresentationControllerの特性

関連セッション

コメント

GitHub Issues · utterances