Search code examples
swiftmacosdrag-and-dropnsview

Subclassed NSView to notify ViewController of action


I have subclassed NSView to receive a dropped folder in order to get its URL. I've been getting the URL to my ViewController class by accessing a property set in my custom NSView class.

import Cocoa

class DropView: NSView {
    var droppedURL : URL!

    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)

        // Drawing code here.
    }
    public required init?(coder: NSCoder) {
        super .init(coder: coder)
        registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
    }
    
    public override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        return NSDragOperation.copy
    }
    public override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
        NSDragOperation.copy
    }
    public override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        let pboard = sender.draggingPasteboard
        let urlString = pboard.string(forType: NSPasteboard.PasteboardType.fileURL)
        let folderURL = URL(string: urlString!)
        print(folderURL)
        droppedURL = folderURL
        return true
    }
    
}

How can I let my ViewController know when a folder has been dropped onto my NSView and that a URL has been successfully captured? Is there a way other than posting a notification?


Solution

  • Usually you'd use delegates or closures for this. I prefer closures because they're so clean, but it's up to you.

    First, define your closure in DropView:

    class DropView: NSView {
        var droppedURL : URL!
        var droppedSuccessfully: ((URL) -> Void)? /// here!
    

    Then, call it just like how you'd call a function. Make sure to pass in your folderURL too.

    public override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        let pboard = sender.draggingPasteboard
        let urlString = pboard.string(forType: NSPasteboard.PasteboardType.fileURL)
        let folderURL = URL(string: urlString!)
        print(folderURL)
        droppedURL = folderURL
        droppedSuccessfully?(folderURL) /// here!
        return true
    }
    

    Finally, assign the closure back in your ViewController.

    override func viewDidLoad() {
        super.viewDidLoad()
    
        ...
                                             /// prevent retain cycle
        yourDropView.droppedSuccessfully = { [weak self] url in
            print("URL received: \(url)")
        }
    }