Create a swift2 class from classname - swift

I'm new in Swift.
In objective C, I pushed custom UIViewController create from its classname:
NSString *classString = "customViewController";
UIViewController *vc = [[NSClassFromString(classString) alloc] init];
[self pushViewController:vc animated:YES];
I cannot build class in swift, this doesn't work:
let myClass = NSClassFromString("customViewController") as! UIViewController.Type
let vc = myClass.init()
self.presentViewController(vc, animated: true, completion: nil)
Error : fatal error: unexpectedly found nil while unwrapping an
Optional value

NSClassFromString uses the fully qualified class name.
class CustomViewController: UIViewController { }
let className = NSStringFromClass(CustomViewController.self)
// let className = "MyAppName.CustomViewController" // Equivalent
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Alternatively you override the fully qualified class name with the #objc attribute:
#objc(CustomViewController)
class CustomViewController: UIViewController { }
let className = NSStringFromClass(CustomViewController.self)
// let className = "CustomViewController" // Equivalent
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Either way, NSStringFromClass will always return the valid class name for NSClassFromString method.

I created a very simple extension to do this more quickly
https://github.com/damienromito/NSObject-FromClassName
extension NSObject {
class func fromClassName(className : String) -> NSObject {
let className = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String + "." + className
let aClass = NSClassFromString(className) as! UIViewController.Type
return aClass.init()
}
}
In my case, i do this to load the ViewController I want:
override func viewDidLoad() {
super.viewDidLoad()
let controllers = ["SettingsViewController", "ProfileViewController", "PlayerViewController"]
self.presentController(controllers.firstObject as! String)
}
func presentController(controllerName : String){
let nav = UINavigationController(rootViewController: NSObject.fromClassName(controllerName) as! UIViewController )
nav.navigationBar.translucent = false
self.navigationController?.presentViewController(nav, animated: true, completion: nil)
}

The reason it fails is that the view controller class name you have referenced (customViewController) is not fully qualified by the module name. This can be found in Interface Builder below the custom class name:
You should change the class name string to represent the fully qualified name of the view controller, for example:
let myClass = NSClassFromString("MyProject.customViewController") as! UIViewController.Type

Related

How do I create an NSView from a nib in Swift [duplicate]

How to load NSView from Xib properly?
My code:
var topLevelArray: NSArray? = nil
let outputValue = AutoreleasingUnsafeMutablePointer<NSArray>(&topLevelArray)
if Bundle.main.loadNibNamed("RadioPlayerView", owner: nil, topLevelObjects: outputValue) {
let views = outputValue.pointee
return views.firstObject as! RadioPlayerView
}
topLevelArray = nil
return nil
The problem is "outputValue" is a auto-release pointer, and as soon as I return from the function, the program crash with ACCESS_BAD_ADDRESS
I made an protocol and extension to do this:
import Cocoa
protocol NibLoadable {
static var nibName: String? { get }
static func createFromNib(in bundle: Bundle) -> Self?
}
extension NibLoadable where Self: NSView {
static var nibName: String? {
return String(describing: Self.self)
}
static func createFromNib(in bundle: Bundle = Bundle.main) -> Self? {
guard let nibName = nibName else { return nil }
var topLevelArray: NSArray? = nil
bundle.loadNibNamed(NSNib.Name(nibName), owner: self, topLevelObjects: &topLevelArray)
guard let results = topLevelArray else { return nil }
let views = Array<Any>(results).filter { $0 is Self }
return views.last as? Self
}
}
Usage:
final class MyView: NSView, NibLoadable {
// ...
}
// create your xib called MyView.xib
// ... somewhere else:
let myView: MyView? = MyView.createFromNib()
I solved this problem with a slightly different approach. Code in Swift 5.
If you want to create NSView loaded from .xib to e.g. addSubview and constraints from code, here is example:
public static func instantiateView<View: NSView>(for type: View.Type = View.self) -> View {
let bundle = Bundle(for: type)
let nibName = String(describing: type)
guard bundle.path(forResource: nibName, ofType: "nib") != nil else {
return View(frame: .zero)
}
var topLevelArray: NSArray?
bundle.loadNibNamed(NSNib.Name(nibName), owner: nil, topLevelObjects: &topLevelArray)
guard let results = topLevelArray as? [Any],
let foundedView = results.last(where: {$0 is Self}),
let view = foundedView as? View else {
fatalError("NIB with name \"\(nibName)\" does not exist.")
}
return view
}
public func instantiateView() -> NSView {
guard subviews.isEmpty else {
return self
}
let loadedView = NSView.instantiateView(for: type(of: self))
loadedView.frame = frame
loadedView.autoresizingMask = autoresizingMask
loadedView.translatesAutoresizingMaskIntoConstraints = translatesAutoresizingMaskIntoConstraints
loadedView.addConstraints(constraints.compactMap { ctr -> NSLayoutConstraint? in
guard let srcFirstItem = ctr.firstItem as? NSView else {
return nil
}
let dstFirstItem = srcFirstItem == self ? loadedView : srcFirstItem
let srcSecondItem = ctr.secondItem as? NSView
let dstSecondItem = srcSecondItem == self ? loadedView : srcSecondItem
return NSLayoutConstraint(item: dstFirstItem,
attribute: ctr.firstAttribute,
relatedBy: ctr.relation,
toItem: dstSecondItem,
attribute: ctr.secondAttribute,
multiplier: ctr.multiplier,
constant: ctr.constant)
})
return loadedView
}
If there is no .xib file with the same name as the class name, then code will create class from code only. Very good solution (IMO) if someone wants to create the view from code and xib files in the same way, and keeps your code organized.
.xib file name and class name must have the same name:
In .xib file you should only have one view object, and this object has to have set class:
All you need to add in class code is instantiateView() in awakeAfter e.g.:
import Cocoa
internal class ExampleView: NSView {
internal override func awakeAfter(using coder: NSCoder) -> Any? {
return instantiateView() // You need to add this line to load view
}
internal override func awakeFromNib() {
super.awakeFromNib()
initialization()
}
}
extension ExampleView {
private func initialization() {
// Preapre view after view did load (all IBOutlets are connected)
}
}
To instantiate this view in e.g. ViewController you can create view like that:
let exampleView: ExampleView = .instantiateView() or
let exampleView: ExampleView = ExampleView.instantiateView()
but Swift have problems sometimes with instantiate like that:
let exampleView = ExampleView.instantiateView()
in viewDidLoad() in your controller you can add this view as subview:
internal override func viewDidLoad() {
super.viewDidLoad()
exampleView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(exampleView)
NSLayoutConstraint.activate(
[exampleView.topAnchor.constraint(equalTo: view.topAnchor),
exampleView.leftAnchor.constraint(equalTo: view.leftAnchor),
exampleView.rightAnchor.constraint(equalTo: view.rightAnchor),
exampleView.bottomAnchor.constraint(equalTo: view.bottomAnchor)]
)
}

