I’ve been “rehabbing” a legacy Objective-C app (the Unbound photo browser), trying to make up for a couple years of neglect, and I wanted to start building new views in SwiftUI. There are a lot of good tutorials online for hosting SwiftUI views in UIKit apps (either iOS/iPadOs or macOS + Catalyst), but not much on how to do this for Mac and AppKit… and especially not when you’re still mostly Obj-C!
The first thing to know is: you can’t create SwiftUI Views directly from Objective-C—you’ll have to call into a Swift wrapper. (Adding Swift into an existing Obj-C project is beyond the scope of this post, but there are tutorials available elsewhere.)
In my case, I was trying to create a little preferences window in SwiftUI, so needed to create an NSWindowController
to hold the View
. This meant I needed:
NSWindowController
NSHostingController
(i.e., an AppKit view controller used to host a SwiftUI view hierarchy—this is the AppKit equivalent of UIKit’s UIHostingController
)View
Here’s what that looks like:
import Cocoa
import SwiftUI
class SwiftUIWindowCtrl<RootView: View>: NSWindowController {
convenience init(rootView: RootView) {
let hostingCtrl = NSHostingController(rootView: rootView.frame(width: 400, height: 300))
let window = NSWindow(contentViewController: hostingCtrl)
window.setContentSize(NSSize(width: 400, height: 300))
self.init(window: window)
}
}
@objc class PrefsWindowObjCBridge: NSView {
@objc class func makePrefsWindow() -> NSWindowController {
SwiftUIWindowCtrl(rootView: PrefWindowView())
}
}
From there, I could call it from my Objective-C AppDelegate
like this:
#import "MyApp-Swift.h" // the autogenerated Swift bridging header
. . .
self.prefsWindow = [PrefsWindowObjCBridge makePrefsWindow];
[self.prefsWindow showWindow:self];
Of course, if you’re just dropping your View
into an existing hierarchy, you can use just an NSHostingController
and skip the NSWindowController
.
Happy hacking!