LoginSignup
4
2

More than 1 year has passed since last update.

画像をNSViewにドラッグして情報を取得する (Swift)

Last updated at Posted at 2020-08-12

追記(2022-02-09)

concludeDragOperation(_:)
For this method to be invoked, the previous performDragOperation(_:) must have returned true.

概要

Aug-12-2020 22-11-12

GitHub

参考

ドラッグ・アンド・ドロップを受け付ける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?) {
    //    }

}
4
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2