So is there another way of conversion without it being explicit?
Lets say, this is the string
var stringValue = "123"
You can convert it into "INT" two ways
first is
let intNumber : Int = Int(stringValue)!
second is
let anotherIntNumber : Int = (stringValue as NSString).integerValue // go with first 1 as this is more of objective-c
If you confirm String to ExpressibleByIntegerLiteral you can just write this:
let myVar: String = 1
print(myVar)
Just note that you need to explicitly provide a type for myVar as compiler doesnt know which type it shouldd choose from.
How do we conform String to ExpressibleByIntegerLiteral? Something like this:
extension String : ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = String(value)
}
}
for the other way round you can look at ExpressibleByStringLiteral
Related
I wanted convert a value type that conform to BinaryFloatingPoint to String, but I noticed there is no initializer for that available from xCode! I used "\()" in this down code, but I what to know is the only way to converting?
func test<T: BinaryFloatingPoint>(value: T) {
let stringValue: String = "\(value)"
print(stringValue)
}
From the other hand I tried to convert a BinaryInteger to String, I found out that there is an initializer for that, But I thought why would we need that initializer even? because we could use "\()" also here without bothering ourself if there is or there is not an initializer!
func test<T: BinaryInteger>(value: T) {
let stringValue: String = String(value)
print(stringValue)
}
Goal: So I want to know why we need String initializer, when we could convert all most everything with "\()"?
No it is not the only way of converting it. You can add another constrain to your generic type requiring it to conform to LosslessStringConvertible as well. Note that all BinaryFloatingPoint types conforms to CustomStringConvertible but not all of them conforms to LosslessStringConvertible (i.e CGFloat).
If you don't care about your method supporting CGFloat you can constrain it LosslessStringConvertible otherwise you need to use CustomStringConvertible's String(describing:) initializer.
This will not support CGFloat
func test<T: BinaryFloatingPoint & LosslessStringConvertible>(value: T) {
let stringValue = String(value)
print(stringValue)
}
This will support all BinaryFloatingPoint floating point types. Note that you don't need to constrain to CustomStringConvertible. It is only for demonstration purposes.
func test<T: BinaryFloatingPoint & CustomStringConvertible>(value: T) {
let stringValue = String(describing: value)
print(stringValue)
}
You can also make CGFloat conform to LosslessStringConvertible as well:
extension CGFloat: LosslessStringConvertible {
private static let formatter = NumberFormatter()
public init?(_ description: String) {
guard let number = CGFloat.formatter.number(from: description) as? CGFloat else { return nil }
self = number
}
}
This will allow you to support all floating point types with your generic method and use the String initializer as well.
I want to know why we need String initializer, when we could convert all most everything with "\()"
Ask yourself what "\()" does. By default it calls an initializer! Namely String(describing:). This in turn depends on CustomStringConvertible etc. See https://developer.apple.com/documentation/swift/string/2427941-init
I was reading through the wonderful blog post by Jon Sundell where he is trying to demonstrate how one can use custom raw values with Swift Enums.
I had a play around with his code and made up a bit of a contrived example to see how robust the Enums with custom raw types are.
I wondered if I can make a type, instance of which can be used as a raw value whilst being expressible by both String and Integer literals. When I implemented ExpressiblebyIntegerLiteral the String part did not work the way I expected and I'm not quite sure why.
Here is the code (the question follows the code):
import Foundation
struct Path: Equatable {
var string: String
var int: Int
}
enum Target: Path {
case content
case resources
case images = "resources/images"
}
extension Path: ExpressibleByIntegerLiteral, ExpressibleByStringLiteral {
init(stringLiteral: String) {
string = stringLiteral
int = 0
}
init(integerLiteral: Int) {
string = ""
int = integerLiteral
}
}
if let stringTarget = Target(rawValue: "content") {
print("stringTarget: \(stringTarget)")
}
if let intTarget = Target(rawValue: 1) {
print("intTarget: \(intTarget)")
}
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
If you remove the conformance to ExpressibleByIntegerLiteral protocol (and the appropriate block of code which initializes intTarget), the stringTarget automagically gets initialized as I expected: into Target.string case.
Could someone please explain to me what is going on here?
Solving your Question
What I expected the above code to produce is both stringTarget and intTarget being initialized to appropriate Enum cases, however, the stringTarget one turns out to be nil.
They aren't nil. They are this: ""
This happens because both the .content and .resources cases are not explicitly defined by a String. And because of this, they both take the ExpressibleByIntegerLiteral route, and are hence defined as this ""
init(integerLiteral: Int) {
string = "" // see
int = integerLiteral
}
Solved for Int
Use this fancy method in place of IntValue(rawValue: 1):
func IntValue(_ this: Int) -> String? {
return Target(rawValue: 0) != nil ? String(describing: Target(rawValue: 0)!) : nil
}
Solved for String
First, conform your enum to CaseIterable, like so:
enum Target: Path, CaseIterable {
Next, use this fancy method in place of Target(rawValue: "content"):
func StringValue(_ this: String) -> String? {
return Target.allCases.contains { this == String(describing: $0) } ? this : nil
}
Truly solved for String
Now, I've removed a crucial bug where case foo = "bar" can be found both as 'foo' or 'bar'. If you don't want this, use this code instead:
func StringValue(_ this: String) -> String? {
var found: String? = nil
_ = Target.allCases.filter {
if let a = Target(rawValue: Path.init(string: this, int: 0)) {
found = String(describing: a)
return this == String(describing: a)
}
found = String(describing: $0)
return this == String(describing: $0)
}
return found
}
Custom Raw Values for Enums
Here's a quick tutorial:
I am wondering if enum can conform it's rawValue to the ClosedRange struct, just like it can conform to String.
enum Foo: ClosedRange<Int> {
case bar = 1...4
}
Obviously this is not an option, since 1...4 is not a literal
This seems to work:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
func overlaps(_ with: Foo) -> Bool { return self.rawValue.overlaps(with.rawValue) }
}
extension ClosedRange: ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public init(stringLiteral value: String) {
let v = value.split(separator: ".")
switch Bound.self {
case is Int.Type: self = (Int(v[0])! as! Bound)...(Int(v[1])! as! Bound)
default: fatalError()
}
}
}
It allows you to do this:
print(Foo.foo.overlaps(Foo.bar))
You can add more types like Double or String using this technique
Side Note: My attempt allows for non-unique rawValues (SR-13212) which is a shame. But I'm not thinking that is fixable:
enum Foo: ClosedRange<Int> {
case foo = "1...3"
case bar = "1...4"
case bar = "1...04" // Duplicate, but Swift allows it.
}
I'd like to implement automatic type conversions between known types in Swift. The C# way of doing it has been overloading type-casting operators. If I want my X type to be cross-assignable with, say, string, I would write:
public class X
{
public static implicit operator string(X value)
{
return value.ToString();
}
public static implicit operator X(string value)
{
return new X(value);
}
}
After that I could write stuff like:
string s = new X();
X myObj = s;
and they would be automatically converted. Is that possible in any way in Swift?
No, the language doesn't provide such functionality for custom types. There is bridging between Objective-C collections and Swift collections but that's baked in and not customizable.
// Swift array of `String` elements
let swiftArray: [String] = ["Bob", "John"]
// Obj-C array of `NSString` elements, but element type information
// is not known to the compiler, so it behaves like an opaque NSArray
let nsArray: NSArray = ["Kate", "Betty"]
// Obj-C array of an `NSString` and an `NSNumber`, element type
// information is not known to the compiler
let heterogeneousNSArray: NSArray = ["World", 3]
// Casting with `as` is enough since we're going from Swift array to NSArray
let castedNSArray: NSArray = swiftArray as NSArray
// Force casting with `as!` is required as element type information
// of Obj-C array can not be known at compile time
let castedSwiftArray: [String] = nsArray as! [String]
// Obj-C arrays can not contain primitive data types and can only
// contain objects, so we can cast with `as` without requiring a
// force-cast with `!` if we want to cast to [AnyObject]
let heterogeneousCastedNSArray: [AnyObject] = heterogeneousNSArray as [AnyObject]
Documentation for type casting is available here.
I think you can achieve what you want to do with initializers.
extension X {
init(string: String) {
self = X(string)
}
}
extension String {
init(x: X) {
// toString is implemented elsewhere
self = x.toString
}
}
let x = X()
let string = "Bobby"
let xFromString: X = X(string: string)
let stringFromX: String = String(x: x)
Not directly related to your question but there is also a family of protocols that start with ExpressibleBy..., enabling you to do things like the following:
Let's say we want to initialize strings from integer literals. We can do that by conforming to and implementing ExpressibleByIntegerLiteral
// Strings can not be initialized directly from integer literals
let s1: String = 3 // Error: Can not convert value of type 'Int' to specified type 'String'
// Conform to `ExpressibleByIntegerLiteral` and implement it
extension String: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
// String has an initializer that takes an Int, we can use that to
// create a string
self = String(value)
}
}
// No error, s2 is the string "4"
let s2: String = 4
A nice use case for ExpressibleByStringLiteral can be found here.
I have a simple question, I have this dictionary:
let dict: [String: Any] = ["Area": "100", "YessorNo" : true]
And, for the key area, I want to cast it's value to a double, like so:
let a = dict["Area"] as? Double
When I print a, I get a nil, why? the value 100 is although a string but it is a number isn't? why can't I cast it to a double?
You can't directly cast a String to a Double, you need to use the proper initializer.
guard let numStr = dict["Area"] as? String else {
return
}
let a = Double(numStr)
First, cast from Any to String, and then use an initializer to convert it to Double.
if let a = dict["Area"] as? String, let aDouble = Double(a) {
print(aDouble)
}
Since the other answers provided the code, I will focus on the why of the question. Type casting won't work because, according to the Swift docs, type casting allows you to "treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy." Since a double is not a subclass or a superclass of a string, you can't cast a string to a double even if that string is a string of a number. This is why trying to cast a string to a double returns nil.
How can I initialize CChar or UInt8 with string literals in Swift?
extension UInt8 : ExtendedGraphemeClusterLiteralConvertible {
public static func convertFromExtendedGraphemeClusterLiteral(value: String) -> UInt8 {
let num = value.unicodeScalars[value.unicodeScalars.startIndex]
return UInt8(num.value)
}
}
let a: UInt8 = "A"
println(a)
This is my try, but I guess it is executed in run-time. How can I do that with zero run-time overhead?
Simply you can't. That's what swift uses for any other data type initializable with a literal. For instance, the String type implements:
extension String : ExtendedGraphemeClusterLiteralConvertible {
static func convertFromExtendedGraphemeClusterLiteral(value: String) -> String
}
and that enables String variables to be initialized with string literals
So, the way you are doing is the right way.