【问题标题】:How to change the tint colour of NSSwitch?如何更改 NSSwitch 的 tint 颜色?
【发布时间】:2020-04-22 16:40:22
【问题描述】:

NSSwitch 现在看起来很苍白:

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

但是 IB 上没有像 iOS 上那样的 tint color 部分。

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

@IBOutlet weak var alwaysConnectedSwitch: NSSwitch!

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

self.alwaysConnectedSwitch.layer 似乎为零,所以没有设置任何内容。

【问题讨论】:

  • 看看这个SO 帖子。也许您可以使用其中一个库或看看它是如何完成的。
  • 它很有趣,但也有风险,因为它已经三年没有更新了。
  • 您是要更改禁用的颜色还是启用的颜色?
  • 禁用颜色。但很高兴知道两者。谢谢
  • @Houman 看起来这变得非常棘手,因为颜色由 NSWidgetView(私有)确定并且它通过 updateLayer 传递。如果我弄清楚它是如何创建特定颜色的,我会告诉你,但没有公共 API。 NSSwitch 也不使用 NSCell 进行绘图,因此没有很多钩子。无论解决方案是什么,最终都会使用私有 API。

标签: swift xcode macos cocoa


【解决方案1】:

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

也就是说,剖析NSSwitch 的组件很有趣。与许多 Cocoa 控件不同,此控件没有支持 NSCell。文档明确说明了这一点:

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

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

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

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

不幸的是,这意味着样式主要由widget = kCUIWidgetSwitchFill; 所见的预定义字符串确定,它完全涉及如何根据系统颜色(暗/亮)或突出显示颜色绘制填充颜色或禁用颜色。颜色与 NSAppearance 相关联,并且没有明确的方法可以覆盖其中任何一个。

一个(不推荐,真的不要这样做)解决方案是调出updateLayer 对 NSWidgetView 的调用,并在您需要的情况下进行自己的附加层自定义。用 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。

【讨论】:

  • 非常有帮助,谢谢卢卡斯。然后我将研究自定义控件。至少有机会设置边界吗? self.alwaysConnectedSwitch.layer?.borderColor = .black 即使这样也行不通。
  • 我实际上同意了您粘贴的链接中的第一个建议。我选择了一个复选框,这也很有意义。但它也带来了问题。 stackoverflow.com/questions/59672767/…
【解决方案2】:

我不完全了解您所面临的问题,但这对我有用:

override func viewDidLoad() {
    super.viewDidLoad()


    zzz.layer!.backgroundColor = NSColor.systemRed.cgColor
    zzz.layer!.masksToBounds = true
    zzz.layer!.cornerRadius = 10

这个结果

您可能希望播放/调整其他边框设置,例如borderWidth 或borderColor

【讨论】:

    猜你喜欢
    • 2013-10-30
    • 2015-11-11
    • 1970-01-01
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-05
    相关资源
    最近更新 更多