一、需求来源
文件拖拽的核心是拖拽目标视图 (DragDestinationView),此方法会检测目标是否可拖拽类型,拖拽文件信息,图像会返回 image,颜色返回 Color,docx,ppt,zip 等返回路径(URL),支持拖拽任意文件。
二、使用示例
import Cocoa
import SnapKit
import SwiftExpand
@available(macOS 10.13, *)
class DragFileController: NSViewController {
lazy var destinationImageView: NSImageView = {
let view = NSImageView()
view.imageScaling = .scaleProportionallyDown
view.layer?.cornerRadius = 8;
return view
}()
lazy var destinationView: DragDestinationView = {
let view = DragDestinationView(types: [.tiff, .color, .string, .fileURL, .html])
view.delegate = self
return view
}()
lazy var textView: NNTextView = {
let view = NNTextView.create(.zero)
view.font = NSFont.systemFont(ofSize: 17, weight: .semibold)
view.isEditable = false
view.backgroundColor = NSColor.systemYellow
view.alignment = .center
view.string = "描述信息"
return view;
}()
// MARK: -lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
view.addSubview(destinationImageView)
view.addSubview(destinationView)
view.addSubview(textView.enclosingScrollView!)
destinationImageView.layer?.backgroundColor = NSColor.white.cgColor
textView.backgroundColor = NSColor.clear
// destinationView.layer?.backgroundColor = NSColor.systemGreen.cgColor
// destinationImageView.layer?.wantsLayer = true
// destinationView.layer?.wantsLayer = true
}
override func viewDidLayout() {
super.viewDidLayout()
destinationView.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
textView.enclosingScrollView?.snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(10)
make.left.equalToSuperview().offset(-10)
make.right.equalToSuperview().offset(-10)
make.height.equalTo(25)
}
destinationImageView.snp.makeConstraints { (make) in
make.top.equalTo(textView.enclosingScrollView!.snp.bottom).offset(10)
make.centerX.equalToSuperview().offset(0)
make.width.equalTo(500)
make.height.equalTo(500)
}
}
}
@available(OSX 10.13, *)
extension DragFileController: DragDestinationViewDelegate {
func processImage(_ image: NSImage, pasteBoard: NSPasteboard) {
destinationImageView.image = image
textView.string = "\(image.sizePixels.width) * \(image.sizePixels.height)"
if let property = pasteBoard.propertyList?.first as? String {
let list = property.components(separatedBy: "/")
textView.string = "\(list.last ?? "")(\(Int(image.size.width))x\(Int(image.size.height)))"
}
}
func process(_ obj: Any, pasteBoard: NSPasteboard) {
switch obj {
case let value as String:
print(#function, #line, "String", value)
textView.string = value
case let value as NSColor:
print(#function, #line, "NSColor", value)
destinationImageView.layer?.backgroundColor = value.cgColor
textView.textColor = value
case let value as URL:
print(#function, #line, "URL", value.absoluteString.removingPercentEncoding ?? "")
textView.string = value.lastPathComponent.removingPercentEncoding ?? ""
default:
print(#function, #line, obj)
break
}
}
}
三、源码
import Cocoa
import CocoaExpand
@objc protocol DragDestinationViewDelegate: NSObjectProtocol {
@objc optional func processImage(_ image: NSImage, pasteBoard: NSPasteboard)
@objc optional func process(_ obj: Any, pasteBoard: NSPasteboard)
}
///拖拽目标视图
@available(macOS 10.13, *)
class DragDestinationView: NSView {
var delegate: DragDestinationViewDelegate?
///支持拖入的类型
var supportedTypes: [NSPasteboard.PasteboardType] = [.tiff, .color, .string, .fileURL, .html]{
willSet{
self.registerForDraggedTypes(newValue)
}
}
///支持拖入的子类型
var acceptableUTITypes: [NSPasteboard.ReadingOptionKey : Any] {
let types = [NSImage.imageTypes,
NSString.readableTypeIdentifiersForItemProvider,
NSURL.readableTypeIdentifiersForItemProvider].flatMap { $0 }
return [NSPasteboard.ReadingOptionKey.urlReadingContentsConformToTypes : types]
}
var isReceivingDrag = false {
didSet {
needsDisplay = true
}
}
// MARK: -lifecycle
convenience init(types: [NSPasteboard.PasteboardType]) {
self.init()
self.registerForDraggedTypes(types)
}
// MARK: -drag
override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return .copy
}
override func draggingExited(_ sender: NSDraggingInfo?) {
isReceivingDrag = false
}
override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
return true
}
// //仅支持 image 可用此方法
// override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
//
// isReceivingDrag = false
// let pasteBoard = sender.draggingPasteboard
// guard let image = NSImage(pasteboard: pasteBoard) else {
// return false
// }
// delegate?.processImage?(image, pasteBoard: pasteBoard)
// return true
// }
override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
isReceivingDrag = false
let pasteBoard = sender.draggingPasteboard
let classArray: [AnyClass] = [NSImage.self, NSColor.self, NSString.self, NSURL.self]
return pasteBoard.readObjects(forClasses: classArray, options: acceptableUTITypes) { (obj) in
if let value = obj as? NSImage {
self.delegate?.processImage?(value, pasteBoard: pasteBoard)
} else {
self.delegate?.process?(obj, pasteBoard: pasteBoard)
}
}
}
override func concludeDragOperation(_ sender: NSDraggingInfo?) {
}
}
public extension NSPasteboard{
///获取拖拽元素的信息
@available(OSX 10.13, *)
var propertyList: [Any]? {
if let board = self.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "NSFilenamesPboardType")) as? [String] {
print("FILE: \(board)")
return board
}
if let board = self.propertyList(forType: .URL) as? [URL] {
print("URL: \(board)")
return board
}
if let board = self.propertyList(forType: .string) as? [String] {
print("STRING: \(board)")
return board
}
if let board = self.propertyList(forType: .html) as? [String] {
print("HTML: \(board)")
return board
}
return nil
}
///拖拽的本地文件路径
@available(OSX 10.13, *)
var draggedFileURL: NSURL? {
if let property = propertyList?.first as? String {
print(property)
return NSURL(fileURLWithPath: property)
}
return nil
}
///解析拖拽到目标视图上的信息同时options 参数限制可拖入子元素的类型( nil 表示不限类型)
func readObjects(forClasses classArray: [AnyClass] = [NSImage.self, NSColor.self, NSString.self, NSURL.self],
options: [NSPasteboard.ReadingOptionKey : Any]? = nil,
handler: ((Any)->Void)? = nil) -> Bool {
guard let pasteboardObjects = readObjects(forClasses: classArray, options: options),
pasteboardObjects.count > 0 else {
return false
}
pasteboardObjects.forEach { (obj) in
switch obj {
case let value as NSImage:
print(#function, #line, "NSImage", value)
handler?(value)
case let value as NSString:
print(#function, #line, "NSString", value)
handler?(value)
case let value as NSColor:
print(#function, #line, "NSColor", value)
handler?(value)
case let value as URL:
print(#function, #line, "URL", value.absoluteString.removingPercentEncoding ?? "")
if let image = NSImage(contentsOfFile: value.path) {
print(#function, #line, image)
handler?(image)
} else {
handler?(value)
}
default:
print(#function, #line, obj)
handler?(obj)
break
}
}
return true
}
}