How do I get a CustomStringConvertible description from this enum? - swift

I have the following enum
enum Properties: CustomStringConvertible {
case binaryOperation(BinaryOperationProperties),
brackets(BracketsProperties),
chemicalElement(ChemicalElementProperties),
differential(DifferentialProperties),
function(FunctionProperties),
number(NumberProperties),
particle(ParticleProperties),
relation(RelationProperties),
stateSymbol(StateSymbolProperties),
symbol(SymbolProperties)
}
and the structs all look like this
struct BinaryOperationProperties: Decodable, CustomStringConvertible {
let operation: String
var description: String { return operation }
}
So how do I make that enum conform to CustomStringConvertible? I tried with a simple getter but obviously that calls itself and I'd like to call the specific struct's instead.
Bonus points: does an enum defined like that have a name?

Such an enum is called enum with associated values.
I'd implement description by manually switching over the cases:
extension Properties: CustomStringConvertible {
var description: String {
switch self {
case .binaryOperation(let props):
return "binaryOperation(\(props))"
case .brackets(let props):
return "brackets(\(props))"
...
}
}
}
Edit: an alternative is to use Swift's Mirror reflection API. The enum case of an instance is listed as the mirror's child and you can print its label and value like this:
extension Properties: CustomStringConvertible {
var description: String {
let mirror = Mirror(reflecting: self)
var result = ""
for child in mirror.children {
if let label = child.label {
result += "\(label): \(child.value)"
} else {
result += "\(child.value)"
}
}
return result
}
}
(This is a generic solution that should be usable for many types, not just enums. You'll probably have to add some line breaks for types that have more than a single child.)
Edit 2: Mirror is also what print and String(describing:) use for types that don't conform to Custom[Debug]StringConvertible. You can check out the source code here.

Related

SOLVED - Swift Enum - Casting Nested Enums to String Enum to allow .rawValue

SOLVED
Thank you #New Dev and #Joakim Danielson for your help. I used #Joakim Danielson's answer to improve my code.
I have an extension method to assign accessibilityIdentifiers to views based on a given String Enum. I updated the method to directly accept String Enum Cases as a parameter, thus COMPLETELY eliminating the need for the AccessibilityId enum class as shown below, awesome!
Changes
Before:
.accessibility(identifier: .home(.clickButton))
// Simplified for StackOverflow.
// Imagine 20 more cases..
enum AccessibilityId {
case home(HomeId)
var rawValue: String {
switch self {
case .home(let id):
return id.rawValue
}
}
}
extension View {
func accessibility(identifier: AccessibilityId) -> ModifiedContent<Self, AccessibilityAttachmentModifier> {
self.accessibility(identifier: identifier.rawValue)
}
}
After:
.accessibility(identifier: HomeId.clickButton)
extension View {
func accessibility<T: RawRepresentable>(identifier: T) -> ModifiedContent<Self, AccessibilityAttachmentModifier> where T.RawValue == String {
self.accessibility(identifier: identifier.rawValue)
}
}
---------------------------------------------------------------
Original Question
What I have
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case .animal(let id):
return id.rawValue
case .vehicle(let id):
return id.rawValue
case .food(let id):
return id.rawValue
}
}
}
enum AnimalId: String {
case cat
case dog
}
// etc.
// Imagine more cases and more enums.
What I want
enum Item {
case animal(AnimalId)
case vehicle(VehicleId)
case food(FoodId)
var rawValue: String {
switch self {
case self as StringEnum:
return id.rawValue
default:
return ""
}
}
}
Usage
func test() {
foo(.animal(.cat))
foo(.vehicle(.plane))
foo(.food(.tacos))
}
func foo(_ item: Item) {
print(item.rawValue)
}
I am happy with the usage, but I'd like to reduce the amount of duplicate cases in the given switch statement. Notice how they all have return id.rawValue. The above is just an example, in reality I have around 30 cases.
My Question
Is there a way for me to catch all Nested String Enums in a single switch or let case to reduce the duplicate code I have to write without losing the intended usage?
Thank you for your efforts, I hope to find an improvement for my code!
Here is a solution that is not based on Item being an enum but instead a generic struct
struct Item<T: RawRepresentable> where T.RawValue == String {
let thing: T
var rawValue: String {
thing.rawValue
}
}
With this solution you don't need to change your other enums.
Example
let item1 = Item(thing: AnimalId.cat)
let item2 = Item(thing: VehicleId.car)
print(item1.rawValue, item2.rawValue)
outputs
cat car
You need something common between all these associated values, like a conformance to a shared protocol, e.g. protocol RawStringValue:
protocol RawStringValue {
var rawValue: String { get }
}
// String enums already conform without any extra implementation
extension AnimalId: RawStringValue {}
extension VehicleId: RawStringValue {}
extension FoodId: RawStringValue {}
Then you could create a switch self inside like so:
var rawValue: String {
switch self {
case .animal (let id as RawStringValue),
.vehicle (let id as RawStringValue),
.food (let id as RawStringValue):
return id.rawValue
}
}
That being said, enum with associated values isn't the most convenient type to work with, so be sure that it's the right choice.

