1
votes

I know there are a bunch of questions similar to this, but they are all very outdated, basically I have a statusBarItem and I want to make it responsive to dragging and dropping files, here is some of the code I've tried:

extension NSStatusBarButton {
    open override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
        true
    }

    open override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        print("dragging entered")
        return .copy
    }

    open override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        print("Something was dragged!")
        return true
    }

    open override func draggingEnded(_ sender: NSDraggingInfo) {
        print("Draging ended")
    }
}

class DragAndDropButton: NSStatusBarButton {

    open override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
        true
    }

    open override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        print("dragging entered")
        return .copy
    }

    open override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        print("Something was dragged!")
        return true
    }

    open override func draggingEnded(_ sender: NSDraggingInfo) {
        print("Draging ended")
    }
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!
    var popover: NSPopover!
    var statusBarItem: NSStatusItem!


    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
        // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
        let popoverView = PopoverView()

        popover = NSPopover()

        popover.contentSize = NSSize(width: AppConstants.popoverWidth, height: AppConstants.popoverHeight)
        popover.animates = true
        popover.behavior = .transient
        popover.contentViewController = NSHostingController(rootView: popoverView)

        statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))

        let dropSubview = DragAndDropButton()
        dropSubview.registerForDraggedTypes([.png])
        dropSubview.image = NSImage(named: "CognitionSmall")

        statusBarItem.button = drop
//        statusBarItem.view = dropSubview
//        statusBarItem.title = "Test"

        if let button = self.statusBarItem.button {
//            button.view

//            button.registerForDraggedTypes([.multipleTextSelection])
            button.addSubview(dropSubview)
            button.imagePosition = NSControl.ImagePosition.imageLeft
            button.image = NSImage(named: "CognitionSmall")
            button.action = #selector(togglePopover(_:))
//            button.window!.delegate = self
//            button.addSubview(cognitoSubview)
//            button.performDragOperation = #selector(onDrag(_:))
        }
    }

as you can see I've tried to extend and override the default NSStatusBarButton behavior as well as replacing it with my own NSView and adding a subview to the button, but nothing seems to work, does anybody have an idea of how to make this work?

3
Did you try registering a file URL type? .png is PNG image data.Willeke
ah, I didn't know that, I thought it was the file type, I will try itOscar Franco

3 Answers

1
votes

This works on my system using CommandLine (haven't tried it in Xcode)

import Cocoa

class CustomView:NSView {

var filePaths : [String] = []

override init(frame frameRect: NSRect) {
 super.init(frame:frameRect)
 self.registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
}

required init?(coder  aDecoder : NSCoder) {
 super.init(coder: aDecoder)
}

override func draw(_ rect: NSRect) {
 super.draw(rect)
 let bkgrnd = NSBezierPath(rect:rect)
 NSColor.red.set()
 bkgrnd.fill()
 NSColor.black.set()
let str = String("DND")
let font = NSFont.init(name: "Menlo", size:14.0)
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.center
let attrText = NSAttributedString(string:str, attributes: [.paragraphStyle: style, .font:font!])
attrText.draw(in: rect)
}

override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
 print("draggingEntered")
 return .copy
}

override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
 print("draggingUpdated")
 return .copy
}

override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
let board = sender.draggingPasteboard.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "NSFilenamesPboardType")) as? NSArray
var draggedFilePath : String
draggedFilePath = board![0]  as! String
print("draggedFilePath = \(draggedFilePath)")  
return true
}   

}

// **** main.swift **** //
let app = NSApplication.shared
app.setActivationPolicy(NSApplication.ActivationPolicy.regular)
let statusItem = NSStatusBar.system.statusItem(withLength: 100)
var view : CustomView!
view = CustomView(frame:NSMakeRect(0, 0, 100, 40))
statusItem.button?.addSubview(view)
app.run()

1
votes

@Oscar - I revised your original post and can confirm that the following code will add drag and drop capability to an NSStatusBarButton by using an extension and that it obviates the need for an NSView subview. Thanks.

import Cocoa

extension NSStatusBarButton {
 open override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
 true
}

 open override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
 print("Dragging entered.")
 return .copy
}

 open override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
 print("Something was dragged!")
 return true
}

 open override func draggingEnded(_ sender: NSDraggingInfo) {
 print("Dragging ended.")
}
}

class AppDelegate: NSObject, NSApplicationDelegate {
 var window: NSWindow!
 var statusItem: NSStatusItem!

func applicationDidFinishLaunching(_ aNotification: Notification) {
 statusItem = NSStatusBar.system.statusItem(withLength: 100)
 statusItem.button!.title = "DND Test"
 statusItem.button!.registerForDraggedTypes([.fileURL])
}
}
let appDelegate = AppDelegate()

// **** main.swift **** //
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.delegate = appDelegate
app.activate(ignoringOtherApps:true)
app.run()

If the file is saved as statusItem.swift it may be run from Terminal using:

swiftc statusItem.swift -framework Cocoa -o statusItem && ./statusItem

0
votes

Turns out the extension I did to the NSStatusBarButton was fine, with the exception that I did not test it properly, if you declare more registered types it works just fine:

extension NSStatusBarButton {
    open override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
        true
    }

    open override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        print("dragging entered")
        return .copy
    }

    open override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
        print("Something was dragged!")
        return true
    }

    open override func draggingEnded(_ sender: NSDraggingInfo) {
        print("Draging ended")
    }
}

... more initialization code

button.registerForDraggedTypes([.fileURL, .multipleTextSelection, .png])