protocol Foo {
var name: String { get }
}
enum A: Foo, CaseIterable {
case a, b, c
static var allFoos: [Foo] {
Self.allCases as [Foo]
}
var name: String {
String(describing: self)
}
}
enum B: Foo, CaseIterable {
case one, two, three
static var allFoos: [Foo] {
Self.allCases as [Foo]
}
var name: String {
String(describing: self)
}
}
// want to just operate on Foo's
//(A.allCases + B.allCases).forEach { // error: Binary operator '+' cannot be applied to operands of type '[A]' and '[B]'
(A.allFoos + B.allFoos).forEach { // Q: is this the best way?
print($0.name)
}
If all instances of Foo
are known, I would personally just write it as an extension on Foo. e.g.
extension Foo {
static let allCases: [Foo] = [A.allCases, B.allCases].flatMap { $0 }
}
It just dinged on me I might have misunderstood your question. If you want each Foo
type to have an allFoos
, you could do this:
extension Foo where Self: CaseIterable {
static var allFoos: [Foo] {
allCases as! [Foo]
}
}
I need Foo
to be either Hashable
or Identifiable
for use with SwiftUI.ForEach
, this cause problem:
protocol Foo: Hashable {
var name: String { get }
}
extension Foo where Self: CaseIterable {
static var allFoos: [Foo] { // Error: Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements
allCases as! [Foo] // Error: Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements
}
}
Is there anyway to overcome this?
This (Error: Protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements
) seems to be a classic problem in Swift. Unfortunately, I don't think the solution here is very straightforward without a lot of boilerplate . So it's probably best to wait for someone more experienced to chime in.
Need a Bad Explanation?
BTW If you are unaware, basically what it is saying is that you can't use Foo
as a type (i.e. as a existential) because somewhere in its requirements it has an associatedtype
. In this case, any type that conforms to Foo
requires an associated type ID
via its inheritance of Identifiable
.
This means that it fails when you try to declare a value of type [Foo]
, because Foo
, due to its associatedtype requirement, is not a valid type.
For example, you should still get the same error if you tried to instantiate a simple var (let foo: Foo = A.a
). Though I believe the restriction in this simple case is being removed in a future version of Swift (unless it has already)? [Foo]
from my knowledge will remain invalid however.
Usually this is solved using generic type, or some Foo
. But in my case here, I'm trying to mix different enum types by using Foo
as an abstract type. Unfortunately, Swift doesn't allow this it seems.
I think as of now, the solution is to create type erase by making a AnyFoo
. Until generalized existential when we no longer need to hand code type erase.
Please correctly if I'm wrong.
I think as of now, the solution is to create type erase by making a
AnyFoo
.
This was my immediate inclination as well as someone who is new(er) to the language btw. I just wanted to be sure someone smarter than me agreed before I said anything
I believe 5.7 will allow any protocol with associatedType be used as type.
Maybe your example is oversimplified, in this case make it more realistic.
The following works for me given the current restrictions you'd specified.
enum A: String, CaseIterable {
case a, b, c
}
enum B: String, CaseIterable {
case one, two, three
}
func test() {
let cases = (A.allCases.map { $0.rawValue } + B.allCases.map { $0.rawValue })
cases.forEach {
print($0)
}
}
struct ContentView: View {
var body: some View {
let cases = (A.allCases.map { $0.rawValue } + B.allCases.map { $0.rawValue })
List {
ForEach(cases, id: \.self) { v in
Text(v)
}
}
}
}
The cases
in your example is [String]
, that why it works. My enum case is this:
protocol ColorCase: Hashable {
var name: String { get }
var color: Color { get }
}
and each of my enum
's implements it and I want to do ForEach
over [ColorCase]
. Can you get this to work with SwiftUI.ForEach
?
Make it a struct?
struct ColorCase: Hashable {
var name: String
var color: Color
}
enum A: String, CaseIterable {
case a, b, c
var colorCase: ColorCase {
switch self {
case .a: return ColorCase(name: rawValue, color: .red)
case .b: return ColorCase(name: rawValue, color: .green)
case .c: return ColorCase(name: rawValue, color: .blue)
}
}
}
enum B: String, CaseIterable {
case one, two, three
var colorCase: ColorCase {
switch self {
case .one: return ColorCase(name: rawValue, color: .red)
case .two: return ColorCase(name: rawValue, color: .green)
case .three: return ColorCase(name: rawValue, color: .blue)
}
}
}
func test() {
let cases = (A.allCases.map { $0.colorCase } + B.allCases.map { $0.colorCase })
cases.forEach {
print($0)
}
}
struct ContentView: View {
var body: some View {
let cases = (A.allCases.map { $0.colorCase } + B.allCases.map { $0.colorCase })
List {
ForEach(cases, id: \.self) { v in
Text(v.name)
.foregroundColor(v.color)
}
}
}
}
Alright. This indirection/conversion works. A lot of copying going on every time. Not ideal but ok for now.
I wonder when protocol with associatedType can be used as type in 5.7, is there any copying/conversion/boxing going on behind the scene?
Thanks!
By copying are you referring to "....map { $0.rawValue }" or what?
Here's yet another alternative for you for the time being.
protocol ColorCaseable: Hashable {
var name: String { get }
var color: Color { get }
}
enum A: String, CaseIterable, ColorCaseable {
case a, b, c
var name: String { rawValue }
var color: Color { .red }
}
enum B: String, CaseIterable, ColorCaseable {
case one, two, three
var name: String { rawValue }
var color: Color { .green }
}
struct ContentView: View {
var body: some View {
List {
ForEach(A.allCases, id: \.self) { v in
CommonView(v: v)
}
ForEach(B.allCases, id: \.self) { v in
CommonView(v: v)
}
}
}
}
struct CommonView<T: ColorCaseable>: View {
let v: T
var body: some View {
Text(v.name)
.foregroundColor(v.color)
}
}
Would be glad to know this. I guess we'll know the answer in a week's time one way or another.
I remember sidestepping the whole "Self or associated type requirement" issue this creative way in one of my projects.
protocol MyEquatable {
func eq(_ v: MyEquatable) -> Bool
}
struct S: MyEquatable {
var x: Int
func eq(_ v: MyEquatable) -> Bool {
guard let v = v as? S else { return false }
return x == v.x
}
}
struct D: MyEquatable {
var y: String
func eq(_ v: MyEquatable) -> Bool {
guard let v = v as? D else { return false }
return y == v.y
}
}
func prototest() {
let v: [MyEquatable] = [S(x: 1), S(x: 1), S(x: 2), D(y: "2")]
print(v[0])
print(v[1])
print(v[2])
print(v[3])
assert(v[0].eq(v[1]))
assert(!v[0].eq(v[2]))
assert(!v[0].eq(v[3]))
// a bit strange you can compare S and D directly, but that's fine with me so long as they compare not equal:
let s = S(x: 0)
let d = D(y: "0")
assert(!s.eq(d))
print("done")
}
prototest()
Note that such an existential type will be usable as a type in upcoming versions of Swift, but they will not conform to any protocols, not themselves and not the protocols they refine—this is not an implementation shortcoming: it is not (ever) possible in the general case.
The confusion around this concept was the reason these protocols were not permitted to be used as types in the first place, but it has been decided better to allow you to use the type in some way than to ban them altogether. However, it does not mean that you will be able to have Hashable
existential types.
I mean this:
a ColorCase
is created on every access.
So hand coded type erase is still needed?
Still awaits "Generalized Existential" to eliminate the need to hand code type erase?
It is a small value type, normally allocated on stack, and should as fast as memcpy-ing 24 bytes worth of data. I doubt you'll even see it in profiler unless you do a microbenchmark.
Plus, you can "create" it just once as shown here.
struct ColorCase: Hashable {
var name: String
var color: Color
}
struct A {
static let allCases = [
ColorCase(name: Kind.a.rawValue, color: .red),
ColorCase(name: Kind.b.rawValue, color: .green),
ColorCase(name: Kind.c.rawValue, color: .blue)
]
enum Kind: String { case a, b, c }
let kind: Kind
let colorCase: ColorCase
}
struct B {
static let allCases = [
ColorCase(name: Kind.one.rawValue, color: .red),
ColorCase(name: Kind.two.rawValue, color: .green),
ColorCase(name: Kind.three.rawValue, color: .blue)
]
enum Kind: String { case one, two, three }
let kind: Kind
let colorCase: ColorCase
}
struct ContentView: View {
var body: some View {
let cases = A.allCases + B.allCases
List {
ForEach(cases, id: \.self) { v in
Text(v.name)
.foregroundColor(v.color)
}
}
}
}
(Here the boilerplate is moved from one place into another - manual allCases implementation.)
I decided to keep my enum
's, so I did the precreate as static var inside my enum
's by mapping allCases
to allColors
. I use enum as a way to define my colors. When I add or delete color, the compiler will make sure update everything.
The most generalized existential will not eliminate the need for custom code.
Have a look. This sidesteps Hashable requirement:
protocol ColorCase: CaseIterable {
var name: String { get }
var color: Color { get }
}
enum A: String, ColorCase {
case a, b, c
var name: String { rawValue }
var color: Color { .red }
}
enum B: String, ColorCase {
case one, two, three
var name: String { rawValue }
var color: Color { .green }
}
struct ContentView: View {
var body: some View {
let a: [ColorCase] = A.allCases
let b: [ColorCase] = B.allCases
let cases = a + b
List {
// the range is constant here, 0 ..< A.allCases.count + B.allCases.count
ForEach(0 ..< cases.count) { i in
Text(cases[i].name)
.foregroundColor(cases[i].color)
}
}
}
}