- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2018-05-07T13:58:18+09:00","default:wikiwriter","wikiwriter")
#author("2018-05-07T16:31:32+09:00","default:wikiwriter","wikiwriter")
&tag(Drag and Drop Tutorial for macOS);
*目次 [#ucabbfd7]
#contents
*関連ページ [#qaf5514b]
*参考情報 [#bc66b812]
-[[Drag and Drop Tutorial for macOS:https://www.raywenderlich.com/136272/drag-and-drop-tutorial-for-macos]]
-NSViewサブクラスのドラッグ&ドロップ実装
-アプリのその他のビューにドラッグできるデータを提供
-カスタムドラッグタイプ
*Getting Started [#laf58f4a]
-同ページからスタータープロジェクトをダウンロード。
-メインのSticker Viewは画像などをドラッグ先。
-画面下部の二つのビューはデータソース。
-Dragging DestinationとDragging Sourcesに大別できる。
-Dragging Destination
--StickerBoardViewController.swift: メインビューコントローラー
--DestinationView.swift: 画面上部のビュー
-Dragging Source
--ImageSourceView.swift: ユニコーンのイメージがついたビュー。
--AppActionSourceView.swift Sparklesというラベルがつい他ビュー
*ペーストボードとドラッギングセッション [#jd409a75]
-ドラッグとドロップはソースとデスティネーションが関連する。
-sourceからドラッグし、NSDraggingSourceプロトコルを実装する。
-destinationにドロップし、NSDraggingDestinationプロトコルを実装する。
-NSPasteboardはデータ交換を円滑する。
*Dragging Destinationの作成 [#lc882280]
-Dragging Destinationは、dragging pasteboardからのデータを受け入れることができるビューまたはWindow。
-NSDraggingDestinationを実装する。
-手順
--ビュー構築時にdragging sessionから受け取るデータ型を宣言する
--dragging imageがビューに入ったとき、ビューがデータを受け入れるかどうかを決定する。
--dragging imageがビューの上になったら、ビューの上でその画像を表示する。
-実装
- DestinationView.swiftを開き、setup()含め以下のように変更する。
#pre{{
var acceptableTypes: Set<String> { return [NSURLPboardType] }
func setup() {
register(forDraggedTypes: Array(acceptableTypes))
}
}}
-さらに以下を追加(ドラッグエンターで呼び出される内部処理)。
#pre{{
//1.受け入れるタイプ。
let filteringOptions = [NSPasteboardURLReadingContentsConformToTypesKey:NSImage.imageTypes()]
func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
var canAccept = false
//2.ペーストボードを取得
let pasteBoard = draggingInfo.draggingPasteboard()
//3.ペーストボードのデータが読み取れるかどうかを決定
if pasteBoard.canReadObject(forClasses: [NSURL.self], options: filteringOptions) {
canAccept = true
}
return canAccept
}
}}
-NSViewはNSDraggingDestinationを実装している。このメソッドをオーバーライドしていく。
-まずdraggingEnteredを実装する。
#pre{{
//1.ドラッグを受け入れるかどうか
var isReceivingDrag = false {
didSet {
needsDisplay = true
}
}
//2.ドラッグがエンター為たときの処理
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
let allow = shouldAllowDrag(sender)
isReceivingDrag = allow
return allow ? .copy : NSDragOperation()
}
}}
-exit処理の作成
#pre{{
override func draggingExited(_ sender: NSDraggingInfo?) {
isReceivingDrag = false
}
}}
-描画処理の変更
#pre{{
override func draw(_ dirtyRect: NSRect) {
if isReceivingDrag {
NSColor.selectedControlColor.set()
let path = NSBezierPath(rect:bounds)
path.lineWidth = Appearance.lineWidth
path.stroke()
}
}
}}
-ここまでで画像をドラッグしたらカーソルが+にかわり、外にでると通常に戻る。
-最後にデータを受け取る処理
-DestinationViewに以下を追加
#pre{{
override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
let allow = shouldAllowDrag(sender)
return allow
}
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
//1.ドラッグ中フラグをリセット
isReceivingDrag = false
let pasteBoard = draggingInfo.draggingPasteboard()
//2.ドロップポイントを取得
let point = convert(draggingInfo.draggingLocation(), from: nil)
//3.データ処理
if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options:filteringOptions) as? [URL], urls.count > 0 {
delegate?.processImageURLs(urls, center: point)
return true
}
return false
}
}}
-StickerBoardViewController.swiftを開き、以下のメソッドを追加。
#pre{{
func processImage(_ image: NSImage, center: NSPoint) {
//1.ラベルを非表示にする
invitationLabel.isHidden = true
//2.サイズを取得
let constrainedSize = image.aspectFitSizeForMaxDimension(Appearance.maxStickerDimension)
//3.NSImageViewを作成して追加
let subview = NSImageView(frame:NSRect(x: center.x - constrainedSize.width/2, y: center.y - constrainedSize.height/2, width: constrainedSize.width, height: constrainedSize.height))
subview.image = image
targetLayer.addSubview(subview)
//4.ランダム
let maxrotation = CGFloat(arc4random_uniform(Appearance.maxRotation)) - Appearance.rotationOffset
subview.frameCenterRotation = maxrotation
}
}}
-以下のイメージも追加。
#pre{{
func processImageURLs(_ urls: [URL], center: NSPoint) {
for (index,url) in urls.enumerated() {
//1.イメージを作成
if let image = NSImage(contentsOf:url) {
var newCenter = center
//2.一つ伊集か
if index > 0 {
newCenter = center.addRandomNoise(Appearance.randomNoise)
}
//3.画像処理
processImage(image, center:newCenter)
}
}
}
}}
*Dragging Sourceの作成 [#iba71ca8]
-ドラッグ元はNSDraggingSourceプロトコルを実装する必要がある。
-ImageSourceViewはユニコーンを持つビュー。ユニコーンをドラッグできるようにする。
-クラスはNSDraggingSourceとNSPasteboardItemDataProviderを実装する必要がある。
-ImageSourceView.swiftに以下のメソッドを実装する。
#pre{{
// MARK: - NSDraggingSource
extension ImageSourceView: NSDraggingSource {
//1.NSDraggingSourceに必要。どのようなドラッグ操作を実装しているか。
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return .generic
}
}
// MARK: - NSDraggingSource
extension ImageSourceView: NSPasteboardItemDataProvider {
//2.この段階ではスタブ
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String) {
//TODO: Return image data
}
}
}}
-ドラッグの開始。
-ImageSourceViewに以下を実装する。
#pre{{
override func mouseDown(with theEvent: NSEvent) {
//1.ペーストボードアイテムを生成(要求に応じてデータを供給する)
let pasteboardItem = NSPasteboardItem()
pasteboardItem.setDataProvider(self, forTypes: [kUTTypeTIFF])
//2.ドラッギングデータを生成
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(self.bounds, contents:snapshot())
//3.ドラッギングセッションを開始
beginDraggingSession(with: [draggingItem], event: theEvent, source: self)
}
}}
-この段階でユニコーンがドラッグできるようになる。
-これを受け入れるためにはDestinationView.swiftでTIFFを受け入れるようにする。
#pre{{
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF)] }
var acceptableTypes: Set<String> { return nonURLTypes.union([NSURLPboardType]) }
}}
-setupを変更。
#pre{{
register(forDraggedTypes: Array(nonURLTypes))
}}
-shouldAllowDragを変更する。
#pre{{
else if let types = pasteBoard.types, nonURLTypes.intersection(types).count > 0 {
canAccept = true
}}
-performDragOperationw変更する
#pre{{
else if let image = NSImage(pasteboard: pasteBoard) {
delegate?.processImage(image, center: point)
return true
}
}}
-この段階でドラッグすると+が表示されるようになる。
-実際にデータを追加できるようにするため、ImageSourceView.swiftのpasteboardメソッドを実装する。
#pre{{
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String) {
//1.
if let pasteboard = pasteboard, type == String(kUTTypeTIFF), let image = NSImage(named:"unicorn") {
//2.
let finalImage = image.tintedImageWithColor(NSColor.randomColor())
//3.
let tiffdata = finalImage.tiffRepresentation
pasteboard.setData(tiffdata, forType:type)
}
}
}}
-さまざまな色のユニコーンが追加できるようになる。
*カスタムデータのドラッグ [#ub2f8668]
-カスタムデータソースの実装。新しいデータタイプ。
-AppActionSourceView.swiftを開く。
#pre{{
enum SparkleDrag {
static let type = "com.razeware.StickerDrag.AppAction"
static let action = "make sparkles"
}
}}
-ドラッグデータソースはUTIで表す。
-AppActionSourceViewに以下を実装。
#pre{{
// MARK: - NSDraggingSource// MARK: - NSDraggingSource
extensionextension AppActionSourceViewAppActionSourceView: : NSDraggingSourceNSDraggingSource {
{
funcfunc draggingSessiondraggingSession((__ session: NSDraggingSession, sourceOperationMaskFor
context: NSDraggingContext) session: NSDraggingSession, sour -> NSDragOperation {
switch(context) {
case .outsideApplication:
return NSDragOperation()
case .withinApplication:
return .generic
}
}
}
}}
-アプリ外にはドロップできないようになっている。
-AppActionSourceViewにmouseDownを実装。
#pre{{
override func mouseDown(with theEvent: NSEvent) {
let pasteboardItem = NSPasteboardItem()
pasteboardItem.setString(SparkleDrag.action, forType: SparkleDrag.type)
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
draggingItem.setDraggingFrame(self.bounds, contents:snapshot())
beginDraggingSession(with: [draggingItem], event: theEvent, source: self)
}
}}
-新しいタイプを受け入れる。DestinationView.swiftを変更する。
#pre{{
var nonURLTypes: Set<String> { return [String(kUTTypeTIFF),SparkleDrag.type] }
}}
-performDragOperation新しいelseifを追加。
#pre{{
else if let types = pasteBoard.types, types.contains(SparkleDrag.type),
let action = pasteBoard.string(forType: SparkleDrag.type) {
delegate?.processAction(action, center:point)
return true
}
}}
-StickerBoardViewControllerにprocessActionを実装。
#pre{{
func processAction(_ action: String, center: NSPoint) {
//1.アクションを確認
if action == SparkleDrag.action {
invitationLabel.isHidden = true
//2.画像を生成
if let image = NSImage(named:"star") {
//3.
for _ in 1..<Appearance.numStars {
//A.
let maxSize:CGFloat = Appearance.maxStarSize
let sizeChange = CGFloat(arc4random_uniform(Appearance.randonStarSizeChange))
let finalSize = maxSize - sizeChange
let newCenter = center.addRandomNoise(Appearance.randomNoiseStar)
//B.
let imageFrame = NSRect(x: newCenter.x, y: newCenter.y, width: finalSize , height: finalSize)
let imageView = NSImageView(frame:imageFrame)
//C.
let newImage = image.tintedImageWithColor(NSColor.randomColor())
//D.
imageView.image = newImage
targetLayer.addSubview(imageView)
}
}
}
}
}}