Hiya 😀! I've hit a bit of a hiccup with my code and thought maybe someone might be able to assist. Essentially, I have a collection of classes from which I would like to select one to assign to an optional instance variable. I'm looking to have that selection made by an index value that picks the class name from an array. I'm having trouble figuring out how to determine the class directly from the class name string.
How this is supposed to work: I have a number of classes, in this example Elephant, Gorilla and Tiger. I also have a String array, classNames, with values that match the names of those classes. Additionally, I have a Int variable, classIndex, which is used to select which class to use. What I want is for my instance variable, animal, to be able to assume the selected class after its initial declaration. Finally, I would like animal to be able to run custom methods in the class that it assumed.
Example code:
var animal: Any?
var classNames = ["Elephant", "Gorilla", "Tiger"]
var classIndex = 2
setupAnimal()
moveAnimal()
func setupAnimal() {
if classNames[classIndex] == "Elephant" {
animal = Elephant()
addChild(animal! as! Elephant)
} else if classNames[classIndex] == "Gorilla" {
animal = Gorilla()
addChild(animal! as! Gorilla)
} else if classNames[classIndex] == "Tiger" {
animal = Tiger()
addChild(animal! as! Tiger)
}
}
func moveAnimal() {
if classNames[classIndex] == "Elephant" {
(animal! as! Elephant).move()
} else if classNames[classIndex] == "Gorilla" {
(animal! as! Gorilla).move()
} else if classNames[classIndex] == "Tiger" {
(animal! as! Tiger).move()
}
}By setting animal initially to Any?, I'm able to initiate it later on with any of my classes, in this example Tiger, since classIndex is 2. When I want to access the custom method move() in Tiger, I use the as operator to access animal as a Tiger class. This works all well and good, but it's not dynamic. For me to continuously force the class, I have to hardcode conditionals to check for each class name, which is far from ideal.
Is there a way to avoid these conditionals by determining the class directly from the selected class name: classNames[classIndex]?
Interacting with text data looks like the right reason to use `NSClassFromString`.
When you want to get a class from its class name in Swift, you need a fully qualified class name, which means, a module name prefixed.
x NSClassFromString("Tiger")
o NSClassFromString("MyModuleName.Tiger")
(Unless you give @objc names as suggested by Claud31.)
So, you can write something like this:
extension String {
func toMyModuleClass() -> AnyClass? {
struct My {
static let moduleName = String(reflecting: Animal.self).prefix{$0 != "."}
}
return NSClassFromString("\(My.moduleName).\(self)")
}
}(You may define `moduleName` with a String literal, if you prefer. Assuming `Animal` and its all subclasses exist in the same module.)
And your Animal class needs to have a required initializer to instatiate an instance from its subclasses:
class Animal {
required init() {
//...
}
func move() {
//...
}
//...
}
class Elephant: Animal {
//...
}
class Gorilla: Animal {
//...
}
class Tiger: Animal {
//...
}And then, you can write your `setupAnimal` like this:
func setupAnimal() {
if let animalClass = classNames[classIndex].toMyModuleClass() as? Animal.Type {
animal = animalClass.init()
addChild(animal!)
} else {
print("Class \(classNames[classIndex]) does not exist")
}
}Or, you can put fully qualified names in your data resource.