NSSwitch现在看起来很苍白:

swift - 如何更改NSSwitch的色彩?-LMLPHP

我正在尝试在iOS上更改色调颜色:

swift - 如何更改NSSwitch的色彩?-LMLPHP

但是IB上没有像iOS上的着色颜色部分。

所以我在代码中试试运气:

@IBOutlet weak var alwaysConnectedSwitch: NSSwitch!

override func viewWillAppear() {
    super.viewWillAppear()
    self.alwaysConnectedSwitch.layer?.backgroundColor = CGColor.init(gray: 1, alpha: 1)
}


但是self.alwaysConnectedSwitch.layer似乎为零,因此未设置任何内容。

最佳答案

如果您绝对需要更改样式,我建议您使用第三方自定义控件,该控件可让您根据需要自定义外观。可以找到一些可行的建议here。从10.15开始,NSSwitch根本没有您想要的自定义。如果您不关心细节,请立即停止阅读😅

也就是说,剖析NSSwitch的组件很有趣。与许多可可控件不同,此控件没有后备NSCell。文档明确指出:


  NSSwitch不使用NSCell实例来提供其功能。 cellClass类属性和单元实例属性都返回nil,并且它们忽略设置非nil值的尝试。


因此,它是一个相当现代的控件,由绘制内容的图层组成。有3个NSWidgetView是私有NSView子类。

swift - 如何更改NSSwitch的色彩?-LMLPHP

这些视图主要利用-[NSView updateLayer]根据通过-[NSWidgetView setWidgetDefinition:]馈入的内容提取其支持CALayer所需的值。此方法传入一个值字典,该值字典定义NSWidgetView应如何绘制到图层中。最背面视图的示例字典:

{
    kCUIPresentationStateKey = kCUIPresentationStateInactive;
    kCUIScaleKey = 2;
    kCUIUserInterfaceLayoutDirectionKey = kCUIUserInterfaceLayoutDirectionLeftToRight;
    size = regular;
    state = normal;
    value = 0;
    widget = kCUIWidgetSwitchFill;
}


不幸的是,这意味着样式主要由预定义的字符串确定,如widget = kCUIWidgetSwitchFill;所示。它完全取决于如何根据系统颜色(深色/浅色)或突出显示颜色来绘制此填充颜色或禁用颜色。颜色与NSAppearance相关,并且没有明确的方法可以覆盖这些颜色。

一种解决方案(不推荐使用,严重的请不要这样做)是解决NSWidgetView的updateLayer调用,并在需要的情况下进行其他层的自定义。用Swift编写的示例:

/// Swizzle out NSWidgetView's updateLayer for our own implementation
class AppDelegate: NSObject, NSApplicationDelegate {

    /// Swizzle out NSWidgetView's updateLayer for our own implementation
    func applicationWillFinishLaunching(_ notification: Notification) {
        let original = Selector("updateLayer")
        let swizzle = Selector("xxx_updateLayer")
        if let widgetClass = NSClassFromString("NSWidgetView"),
            let originalMethod = class_getInstanceMethod(widgetClass, original),
            let swizzleMethod = class_getInstanceMethod(NSView.self, swizzle) {
            method_exchangeImplementations(originalMethod, swizzleMethod)
        }
    }
}

extension NSView {
    @objc func xxx_updateLayer() {
        // This calls the original implementation so all other NSWidgetViews will have the right look
        self.xxx_updateLayer()

        guard let dictionary = self.value(forKey: "widgetDefinition") as? [String: Any],
            let widget = dictionary["widget"] as? String,
            let value = (dictionary["value"] as? NSNumber)?.intValue else {
            return
        }

        // If we're specifically dealing with this case, change the colors and remove the contents which are set in the enabled switch case
        if widget == "kCUIWidgetSwitchFill" {
            layer?.contents = nil;
            if value == 0 {
                layer?.backgroundColor = NSColor.red.cgColor;
            } else {
                layer?.backgroundColor = NSColor.yellow.cgColor;
            }
        }
    }
}


再次,不要这样做!您将来必定会遇到麻烦,Apple会在App Store提交中拒绝这样做,并且使用自定义控件可以完全满足您的需要,而无需使用私有API,这将使您更加安全。

07-25 23:41