I have made a demo for this article, which has been uploaded to my GitHub: KTColor. If you think it is helpful, please send a STAR to show your support.
UIColor provides several default colors. To create other colors, you typically use RGB and alpha values (hexadecimal colors are also converted to RGB). In Objective-C, this can be done with custom macros, and in Swift, we can take advantage of some of Swift’s syntactic features to simplify the process of creating UIColor objects. I think the ideal solution would be something like this:
override func viewDidLoad(a) {
super.viewDidLoad()
self.view.backgroundColor = "224, 222, 255"
}
Copy the code
workarounds
Unfortunately, this is not currently available in Swift (2.1). As far as I know, Swift3.0 doesn’t support this either, for reasons that will be discussed later. Currently, we can use two workarounds:
self.view.backgroundColor = "224, 222, 255".ktColor / / plan 1
self.view.backgroundColor = "224, 222, 255" as KtColor 2 / / solution
Copy the code
The two are written similarly, but the implementation principle is actually completely different. The first is implemented by extending the String type, and the second by inheriting UIColor.
Scheme 1 has a better code hint, but it makes a change to String. I have the full implementation in my demo, and it supports the following input:
self.view.backgroundColor = "224, 222, 255, 0.5".ktcolor // This is the full version
self.view.backgroundColor = "224, 222, 255".ktcolor // The alpha value defaults to 1
self.view.backgroundColor = "224222255".ktcolor // Can be separated by comma
self.view.backgroundColor = "224, 222, 255".ktcolor // Can be separated by Spaces
self.view.backgroundColor = "#DC143C".ktcolor // Hexadecimal numbers can be used
self.view.backgroundColor = "#dc143c".ktcolor // Letters can be lowercase
self.view.backgroundColor = "SkyBlue".ktcolor // You can use the English name of the color directly
Copy the code
Although scenario 2 does not make changes to existing code, it does not apply to all system types, such as NSDate or NSURL types, and for this reason only the critical logic is implemented in the demo. But this implementation approach is the closest to the ideal solution, and once the time is right, we can get rid of the ugly AS KtColor.
Extended string
The first solution is implemented by extending the String type, it adds a KTColor calculation attribute, mainly involving String segmentation and processing, and some fault tolerance, judgment, etc., these are not the focus of this article, if interested, readers can read the source code for more in-depth understanding.
The advantage of this scheme is that it also works with NSDate, NSURL, and so on. For example, the following code could be implemented using a similar technique:
let date = "The 2016-02-17 24:00:00".ktdate
let url = "http://bestswifter.com".kturl
Copy the code
However, the technology chosen for option one means there is no room for further simplification. If you can’t significantly reduce the amount of code, there’s no reason for it to replace the native solution.
String literals
Plan 2 and the ideal plan follow the same idea: “Create objects using string literals.” I explained this in some detail in this article.
In short, all we need to do is add the following extensions to the UIColor type:
extension UIColor: StringLiteralConvertible {
public init(stringLiteral value: String) {
// The numbers here are arbitrary, and you actually need to parse the string
self.init(red: 0.5, green: 0.8, blue: 0.25, alpha: 1)}public init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
public init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
}
Copy the code
But you’ll get an error like this:
Initializer requirement ‘init(stringLiteral:)’ can only be satisfied by a
required
initializer in the definition of non-final class ‘UIColor’
Xcode’s error sometimes means that ‘UIColor’ is not a class marked final, meaning that it can be inherited. So the init(stringLiteral:) function needs to be marked as required to ensure that all subclasses implement the function. Otherwise, if a subclass does not implement it, then the subclass does not satisfy the StringLiteralConvertible protocol.
Well, we follow Xcode’s instructions and mark each function as required, and a new problem arises:
‘required’ initializer must be declared directly in class ‘UIColor’ (not in an extension)
This is because Swift does not allow you to declare required functions in type extension. The required function must be declared directly inside the class.
This leads to an endless loop, so the current ideal cannot be implemented unless Swift allows the required function to be declared in future extensions.
inheritance
Plan two uses a workaround solution. First create a subclass of UIColor that implements the StringLiteralConvertible protocol, and then assign the subclass object to the parent class:
class KtColor: UIColor.StringLiteralConvertible {
required init(stringLiteral value: String) {
// The numbers here are arbitrary, and you actually need to parse the string
super.init(red: 0.5, green: 0.8, blue: 0.25, alpha: 1)}required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
required convenience init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
required init? (coder aDecoder:NSCoder) {
fatalError("init(coder:) has not been implemented")}required convenience init(colorLiteralRed red: Float, green: Float, blue: Float, alpha: Float) {
self.init(colorLiteralRed: red, green: green, blue: blue, alpha: alpha)
}
}
override func viewDidLoad(a) {
super.viewDidLoad()
self.view.backgroundColor = "224, 222, 255" as KtColor
}
Copy the code
One obvious benefit of this approach is that once Swift has made changes, such as allowing the required function to be declared in extensions, we can achieve the desired solution with only minor changes.
limitations
Inheriting classes from UIKit is not always a viable option. For example, the NSDate class is actually a class cluster.
The major reason for subclassing NSDate is to create a class with convenience methods for working with a particular calendrical system. But you could also require a custom NSDate class for other reasons, such as to get a date and time value that provides a finer temporal granularity. If you want to subclass NSDate to obtain behavior different than that provided by the private or public subclasses, you must do these things:
Make a list of things you don’t want to do, skip 10,000 words…
In short, if you want to inherit NSDate, you have to re-implement it.
In addition to class clusters, classes like NSURL that specify that the constructor is a failable constructor cannot use inheritance:
class KtURL : NSURL.StringLiteralConvertible {
required init(stringLiteral value: StringLiteralType) {
super.init(string: value, relativeToURL: nil)}// Other functions are omitted
}
Copy the code
The constructor defined in the StringLiteralConvertible protocol is a non-failable constructor that does not return nil. It calls the parent class, init(string:relativetoURL:), inside it, which is a failable constructor. Swift doesn’t allow that, otherwise if you pass in an invalid parameter value, do you get nil, what do you get if it’s not nil?
conclusion
Using string literals entirely to create instance variables of an existing class is currently impossible. A similar but limited approach is to use subclasses. Or you can extend strings, but I don’t recommend doing so if you can’t reduce the amount of code significantly compared to the native implementation.
References:
-
Devforums.apple.com/message/105… : This seems to be a question from cat god.
-
Swift: Simple, Safe, Inflexible