2
votes

How could I correct this code from failing build? Basically wanting to use ForEach to iterate through a dictionary which is based on a [ customEnum : customStrut ].

Otherwise if this is problematic a different way to achieve that SwiftUI supports?

Errors

Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that '[GCFilterViewOptions.FilterOptions : GCFilterViewOptions.FilterOptionValues]' conform to 'RandomAccessCollection'

Type '(key: GCFilterViewOptions.FilterOptions, value: GCFilterViewOptions.FilterOptionValues)' cannot conform to 'Hashable'; only struct/enum/class types can conform to protocols

Code

import SwiftUI

struct GCFilterViewOptions: View {
    enum FilterOptions {
        case NewLine
        case Comma
        case Space
    }
    struct FilterOptionValues {
        var title : String
        var selected : Bool
    }
    var filterSelections : [FilterOptions : FilterOptionValues] = [
        FilterOptions.NewLine : FilterOptionValues(title: "New Line", selected: true),
        FilterOptions.Comma : FilterOptionValues(title: "Comma", selected: true),
        FilterOptions.Space : FilterOptionValues(title: "Space", selected: false)
    ]

    var body : some View {
        HStack {
            ForEach(filterSelections, id:\.self) { filterOption in.  // ** ERRORS HERE **
                Text("TBD")
                // Will be putting checkboxes here - i.e. so can chose which ones 
            }
        }
    }
}
2

2 Answers

2
votes

As states dictionary is not "random access" capable collection, so it cannot be used directly in ForEach, here is possible approach

HStack {
    ForEach(Array(filterSelections.keys.enumerated()), id:\.element) { _, key in
        Text("TBD \(self.filterSelections[key]?.title ?? "")")
        // Will be putting checkboxes here - i.e. so can chose which ones
    }
}
1
votes

ForEach loops only support RandomAccess capable data structures (Apple's documentation about what is RandomAccessCollection great).

If you do not really need the dictionary structure you can use a simple struct. This is basically the same as yours but the enum is nested into it and is a property:

struct FilterOption {
    enum FilterOptionType {
        case newLine
        case comma
        case space
    }

    var type: FilterOptionType
    var title: String
    var selected: Bool
}

var filterSelection: [FilterOption] = [
    FilterOption(type: .newLine, title: "New line", selected: true),
    FilterOption(type: .comma, title: "Comma", selected: false),
    FilterOption(type: .space, title: "Space", selected: true),
]

If you want yo used a dictionary (for example to assert that options are only listed once) you should keep your current data structure and just use the dictionary keys to access the elements. Just keep in mind that a dictionary is not ordered, if you want a stable order you should sort it (for example with the rawValue of the keys):

enum FilterOption: String {
    case newLine
    case comma
    case space
}
struct FilterOptionValue {
    var title: String
    var selected: Bool
}
var filterSelection: [FilterOption: FilterOptionValue] = [
    .newLine: FilterOptionValue(title: "New Line", selected: true),
    .comma: FilterOptionValue(title: "Comma", selected: true),
    .space: FilterOptionValue(title: "Space", selected: false)
]

// Guarantees a stable print order
for option in filterSelection.keys.sorted(by: { $0.rawValue < $1.rawValue }) {
    let optionValue = filterSelection[key]!
    print(option, optionValue)
}

Note that in Swift enum cases are lowercase as properties are. Only types begin with a capital letter (newLine, not NewLine).

Moreover enum type name should be singular as when instantiated they represent only a single option. Use FilterOption instead of FilterOptions.

Same for filterSelections that should be singular as a selection already contains multiple elements.