Unable to make a protocol for enum

I am not very good with protocols, and I'm quite stuck right now. I am trying to make a protocol for an enum, which has a name property (or function if it needs to be).
This will allow me to get the name of an enum value, similar to conforming to String alone, but with the first letter capitalised instead. For example, what I want to happen:
enum Furniture: Named {
case table, chair, lamp
}
Furniture.table.name // Returns 'Table'
Furniture.chair.name // Returns 'Chair'
Furniture.lamp.name // Returns 'Lamp'
Here is what I have so far:
protocol Named {
var name: String { get }
}
extension Named where Named: String {
var name: String {
let str: String = self.rawValue
return str.prefix(1).uppercased() + str.dropFirst()
}
}
This gives me errors, and when I try to correct them, another error pops up.
Right now, this shows me the following errors:
Type 'Named' constrained to non-protocol, non-class type 'String'
Value of type 'Self' has no member 'rawValue'
How can I fix this?
Enums get their raw value via the RawRepresentable protocol, so that's what you should use when constraining your extension:
extension Named where Self: RawRepresentable, RawValue == String {
var name: String {
let str: String = self.rawValue
return str.prefix(1).uppercased() + str.dropFirst()
}
}
You also need to declare that your enum uses a String as the type of its rawValue:
enum Furniture: String, Named {
case table, chair, lamp
}
Set the Furniture enum to a string raw type, the Swift compiler automatically adds RawRepresentable conformance.
protocol Named {
var name: String { get }
}
enum Furniture: String, Named {
case table, chair, lamp
}
extension Named where Self: RawRepresentable, RawValue == String {
var name: String {
return rawValue.capitalized
}
}
A Unit Tests to validate the Furniture enum,
import XCTest
class FurnitureTests: XCTestCase {
func testEnumDisplayNameFirstLetterCapitlized() {
XCTAssertEqual(Furniture.table.name, "Table")
XCTAssertEqual(Furniture.chair.name, "Chair")
XCTAssertEqual(Furniture.lamp.name, "Lamp")
}
func testEnumRawValue() {
XCTAssertEqual(Furniture.table.rawValue, "table")
XCTAssertEqual(Furniture.chair.rawValue, "chair")
XCTAssertEqual(Furniture.lamp.rawValue, "lamp")
}
}

Is there a way to simplify this 'matrix of overloads' based on argument types which are all ultimately representable by a specific type?

