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

huangapple go评论69阅读模式
英文:

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()
}

huangapple
  • 本文由 发表于 2023年6月26日 01:33:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76551669.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定