目次
Session概要
Magic Keyboard、マウス、トラックパッド、またはその他の入力デバイスをiPadにつなげて使用される方がお持ちのアプリを最大限に活用できるようにしましょう。Pointer Interaction APIを使って、iPadのポインタのカスタマイゼーションを追加して、ボタンやカスタムビューをポインタと連動させ、特定のエリアでポインタの形状を変える方法について。iPadでのポインタのデザインに関してはこちらを参照
https://developer.apple.com/videos/play/wwdc2020/10093/
iPadOSのポインタのサポート
UIKitにおけるインタラクション
- UIBarButtonItem、UISegmentControl、UIMenuControllerといったコントロールにはポインタのエフェクトや動作が組み込まれている
- UITextViewやUITextInteractionを使うコンポーネントでは、一連のテキスト操作やジェスチャーの編集が可能
- UIDragInteractionはクリック&ドラッグで素早くドラッグ可能
- UIContextMenuInteractionは2回クリックすれば、新しくコンパクトなメニューを表示する
アップデート方法
- アップデート対象UI
- Controls
- コントロールの多くはポインタエフェクトを内蔵している
- UIBarButtonItemのようにデフォルトでエフェクトが利用可能
- UIButtonはAPIでエフェクトをカスタマイズ可能
- Interactions
- UIPointerInterractionはカスタムUIがポインタに反応し相互作用できるように他のシステムと一貫性を持たせる
- Gesture
- UIHoverGestureRecognizerはポインタの動きを直接反映し、ホバーやハイライトの適用またはポインタの形の修正の必要がないカスタム動作に最適
- Controls
- アップデート対象の参考
- Human Interface Guidelinesや内蔵アプリを参考にする
- アップデートのステップ
- まずはコントロールに適切なエフェクトがあるか確認する
- UIBarButtonItemsはSystemItemやimageもしくはtitleのAPIを使って作り、ポインタエフェクトが自動的に適用されるようにする
- カスタムViewに実装したUIButtonには、元から有効なインタラクションが組み込まれている
- システムがUIBarButtonに適切だと考えたエフェクトを設定する
- UIButtonのAPIを使用することでエフェクトを微調整することも可能
- まずはコントロールに適切なエフェクトがあるか確認する
UIButtonのエフェクトにおけるAPI(iOS 13.4+)
- 自動的にエフェクトを有効にするために、button.isPointerInteractionEnabled をtrueにする
- button.pointerStyleProviderを使用してカスタマイズする
- PointerStyleProviderはボタンの見た目や大きさをコンテンツを元に設定される
// Enable the button's built-in pointer interaction.
myButton.isPointerInteractionEnabled = true
// Customize the default interaction effect.
myButton.pointerStyleProvider = { button, proposedEffect, proposedShape -> UIPointerStyle? in
// In this example, we'll switch to using the .lift effect by creating a new
// UIPointerEffect with the .lift type using the proposedEffect's preview.
return UIPointerStyle(effect: .lift(proposedEffect.preview), shape: proposedShape)
}
ポインタのスタイル(UIPointerStyle)
- コンテンツエフェクト
- アプリの画面上でポインタを変形させ、視覚処理を適用する
- 例:BarButtonのハイライトのエフェクト
- ポインタが角丸長方形に変形したり、ボタンの下に消えたり、微細なパララックスエフェクトを適用したりする
- UIPointerEffect
- Viewに適用された視覚処理を記述する
- UIPointerShape
- ポインタの変形を記述する
- アプリの画面上でポインタを変形させ、視覚処理を適用する
- シェイプカスタマイゼーション
- UIPointerShapeとUIAxisマスクに記述する
- 適用するとアクティブな範囲内で移動する方向を紐付けされ、変形する
// 下記コンテンツエフェクト画像のポインタ実装サンプル
// Create a UIPointerStyle that applies the .highlight effect.
// Outset the view's frame so the pointer shape has some generous padding around the view's contents.
// Note that this frame must be in the provided UITargetedPreview's container's coordinate space.
// In the majority of cases (where the preview doesn't have a custom container), this is just the view's superview.
let rect = myView.frame.insetBy(dx: -8.0, dy: -4.0)
let preview = UITargetedPreview(view: myView)
return UIPointerStyle(effect: .highlight(preview), shape: .roundedRect(rect))

// 下記シェイプカスタマイゼーションの画像を実装するサンプル
// Create a UIPointerStyle that changes the pointer into a vertical beam.
let beamLength = myFont.lineHeight
// constrainedAxes vertical指定でポインタがテキスト上を水平に動くように見せる
return UIPointerStyle(shape: .verticalBeam(length: beamLength), constrainedAxes: .vertical)

カスタムUI
- スタイル(UIPointerStyle)が適用される範囲を限定したい場合は、UIPointerRegionを使用する
- インタラクション(UIPointerInteraction)は自動的にポインタのエフェクトを全体のViewに適用する
- Viewの特定の小さな範囲内での動作を定義する場合
- UIPointerInteractionDelegateのregionFor request APIを実装する
- ポインタが特定の範囲に入ったら、インタラクションはスタイルを要求するためにstyleForRegionメソッドを呼び出し、シェイプカスタマイゼーションを設定する
- 例:特定の部分ではポインタを十字アイコンに変えるなど
- 例:特定の部分ではポインタを十字アイコンに変えるなど

ベストプラクティス
ポインタエクスペリエンスを良くするための方法
- Viewの周りに余白を指定すること
- ポインタの戻る力を増強して、UIで重要な要素に作用しやすくなり、ボタンの端にたどり着く前にポインタはボタンに戻る
- ただし、余白には制限がありインタラクションのViewにあるhittestable内でなければならない
- Viewより大きな範囲を設定したい場合はViewにhittestされていることを確認する
- ポインタの範囲を調整することで、エフェクトが変化する具合を制御できる
- ポインタのインタラクションにアニメーションを追加する
- 役立つ情報を付与でき、エフェクト中のクラッタ(不要なノイズ)を減らすことができる
- ポインタのアニメーションを調整するためには、DelegateにwillEnterやwillExit regionを実装し、animatorオブジェクトにアニメーションを付与する

隙間でシステムのシェイプに戻ることがない
// ポインタが範囲に来たら区切り線のViewをフェードアウトし、範囲から出たらフェードバックする
func pointerInteraction(_ interaction: UIPointerInteraction,
willEnter region: UIPointerRegion,
animator: UIPointerInteractionAnimating) {
// Fade out separator when entering region.
animator.addAnimations {
self.separatorView.alpha = 0.0
}
}
func pointerInteraction(_ interaction: UIPointerInteraction,
willExit region: UIPointerRegion,
animator: UIPointerInteractionAnimating) {
// Fade separator back in when exiting region.
animator.addAnimations {
self.separatorView.alpha = 1.0
}
}