Base on macOS 10.15, Xcode 11.7.
1、首先,NSColorPanel 有一个静态“单例”对象:NSColorPanel.shared。我们可以直接使用这个静态对象。这个静态对象也可以被多个组件共享,但要注意如果共享的话,获得的 color 值也是共享的。
2、但 NSColorPanel 又不纯粹的“单例”模式,它允许我们手动新建 NSColorPanel 对象,而不使用 shared 静态对象。
(手动新建对象之前,最好调用一次 NSColorPanel 的静态函数,或 shared 静态属性,确保已经创建了 shared 静态对象后,才新建对象。否则 NSColorPanel() 返回的就会是 shared 对象。)
3、点击 NSColorWell 对象唤起的 NSColorPanel,似乎就是 NSColorPanel.shared 静态对象。但是几个 NSColorWell 的 color 属性并不会互相影响!我猜测 NSColorWell 组件应该是注册了 NSColorPanel.shared.accessoryView 属性,每次收到新的 NSColorPanel.shared.color 值的时候,会判断当前的 accessoryView 是不是自己。👈 accessoryView 这个猜测被验证是错的。好奇它到底是怎么区分不同的 NSColorWell 的啊。
4、NSColorPanel 继承自 NSPanel,所以它的 isReleasedWhenClosed = false。但实际上,对于手动新建的 NSColorPanel 对象,它是会在窗口关闭后自动释放的!
5、由于 shared 是静态对象,所以 MyNSColorPanel.setPickerMask() 对 shared 对象是无效的。即 NSColorPanel.shared 对象(包括 NSColorWell 弹出的)必定是最全的颜色选择器。
实例代码:
import SwiftUI
struct ColorView: View {
@State var hovered: Bool = false
@State var color1: NSColor = .red
@State var color2: NSColor = .green
@State var color3: NSColor = .blue
@State var coordinator: Coordinator? = nil
#if DEBUG
private let deallocPrinter = DeallocPrinter(forType: String(describing: Self.self))
#endif
var body: some View {
VStack {
Text("颜色啊")
VStack(spacing: 1) {
HStack {
ForEach(Theme.colors, id: \.self) { color in
ZStack {
Rectangle()
.cornerRadius(10)
.foregroundColor(Color(color))
.frame(width: 30, height: 50)
HStack {
Text(color.isLight() ? "L" : "D")
.foregroundColor(color.isLight() ? Color.black : Color.white)
}.font(.body)
}
}
}
Divider()
HStack {
Rectangle().fill(Color(color1)).frame(width: 80, height: 30)
Rectangle().fill(Color(color2)).frame(width: 80, height: 30)
Rectangle().fill(Color(color3)).frame(width: 80, height: 30)
}
HStack {
Button("Color1"){
// let _ = MyNSColorPanel.shared
MyNSColorPanel.setPickerMask([.grayModeMask, .rgbModeMask, .colorListModeMask])
let cp = MyNSColorPanel() // ps: 应该把这个 cp 设置成结存储属性才对。
// 否则这样每次点击 color1 按钮都会新建一个 NSColorPanel 窗口来 =。=#
// 算了,这里懒得改了。😄
log.debug("new address: \(cp)")
log.debug("shared address: \(MyNSColorPanel.shared)")
log.debug("isReleasedWhenClosed? \(cp.isReleasedWhenClosed)")
cp.color = self.color1
cp.setTarget(self.coordinator!)
cp.setAction(#selector(Coordinator.onColorChanged(sender:)))
cp.orderFront(nil)
}.frame(width: 80, height: 30)
Button("Color2"){
let cp = NSColorPanel.shared
log.debug("new address: \(cp)")
log.debug("shared address: \(NSColorPanel.shared)")
cp.color = self.color2
cp.orderFront(nil)
}.frame(width: 80, height: 30)
CustomNSColorWell(color: $color3).frame(width: 80, height: 30)
}
}
}
.onAppear(){
if self.coordinator == nil {
self.coordinator = Coordinator(color: self.$color1)
}
}
.onReceive(NotificationCenter.default.publisher(for: NSColorPanel.colorDidChangeNotification, object: NSColorPanel.shared), perform: { v in
log.debug("接收到 colorpannel 消息: \(v)")
self.color2 = NSColorPanel.shared.color
})
}
// 自定义协同器类
class Coordinator: NSObject {
// 绑定 SwiftUI 中需要交互的数据
@Binding var color: NSColor
// 注意!这是在构造器中传递 @Binding 属性的正确方式
init(color: Binding<NSColor>) {
self._color = color
}
@objc func onColorChanged(sender: NSColorPanel){
log.verbose("Coordinator color1 changed: \(sender)")
self.color = sender.color
}
}
}
struct ColorView_Previews: PreviewProvider {
static var previews: some View {
ColorView()
}
}
struct CustomNSColorWell: NSViewRepresentable {
typealias NSViewType = NSColorWell
@Binding var color: NSColor
func makeCoordinator() -> Coordinator {
return Coordinator(color: $color)
}
func makeNSView(context: Context) -> NSColorWell {
let colorWell = NSColorWell()
colorWell.target = context.coordinator
colorWell.action = #selector(Coordinator.onColorChanged(sender:))
return colorWell
}
func updateNSView(_ nsView: NSColorWell, context: Context) {
nsView.color = color
}
// 自定义协同器类
class Coordinator: NSObject {
// 绑定 SwiftUI 中需要交互的数据
@Binding var color: NSColor
// 注意!这是在构造器中传递 @Binding 属性的正确方式
init(color: Binding<NSColor>) {
self._color = color
}
@objc func onColorChanged(sender: NSColorWell){
log.verbose("NSColorWell color3 changed \(sender)")
self.color = sender.color
}
}
}
class MyNSColorPanel: NSColorPanel {
deinit {
log.debug("释放啦")
}
}