プログラマ英語日記

プログラミング学習のまとめなど

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フェイズ終了。
  • UIDragItemとは?

    • 以下の2つを持つ
      • Dragプレビュー
      • NSItemProvider

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 送信

  • ドラッグが成功したかどうかに応じてDelegateを実装する
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(...)
   }
}