在AppKit生命周期Mac应用中用SwiftUI管理NSWindow工具栏的方案
在AppKit生命周期应用中用SwiftUI管理NSWindow工具栏
核心问题解答:可以实现
直接使用NSHostingController而非NSHostingView承载SwiftUI视图,就能让SwiftUI的.toolbar修饰符生效。因为SwiftUI的工具栏配置是通过视图控制器层级传递给窗口的,仅用NSHostingView会缺少这一层的处理逻辑。
具体实现步骤
- 创建包含
.toolbar修饰符的SwiftUI视图 - 实例化
NSHostingController并将SwiftUI视图设为根视图 - 手动创建
NSWindow,将其contentViewController设置为该NSHostingController
代码示例
// 带工具栏的SwiftUI视图 struct SwiftUIToolbarContentView: View { @State private var isActionEnabled = true var body: some View { VStack { Text("SwiftUI Content with Native Toolbar") .padding() } .toolbar { // 主操作区工具栏项 ToolbarItem(placement: .primaryAction) { Button("Perform Action") { print("Action triggered") } .disabled(!isActionEnabled) } // 导航区工具栏项 ToolbarItem(placement: .navigation) { Toggle(isOn: $isActionEnabled) { Text("Enable Action") } } } } } // 在AppKit代码中创建窗口 func setupSwiftUIToolbarWindow() { // 创建HostingController let hostingVC = NSHostingController(rootView: SwiftUIToolbarContentView()) // 创建NSWindow let window = NSWindow( contentRect: NSRect(x: 200, y: 200, width: 600, height: 400), styleMask: [.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false ) // 关联HostingController到窗口 window.contentViewController = hostingVC window.title = "SwiftUI Toolbar Window" window.center() window.makeKeyAndOrderFront(nil) }
替代方案(若上述方法不适用)
1. 无需重构应用,共存SwiftUI管理的窗口
上面的实现本身就支持在AppKit生命周期应用中,按需创建完全由SwiftUI管理的窗口(包括工具栏),和原有AppKit窗口共存,不需要修改整个应用的生命周期。只需在合适的时机(比如菜单点击、按钮触发)调用上述创建窗口的方法即可。
2. 简化AppKit工具栏与SwiftUI状态的同步
使用ObservableObject作为共享状态容器,让SwiftUI视图和AppKit工具栏代码共用同一状态实例,通过@Published属性自动同步状态变更:
代码示例
// 共享状态容器 class ToolbarSharedState: ObservableObject { @Published var isButtonEnabled = true } // SwiftUI视图中使用状态 struct ContentView: View { @StateObject private var sharedState = ToolbarSharedState() var body: some View { Text("SwiftUI Content") .onAppear { // 将状态传递给AppKit工具栏 if let window = NSApp.windows.first(where: { $0.title == "AppKit Toolbar Window" }) { if let customToolbar = window.toolbar as? CustomAppKitToolbar { customToolbar.sharedState = sharedState } } } } } // 自定义AppKit工具栏 class CustomAppKitToolbar: NSToolbar { weak var sharedState: ToolbarSharedState? { didSet { // 监听状态变更,更新工具栏按钮 sharedState?.$isButtonEnabled .receive(on: DispatchQueue.main) .sink { [weak self] isEnabled in self?.updateButtonState(isEnabled: isEnabled) } .store(in: &cancellables) } } private var cancellables = Set<AnyCancellable>() override init(identifier: NSToolbar.Identifier) { super.init(identifier: identifier) setupToolbarItems() } required init?(coder: NSCoder) { super.init(coder: coder) setupToolbarItems() } private func setupToolbarItems() { let actionItem = NSToolbarItem(itemIdentifier: .actionButton) actionItem.label = "Action" actionItem.image = NSImage(systemSymbolName: "gear", accessibilityDescription: nil) actionItem.target = self actionItem.action = #selector(actionButtonTapped) self.items = [actionItem] } private func updateButtonState(isEnabled: Bool) { if let actionItem = items.first(where: { $0.itemIdentifier == .actionButton }) { actionItem.isEnabled = isEnabled } } @objc private func actionButtonTapped() { sharedState?.isButtonEnabled.toggle() } } // 扩展工具栏标识符 extension NSToolbarItem.Identifier { static let actionButton = NSToolbarItem.Identifier("com.example.actionButton") }
内容的提问来源于stack exchange,提问作者zaphoyd
相关产品推荐
相关产品推荐

