简体   繁体   中英

Swift Value of protocol type 'XXX' cannot conform to 'Identifiable'; only struct/enum/class types can conform to protocols

I'm getting the following error:

Value of protocol type 'MenuProtocol' cannot conform to 'Identifiable'; only struct/enum/class types can conform to protocols

I have a main menu which some of its options have a submenu. I decided to use an enum for the main menu, and another enum for any submenus. All of the enums implement a protocol I defined, which allows me to specify the text that I'm going to show for each menu option.

I'm not sure if this is the best approach, but I found it useful.

Any ideas how can I fix this error? Thanks for your help!

Protocol MenuProtocol {
    var submenu: [OptionProtocol] { get }
}

protocol OptionProtocol {
    var name: String { get }
}

enum MainMenu: Int, Identifiable, CaseIterable, MenuProtocol, OptionProtocol {
    case One
    case Two
    case Three
    case Four
    case Five
    
    var id: Int { rawValue }
    
    var name: String {
        switch self {
        case .One:
            return "One"
        case .Two:
            return "Two"
        case .Three:
            return "Three"
        case .Four:
            return "Four"
        case .Five:
            return "Five"
        }
    }
    
    var submenu: [OptionProtocol] {
        switch self {
        case .One:
            return SubMenu1.allCases
        case .Two:
            return SubMenu2.allCases
        default:
            return []
        }
    }
}

enum SubMenu1: Int, Identifiable, CaseIterable, OptionProtocol {
    case SubMenu1_Option1
    case SubMenu1_Option2
    case SubMenu1_Option3
    case SubMenu1_Option4
    case SubMenu1_Option5
    
    var id: Int { rawValue }
    
    var name: String {
        switch self {
        case .SubMenu1_Option1:
            return "Submenu1 Option 1"
        case .SubMenu1_Option2:
            return "Submenu1 Option 2"
        case .SubMenu1_Option3:
            return "Submenu1 Option 3"
        case .SubMenu1_Option4:
            return "Submenu1 Option 4"
        case .SubMenu1_Option5:
            return "Submenu1 Option 5"
        }
    }
}

enum SubMenu2: Int, Identifiable, CaseIterable, OptionProtocol {
    case SubMenu2_OptionA
    case SubMenu2_OptionB
    case SubMenu2_OptionC
    case SubMenu2_OptionD
    case SubMenu2_OptionE
    
    var id: Int { rawValue }
    
    var name: String {
        switch self {
        case .SubMenu2_OptionA:
            return "Submenu2 Option A"
        case .SubMenu2_OptionB:
            return "Submenu2 Option B"
        case .SubMenu2_OptionC:
            return "Submenu2 Option C"
        case .SubMenu2_OptionD:
            return "Submenu2 Option D"
        case .SubMenu2_OptionE:
            return "Submenu2 Option E"
        }
    }
}

struct EnumProtTest: View {
    var body: some View {
        VStack {
            HStack {
                ForEach(MainMenu.allCases) { value in
                    Text("\(theName(value))")
                        .padding()
                        .background(Color.blue)
                }
            }
            
            HStack {
                TheContentView(data: MainMenu.One.submenu) { item in
                    Text("\(item.name)")
                        .padding()
                        .background(Color.purple)
                }
            }
            
            HStack {
                TheContentView(data: MainMenu.Two.submenu) { item in
                    Text("\(item.name)")
                        .padding()
                        .background(Color.purple)
                }
            }
        }
    }
}

struct TheContentView<Data: RandomAccessCollection, ElementView: View>: View where Data.Element: Identifiable, Data.Element: Hashable {
    
    var data: Data
    var itemView: (Data.Element) -> ElementView
    
    var body: some View {
        ForEach(data) { item in
            itemView(item)
                .padding()
                .background(Color.purple)
        }
    }
}

ForEach expects an Identifiable type, that's why your Menu*-types conform to Identifiable . But what is passed to ForEach is a MenuProtocol that doesn't conform to Identifiable .

In general, when upcasting an object (eg MainMenu ) to a base-class or protocol (in your case MenuProtocol ), the compiler is only able to access the properties/functions that are provided by that protocol. This limitation is due the fact that you may also pass other objects that conform to the protocol but miss all the other properties like the id In your case.

In your example I don't see a reason for having a protocol-oriented implementation for the Menu, because you don't use MainMenu and SubMenu1 in a polymorphism way. My suggestion when it comes to protocols and generics and inheritance: try to keep it as simple as possible without any of those features and add those if you cannot solve your problem without.

Good WWDC video and podcast about protocol-oriented programming https://developer.apple.com/wwdc15/408 https://www.swiftbysundell.com/podcast/71/

You can use indices and retrieve your data using an index. Just change your HStack .

HStack {
    ForEach(MainMenu.One.items().indices) { index in
        let value = MainMenu.One.items()[index]
        Text(value.name())
            .padding()
            .background(Color.purple)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM