在SwiftUI的macOS应用中,默认隐藏所有窗口

3
我正在开发一个全新的SwiftUI菜单栏应用程序,并遇到了以下问题:无论何时在SwiftUI中定义Window或WindowGroup,至少会在应用程序启动时打开一个窗口。条件渲染(例如if x { Window() })也不受支持。该应用程序应该有一个入门窗口,仅在用户默认设置下显示。还应该有另一个窗口,可以通过菜单栏项手动打开。
这是我的SwiftUI应用程序类:
import SwiftUI

@main
struct ExampleApp: App {
    @Environment(\.openWindow) var openWindow
    @AppStorage("showIntroduction") private var showIntroduction = true

    init() {
        if showIntroduction {
            print("Show introduction")
            openWindow(id: "introduction")
        }
    }

    var body: some Scene {
        // How to hide this window by default?
        Window("Intro", id: "introduction") {
            WelcomeView()
        }
            .windowStyle(.hiddenTitleBar)
        Settings {
            SettingsView()
        }
        MenuBarExtra {
            MenuBarView()
        } label: {
            Text("Test")
        }
    }
}

Views有.hidden()修饰符,但这不支持Windows或WindowGroups。当我的视图被隐藏,但包装在一个Window或WindowGroup中时,会渲染出一个空窗口。
是否有办法用纯SwiftUI实现这个功能?或者是否需要通过编程方式创建和打开一个NSWindow,如果它不应该默认打开的话?
2个回答

1
选项1:将您的应用程序声明为菜单栏实用程序
将以下内容放入您的info.plist文件或项目设置的信息选项卡中:
Application is agent (UIElement) YES

选项2:通过编程切换激活策略
普通应用程序,在启动时打开窗口,具有菜单和Dock图标。
NSApp.setActivationPolicy(.regular)

代理,启动时没有窗口,如果设置状态项,可以成为菜单栏应用:

NSApp.setActivationPolicy(.accessory)

您可以设置一个 `@NSApplicationDelegateAdaptor` 并在 `applicationWillFinishLaunching(_:)` 中切换它。
像这样:

App.swift

import SwiftUI

@main
struct ExampleApp: App {
    
    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
   
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        Settings {
            SettingsView()
        }
    }
}

AppDelegate.swift

import AppKit

class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationWillFinishLaunching(_ notification: Notification) {
        let isAgent = UserDefaults.standard.bool(forKey: "activationIsAgent")
        print("isAgent: \(isAgent)")
        if isAgent {
            NSApp.setActivationPolicy(.accessory)
        } else {
            NSApp.setActivationPolicy(.regular)
        }
    }
}

然后你仍然需要一个状态栏项目,并为UserDefaults键放置一个切换菜单项。
@AppStorage("activationIsAgent") private var activationIsAgent: Bool = false

2
感谢回复!我已经设置了info.plist,但是在我的AppDelegate中缺少NSApp.setActivationPolicy(.accessory)。现在没有Dock图标了,但是这仍然不能改变第一个窗口自动打开的行为。在我的应用程序主体中,我有两个Windows(而不是WindowGroup)。 - undefined
这完全没有回答作者的问题。 - undefined

1

我没有找到在纯SwiftUI中实现这个的方法。

不过,这里有一个使用NSWindow的轻量级方法:

import SwiftUI

var welcomeWindow: NSWindow?

@main
struct ExampleApp: App {
    @AppStorage("showIntroduction") private var showIntroduction = true
    
    init() {
        if showIntroduction {
            print("Show introduction")
            
            welcomeWindow = NSWindow()
            welcomeWindow?.contentView = NSHostingView(rootView: WelcomeView())
            welcomeWindow?.identifier = NSUserInterfaceItemIdentifier(rawValue: "introduction")
            welcomeWindow?.styleMask = [.closable, .titled]
            welcomeWindow?.isReleasedWhenClosed = true
            welcomeWindow?.center()
            welcomeWindow?.becomeFirstResponder()
            welcomeWindow?.orderFrontRegardless()
        }
    }
    
    var body: some Scene {        
        Settings {
            SettingsView()
                .navigationTitle("Settings")
        }
        MenuBarExtra {
            MenuBarView()
        } label: {
            Text("Test")
        }
    }
}

正如你所见,没有专门的Window场景。

如果你不喜欢有标题和可关闭按钮的设计,你可以在你的WelcomeView中使用这个函数关闭窗口,例如在一个Button上:

Button("Done") {
    NSApp.windows.filter { $0.identifier?.rawValue == "introduction" }.first?.close()
}

这是一个不错的解决方法,但是你会失去一些功能,比如在场景中添加命令或者将所有内容重用于多平台应用程序。 - undefined
我不确定菜单栏应用程序多平台是否能很好地结合在一起,所以这真的是一个损失吗?但我承认这些只是一些权宜之计,如果你在一个简单的AppDelegate生命周期中保持,构建一个菜单栏应用程序会更容易。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接