英文:
Hide All Windows by Default in SwiftUI macOS App
问题
我正在开发一个新的仅使用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 {
// 如何默认隐藏此窗口?
Window("Intro", id: "introduction") {
WelcomeView()
}
.windowStyle(.hiddenTitleBar)
Settings {
SettingsView()
}
MenuBarExtra {
MenuBarView()
} label: {
Text("Test")
}
}
}
视图具有.hidden()修饰符 - 但这不支持Windows或WindowGroups。当我的视图被隐藏但包装在Window或WindowGroup中时,会渲染一个空窗口。
是否有办法仅使用纯SwiftUI来实现这一点?还是必须在不应默认打开的情况下以编程方式创建和打开NSWindow?
(注意:由于您的请求,我没有对代码进行翻译,只提供了有关问题的回答。)
英文:
I am working on a new SwiftUI-only menu bar application and stumbled into the following problem; Whenever I define either a Window or WindowGroup in SwiftUI, at least one window is always opened on app launch. Conditional rendering (like if x { Window() }) isn't supported either. The app should have an onboarding Window that is only shown depending on a user defaults setting. And there should be another window that can be manually opened via the menu bar item.
This is my SwiftUI App's class:
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 have the .hidden() modifier - but this doesn't support Windows or WindowGroups. When my view is hidden but wrapped in a Window or WindowGroup an empty window is rendered instead.
Is there any way to achieve this with plain SwiftUI? Or is it necessary to programmatically create and open an NSWindow if it shouldn't be open by default?
答案1
得分: 2
Option 1: 将您的应用程序声明为菜单栏实用程序
将以下内容放入您的 info.plist 文件或项目设置中的 info 选项卡:
Application is agent (UIElement) YES
Option 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
英文:
Option 1: Declare your app as menu bar utility
put this in your info.plist or info tab in project settings:
Application is agent (UIElement) YES
Option 2: Switch activation policy programmatically
Normal app, opens window at launch, has menu, dock item:
NSApp.setActivationPolicy(.regular)
Agent, no window at launch, can be a menu bar app if a status item is set:
NSApp.setActivationPolicy(.accessory)
You could set up a @NSApplicationDelegateAdaptor
and switch it in applicationWillFinishLaunching(_:)
.
Like this:
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)
}
}
}
Then you'd still need a status bar item and put e.g. a toggling menu item for that UserDefaults key.
@AppStorage("activationIsAgent") private var activationIsAgent: Bool = false
答案2
得分: 0
我在纯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()
}
英文:
I did not find a way to do this in pure SwiftUI.
However, here is a light weight approach with 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")
}
}
}
As you can see, there's no dedicated Window
Scene.
If you don't like the titled, closable design, you can close the window with this function in your WelcomeView
, e.g. on a Button
:
Button("Done") {
NSApp.windows.filter { $0.identifier?.rawValue == "introduction" }.first?.close()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论