Swift Struct Property with Generic Enum - swift

I am currently creating settings page. I am trying to dynamically assign an Enum into my struct property.
What I want to achieve is that whenever Settings struct is being called, it will automatically append all respective settings together. Below are the enums
protocol BaseSettings {}
enum SettingsCategory: String, CaseIterable {
case sales = "Sales"
case payment = "Payment"
}
struct SettingsSection {
var settingsNameArray: [BaseSettings]
var settingsCategoryName: SettingsCategory
init(settingsCategory: SettingsCategory) {
self.settingsCategoryName = settingsCategory
switch settingsCategory {
case .sales:
self.settingsNameArray = SalesSettings.allCases
case .payment:
self.settingsNameArray = PaymentSettings.allCases
}
}
}
struct Settings {
var sections = [SettingsSection]()
init() {
for eachSettingCategory in SettingsCategory.allCases {
self.sections.append(SettingsSection(settingsCategory: eachSettingCategory))
}
}
}
enum SalesSettings: String, BaseSettings, CaseIterable {
case testSettings = "Test Sales Settings"
}
enum PaymentSettings: String, BaseSettings, CaseIterable {
case testSettings = "Test Payment Settings"
}
Above codes are working fine. The settings are grouped by section. However, when I want to populate the String raw value of each enum case, I am facing a problem. I couldn't get the rawValue because of the Protocol BaseSettings is not an enum
let settingsDataSource = SettingsConstant.Settings()
let settingsName = settingsDataSource.sections[indexPath.section].settingsNameArray[indexPath.row]
cell.textLabel?.text = "\(settingsName)"
Instead of displaying the rawValue of each enum case, the tableView is displaying each of the enum case.
How can I fix this data structure? Can anyone guide me?
Thanks

Specify in the protocol declaration that you need a raw value!
protocol BaseSettings {
var rawValue: String { get }
}
Enums such as SalesSettings would automatically conform to this new requirement because it is exactly the same as RawRepresentable.rawValue when RawValue is String.

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.

Kotlin enum classes in Swift

I would like to use this Kotiln code in Swift, but I don't know how to get the best and clean solution:
enum class ProType(val gCode: String, val cCode: String) {
FUND("FN", "PP"),
STOCK("VA", "")
}
Technically #esemusa answer is right. But if you have more than ~5 values in enum, you end up with difficult to maintain giant switch statements for every property.
So for cases like that I prefer to do this:
struct ProTypeItem {
var gCode: String
var cCode: String
}
struct ProType {
static let fund = ProTypeItem(gCode: "FN", cCode: "PP")
static let stock = ProTypeItem(gCode: "VA", cCode: "")
}
And you use it simply as ProType.stock, ProType.fund.gCode etc
You can also make ProTypeItem Comparable, Equatable etc.
should be like this:
enum ProType {
case fund
case stock
var gCode: String {
switch self {
case .fund:
return "FN"
case .stock:
return "VA"
}
}
var cCode: String {
switch self {
case .fund:
return "PP"
case .stock:
return ""
}
}
}

How to define a common interface for a group of string enums to be used by another class interchangeably

I want to do this or something like it to get the same functionality
could someone help?
protocol FilterEnum: CaseIterable {
}
enum SomeFilterEnum: String, FilterEnum {
case owner = "Owner"
case karat = "Karat"
}
class SomeCls {
private let filterTypes: [FilterEnum]
init(filterTypes: [FilterEnum]){
self.filterTypes = filterTypes
}
func useFilterTypes() {
for type in filterTypes{
print(type.rawValue)
}
}
}
let sm = SomeCls(filterTypes: SomeFilterEnum.allCases)
Judging from your usage, you need this common interface to be both CaseIterable and RawRepresentable. Well, that is CaseIterable & RawRepresentable, but we can't use that as a type directly because they both have associated types. We have to introduce a generic parameter on SomeCls:
class SomeCls<T> where T : CaseIterable & RawRepresentable, T.RawValue == String {
private let filterTypes: [T]
init(filterTypes: [T]){
self.filterTypes = filterTypes
}
func useFilterTypes() {
for type in filterTypes{
print(type.rawValue)
}
}
}

How do I get a CustomStringConvertible description from this enum?

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.

Is there a way to make a function to accept any Enum types that have a rawValue of String?

One way I came up with is to make a protocol that other Enum must conform to.
protocol StringRepresentable
{
var rawValue: String { get }
}
struct Endpoint
{
enum User: String, StringRepresentable
{
case Login = "/user/login"
case Register = "/user/register"
}
enum Item: String, StringRepresentable
{
case Like = "/item/like"
case Buy = "/item/buy"
}
}
func urlString(endpoint: StringRepresentable) -> String
{
return "http://www.example.com\(endpoint.rawValue)"
}
let userLoginEndpoint = urlString(Endpoint.User.Login)
let buyItemEndpoint = urlString(Endpoint.Item.Buy)
Is there any other way that better than this?
Or is there a protocol, already provided something like this, that I missed?
There is already the RawRepresentable protocol which does what you want.
And you can extend based on whether RawValue == String