MacOS 开发(十六) : 文件拖拽

2,508 阅读2分钟

一、需求来源

文件拖拽的核心是拖拽目标视图 (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
    }
}

DragFileController