I'm a complete noob at macOS UI programming. Last time I did that was sometime in the 20th century using Visual Mac Standard Basic or REALbasic…
I'm trying to build a graphical UI to interact with some algorithms written in Swift using SwiftUI. I don't intend on distributing a runnable application bundle, instead the GUI should be launched from the command line using swift run
or similar.
I've managed to get a window with my UI shown on the screen but it doesn't really behave. It doesn't get focus when running the executable from the command line and clicking the window makes it look like the frontmost window but the window from Terminal.app still has the focus and receives keystrokes. There's no app icon in the Dock (even a generic one) and I can't switch to the application using Command-Tab.
What am I missing to get the usual behaviour from the application in this regard?
I'm using macOS 10.15.7 and Xcode 12.4. I want the application to be build using the Swift Package Manager from the command line. Below are the files from the minimal project I'm woking with:
// swift-tools-version:5.3
import PackageDescription
let linkerFlags = [
"-Xlinker", "-sectcreate",
"-Xlinker", "__TEXT",
"-Xlinker", "__info_plist",
"-Xlinker", "Resources/Info.plist"]
let package = Package(
name: "ConnectedRegions",
platforms: [.macOS(.v10_15)],
products: [
.executable(name: "ConnectedRegions", targets: ["ConnectedRegions"])],
targets: [
.target(
name: "ConnectedRegions",
linkerSettings: [.unsafeFlags(linkerFlags)])])
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 200, height: 200),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered,
defer: false)
window.center()
window.contentView = NSHostingView(rootView: Text("Hello").padding(50))
window.makeKeyAndOrderFront(nil)
}
}
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
With the new App
protocol from SwiftUI
in macOS 11, which provides an entry point, this is quite easy to achieve. No Info.plist
is necessary to get a default menu and Dock icon:
import SwiftUI
@main
struct MyApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
VStack {
Button("Done") { exit(0) }
}
.padding(100)
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
NSApplication.shared.setActivationPolicy(.regular)
NSApplication.shared.activate(ignoringOtherApps: true)
}
}