Static function that returns the dynamic type of class [duplicate]

I'm trying to make this extension:
extension UIViewController
{
class func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! Self
return controller
}
}
But I get compile error:
error: cannot convert return expression of type 'UIViewController' to
return type 'Self'
Is it possible? Also I want to make it as init(storyboardName: String, storyboardId: String)
Similar as in Using 'self' in class extension functions in Swift, you can define a generic helper method which infers the type of self from the calling context:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
return instantiateFromStoryboardHelper(storyboardName, storyboardId: storyboardId)
}
private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! T
return controller
}
}
Then
let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
compiles, and the type is inferred as MyViewController.
Update for Swift 3:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
return instantiateFromStoryboardHelper(storyboardName: storyboardName, storyboardId: storyboardId)
}
private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) as! T
return controller
}
}
Another possible solution, using unsafeDowncast:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: storyboardId)
return unsafeDowncast(controller, to: self)
}
}
Self is determined at compile-time, not runtime. In your code, Self is exactly equivalent to UIViewController, not "the subclass that happens to be calling this." This is going to return UIViewController and the caller will have to as it into the right subclass. I assume that's what you were trying to avoid (though it is the "normal Cocoa" way to do it, so just returning UIViewController is probably the best solution).
Note: You should not name the function initialize in any case. That's an existing class function of NSObject and would cause confusion at best, bugs at worst.
But if you want to avoid the caller's as, subclassing is not usually the tool to add functionality in Swift. Instead, you usually want generics and protocols. In this case, generics are all you need.
func instantiateViewController<VC: UIViewController>(storyboardName: String, storyboardId: String) -> VC {
let storyboad = UIStoryboard(name name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! VC
return controller
}
This isn't a class method. It's just a function. There's no need for a class here.
let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)
A cleaner solution (at least visually tidier):
Swift 5.1
class func initialize(storyboardName: String, storyboardId: String) -> Self {
return UIStoryboard(name: storyboardName, bundle: nil)
.instantiateViewController(withIdentifier: storyboardId).view as! Self
}
Another way is to use a protocol, which also allows you to return Self.
protocol StoryboardGeneratable {
}
extension UIViewController: StoryboardGeneratable {
}
extension StoryboardGeneratable where Self: UIViewController
{
static func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewController(withIdentifier: storyboardId) as! Self
return controller
}
}

Object conforming to a protocol should hide the protocol methods

