'Initializer requirement 'init(json:)' can only be satisfied by a `required` initializer in the definition of non-final class 'UIColor'
I'm trying to write an extension to satisfy a protocol in an extension like so:
extension UIColor: JSONRepresentable {
convenience init?(json: Any) {
guard let colourArray = json as? [CGFloat] else {
print("json was not an array of CGFloats")
return nil
}
self.init(
red: colourArray[0],
green: colourArray[1],
blue: colourArray[2],
alpha: colourArray[3]
)
}
}
I'm getting this error:
Initializer requirement 'init(json:)' can only be satisfied by a required initializer in the definition of non-final class 'UIColor'.
If I add a required
keyword, I get this error
'required' initializer must be declared directly in class 'UIColor' (not in an extension).
Is there a reason for this or any way to work around it?
Edit: Just to be clear, here's the protocol
protocol JSONRepresentable {
init?(json: Any)
}
Solution 1:[1]
struct Color: Codable {
let red, green, blue, alpha: CGFloat
}
extension Color {
var uiColor: UIColor { return UIColor(color: self) }
var cgColor: CGColor { return uiColor.cgColor }
var ciColor: CIColor { return CIColor(color: uiColor) }
var data: Data { return try! JSONEncoder().encode(self) }
}
extension UIColor {
convenience init(color: Color) {
self.init(red: color.red, green: color.green, blue: color.blue, alpha: color.alpha)
}
var color: Color {
let color = CIColor(color: self)
return Color(red: color.red, green: color.green, blue: color.blue, alpha: color.alpha)
}
}
extension Data {
var string: String {
return String(data: self, encoding: .utf8) ?? ""
}
}
Playground testing
let json = """
{"red": 0.5, "green": 0.0, "blue": 0.0, "alpha": 1.0}
"""
if let color = try? JSONDecoder().decode(Color.self, from: Data(json.utf8)) {
print(color) // "Color(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)\n"
print(color.uiColor) // "UIExtendedSRGBColorSpace 0.5 0 0 1\n
print(color.data) // "40 bytes\n"
print(color.data.string) // "{"red":0.5,"alpha":1,"blue":0,"green":0}\n"
}
let redColor = UIColor.red.color
let jsonData = redColor.data.string // "{"red":1,"alpha":1,"blue":0,"green":0}"
If you need to work with your array of CGFloats you can override JSON Encoder and Decoder initializers:
extension Color {
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
red = try container.decode(CGFloat.self)
green = try container.decode(CGFloat.self)
blue = try container.decode(CGFloat.self)
alpha = try container.decode(CGFloat.self)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(red)
try container.encode(green)
try container.encode(blue)
try container.encode(alpha)
}
}
Testing
let values: [CGFloat] = [0.5,0.0,0.0,1.0]
let jsonData = try JSONSerialization.data(withJSONObject: values) // 11 bytes
let json = jsonData.string // "[0.5,0,0,1]"
do {
let color = try JSONDecoder().decode(Color.self, from: jsonData)
print(color) // "Color(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)\n"
print(color.uiColor) // "UIExtendedSRGBColorSpace 0.5 0 0 1\n
print(color.data) // "11 bytes\n"
print(color.data.string) // "[0.5,0,0,1]\n"
let encodedData = try JSONEncoder().encode(color) // 11 bytes
print(encodedData == jsonData) // true
} catch {
print(error)
}
Solution 2:[2]
The message I get for this code is (Swift 5.0)
Initializer requirement 'init(json:)' can only be satisfied by a 'required' initializer in non-final class 'MyColor'
Trying replacing
extension UIColor: JSONRepresentable
with
class MyColor: UIColor, JSONRepresentable
, I've the same message.
It means that the MyColor
class can't be extended 'as this', but if you use the mentioned required
keyword, all subclasses would be mandatory to implement this initializer.
For example, adding final
to the class (=> final class MyColor: UIColor, JSONRepresentable
) you can forgot the 'required
' initializer, because you explicitly mention that there will never be subclasses for 'MyColor
'.
It's nice, it's the principle for protocols, to says (to other developers, but to you too) that some functions are required or not.
Solution 3:[3]
You can make UIColor
(and all of its descendants) Codable
and use embedded JSONEncoder/Decoder to handle it.
import UIKit
extension Encodable where Self: UIColor {
public func encode(to encoder: Encoder) throws {
var r, g, b, a: CGFloat
(r, g, b, a) = (0, 0, 0, 0)
var container = encoder.singleValueContainer()
self.getRed(&r, green: &g, blue: &b, alpha: &a)
try container.encode([r,g,b,a])
}
}
extension Decodable where Self: UIColor {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let components = try container.decode([CGFloat].self)
self = Self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3])
}
}
extension UIColor: Codable { }
Test it
class ColorDescendant: UIColor { }
let testColor = ColorDesedant.green
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let colorAsJSON = try encoder.encode(testColor)
print(String(data: colorAsJSON, encoding: .utf8)!)
let uiColor = try? decoder.decode(ColorDesedant.self, from: colorAsJSON)
uiColor! == testColor
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | tontonCD |
Solution 3 |