I've configured my app so that I can run it in either a GUI mode or a CLI mode, by checking an environment variable at runtime.
I've got it working, at least on the surface, but the GUI version (SwiftUI) doesn't seem to be tied to the parent process, so the parent process terminates straight away and leaves the SwiftUI window orphaned. In real terms, this translates to parts of the app, especially async stuff, not working properly.
Any ideas as to what I'm doing wrong? There isn't many examples of invoking a SwiftUI app explicitly from main() on the web, so I've mostly written this following my nose.
Here's the code:
@main
struct CourierApp {
static func main() async throws {
guard let launchMode = ProcessInfo.processInfo.environment["LAUNCH_MODE"], launchMode == "GUI" else {
try await launchCLI()
return
}
try await launchGUI()
}
private static func launchCLI() async throws {
guard var courierCLI = try? CourierCLI.parse(ProcessInfo.processInfo.arguments) else {
throw ValidationError("Invalid arguments. You should invoke Courier providing first your archive and then the output directory for the program. Optionally, some flags can be provided - see the manual for details.")
}
try await courierCLI.run()
}
private static func launchGUI() async throws {
await CourierGUI.main()
}
}
struct CourierCLI: AsyncParsableCommand {
@Argument var archivePath: String
@Argument var outputPath: String
@Flag var uploadToAppStoreConnect = false
@Option(name: [.customShort("e"), .customLong("entitlements")]) var overrideEntitlements: String? = nil
mutating func run() async throws {
}
func validate() throws {
if let overrideEntitlements {
let arrayEntitlements = overrideEntitlements.split(separator: ",")
guard arrayEntitlements.count <= 2 else {
throw ValidationError("When passing entitlements to override, you must provide a comma-delimited list of entitlements, taking care to only include entitlements to grant - any omitted will not be granted to the app. Example: 'tracking,bluetooth'.")
}
}
}
}
struct CourierGUI: App {
var body: some Scene {
WindowGroup {
MasterView()
.navigationTitle("Courier")
}
.defaultPosition(.center)
}
}
It is possible to use a main.swift to manually invoke your SwiftUI App's main function.
Remove the @main annotation from struct CourierApp and create a main.swift
// Just example App.
struct TheApp: App {
var body: some Scene {
WindowGroup(id: "Root SwiftUI Window") {
}
}
}
if shouldLaunchGUI {
// Here call the SwiftUI App's static main function
TheApp.main()
} else {
guard var courierCLI = try? CourierCLI.parse(ProcessInfo.processInfo.arguments) else {
return
}
courierCLI.run()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With