I have an app designed with Viper architecture. To avoid exeptions, each module is created by a factory class which comply to BaseFactory protocol.
Two of one hundred (2%) modules in my app should be created with a custom factory method which is not enforced via protocol, a factory method which accept an argument.
Is it possible to "hide/disable" a function createViperModule() in the MemberProfileFactory class?
protocol BaseFactory {
static func createViperModule () -> UIViewController
}
class HelloFactory: BaseFactory {
static func creatViperModule() -> UIViewController {
let p = HelloPresenter()
let storyboard = UIStoryboard.init(name: "Hello", bundle: nil)
let vc = (storyboard.instantiateInitialViewController() as? HelloVC)!
p.vc = vc
vc.p = p
return vc
}
}
class MemberProfileFactory: BaseFactory {
static func createViperModule() -> UIViewController {
return PublicProfileVC()
}
static func createViperModule(withMember member: MemberModel) -> UIViewController {
let p = MemberProfilePresenter()
let storyboard = UIStoryboard.init(name: "MemberProfile", bundle: nil)
let vc = (storyboard.instantiateInitialViewController() as? MemberProfileVC)!
p.vc = vc
p.user = user
vc.p = p
return vc
}
}
You can't make 'createViperModule' private because of 'createViperModule' declared as internal in 'BaseFactory'. but you can declare it optional so it is not mandatory to implement.
#objc protocol BaseFactory {
#objc optional static func createViperModule () -> UIViewController
}

swift - Initialize view controller from storyboard by overriding init

I have a ViewController instance defined in a storyboard. I can initialize it by the following
var myViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("myViewControllerIdentifier") as! ViewController
Is there a way to override the init method of ViewController so that I can initialize it using
var myViewController = ViewController()
I tried overriding init
convenience init() {
self = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SearchTableViewController") as! SearchTableViewController
}
but the compiler doesn't like that. Any ideas?
A convenience initializer must always delegate to a designated initializer for the same class, and a designated initializer must call a superclass initializer.
Since the superclass doesn't have an appropriate initializer, you would probably be better served by a class factory method:
static func instantiate() -> SearchTableViewController
{
return UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SearchTableViewController") as! SearchTableViewController
}
then use:
var myViewController = SearchTableViewController.instantiate()
A class factory method is the way to go for now. Here's a protocol that you can use to quickly add makeFromStoryboard support to all UIViewControllers.
protocol StoryboardInstantiable {
static var storyboardName: String { get }
static var storyboardBundle: NSBundle? { get }
static var storyboardIdentifier: String? { get }
}
​
extension StoryboardInstantiable {
static var storyboardBundle: NSBundle? { return nil }
static var storyboardIdentifier: String? { return nil }
static func makeFromStoryboard() -> Self {
let storyboard = UIStoryboard(name: storyboardName, bundle: storyboardBundle)
if let storyboardIdentifier = storyboardIdentifier {
return storyboard.instantiateViewControllerWithIdentifier(storyboardIdentifier) as! Self
} else {
return storyboard.instantiateInitialViewController() as! Self
}
}
}
Example:
extension MasterViewController: StoryboardInstantiable {
static var storyboardName: String { return "Main" }
static var storyboardIdentifier: String? { return "Master" }
}
In case the view controller is the initial view controller in the storyboard, you can simply ignore storyboardIdentifier.
In case all the view controllers are in the same storyboard, you can also override storyboardName under the StoryboardInstantiable extension and return the name.
You can try Generics. Like this:
func Tvc<T>(_ vcType: T.Type) -> T {
let vc = (vcType as! UIViewController.Type).vc//限制或明确为 UIViewController.Type
return vc as! T
}
extension UIViewController {
fileprivate static var vc: UIViewController {
let M = UIStoryboard.init(name: "V", bundle: Bundle.main)
return M.instantiateViewController(withIdentifier: "\(self)")
}
}
Example
let vc = Tvc(HLCases.self)
_vc.navigationController?.pushViewController(vc, animated: true)

howto determine the class when i get a NSObject in a function with swift

I have a function like this, which I can provide 2 different classes (MyClass1 or MyClass2) :
func getClassName(anyobject: NSObject) -> String {
return anyobject.getClassName(); // <---- Howto do that ?
}
class MyClass1: NSObject {
}
class MyClass2: NSObject {
}
var myclass:MyClass1;
var sClassName:String=getClassName(myclass); <---- Howto get that ?
I would like to get the Classname of an provided object. Howto do that in swift ? I would like to provide my Class myclass to the func getClassName(anyobject: NSObject) and get the result "MyClass1".
Add this extension
extension NSObject {
var theClassName: String {
return NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
}
}
class MyClass1: NSObject{
}
class MyClass2: NSObject {
}
func getClassName(obj: NSObject) -> String {
return obj.theClassName
}
var myClass: MyClass1 = MyClass1()
var sClassName: String = getClassName(myClass)
Let me know if this is what you asked for.
If use static var ,you don't need to make instance.
extension NSObject {
static var className: String { String(describing: self) }
}
For example in StroyBoard
func changeWindow() {
let vc: UIViewController!
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if Auth.auth().currentUser?.uid != nil {
//here
vc = storyboard.instantiateViewController(identifier: HomeViewController.className)
}
else {
vc = storyboard.instantiateViewController(identifier: LoginViewController.className)
}