We're trying to create a function addQueryItem which ultimately uses a string and an optional string internally.
For more flexibility in the API, rather than use String for the argument types, we are instead using CustomStringConvertible (which String implements) so we can use anything that can be represented as a string.
Additionally, so we can pass it String-based enums, we also want it to accept RawRepresentable types where RawValue is a CustomStringConvertible itself.
However, since we're now technically accepting two different kinds of values for each parameter, we end up having to create a 'matrix of overloads'--four total--for each combination of the two types.
My first thought was to use protocol-oriented programming by extending RawRepresentable so it adheres to CustomStringConvertible if its RawValue was also a CustomStringConvertible. Then I could just pass that directly to the version which takes two CustomStringConvertible arguments and eliminate the other three. However, the compiler didn't like it because I'm trying to extend a protocol, not a concrete type.
// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {
var description: String {
return self.rawValue
}
}
As a result of not being able to do the above, as mentioned, I have to have all four of the following:
func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){
if let valueAsString = value.flatMap({ String(describing:$0) }) {
queryItems.append(name: String(describing:name), value: valueAsString)
}
}
func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
addQueryItem(name: name.rawValue, value: value)
}
func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {
addQueryItem(name: name, value: value?.rawValue)
}
func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
TValue.RawValue:CustomStringConvertible
{
addQueryItem(name: name.rawValue, value: value?.rawValue)
}
So, since it doesn't look like it's possible to make RawRepresentable to adhere to CustomStringConvertible, is there any other way to solve this 'matrix-of-overloads' issue?
To expand on my comments, I believe you're fighting the Swift type system. In Swift you generally should not try to auto-convert types. Callers should explicitly conform their types when they want a feature. So to your example of an Order enum, I believe it should be implemented this way:
First, have a protocol for names and values:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
Now for string-convertible enums, it's nice to not have to implement this yourself.
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
But, for type-safety, you need to explicitly conform to the protocol. This way you don't collide with things that didn't mean to be used this way.
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
Now maybe QueryItems really has to take strings. OK.
class QueryItems {
func append(name: String, value: String) {}
}
But the thing that wraps this can be type-safe. That way Order.buy and Purchase.buy don't collide (because they can't both be passed):
class QueryBuilder<Name: QueryName, Value: QueryValue> {
var queryItems = QueryItems()
func addQueryItem(name: QueryName, value: QueryValue?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
You can use the above to make it less type-safe (using things like StringCustomConvertible and making QueryBuilder non-generic, which I do not recommend, but you can do it). But I would still strongly recommend that you have callers explicitly tag the types they plan to use this way by explicitly labelling (and nothing else) that they conform to the protocol.
To show what the less-safe version would look like:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
extension QueryName where Self: CustomStringConvertible {
var queryName: String { return self.description }
}
extension QueryValue where Self: CustomStringConvertible {
var queryValue: String { return self.description }
}
class QueryItems {
func append(name: String, value: String) {}
}
class QueryBuilder {
var queryItems = QueryItems()
func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
No, you cannot conform a protocol to another protocol via an extension. The language does not support it.

Multiple enum implementing protocols questions

I defined enums as confirming to a protocol Eventable:
protocol Eventable {
var name: String { get }
static var all: [Eventable] { get }
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
var name: String {
return self.rawValue
}
static var all: [Eventable] {
return [
MyEnum.bla,
MyEnum.blu
]
}
}
I have other enums like MyEnum also under the form:
enum Bla: String, Eventable {
}
I have two questions:
for the enums having a String data type, I would like to avoid duplicating the generation of the variable name:
var name: String
I am not sure how to write that in Swift. I tried to play around with the "where" clause but not success. How can I achieve that?
when I write my enums and conform to the protocol for that part:
static var all: [Eventable] { get }.
I would like that for the enum MyEnum, it constrains the variable to:
static var all: [MyEnum] { ... }
because for now I can put in the returned array any element being an Eventable and it's not what I need.
Amongst other things, I tried to define a generic constraint in the protocol for it, but I get the following error:
Protocol 'Eventable' can only be used as a generic constraint because
it has Self or associated type requirements
Thank you very much for the help!
For your second question, you just need to use Self:
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
Self, similar to self, just means "the current type".
The first question is a little bit harder because you can't really get all the values of an enum safely. See here for more info. The closest I got was:
extension Eventable where Self: RawRepresentable, Self.RawValue == String {
var name: String {
return self.rawValue
}
}
This means that you can omit the declaration of name in MyEnum, but not all.
For the part of the question we need to extend Eventable protocol where Self inherits RawRepresentable
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
extension Eventable where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
static var all: [MyEnum] = [bla, blu]
}
For the second part of your question we need to configure the function to handle a generic type
I would suggest making the function generic as well here is an example
func printEnum<T: Eventable>(_ event: T) {
print(event.name)
}
and we can use it for any object confirms to Eventable
printEnum(MyEnum.bla)

How to get the name of enumeration value in Swift?

If I have an enumeration with raw Integer values:
enum City: Int {
case Melbourne = 1, Chelyabinsk, Bursa
}
let city = City.Melbourne
How can I convert a city value to a string Melbourne? Is this kind of a type name introspection available in the language?
Something like (this code will not work):
println("Your city is \(city.magicFunction)")
> Your city is Melbourne
As of Xcode 7 beta 5 (Swift version 2) you can now print type names and enum cases by default using print(_:), or convert to String using String's init(_:) initializer or string interpolation syntax. So for your example:
enum City: Int {
case Melbourne = 1, Chelyabinsk, Bursa
}
let city = City.Melbourne
print(city)
// prints "Melbourne"
let cityName = "\(city)" // or `let cityName = String(city)`
// cityName contains "Melbourne"
So there is no longer a need to define & maintain a convenience function that switches on each case to return a string literal. In addition, this works automatically for any enum, even if no raw-value type is specified.
debugPrint(_:) & String(reflecting:) can be used for a fully-qualified name:
debugPrint(city)
// prints "App.City.Melbourne" (or similar, depending on the full scope)
let cityDebugName = String(reflecting: city)
// cityDebugName contains "App.City.Melbourne"
Note that you can customise what is printed in each of these scenarios:
extension City: CustomStringConvertible {
var description: String {
return "City \(rawValue)"
}
}
print(city)
// prints "City 1"
extension City: CustomDebugStringConvertible {
var debugDescription: String {
return "City (rawValue: \(rawValue))"
}
}
debugPrint(city)
// prints "City (rawValue: 1)"
(I haven't found a way to call into this "default" value, for example, to print "The city is Melbourne" without resorting back to a switch statement. Using \(self) in the implementation of description/debugDescription causes an infinite recursion.)
The comments above String's init(_:) & init(reflecting:) initializers describe exactly what is printed, depending on what the reflected type conforms to:
extension String {
/// Initialize `self` with the textual representation of `instance`.
///
/// * If `T` conforms to `Streamable`, the result is obtained by
/// calling `instance.writeTo(s)` on an empty string s.
/// * Otherwise, if `T` conforms to `CustomStringConvertible`, the
/// result is `instance`'s `description`
/// * Otherwise, if `T` conforms to `CustomDebugStringConvertible`,
/// the result is `instance`'s `debugDescription`
/// * Otherwise, an unspecified result is supplied automatically by
/// the Swift standard library.
///
/// - SeeAlso: `String.init<T>(reflecting: T)`
public init<T>(_ instance: T)
/// Initialize `self` with a detailed textual representation of
/// `subject`, suitable for debugging.
///
/// * If `T` conforms to `CustomDebugStringConvertible`, the result
/// is `subject`'s `debugDescription`.
///
/// * Otherwise, if `T` conforms to `CustomStringConvertible`, the result
/// is `subject`'s `description`.
///
/// * Otherwise, if `T` conforms to `Streamable`, the result is
/// obtained by calling `subject.writeTo(s)` on an empty string s.
///
/// * Otherwise, an unspecified result is supplied automatically by
/// the Swift standard library.
///
/// - SeeAlso: `String.init<T>(T)`
public init<T>(reflecting subject: T)
}
See the release notes for info about this change.
There is no introspection on enum cases at the moment. You will have to declare them each manually:
enum City: String, CustomStringConvertible {
case Melbourne = "Melbourne"
case Chelyabinsk = "Chelyabinsk"
case Bursa = "Bursa"
var description: String {
get {
return self.rawValue
}
}
}
If you need the raw type to be an Int, you will have to do a switch yourself:
enum City: Int, CustomStringConvertible {
case Melbourne = 1, Chelyabinsk, Bursa
var description: String {
get {
switch self {
case .Melbourne:
return "Melbourne"
case .Chelyabinsk:
return "Chelyabinsk"
case .Bursa:
return "Bursa"
}
}
}
}
In Swift-3 (tested with Xcode 8.1) you can add the following methods in your enum:
/**
* The name of the enumeration (as written in case).
*/
var name: String {
get { return String(describing: self) }
}
/**
* The full name of the enumeration
* (the name of the enum plus dot plus the name as written in case).
*/
var description: String {
get { return String(reflecting: self) }
}
You can then use it as a normal method call on your enum instance.
It might also work in previous Swift versions, but I haven't tested it.
In your example:
enum City: Int {
case Melbourne = 1, Chelyabinsk, Bursa
var name: String {
get { return String(describing: self) }
}
var description: String {
get { return String(reflecting: self) }
}
}
let city = City.Melbourne
print(city.name)
// prints "Melbourne"
print(city.description)
// prints "City.Melbourne"
If you want to provide this functionality to all your enums, you can make it an extension:
/**
* Extend all enums with a simple method to derive their names.
*/
extension RawRepresentable where RawValue: Any {
/**
* The name of the enumeration (as written in case).
*/
var name: String {
get { return String(describing: self) }
}
/**
* The full name of the enumeration
* (the name of the enum plus dot plus the name as written in case).
*/
var description: String {
get { return String(reflecting: self) }
}
}
This only works for Swift enums.
The String(describing:) initializer can be used to return the case label name even for enums with non-String rawValues:
enum Numbers: Int {
case one = 1
case two = 2
}
let one = String(describing: Numbers.one) // "one"
let two = String(describing: Numbers.two) // "two"
Note that this does not work if the enum uses the #objc modifier:
Getting String name of Objective-C #objc enum value in Swift?
Why is an Enum returning “EnumName” rather than “caseLabel” for String(describing:)?
Generated Swift interfaces for Objective-C types sometimes do not include the #objc modifier. Those Enums are nevertheless defined in Objective-C, and thus do not work like above.
For Objective-C enums the only way currently seems to be, for example, to extend the enum with CustomStringConvertible ending up with something like:
extension UIDeviceBatteryState: CustomStringConvertible {
public var description: String {
switch self {
case .Unknown:
return "Unknown"
case .Unplugged:
return "Unplugged"
case .Charging:
return "Charging"
case .Full:
return "Full"
}
}
}
And then casting the enum as String:
String(UIDevice.currentDevice().batteryState)
On top of the String(…) (CustomStringConvertible) support for enums in Swift 2.2, there's also somewhat broken reflection support for them. For enum cases with associated values it is possible to get the label of the enum case using reflection:
enum City {
case Melbourne(String)
case Chelyabinsk
case Bursa
var label:String? {
let mirror = Mirror(reflecting: self)
return mirror.children.first?.label
}
}
print(City.Melbourne("Foobar").label) // prints out "Melbourne"
By being broken, I however meant that for "simple" enums, the above reflection based label computed property just returns nil (boo-hoo).
print(City.Chelyabinsk.label) // prints out nil
The situation with reflection should be getting better after Swift 3, apparently. The solution for now though is String(…), as suggested in one of the other answers:
print(String(City.Chelyabinsk)) // prints out Cheylabinsk
I bumped into this question and wanted to share a simple way to create the magicFunction mentioned
enum City: Int {
case Melbourne = 1, Chelyabinsk, Bursa
func magicFunction() -> String {
return "\(self)"
}
}
let city = City.Melbourne
city.magicFunction() //prints Melbourne
Swift now has what are known as Implicitly Assigned Raw Value. Basically if you don't give raw values to each case and the enum is of type String, it deduces that the case's raw value is itself in string format. Go on give it a try.
enum City: String {
case Melbourne, Chelyabinsk, Bursa
}
let city = City.Melbourne.rawValue
// city is "Melbourne"
This is so disappointing.
For the case when you need those names (that the compiler perfectly knows the exact spelling of, but refuses to let access -- thank you Swift team!! --) but do not want or cannot make String the base of your enum, a verbose, cumbersome alternative is as follows:
enum ViewType : Int, Printable {
case Title
case Buttons
case View
static let all = [Title, Buttons, View]
static let strings = ["Title", "Buttons", "View"]
func string() -> String {
return ViewType.strings[self.rawValue]
}
var description:String {
get {
return string()
}
}
}
You can use the above as follows:
let elementType = ViewType.Title
let column = Column.Collections
let row = 0
println("fetching element \(elementType), column: \(column.string()), row: \(row)")
And you'll get the expected result (code for the Column similar, but not shown)
fetching element Title, column: Collections, row: 0
In the above, I have made the description property refer back to the string method, but this is a matter of taste. Also note that so called static variables need to be scope qualified by the name of their enclosing type, as the compiler is too amnesic and cannot recall the context all by itself...
The Swift team must really be commanded. They created enum that you cannot enumerate and that which you can use enumerate on are "Sequences" but not enum!
For Swift:
extension UIDeviceBatteryState: CustomStringConvertible {
public var description: String {
switch self {
case .unknown:
return "unknown"
case .unplugged:
return "unplugged"
case .charging:
return "charging"
case .full:
return "full"
}
}
}
if your variable "batteryState" then call:
self.batteryState.description
Introspection in Swift Enums seems to work partially.
I saw #drewag's response, and found that an Enum with no rawValues can indeed have introspection in Swift 5.X with Xcode 11.5. This code works.
public enum Domain: String {
case network
case data
case service
case sync
var description: String {
return "\(self)" // THIS INTROSPECTION WORKS
}
}
enum ErrorCode: Int, CustomStringConvertible {
case success = 200
case created = 201
case accepted = 202
case badRequest = 400
case unauthorized = 401
case forbidden = 403
case notFound = 404
var code: Int {
return self.rawValue
}
var description: String {
return "\(self)" //THIS DOES NOT WORK - EXEC_BAD_ACCESS
}
}
let errorCode = ErrorCode.notFound
let domain = Domain.network
print(domain.description, errorCode.code, errorCode.description)
Replace the "\(self)" for "string" in the second Enum and you will get this printout:
network 404 string
NOTE: Using String(self) instead of "\(self)" in the first Enumwill require the Enum to conform to theLosslessStringConvertible` protocol, and also add other initializers, so a string interpolation seems to be a good workaround.
To Add a var description: String to the enum, you will have to use a Switch statement will all the enum cases as pointed before
var description: String {
switch self {
case .success: return "Success"
case .created: return "Created"
case .accepted: return "Accepted"
}
}
Simple but works...
enum ViewType : Int {
case Title
case Buttons
case View
}
func printEnumValue(enum: ViewType) {
switch enum {
case .Title: println("ViewType.Title")
case .Buttons: println("ViewType.Buttons")
case .View: println("ViewType.View")
}
}