1

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?

Community
  • 1
  • 1
George
  • 25,988
  • 10
  • 79
  • 133
  • 1
    Isn't there a `.capitalized()` on String? – Alexander Dec 10 '19 at 21:55
  • @Alexander-ReinstateMonica Didn't realise that, thanks! I got that part from [here](https://stackoverflow.com/questions/26306326/swift-apply-uppercasestring-to-only-the-first-letter-of-a-string), where the first two answers are using this method (even though they _say_ it's for Swift 5). – George Dec 10 '19 at 21:58
  • That would be good for people who can't `import Foundation` – Alexander Dec 10 '19 at 22:02

2 Answers2

4

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
}
dan
  • 9,695
  • 1
  • 42
  • 40
  • I don't think it is possible, but is there a way to collapse `enum Furniture: String, Named` to `enum Furniture: Named`? Or any way to make `rawValue` not visible (like somehow using `private`)? – George Dec 10 '19 at 22:10
0

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")
    }
}
Blazej SLEBODA
  • 8,936
  • 7
  • 53
  • 93