显示/隐藏 SwiftUI 菜单栏使用绑定的额外菜单

菜单栏额外访问

平台 - macOS 13.0 斯威夫特 5.7-5.8 Xcode 14 执照:麻省理工学院

为您提供 SwiftUI MenuBarExtra 的额外访问权限。

  • 通过标准 Bool 绑定以编程方式隐藏、显示或切换菜单
    • 完全兼容一个或多个MenuBarExtra
    • 兼容基于 .menu.window 的样式
  • 内省基础样式NSWindow.window

为什么?

没有第一方 MenuBarExtra API 来获取或设置菜单显示状态,或访问弹出窗口的 NSWindow。(从Xcode 1.14 / SwiftUI 3开始)

如何?

  • 具有隐藏/显示/切换菜单的绑定的新场景修改器.menuBarExtraAccess(isPresented:)
  • 传入引用的新视图修饰符.introspectMenuBarExtraWindow { window in }NSWindow
  • 基于窗口的菜单额外状态项现在在窗口打开时保持突出显示,因此感觉就像本机菜单
  • 不使用私有API,因此它是Mac App Store安全的

开始

该库可作为 Swift Package Manager (SPM) 包提供。

在将库添加到项目或 Swift 包时使用 URL。https://github.com/orchetect/MenuBarExtraAccess

然后导入库:

import SwiftUI
import MenuBarExtraAccess

标准菜单样式

通过单击窗口中的按钮来显示菜单额外菜单的示例:

@main struct MyApp: App {
    @State var isMenuPresented: Bool = false
    
    var body: some Scene {
        WindowGroup {
            Button("Show Menu") { isMenuPresented = true }
        }
        
        MenuBarExtra("MyApp Menu", systemImage: "folder") {
            Button("Menu Item 1") { print("Menu Item 1") }
            Button("Menu Item 2") { print("Menu Item 2") }
        }
        .menuBarExtraStyle(.menu)
        .menuBarExtraAccess(isPresented: $isMenuPresented) // <-- the magic ✨
    }
}

Window Style

An example of a button in the popup window dismissing the popup and performing an action:

@main struct MyApp: App {
    @State var isMenuPresented: Bool = false
    
    var body: some Scene {
        MenuBarExtra("MyApp Menu", systemImage: "folder") {
            MyMenu(isMenuPresented: $isMenuPresented)
            	.introspectMenuBarExtraWindow { window in // <-- the magic ✨
                    window.animationBehavior = .alertPanel
                }
        }
        .menuBarExtraStyle(.window)
        .menuBarExtraAccess(isPresented: $isMenuPresented) // <-- the magic ✨
    }
}

struct MyMenu: View {
    @Binding var isMenuPresented: Bool

    var body: some View {
        Button("Perform Action") { 
            isMenuPresented = false 
            performSomeAction()
        }
    }
}

Multiple MenuBarExtra

MenuBarExtraAccess is fully compatible with one or multiple MenuBarExtra in an app.

Just add an index number parameter to and like this:menuBarExtraAccessintrospectMenuBarExtraWindow

var body: some Scene {
    MenuBarExtra("MyApp Menu A", systemImage: "folder") {
        MyMenu(isMenuPresented: $isMenuPresented)
            .introspectMenuBarExtraWindow(index: 0) { window in // <-- add index 0
                // ...
            }
    }
    .menuBarExtraStyle(.window)
    .menuBarExtraAccess(index: 0, isPresented: $isMenuPresented) // <-- add index 0
    
    MenuBarExtra("MyApp Menu B", systemImage: "folder") {
        MyMenu(isMenuPresented: $isMenuPresented)
            .introspectMenuBarExtraWindow(index: 1) { window in // <-- add index 1
                // ...
            }
    }
    .menuBarExtraStyle(.window)
    .menuBarExtraAccess(index: 1, isPresented: $isMenuPresented) // <-- adde index 1
}

Future

The hope is that Apple implements this feature (and more) in future iterations of SwiftUI!

Until then, a radar has been filed as a feature request: FB11984872

Known Issues

  • When using , the popup menu blocks the runloop so setting the binding to while the menu is presented has no effect. The user must dismiss the menu themself to allow event flow to continue. We have no control over this until Apple decides to change the MenuBarExtra behavior..menuBarExtraStyle(.menu)isPresentedfalse

Author

Coded by a bunch of 🐹 hamsters in a trenchcoat that calls itself @orchetect.

License

Licensed under the MIT license. See LICENSE for details.

Sponsoring

If you enjoy using MenuBarExtraAccess and want to contribute to open-source financially, GitHub sponsorship is much appreciated. Feedback and code contributions are also welcome.

Contributions

Contributions are welcome. Posting in Discussions first prior to new submitting PRs for features or modifications is encouraged.

GitHub

点击跳转