如何使用 EventChannels 将颜色选择从原生 macOS 颜色选择器流式传输到 Flutter?

How can I stream the selection of colors from the native macOS color picker to Flutter using EventChannels?

我正在尝试向 macos_ui 添加一项功能,允许用户启动本机 macOS 颜色选择器并通过 EventChannel 流回他们的颜色选择。

我可以启动选择器(Cocoa 的 NSColorPanel),但颜色选择没有流回。当我通过 XCode 运行 示例应用程序时,我可以看到颜色选择已注册,但它们似乎没有通过 EventChannel 流回 Flutter。处理这个问题的正确方法是什么?考虑到这些事件发生在本机 macOS 视图中,甚至可以将这些事件流式传输回 Flutter 吗?

这是我目前的 Swift 代码:

MacosUIPlugin.swift

import Cocoa
import FlutterMacOS

public class MacOSUiPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
  private let colorPanelProvider: ColorPanelProvider
  private var eventSink: FlutterEventSink?

  init(colorPanelProvider: ColorPanelProvider) {
    self.colorPanelProvider = colorPanelProvider
    super.init()
  }
  
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(
      name: "dev.groovinchip.macos_ui",
      binaryMessenger: registrar.messenger)

    let colorSelectionChannel = FlutterEventChannel(
      name: "dev.groovinchip.macos_ui/color_panel",
      binaryMessenger: registrar.messenger)
  
    let colorPanelProvider = ColorPanelProvider()
      
    let instance = MacOSUiPlugin(colorPanelProvider: colorPanelProvider)
    colorSelectionChannel.setStreamHandler(instance)
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    switch call.method {
    case "color_panel":
      colorPanelProvider.openPanel()
      result(true)
    default:
      result(FlutterMethodNotImplemented)
    }
  }
  
  public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
    print("listening to MacosUIPluginEvents")
    eventSink = events
    //colorPanelProvider.startStream()
    return nil
  }
  
  public func onCancel(withArguments arguments: Any?) -> FlutterError? {
    eventSink = nil
    return nil
  }
}

extension NSColor {
  var hexString: String {
    let red = Int(round(self.redComponent * 0xFF))
    let green = Int(round(self.greenComponent * 0xFF))
    let blue = Int(round(self.blueComponent * 0xFF))
    let hexString = NSString(format: "#%02X%02X%02X", red, green, blue)
    return hexString as String
  }
}

ColorPanelProvider.swift

import FlutterMacOS

class ColorPanelProvider: NSObject, FlutterStreamHandler {
  var eventSink: FlutterEventSink?
  let colorPanel = NSColorPanel.shared

  func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
    print("listening to ColorPanelProvider events")
    eventSink = events
    return nil
  }

  func openPanel() {
    colorPanel.setTarget(self)
    colorPanel.setAction(#selector(startStream))
    colorPanel.makeKeyAndOrderFront(self)
    colorPanel.isContinuous = true
    startStream()
  }

  @objc private func currentColor() -> String {
    print("currentColor: \(colorPanel.color.asFlutterHexString)")
    return colorPanel.color.asFlutterHexString
  }

  @objc public func startStream() {
    print("starting ColorPanelProvider stream")
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(currentColor),
      name: NSColorPanel.colorDidChangeNotification,
      object: colorPanel)
    
    eventSink?(currentColor())
  }

  func onCancel(withArguments arguments: Any?) -> FlutterError? {
    eventSink = nil
    return nil
  }
}

extension NSColor {
  var asFlutterHexString: String {
    let red = Int(round(self.redComponent * 0xFF))
    let green = Int(round(self.greenComponent * 0xFF))
    let blue = Int(round(self.blueComponent * 0xFF))
    let hexString = NSString(format: "#%02X%02X%02X", red, green, blue)
    return hexString.replacingOccurrences(of: "#", with: "0xFF") as String
  }
}

您一直在正确的轨道上,但有一些事情需要修正。有三件事:

  1. MacosUIPlugin.swift 中,您需要在 register 函数中收听 colorChannelProvider 而不是 instance。所以你需要更改以下行
colorSelectionChannel.setStreamHandler(instance)

对此:

colorSelectionChannel.setStreamHandler(colorChannelProvider)
  1. ColorPanelProvider.swift 中,您可以听到 startStream 中的颜色变化。虽然这是执行此操作的正确方法,但您必须提交发送这些更改的 colorPanel,以便函数可以使用 colorPanel.color 访问颜色。函数签名应如下所示:
@objc public func startStream(colorPanel: NSColorPanel)
  1. 当您将 startStream 方法注册为从 openPanel() 中的 colorPanel 接收更新的目标时,您需要对其进行设置,以便正确调用它。因此,将方法的第二行更改为如下所示,并且一切正常:
colorPanel.setAction(#selector(startStream(colorPanel:)))

希望这会解决所有问题。如果还有问题,请告诉我。