DSFMenuBuilder

一种使用SwiftUI风格的DSL来为AppKit生成NSMenu实例的工具。

Swift Package Manager

为什么需要它?

我之前在DSFAppKitBuilder中做过类似的事情,所以我想把它提取出来,作为一个独立的微框架。

tl;dr (太长不看) 给我看点实际的例子!

创建一个带有“剪切、复制、粘贴、分隔符、清除选择”菜单项,并包含启用状态和动作的菜单。

 let menu = NSMenu {
    MenuItem("Cut")
       .enabled { [weak self] in self?.hasSelection ?? false }
       .onAction { [weak self] in /* perform cut action */ }
    MenuItem("Copy")
       .enabled { [weak self] in self?.hasSelection ?? false }
       .onAction { [weak self] in /* perform copy action */ }
    MenuItem("Paste")
       .enabled { [weak self] in self?.clipboardHasText ?? false }
       .onAction { [weak self] in /* perform paste action */ }
    Separator()
    MenuItem("Clear selection")
       .onAction { [weak self] in /* clear the current selection */ }
 }
 
 menu.popUp(
    positioning: nil,
    at: .init(x: sender.bounds.minX, y: sender.bounds.maxY),
    in: sender
 )
更复杂的例子
 // A fictional NSViewController that displays an interactive position matrix
 let positionMatrixViewController = PositionMatrixViewController()

 // A menu to be displayed as a submenu of the main menu
 private lazy var presets = Menu {
    MenuItem("Github")
       .onAction { [weak self] in
          // Change the style to github
       }
    }
    MenuItem("BitBucket")
       .onAction { [weak self] in
          // Change the style to bitbucket
       }
    }
 }
 
 let menu = Menu {
    MenuItem("Convert tabs to spaces")
       .onAction { [weak self] in /* perform tabs to spaces */ }
    MenuItem("Convert spaces to tabs")
       .onAction { [weak self] in /* perform spaces to tabs */ }
    Separator()
    ViewItem("Position Matrix", positionMatrixViewController)
    Separator()
    MenuItem("Preset Styles", subMenu: presets)
 }

可用的菜单项类型

Separator(分隔符)

一个基本的分隔符。

MenuItem(菜单项)

一个标准的菜单项,提供以下功能:

MenuItemCollection(菜单项集合)

MenuItemCollection 菜单项是一种为对象集合中的每个“对象”创建菜单项的机制。 例如,你可能有一个名称集合要添加到菜单中,你可以使用MenuItemCollection对象来迭代该集合,并为每个集合项生成一系列菜单项。

 MenuItemCollection(1...4) { [weak self] item in
    MenuItem("Item \(item)")
       .tag(item)
       .onAction {
          Swift.print("\(item)")
       }
    Separator()
 }

ViewItem(视图项)

一个视图项包含一个自定义视图。该视图可以来自 NSViewController 或 SwiftUI 视图。

ViewItem 继承自 MenuItem,因此它拥有 MenuItem 提供的所有属性。

NSViewController 示例

ViewItem("NSViewController menu item title", /* some NSViewController */)

如果你想为ViewItem提供一个动作,你需要手动触发动作,作为你的自定义NSViewController实例的一部分。

override func mouseUp(with event: NSEvent) {
   super.mouseUp(with: event)
   self.performActionForViewItemAndDismiss()
}

SwiftUI 视图

ViewItem("SwiftUI View menu item title", /* some SwiftUI view */)
SwiftUI 视图示例

集成 SwiftUI 视图非常简单,但是将值传入和传出该视图可能有点棘手。

class SwiftUIModel {
   var doubleValue: Double = 20
}

struct SwiftUIMenuItemView: View {
   let model: SwiftUIModel
   @State var currentValue: Double

   init(model: SwiftUIModel) {
      self.model = model
      currentValue = model.doubleValue
   }

   var body: some View {

      let valueBinding = Binding<Double>(
         get: {
            self.currentValue
         },
         set: {
            self.currentValue = $0
            self.model.doubleValue = $0
         }
      )

      VStack(alignment: .leading, spacing: 0) {
         Text("Using a SwiftUI view").font(.callout)
         HStack {
            Slider(value: valueBinding, in: 0 ... 100).controlSize(.small)
               .frame(maxWidth: .infinity)
            Text("\(currentValue, specifier: "%.1f")")
               .frame(maxWidth: 38)
         }
      }
      .padding(EdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 12))
   }
}

let menu = NSMenu {
   ViewItem("SwiftUI", SwiftUIMenuItemView(model: swiftUIModel))
}

License(许可协议)

MIT 许可。 你可以随意使用它,只需在使用时署名我的作品即可。 如果你确实在某个地方使用了它,请告诉我,我很乐意听到这件事!

MIT License

Copyright (c) 2022 Darren Ford

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.