2009-05-24原図をカット&ペーストできる様にするドラッグアウトで爆発アニメーションと共に消去するNVKTableViewに「ドラッグアウトで爆発アニメーションと共に消去する」機能を追加してみます。これは要するにDockと同じユーザインタフェースにしようという事です。テーブルビューのアイテムをドラッグして、ドロップが受け入れられなかった場合は爆発アニメーションを表示し、そのアイテムを削除します。Dockの場合は元のファイルが削除されるわけではありませんが、こちらはモデルオブジェクトが管理対象オブジェクトコンテキストから削除されます。爆発アニメーションはNSShowAnimationEffect()を使えば実現できます。NSDraggingSource非形式プロトコルドラッグ元のオブジェクトがドラッグ操作中に受け取るメッセージはNSDraggingSource非形式プロトコルで定義されています。実装が必須とされているのはdraggingSourceOperationMaskForLocal:メソッドだけで、後はオプションです。実装が必須とされているdraggingSourceOperationMaskForLocal:メソッドを実装していなくてもうまくいっていたのですが、これはNSTableViewが実装してくれていたものと思われます。NVKTableViewではNSDraggingSource非形式プロトコルで規定されているメソッドのうち、以下のメソッドを実装します。draggingSourceOperationMaskForLocal:draggedImage:beganAt:draggedImage:movedTo:draggedImage:endedAt:operation:またドラッグ操作を始めるNSViewのメソッドdragImage:at:offset:event:pasteboard:source:slideBack:をオーバーライドします。このメソッドはNSTableViewが実装してくれているので本来は実装する必要はないのですが、ドラッグ開始時のマウスポインタの座標を知るためにオーバーライドしています。インスタンス変数を追加するドラッグ中に送られてくるメッセージの引数に座標データが含まれているのですが、この座標はドラッグされている画像の原点座標であって、マウスポインタの座標ではありません。処理上で必要なのはマウスポインタの座標なので、これを求めるためにドラッグ開始時の座標データを覚えておく必要があります。そこで、そのためのインスタンス変数startPointを追加します。ドラッグされている画像とマウスポインタの座標の差分を保持するインスタンス変数offsetを追加します。ドラッグ操作中にマウスポインタがテーブルビューの外へでているかどうかでカーソルの形状を変え、ユーザにこれから起こる現象を予告します。そこでマウスポインタがテーブルビューの外へでているかどうかを表すインスタンス変数draggingOutを追加します。*)actionName;@endドラッグ操作をマスクする実装が必須とされているdraggingSourceOperationMaskForLocal:を実装します。これはどんな操作を許すのかというフラグを連結したものを返せばOKです。以下のような定数が定義されているので、必要な操作を論理和で連結して返します。(ただし最新情報はデベロッパドキュメントを参照する事)NSDragOperationNoneNSDragOperationCopyNSDragOperationLinkNSDragOperationGenericNSDragOperationPrivateNSDragOperationAll_Obsolete(廃止予定。代わりにNSDragOperationEveryを使う)NSDragOperationMoveNSDragOperationDeleteNSDragOperationEvery引数は自分のアプリケーション内のドラッグ操作かどうかを表すフラグです。NVKTableViewでは汎用性を考慮して、自分のアプリケーション内では全てのドラッグ操作を許可し、他のアプリケーションへのドラッグ操作はコピーのみ許可する事にします。NSDragOperationCopy;}マウスクリックされた座標を覚えておくドラッグ開始時のマウスポインタの座標を知るためにNSViewのメソッドdragImage:at:offset:event:pasteboard:source:slideBack:をオーバーライドします。引数のeventで渡されるのは、ドラッグ操作を引き起こした左クリックのマウスダウンイベントなので、locationInWindowメソッドでイベントの発生した座標(=クリックされた座標)が得られます。この座標はメソッド名の通りウィンドウ座標なので、スクリーン座標に直してから保存します。スーパークラスのメソッドを呼びだす際にslideBackをNOにしていますが、これはスライドバックする必要がないためです。ドロップを受け入れない場合にドラッグされている画像をもとの場所まで戻すアニメーションをするのがスライドバックですが、ドロップを受け入れない場合は爆発アニメーションと共にオブジェクトを削除するわけですから、スライドバックが不要になります。ドラッグ操作のメソッドで渡される座標はイメージの原点であって、マウスクリックされた座標ではない。そこでマウスクリックされた座標を覚えておく。dragImage:anImageat:imageLocoffset:mouseOffsetevent:theEventpasteboard:pboardsource:sourceObjectslideBack:NO];}ドラッグ操作の初期化処理ドラッグされる画像が表示され、しかしまだ画像をマウスポインタに追随させる処理が始まっていない段階でdraggedImage:beganAt:メッセージが送られてきます。ここでドラッグ操作の初期化処理を行ないます。最初はドラッグアウトされている筈はありませんので、draggingOutをNOで初期化します。引数beganAt:で渡される座標は、ドラッグされる画像の原点で、これはスクリーン座標です。この座標と先ほど保存したstartPointとの差をoffsetとして保存しておきます。aPoint.y;}ドラッグ中の処理ドラッグ操作中はドラッグ元にdraggedImage:movedTo:メッセージが送られてきます。ここではマウスカーソルを変える事でユーザにこれから起こる現象を予告します。movedTo:引数で渡されるスクリーン座標をウィンドウ座標に変換します。これに先ほど計算したオフセットを足して、画像原点の座標をマウスポインタの座標に変換します。得られた座標を更に変換して、自分のビュー座標にします。この座標が自分自身のbounds内にあるかどうかで、マウスポインタがテーブルビューの中にあるか、外にあるかがわかります。マウスポインタが外から中に入ってきた場合は、カーソルを矢印カーソルにします。マウスポインタが中から外へでた場合はカーソルを爆発カーソル(というんでしょうか???)に変えます。最初はこれだけでよいかと思っていたのですが、マウスポインタが自分のウィンドウの外へでるとマウスカーソルが変えられてしまう問題が発生しました。ドロップを受け入れない場合は矢印カーソルに、受け入れる場合は状況に応じたカーソル(コピーの場合はプラスマークのついたカーソル)になります。問題はドロップを受け入れない場合で、この場合は爆発カーソルにしたいわけです。そこでマウスポインタがテーブルビューの外にあって、かつカーソルが矢印カーソルの場合は、カーソルを爆発カーソルに変える様にしました。set];}ドラッグ終了時の処理ドラッグ先にデータ処理の機会が与えられた後で、ドラッグ元にdraggedImage:endedAt:operation:メッセージが送られてきます。爆発アニメーションと共にデータを削除するのは、ドラッグ操作がNSDragOperationNone(何もしない)でマウスポインタがテーブルビューの外にある場合です。この場合にNSShowAnimationEffect()で爆発アニメーションを実行し、マウスカーソルを矢印カーソルに戻します。データの削除処理はデータソースに任せます。NSShowAnimationEffectの引数は以下の様になっています。*contextInfo);animationEffectアニメーションの種類を指定する。NSAnimationEffectPoofで爆発アニメーションになる。centerLocationアニメーションの中心座標をスクリーン座標で指定する。引数の座標にオフセットを足して、マウスポインタの座標としています。sizeアニメーションのサイズ。NSZeroSizeにするとデフォルトのサイズになる。最初、ドラッグ画像のサイズとしましたが、複数の原図をドラッグアウトすると縦長になって見栄えが悪いので、画像の横幅を使った正方形にしました。animationDelegateアニメーション終了後にメッセージが送られるオブジェクト。データの削除処理をデータソースに任せたいので、データソースを指定しています。didEndSelector上記のデリゲートに送られるメッセージのセレクタ。デベロッパドキュメントで指定された通りのセレクタとしています。contextInfo上記セレクタの引数。何か情報を渡したい時に使えます。今回は特に渡したい情報もないのでnilとしています。set];}}データソースに実装するanimationEffectDidEnd:については次のページで説明します。