This pattern matters because SwiftUI handles the interface well, but macOS menu bar apps still need some AppKit lifecycle control.
This article is built around three concrete goals: use NSApplicationDelegateAdaptor inside a SwiftUI app,
accept drag and drop on a menu bar app, and update the SwiftUI view when a dropped item arrives. That makes it a good bridge article
between pure SwiftUI structure and the older delegate-driven parts of macOS.
In practice, the architecture is simple. SwiftUI still owns the app entry point and the popover content view. The delegate owns the status item, the popover wiring, and the drag-and-drop callbacks. A shared observable object carries the dropped URL back into SwiftUI.
Start with a normal SwiftUI app, then bridge the old delegate API back in with @NSApplicationDelegateAdaptor.
In newer Xcode templates, a SwiftUI app no longer starts with an AppDelegate.swift file. That is fine until you need
lifecycle hooks like applicationDidFinishLaunching, which are especially useful for creating menu bar items only after launch.
This article solves that by adding an adaptor property directly to the SwiftUI app entry point:
@NSApplicationDelegateAdaptor(MenuBar_Drag_and_Drop_DemoApp_AppDelegate.self) var appDelegate
Then the app itself stays mostly standard SwiftUI:
@main
struct MenuBar_Drag_and_Drop_DemoApp: App {
@NSApplicationDelegateAdaptor(MenuBar_Drag_and_Drop_DemoApp_AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Because this is macOS, the article uses NSApplicationDelegateAdaptor. The iOS equivalent would be UIApplicationDelegateAdaptor.
class MenuBar_Drag_and_Drop_DemoApp_AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
}
}
Drag and drop starts at the status item window, so the delegate registers accepted types there and adopts the drag destination protocols.
In this setup, the drop target is effectively the menu bar button's window. The article registers supported types there and assigns the delegate so it can receive the drag callbacks.
func applicationDidFinishLaunching(_ notification: Notification) {
// ... existing setup ...
button.window?.registerForDraggedTypes([.URL, .string])
button.window?.delegate = self
}
The delegate class is then extended to conform to NSPopoverDelegate, NSWindowDelegate, and NSDraggingDestination.
The key method is performDragOperation, which reads the first dropped NSURL from the pasteboard.
extension MenuBar_Drag_and_Drop_DemoApp_AppDelegate: NSPopoverDelegate, NSWindowDelegate, NSDraggingDestination {
func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
return .link
}
func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
return sender.draggingSourceOperationMask
}
func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
if let firstObject = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self])?.first {
if let droppedItem = firstObject as? NSURL {
print(droppedItem.absoluteString ?? "")
self.showHidePopover(nil)
}
return true
}
return false
}
}
At that point, the menu bar app already accepts dropped URLs. The remaining problem is how to get that URL back into the SwiftUI view.
A shared ObservableObject is the bridge between the AppKit delegate world and the SwiftUI view tree.
This article uses a singleton-style helper object so both the delegate and the SwiftUI view can access the same state.
The actual data is just the dropped URL string, marked with @Published so SwiftUI redraws when it changes.
class MenuBar_DroppedDataHelper: NSObject, ObservableObject {
static let shared = MenuBar_DroppedDataHelper()
@Published var droppedURLStr: String?
}
The delegate stores a reference to that shared object:
class MenuBar_Drag_and_Drop_DemoApp_AppDelegate: NSObject, NSApplicationDelegate {
var popover: NSPopover!
var statusBarItem: NSStatusItem!
@ObservedObject var dataHelper = MenuBar_DroppedDataHelper.shared
}
Then the drag handler updates the shared value when a dropped URL is found:
if let firstObject = sender.draggingPasteboard.readObjects(forClasses: [NSURL.self])?.first {
if let droppedItem = firstObject as? NSURL {
print(droppedItem.absoluteString ?? "")
self.dataHelper.droppedURLStr = droppedItem.absoluteString
self.showHidePopover(nil)
}
return true
}
Finally, the SwiftUI view observes the same shared object and renders the URL:
struct ContentView: View {
@ObservedObject var dataHelper = MenuBar_DroppedDataHelper.shared
var body: some View {
VStack(alignment: .center) {
Image(systemName: "checkmark.bubble.fill")
.padding(.bottom)
Text("Here's the dropped URL:")
Text(dataHelper.droppedURLStr ?? "")
}
.padding()
}
}
With that in place, dropping a link on the menu bar item updates the popover content automatically.
The important pattern here is not just drag and drop. It is mixing SwiftUI ownership of the UI with AppKit ownership of the system integration points.
Menu bar apps, popovers, delegate callbacks, and drag-and-drop targets still live close to AppKit. SwiftUI is a great layer for the content, but the article shows how to keep the SwiftUI app structure while still reaching into the delegate lifecycle where macOS expects it.
That is why this example holds up. It gives you a complete path from app launch to menu bar item to dropped data to reactive SwiftUI updates, without forcing you back into an all-AppKit application architecture.