Subclassing NSColor

I'm trying to create a custom NSColor subclass that handles light/dark mode dynamically.

extension NSAppearance {
	var isDarkMode: Bool {
		if #available(macOS 10.14, *) {
			return bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
		}

		return false
	}
}

final class AppearanceSensitiveColor: NSColor {
	private let lightModeColor: NSColor
	private let darkModeColor: NSColor

	private var currentColor: NSColor {
		return NSAppearance.current.isDarkMode ? darkModeColor : lightModeColor
	}

	init(lightModeColor: NSColor, darkModeColor: NSColor) {
		super.init()
		self.lightModeColor = lightModeColor
		self.darkModeColor = darkModeColor
	}

	override init() {
		super.init()
	}

	required init?(coder: NSCoder) {
		fatalError("init(coder:) has not been implemented")
	}

	required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType) {
		fatalError("init(pasteboardPropertyList:ofType:) has not been implemented")
	}

	@nonobjc required convenience init(_colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float) {
		fatalError("init(_colorLiteralRed:green:blue:alpha:) has not been implemented")
	}

	override func highlight(withLevel val: CGFloat) -> NSColor? {
		return currentColor.highlight(withLevel: val)
	}

	override func shadow(withLevel val: CGFloat) -> NSColor? {
		return currentColor.shadow(withLevel: val)
	}

	override func set() {
		currentColor.set()
	}

	override func setFill() {
		currentColor.setFill()
	}

	override func setStroke() {
		currentColor.setStroke()
	}

	// …
}

NSColor require me to implement the following methods (all of which were added with a fix-it in Xcode):

required init?(coder: NSCoder)
required init?(pasteboardPropertyList propertyList: Any, ofType type: NSPasteboard.PasteboardType)
@nonobjc required convenience init(_colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float)

The problem is the last one @nonobjc required convenience init(_colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float), which makes the Swift compiler complain:

Overriding non-@objc declarations from extensions is not supported

I'm just using the required methods Xcode suggested to me. Am I doing something wrong or is it not possible to subclass NSColor in Swift? It seems to me that it's a Swift bug, as I'm required to implement a method that is not actually possible to implement.

2 Likes

FWIW, it compiles for me if I edit the convenience init declaration to be this:

    required convenience init(red: Float, green: Float, blue: Float, alpha: Float) {
        fatalError("init(_colorLiteralRed:green:blue:alpha:) has not been implemented")
    }

(although the first two inits need to be fixed to set the values of the let properties before invoking super.init).

It looks to me like this is a bug in the way that the Swift overlay (or API translator, if there's no explicit overlay) works.

1 Like

It's more a bug in the diagnostic. You must implement a required initializer; you cannot override it; so the only way to get it is to inherit it. In this case that means providing all of NSColor's designated initializers. The compiler should be able to tell you this, though.