Generic Function: member does not exist

I have a number of buttons of which inherit from a single parent class and calculate their image based on a single generic function. I would like to use a value that I know exists on the class(es) but get the error that the member doesn't exist.

func determineImage<T>(with item: HelperStrings, forComponent: T?) -> String {
    
    var returnImagery: String = ""
    
    switch item {
    case .firstButton :
        returnImagery = UIDevice.current.userInterfaceIdiom == .pad ? forComponent?.but3PadSm.rawValue ?? "but1PadSm" : forComponent?.but3PhoneSm.rawValue ?? "but1PhoneSm"
    case .secondButton :
        returnImagery = UIDevice.current.userInterfaceIdiom == .pad ? forComponent?.but3PadSm.rawValue ?? "but2PadSm" : forComponent?.but3PhoneSm.rawValue ?? "but2PhoneSm"
    case .thirdButton :
        returnImagery = UIDevice.current.userInterfaceIdiom == .pad ? forComponent?.but3PadSm.rawValue ?? "but3PadSm" : forComponent?.but3PhoneSm.rawValue ?? "but3PhoneSm"
    }
    return returnImagery
}

The way you wrote the code, the generic type is unconstrained. i.e. simply T.
So the code — as the compiler interprets it — will have to work with any type, not just your buttons' type.

If you want to make use of specific properties of the generic component, you can add constraints to it in a where clause before the body of the function, e.g.

func determineImage<T>(with item: HelperStrings, forComponent: T?) -> String where T: MyButtonParentClass {
  /* your code here */
}
1 Like

Many thanks DeFrenZ, such a simple solution.

I would now like to import an array of buttons in one compatible type, specify the output type and then export them in an array, but I am having issues with initialization:

func convertToButton<T, V>(from items: [T], withClass: V) -> [V] where T: Class1, T: Class2 {
        
    var returnButtonArray: [V] = []
    
        for item in items {
            
            let itemButton: V = {
                
                let buttonData = V(imageNamed: "", title: "", buttonAction: {

                    switch buttonLogic {
                    case 1 : print("button1")
                    case 2 : print("button2")
                    case 3 : print("button3")
                    }
                })
                
                buttonData.title = item.title
                buttonData.zPosition = 1
                
                return buttonData
            }()

            returnButtonArray.append(itemButton)
        }
    return returnButtonArray
}

I was underuse if I would have to list the possible types in the where clause, which doesn't make much sense, but found it got rid of some of the errors.

If you want to pass V, the type, and not an instance of V, you need to use V.Type in the function signature.

Also, whenever you find yourself creating a mutable result array, and then iterating over another array, performing some transformation on it, and appending to your result, you're basically creating a one-to-one mapping between some input collection and an output. You could then use .map for clarity, brevity and avoiding mutable state:

func convertToButton<T, V>(from items: [T], withClass: V.Type) -> [V] where T: Class1, T: Class2 {
    return items.map { item in 
        let button = V.init(imageNamed: "", title: item.title, buttonAction: { /* ... */ })
        button.zPosition = 1
        return button
    }
}

That's a sensible use of map for my needs, I never thought of that, thank you.

Playing with the code I cannot get past the error that there Type 'V' has no member 'init', unless using T, but that to me seems backwards as I am obtaining my data from that, not initializing from it.

I tried adding a required init to my class (which has no custom initializers) but it did no good. My understanding of how this generic function works is that items are imported as T, which conform to either of the where classes, and I convert T to V with by initializing with the values, outputting in the specified V class.

Why will only T allow for the init and not V?

I understand now what I was getting wrong.

In the end I used a parent class that had all of the members I would need to switch on in the generic function, so that I could use it in the generic function.

Thanks !