46

I have tried creating an instance of a class using a string in numerous ways with none of them working in Swift 3.

Below are pre-Swift 3 solutions I have tried that are not working

- Making class an objective-c class

@objc(customClass)
class customClass {
    ...
}

//Error here: cannot convert value of type 'AnyClass?' to expected argument type 'customClass'
let c: customClass = NSClassFromString("customClass")

- Specifying class using NSString value (both with and without using @objc attribute)

@objc(customClass)
class customClass {
    ...
}

//Error here: cannot convert value of type 'String' to expected argument type 'AnyClass' (aka 'AnyObject.Type')
var className = NSStringFromClass("customClass")

let c: customClass = NSClassFromString(className)

I'm not doing something right but have not found any solutions online.

How do I create an instance of a class using a string in Swift 3?

Daniel Storm
  • 18,301
  • 9
  • 84
  • 152
Danoram
  • 8,132
  • 12
  • 51
  • 71
  • The difference between my question and the one you have linked is that I am asking how to do this in `Swift3` as the answers from that question do not work with my `Swift3` code – Danoram Nov 02 '16 at 06:03
  • Fair enough. I've retracted my close vote. – par Nov 02 '16 at 06:25

4 Answers4

46

You can try this:

func classFromString(_ className: String) -> AnyClass! {

    /// get namespace
    let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as! String

    /// get 'anyClass' with classname and namespace 
    let cls: AnyClass = NSClassFromString("\(namespace).\(className)")!

    // return AnyClass!
    return cls
}

use the func like this:

class customClass: UITableView {}   

let myclass = classFromString("customClass") as! UITableView.Type
let instance = myclass.init()
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
Nick xu
  • 501
  • 5
  • 6
  • Should it be `let instance = myclass.init()`? – Danoram Nov 02 '16 at 06:43
  • I like this solution! although I would like to know if there is a solution similar previous versions of swift - similar to `NSClassFromString` – Danoram Nov 02 '16 at 06:52
  • Sorry, NSClassFromString is considered not safe in Swift,and you mush build your class with NameSpace in Swift,this is different from OC.So,I can't help more. – Nick xu Nov 02 '16 at 07:02
  • That is very interesting! – Danoram Nov 02 '16 at 07:04
  • 1
    If you're trying to instantiate a custom class then you will need to add a required keyword to an initializer inside that class otherwise the compiler throws an error. – SmileBot Apr 18 '17 at 14:44
  • 2
    NOTE: I don't think this works if your executable name has a space in it. We weren't using CFBundleExecutable, but rather CFBundleName and it didn't work unless we also replaced the spaces with underscores. It was suggested in this (somewhat confusing) blog post: https://medium.com/@maximbilan/ios-objective-c-project-nsclassfromstring-method-for-swift-classes-dbadb721723. The code in the post worked, but the actual post talked about using appname and target name which was different from what their code did. – stuckj Aug 29 '17 at 19:21
  • How can I make this work if the initializer has a required parameter? – Francisco Medina May 18 '18 at 23:08
36

Apple provides a way to achieve this without having to use NSClassFromString.

Bundle.main.classNamed("MyClassName")

https://developer.apple.com/documentation/foundation/bundle/1407299-classnamed

Daniel Storm
  • 18,301
  • 9
  • 84
  • 152
9

If you project contains space you should replace space with '_'

Something like this

let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
let cls = NSClassFromString("\(namespace).\(className)")! as! AnyClass
Arek
  • 375
  • 5
  • 5
  • 1
    Gave this an upvote because the suggestion on replacing the spaces with '_' was exactly what I was looking for. Although "CFBundleName" also works fine in place of "CFBundleExecutable" – Adetunji Mohammed Jun 03 '22 at 12:53
  • do the same replacement if you have "-" in your target name – WalterF Nov 19 '22 at 09:58
2

Typed Extension

You can have a typed class from a string like:

let loadedClass = try! Bundle.main.class(ofType: MyClass.self)

using this simple extension:

extension Bundle {
    func `class`<T: AnyObject>(ofType type: T.Type, named name: String? = nil) throws -> T.Type {
        let name = name ?? String(reflecting: type.self)

        guard name.components(separatedBy: ".").count > 1 else { throw ClassLoadError.moduleNotFound }
        guard let loadedClass = Bundle.main.classNamed(name) else { throw ClassLoadError.classNotFound }
        guard let castedClass = loadedClass as? T.Type else { throw ClassLoadError.invalidClassType(loaded: name, expected: String(describing: type)) }

        return castedClass
    }
}

extension Bundle {
    enum ClassLoadError: Error {
        case moduleNotFound
        case classNotFound
        case invalidClassType(loaded: String, expected: String)
    }
}

Of course you can try catch the error ;)

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278