问题
The NSSwitch looks right now pretty pale:
I'm trying to change the tint colour like this on iOS:
But there is no tint colour section on IB like it is on iOS.
So I'm trying my luck in the code:
@IBOutlet weak var alwaysConnectedSwitch: NSSwitch!
override func viewWillAppear() {
super.viewWillAppear()
self.alwaysConnectedSwitch.layer?.backgroundColor = CGColor.init(gray: 1, alpha: 1)
}
But the self.alwaysConnectedSwitch.layer seems to be nil, so nothing is set.
回答1:
If you absolutely need to change the style, I'd recommend a 3rd-party custom control that lets you customize the look as you desire; some viable suggestions can be found here. As of 10.15, NSSwitch simply doesn't have the customization you desire. If you don't care about details, stop reading now 😅
That said, it's interesting to dissect the components of NSSwitch. Unlike many Cocoa controls, there is no backing NSCell for this control. The documentation says this explicitly:
NSSwitch doesn’t use an instance of NSCell to provide its functionality. The cellClass class property and cell instance property both return nil, and they ignore attempts to set a non-nil value.
So instead, it is a rather modern control made up of layers that draw the content. There are 3 NSWidgetViews which are private NSView subclasses.
These views mostly utilize -[NSView updateLayer] to draw into their backing CALayer desired values based on what they're fed via -[NSWidgetView setWidgetDefinition:]. This method passes in a dictionary of values that defines how NSWidgetView should draw into the layer. An example dictionary for the back-most view:
{
kCUIPresentationStateKey = kCUIPresentationStateInactive;
kCUIScaleKey = 2;
kCUIUserInterfaceLayoutDirectionKey = kCUIUserInterfaceLayoutDirectionLeftToRight;
size = regular;
state = normal;
value = 0;
widget = kCUIWidgetSwitchFill;
}
Unfortunately, this means that the style is mostly determined by predefined strings as seen by widget = kCUIWidgetSwitchFill; It gets quite involved in how this fill color or disabled color is drawn depending on system color (dark/light) or highlight color. The colors are tied to NSAppearance and there isn't a clear way of overriding any of these.
One (not recommended, seriously don't do this) solution is to swizzle out the updateLayer call for NSWidgetView and do your own additional layer customization in the cases that you desire. An example written in 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;
}
}
}
}
Again, DON'T DO THIS! You're bound to break in the future, Apple will reject this in an App Store submission, and you're much more safe with a custom control that does exactly what you want without mucking with private API.
来源:https://stackoverflow.com/questions/59590042/how-to-change-the-tint-colour-of-nsswitch