I am attempting to add a feature to macos_ui that allows the user to launch the native macOS color picker and stream back their color selections via EventChannel
.
I am able to launch the picker (Cocoa's NSColorPanel
), but the color selection does not get streamed back. When I run the example application via XCode, I can see that the color selections are registered, but they do not seem to be streamed back to Flutter through the EventChannel
. What is the proper way to handle this? Is it even possible to stream these events back to Flutter, given that they take place in a native macOS view?
Here is the Swift code I have so far:
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
}
}
You have been on the right track, but there's a few things that require some fixing. There's three things:
MacosUIPlugin.swift
you need to listen to thecolorChannelProvider
instead of theinstance
in yourregister
function. So you need to change the following lineto this:
ColorPanelProvider.swift
you listen to color changes instartStream
. While that is the correct way to do it you have to hand in thecolorPanel
that sends these changes so that the function can access the color withcolorPanel.color
. The function signature should look like this:startStream
method as the target to receive updates from thecolorPanel
inopenPanel()
you need to set it up so that it will get called correctly. So change the second line of the method to look like this and everything works like a charm:Hopefully, this will fix everything. In case there's anymore problems, please let me know.