追記(2022-02-09)
-
performDragOperation(_:)はBool値を返すだけで、実際の処理はconcludeDragOperation(_:)
に書くのが良い。
concludeDragOperation(_:)
For this method to be invoked, the previous performDragOperation(_:) must have returned true.
- 終了処理はdraggingEnded(_:)に記載する
概要
- 以前にファイルをNSViewにドラッグしてパスを取得する (Objective-C) - QiitaというObjective-Cの記事を書きました。
- こちらを
Swift
で書き直してみます。
GitHub
参考
- Drag and Drop Tutorial for macOS
- ファイルをNSViewにドラッグしてパスを取得する (Objective-C) - Qiita
-
クリップボードから画像を取得する - Qiita
- Pasteboardのときと同様の実装を行っています
- Mac や iOS でファイルの種類を表す識別子 Uniform Type Identifiers を拡張子から調べる(Swiftで1行で出来る)
ドラッグ・アンド・ドロップを受け付けるNSViewのカスタムクラスの作成
- はじめに、呼び出し元にドラッグされた画像の情報を返すため、プロトコルのメソッドとして宣言しておきます。
protocol DragAndDropViewDelegate: class {
func dragAndDropView(_ view: DragAndDropView,
didDragImageFileURLs draggedImages: [DraggedImage])
}
- 今回、ローカルに保存されている画像ファイルだけでなく画像自体をドラッグした場合にも対応したいため、ドラッグされるアイテムを下記のstructとして扱うようにします。
struct DraggedImage {
let image: NSImage?
let url: URL?
var uti: String? // 拡張子情報
}
-
NSView
のカスタムクラスDragAndDropView
を作成し、ファイルがドラッグされたときの処理を書いていきます。 - 後ほど使用するプロパティを宣言します。
var delegate: DragAndDropViewDelegate?
// Viewがドラッグを許可するファイルのタイプ
let acceptableTypes: [NSPasteboard.PasteboardType] = [.fileURL, .tiff, .png, .URL, .string]
// URL先の画像のUTIが、NSImage.imageTypesのUTIに合致するかという条件
let filteringOptions: [NSPasteboard.ReadingOptionKey : Any] = [.urlReadingContentsConformToTypes : NSImage.imageTypes]
// ViewのUI制御用
var isDragging = false {
didSet {
needsDisplay = true
}
}
-
View
上に画像がドラッグされている間、Viewの周りをハイライトさせるための処理。
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// ドラッグされている場合にハイライトさせる
if isDragging {
NSColor.selectedControlColor.set()
} else {
NSColor.windowFrameColor.set()
}
let path = NSBezierPath(rect: bounds)
path.lineWidth = 5
path.stroke()
}
- ViewにDragできるファイルのタイプを設定します。
required init?(coder: NSCoder) {
super.init(coder: coder)
self.registerForDraggedTypes(acceptableTypes)
}
- ファイルがドラッグされた場合に処理を続けるかを判断するためのメソッドと、UTI判別用のファイルを宣言しておきます。
// MARK: - Helper Methods
func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
var canAccept = false
let pasteBoard = draggingInfo.draggingPasteboard
if pasteBoard.canReadObject(forClasses: [NSURL.self], options: nil) ||
pasteBoard.canReadObject(forClasses: [NSImage.self], options: nil) {
canAccept = true
}
return canAccept
}
func uti(url: URL) -> String? {
guard let r = try? url.resourceValues(forKeys: [.typeIdentifierKey]) else {
return nil
}
return r.typeIdentifier
}
- 最後にドラッグに関する処理を実装します。
class DragAndDropView: NSView {
// (省略)
// MARK:- NSDraggingDestination Protocol Methods
/// Viewの境界にファイルがドラッグされるときに呼ばれる
/// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
let allow = shouldAllowDrag(sender)
isDragging = allow
// NSDragOperation.copy := The data represented by the image can be copied.
return allow ? NSDragOperation.copy : NSDragOperation()
}
/// View上にファイルがドラッグで保持されている間、短い間隔毎に呼ばれるメソッド
/// 宛先がどのドラッグ操作を実行するのかを示す値を返す必要があります。
// override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
// }
/// View上にファイルがドラッグされなくなった際に呼ばれる
override func draggingExited(_ sender: NSDraggingInfo?) {
isDragging = false
}
/// View上でファイルがドロップされた際に呼ばれる
/// メッセージが返された場合はYES、performDragOperation:メッセージが送信されます。
// override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
// }
/// View上でファイルがドロップされた後の処理
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
isDragging = false
let pasteBoard = draggingInfo.draggingPasteboard
// fileURLがドラッグされた場合
if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: filteringOptions) as? [URL],
urls.count > 0,
urls[0].isFileURL {
var draggedImages = [DraggedImage]()
for url in urls {
let uti = self.uti(url: url)
draggedImages.append(DraggedImage(image: nil, url: url, uti: uti))
}
delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
return true
}
// 画像イメージがドラッグされた場合
if let pasteboardItems = pasteBoard.pasteboardItems {
var draggedImages = [DraggedImage]()
for pasteboardItem in pasteboardItems {
for type in pasteboardItem.types {
if let data = pasteboardItem.data(forType: type) {
if let image = NSImage(data: data) {
draggedImages.append(DraggedImage(image: image, url: nil, uti: type.rawValue))
}
}
}
}
if draggedImages.count > 0 {
delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
return true
}
}
// 画像のURL(http://~)がドラッグされた場合
if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options: nil) as? [URL],
urls.count > 0 {
var draggedImages = [DraggedImage]()
for url in urls {
draggedImages.append(DraggedImage(image: nil, url: url, uti: nil))
}
delegate?.dragAndDropView(self, didDragImageFileURLs: draggedImages)
return true
}
return false
}
/// 一連のドラッグ操作が完了したときに呼ばれる
// override func concludeDragOperation(_ sender: NSDraggingInfo?) {
// }
}