WWDC Introducing Drag and Drop まとめ
今回はiOS11の目玉といえるドラッグ&ドロップ機能のセッションです。
他にもドラッグ&ドロップはセッションが多いので、Appleの気合のいれようが伝わってきますね。
とりあえずIntro部分のセッションを。
Introducing Drag and Drop
ビデオは こちら
Drag&Dropのコンセプト
Drag&dropとは?
- タッチによるデータの移動・コピー
Drag&Dropのゴール
- 素早い応答。非同期取得
- セキュリティ。アクセス制限など
- マルチタッチで直感的に操作できる
基本的にiPad向けの機能
- iPhoneでは同一アプリ内に制限される
API概要
- 4つのフェイズがある
- Lift : 長押しでドラッグ可能になった状態
- Drag : プレビューが移動してる状態。
- SetDown : キャンセル、もしくは転送先の決定
- DataTransfer : ターゲットへのデータ転送
Drag元のAPI
- DragInteraction 系APIが追加
- UIDragInteraction, UIDragInteractionDelegate
- UIDragItem
let view : UIView = ... let delegate : UIDragInteractionDelegate = ... let dragInteraction = UIDragInteraction(delegate : delegate) view.addInteraction(dragInteraction)
Delegateの役割
- Lift時に、
UIDragItem
を返す - データが無い場合はDragフェイズ終了。
- Lift時に、
UIDragItemとは?
- 以下の2つを持つ
- Dragプレビュー
- NSItemProvider
- 以下の2つを持つ
Drop先のAPI
- UIPasteConfiguration が追加
- 対応するデータ種別を指定する
- Drop対象のViewに追加する
let config = UIPasteConfiguration(typeIdentifiersForAcceptingClass : NSStrnig.self) view.pasteConfiguration = config //UIViewを継承して作る? UIResponderのメソッドだった override func paste(itemProiders: [NSItemProvider]) { }
- UIDropInteractinoDelegate で受け取りを行う
- 受け取り可能かどうかを指定するのもこのDelegate
フェーズ詳細
Lift
- ロングプレスで移動可能になった状態
- ドラッグ待ち
- Delegateで「移動するデータとプレビュー用View」を返す
//アイテムの決定 func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] { let itemProvider = NSItemProvider(object: "Hello World" as NSString) let dragItem = UIDragItem(itemProvider: itemProvider) return [ dragItem ] } //プレビュー func dragInteraction(_ interaction:UIDragInteraction, previewForLifting item:UIDragItem, session:UIDragSession) -> UITargetedDragPreview? { let imageView = UIImageView(image: UIImage(named: "MyDragImage")) let dragView = interaction.view! let dragPoint = session.location(in: dragView) let target = UIDragPreviewTarget(container: dragView, center: dragPoint) return UITargetedDragPreview(view: imageView, parameters:UIDragPreviewParameters(), target) } //リフトアニメーション func dragInteraction(_ interaction: UIDragInteraction, willAnimateLiftWith animator: UIDragAnimating, session: UIDragSession) { animator.addAnimations { self.view.backgroundColor = UIColor.gray } animator.addCompletion { position in if position == .end { //リフト完了 } else if position == .start { //リフトキャンセル。元に戻る } } }
Drag 送信
func dragInteraction(_ interaction: UIDragInteraction, previewForCancelling item: UIDragItem, withDefault defaultPreview: UITargetedDragPreview) -> UITargetedDragPreview? func dragInteraction(_ interaction: UIDragInteraction, item: UIDragItem, willAnimateCancelWith animator: UIDragAnimating) func dragInteraction(_ interaction: UIDragInteraction, session: UIDragSession, didEndWith operation: UIDropOperation)
Drag 受信
- 移動先が「データを受け取れるか」を返す
- DropInteractionDelegate
//sessionをHandleできるかどうか func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool { return session.canLoadObjects(ofClass: UIImage.self) } //handle可の後、Viewに入った時に処理可能かどうか func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal { return UIDropProposal(operation: UIDropOperation) } // func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession)
DropProposalは4種類ある
- cancel
- copy
- move
- これは同一アプリ内限定
- 移動するように作り手があわせる必要がある
- 送信元が「move可能」とする必要がある
- これは同一アプリ内限定
- forbidden
- Cancelににてるが、特殊なバッジがつく
- 利用例: ReadOnlyなフォルダに移動させようとした場合
SpringLoading(レインボーカーソルと訳すべきか?)
- UIButtonに追加
let button = UIButton() button.isSpringLoaded = true let springLoadedInteraction = UISpringLoadedInteraction { (interaction, context) in //長時間の処理 } view.addInteraction(springLoadedInteraction)
Drop
- drop後は
dropInteraction
が呼び出される- データを読み込む場所
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) { session.loadObjects(ofClass: UIImage.self) { objects in for image in objects as! [UIImage] { self.imageView.image = image } } }
デモ
動画29分からコードを交えながらのデモ
- コルクボードに写真をおくサンプル
- 気になった部分のみメモ
同一アプリ判定
- DropSessionで同一アプリ内か外部アプリかで処理分岐が必要になる
- アプリの仕様によりますけど
//同一アプリ内なら移動、別アプリからならコピー func dropInteraction(..., sessionDidUpdate ...) -> UIDropProposal { let operation: UIDropOperation if session.localDragSession == nil { operation = .copy } else { operation = .move } return UIDropProposal(operation: operation) } //Drop後 func dropInteraction(..., performDrop ...) { if session.localDragSession == nil { //コピー dropPoint = session.location(in: interaction.view) for dragAtime in session.items { loadImage(dragItem.itemProvider, center: dropPoint) } } else { //ボード内での移動、データコピーは不要 } } //Dropアニメーション func dropInteraction(_ ... previewForDropping: ...) { if session.localDragSession == nil { //外部ならなにもしない } else { //内部なら移動処理 return defaultPreview.retargetedPreview(...) } }