Removing WKWebView Accesory bar in Swift - ios

I am trying for a few days now to get this converted into Swift without really having much background with it.
This is what I've got so far ... and I have been looking on google not really knowing what to search for in order to be more specific. Can you please shed some light on what I'm doing wrong ? Thanks
Update:
I have aded the objective-c tag just so more people that are related to this thread may be able to see it and hopefully get an answer.

For those who are still looking, the WebKit team updated WKWebView (iOS 13+) so that you can subclass it to remove/update the input accessory view:
https://trac.webkit.org/changeset/246229/webkit#file1
In Swift, I subclassed it, and returned nil. Worked as expected. I hope it helps.
FYI: I checked the docs, and it doesn't mention not to subclass WKWebView, so subclassing is allowed.
import WebKit
class RichEditorWebView: WKWebView {
var accessoryView: UIView?
override var inputAccessoryView: UIView? {
// remove/replace the default accessory view
return accessoryView
}
}
You can find a working version of it here: https://github.com/cbess/RichEditorView/commits/master

Michael Dautermann answer has got everything right, but in order to hide the accessory bar you need to swizzle the method inputAccessoryView() of UIView Class with the inputAccessoryView() of the _NoInputAccessoryView class. I have just added the couple of extra lines to the code which does this job of method swizzling.
First you'll need a fake class to swap with
final class FauxBarHelper: NSObject {
var inputAccessoryView: AnyObject? { return nil }
}
Then create this method in your controller class
/// Removes the keyboard accessory view from the web view
/// Source: http://stackoverflow.com/a/32620344/308315 / http://stackoverflow.com/a/33939584/308315
func _removeInputAccessoryView(webView: UIWebView) {
var targetView: UIView? = nil
for view in webView.scrollView.subviews {
if String(describing: type(of: view)).hasPrefix("WKContent") {
targetView = view
}
}
guard let target = targetView else { return }
let noInputAccessoryViewClassName = "\(target.superclass!)_NoInputAccessoryView"
var newClass: AnyClass? = NSClassFromString(noInputAccessoryViewClassName)
if newClass == nil {
let targetClass: AnyClass = object_getClass(target)
newClass = objc_allocateClassPair(targetClass, noInputAccessoryViewClassName.cString(using: String.Encoding.ascii)!, 0)
}
let originalMethod = class_getInstanceMethod(FauxBarHelper.self, #selector(getter: FauxBarHelper.inputAccessoryView))
class_addMethod(newClass!.self, #selector(getter: FauxBarHelper.inputAccessoryView), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
object_setClass(target, newClass)
}
HTH ;)

Here's a slightly safer (no unsafe unwraps) version that works with Swift 4 and (at least) iOS 9 trough 12.
fileprivate final class InputAccessoryHackHelper: NSObject {
#objc var inputAccessoryView: AnyObject? { return nil }
}
extension WKWebView {
func hack_removeInputAccessory() {
guard let target = scrollView.subviews.first(where: {
String(describing: type(of: $0)).hasPrefix("WKContent")
}), let superclass = target.superclass else {
return
}
let noInputAccessoryViewClassName = "\(superclass)_NoInputAccessoryView"
var newClass: AnyClass? = NSClassFromString(noInputAccessoryViewClassName)
if newClass == nil, let targetClass = object_getClass(target), let classNameCString = noInputAccessoryViewClassName.cString(using: .ascii) {
newClass = objc_allocateClassPair(targetClass, classNameCString, 0)
if let newClass = newClass {
objc_registerClassPair(newClass)
}
}
guard let noInputAccessoryClass = newClass, let originalMethod = class_getInstanceMethod(InputAccessoryHackHelper.self, #selector(getter: InputAccessoryHackHelper.inputAccessoryView)) else {
return
}
class_addMethod(noInputAccessoryClass.self, #selector(getter: InputAccessoryHackHelper.inputAccessoryView), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
object_setClass(target, noInputAccessoryClass)
}
}

This code snippet should get you over your issue:
class _NoInputAccessoryView: NSObject {
func removeInputAccessoryViewFromWKWebView(webView: WKWebView) {
// make sure to make UIView an optional here...
var targetView: UIView? = nil
for view in webView.scrollView.subviews {
if String(view.dynamicType).hasPrefix("WKContent") {
targetView = view
}
}
// only optionals can be nil
if targetView == nil {
return
}
let noInputAccessoryViewClassName = "\(targetView!.superclass)_NoInputAccessoryView"
var newClass : AnyObject? = NSClassFromString(noInputAccessoryViewClassName)
if newClass == nil {
let uiViewClass : AnyClass = object_getClass(targetView!)
newClass = objc_allocateClassPair(uiViewClass, noInputAccessoryViewClassName.cStringUsingEncoding(NSASCIIStringEncoding)!, 0)
}
}
You can also use "String(view.dynamicType)" to get the class name of the object you're looking at, as I noticed via this answer as I was researching the way to solve your problem.
Using hasPrefix like that in both Objective-C and Swift is really hacky and perhaps a better way of hiding the keyboard could be found for production code?

Related

Remove the top toolbar from wkwebview keyboard

Im currently creating an app based out of a WKWebView and every time a keyboard pops up a toolbar above it shows like this:
I would like to remove this toolbar when the keyboard expands.
I have searched stack overflow and not found anything current about this topic (I've only found UIWebView), but if you find a post I missed I will be very thankful.
Im using swift.
All help is appreciated, thank you.
Found the answer on this post
Credit to Pavel above.
Removing WKWebView Accesory bar in Swift
Add this code to your view controller:
fileprivate final class InputAccessoryHackHelper: NSObject {
#objc var inputAccessoryView: AnyObject? { return nil }
}
extension WKWebView {
func hack_removeInputAccessory() {
print("s")
guard let target = scrollView.subviews.first(where: {
String(describing: type(of: $0)).hasPrefix("WKContent")
}), let superclass = target.superclass else {
return
}
let noInputAccessoryViewClassName = "\(superclass)_NoInputAccessoryView"
var newClass: AnyClass? = NSClassFromString(noInputAccessoryViewClassName)
if newClass == nil, let targetClass = object_getClass(target), let classNameCString = noInputAccessoryViewClassName.cString(using: .ascii) {
newClass = objc_allocateClassPair(targetClass, classNameCString, 0)
if let newClass = newClass {
objc_registerClassPair(newClass)
}
}
guard let noInputAccessoryClass = newClass, let originalMethod = class_getInstanceMethod(InputAccessoryHackHelper.self, #selector(getter: InputAccessoryHackHelper.inputAccessoryView)) else {
return
}
class_addMethod(noInputAccessoryClass.self, #selector(getter: InputAccessoryHackHelper.inputAccessoryView), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
object_setClass(target, noInputAccessoryClass)
}
}
And then call it with
webView.hack_removeInputAccessory()
After loading your view.

How can I make a Sequence to iterate over all the ancestors (superviews) from a UIView up the view hierarchy to the root?

I'd like for UIView to have a property that returns a sequence of all the ancestors of the view up the hierarchy. That would be useful for purposes like finding the nearest one that matches a particular type:
let tableView = cell.ancestors.first(where: { $0 is UITableView })
What's a nice way of implementing that ancestors property?
Using the sequence(first:next:) function, from the Swift Standard Library, an even shorter solution is possible as well:
extension UIView {
var ancestors: AnySequence<UIView> {
return AnySequence<UIView>(
sequence(first: self, next: { $0.superview }).dropFirst())
}
}
You can implement a type that conforms to Sequence and add a property returning it in an extension. A Sequence normally needs a makeIterator() method that returns a type that conforms to IteratorProtocol, but in this case we can make the sequence act as its own iterator and use one type for both, which makes things very simple:
Swift 3:
struct AncestorSequenceIterator: Sequence, IteratorProtocol {
var current: UIView
mutating func next() -> UIView? {
guard let next = current.superview else { return nil }
current = next
return next
}
}
extension UIView {
var ancestors: AncestorSequenceIterator {
return AncestorSequenceIterator(current: self)
}
}
You could create extension and return IteratorProtocol to be able to do first(where:) comparison like so,
extension UIView {
var ancestors: AnyIterator<UIView> {
var current: UIView = self
return AnyIterator<UIView> {
guard let parent = current.superview else {
return nil
}
current = parent
return parent
}
}
}
Since AnyIterator itself conforms to Sequence, the statement that you showed above should work fine.
let tableView = cell.ancestors.first(where: { $0 is UITableView })
Paulo Mattos's implementation is good, but for your specific use, you probably want something like this:
extension UIView {
func nearestAncestor<T: UIView>(ofType type: T.Type) -> T? {
if let me = self as? T { return me }
return superview?.nearestAncestor(ofType: type)
}
}
Then you can use it like this:
guard let tableView = cell.nearestAncestor(ofType: UITableView.self) else { return }
// tableView at this point is type UITableView

Swift: Get all subviews of a specific type and add to an array

I have a custom class of buttons in a UIView that I'd like to add to an array so that they're easily accessible. Is there a way to get all subviews of a specific class and add it to an array in Swift?
The filter function using the is operator can filter items of a specific class.
let myViews = view.subviews.filter{$0 is MyButtonClass}
MyButtonClass is the custom class to be filtered for.
To filter and cast the view to the custom type use compactMap
let myViews = view.subviews.compactMap{$0 as? MyButtonClass}
Here you go
extension UIView {
/** This is the function to get subViews of a view of a particular type
*/
func subViews<T : UIView>(type : T.Type) -> [T]{
var all = [T]()
for view in self.subviews {
if let aView = view as? T{
all.append(aView)
}
}
return all
}
/** This is a function to get subViews of a particular type from view recursively. It would look recursively in all subviews and return back the subviews of the type T */
func allSubViewsOf<T : UIView>(type : T.Type) -> [T]{
var all = [T]()
func getSubview(view: UIView) {
if let aView = view as? T{
all.append(aView)
}
guard view.subviews.count>0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
You can call it like
let allSubviews = view.allSubViewsOf(type: UIView.self)
let allLabels = view.allSubViewsOf(type: UILabel.self)
So many of the answers here are unnecessarily verbose or insufficiently general. Here's how to get all subviews of a view, at any depth, that are of any desired class:
extension UIView {
func subviews<T:UIView>(ofType WhatType:T.Type) -> [T] {
var result = self.subviews.compactMap {$0 as? T}
for sub in self.subviews {
result.append(contentsOf: sub.subviews(ofType:WhatType))
}
return result
}
}
How to use:
let arr = myView.subviews(ofType: MyButtonClass.self)
To do this recursively (I.e. fetching all subview's views aswell), you can use this generic function:
private func getSubviewsOf<T : UIView>(view:UIView) -> [T] {
var subviews = [T]()
for subview in view.subviews {
subviews += getSubviewsOf(view: subview) as [T]
if let subview = subview as? T {
subviews.append(subview)
}
}
return subviews
}
To fetch all UILabel's in a view hierarchy, just do this:
let allLabels : [UILabel] = getSubviewsOf(view: theView)
I can't test it right now but this should work in Swift 2:
view.subviews.flatMap{ $0 as? YourView }
Which returns an array of YourView
Here's a tested, typical example, to get a count:
countDots = allDots!.view.subviews.flatMap{$0 as? Dot}.count
From Swift 4.1, you can use new compactMap (flatMap is now depcrecated): https://developer.apple.com/documentation/swift/sequence/2950916-compactmap
(see examples inside)
In your case, you can use:
let buttons:[UIButton] = stackView.subviews.compactMap{ $0 as? UIButton }
And you can execute actions to all buttons using map:
let _ = stackView.subviews.compactMap{ $0 as? UIButton }.map { $0.isSelected = false }
If you want to update/access those specific subviews then use this,
for (index,button) in (view.subviews.filter{$0 is UIButton}).enumerated(){
button.isHidden = false
}
func allSubViews(views: [UIView]) {
for view in views {
if let tf = view as? UITextField {
// Do Something
}
self.allSubViews(views: view.subviews)
}
}
self.allSubViews(views: self.view.subviews)
For this case, I think we could use Swift's first.where syntax, which is more efficient than filter.count, filter.isEmpty.
Because when we use filter, it will create a underlying array, thus not effective, imagine we have a large collection.
So just check if a view's subViews collection contains a specific kind of class, we can use this
let containsBannerViewKind = view.subviews.first(where: { $0 is BannerView }) != nil
which equivalent to: find me the first match to BannerView class in this view's subViews collection. So if this is true, we can carry out our further logic.
Reference: https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where
Let me post my variation of this) but this, finds the first of T
extension UIView {
func firstSubView<T: UIView>(ofType type: T.Type) -> T? {
var resultView: T?
for view in subviews {
if let view = view as? T {
resultView = view
break
}
else {
if let foundView = view.firstSubView(ofType: T.self) {
resultView = foundView
break
}
}
}
return resultView
}
}
Swift 5
func findViewInside<T>(views: [UIView]?, findView: [T] = [], findType: T.Type = T.self) -> [T] {
var findView = findView
let views = views ?? []
guard views.count > .zero else { return findView }
let firstView = views[0]
var loopViews = views.dropFirst()
if let typeView = firstView as? T {
findView = findView + [typeView]
return findViewInside(views: Array(loopViews), findView: findView)
} else if firstView.subviews.count > .zero {
firstView.subviews.forEach { loopViews.append($0) }
return findViewInside(views: Array(loopViews), findView: findView)
} else {
return findViewInside(views: Array(loopViews), findView: findView)
}
}
How to use:
findViewInside(views: (YourViews), findType: (YourType).self)
I've gone through all the answers above, they cover the scenario where the views are currently displayed in the window, but don't provide those views which are in view controllers not shown in the window.
Based on #matt answers, I wrote the following function which recursively go through all the views, including the non visible view controllers, child view controllers, navigation controller view controllers, using the next responders
(Note: It can be definitively improved, as it adds more complexity on top of the recursion function. consider it as a proof of concept)
/// Returns the array of subviews in the view hierarchy which match the provided type, including any hidden
/// - Parameter type: the type filter
/// - Returns: the resulting array of elements matching the given type
func allSubviews<T:UIView>(of type:T.Type) -> [T] {
var result = self.subviews.compactMap({$0 as? T})
var subviews = self.subviews
// *********** Start looking for non-visible view into view controllers ***********
// Inspect also the non visible views on the same level
var notVisibleViews = [UIView]()
subviews.forEach { (v) in
if let vc = v.next as? UIViewController {
let childVCViews = vc.children.filter({$0.isViewLoaded && $0.view.window == nil }).compactMap({$0.view})
notVisibleViews.append(contentsOf: childVCViews)
}
if let vc = v.next as? UINavigationController {
let nvNavVC = vc.viewControllers.filter({$0.isViewLoaded && $0.view.window == nil })
let navVCViews = nvNavVC.compactMap({$0.view})
notVisibleViews.append(contentsOf: navVCViews)
// detect child vc in not visible vc in the nav controller
let childInNvNavVC = nvNavVC.compactMap({$0.children}).reduce([],+).compactMap({$0.view})
notVisibleViews.append(contentsOf: childInNvNavVC)
}
if let vc = v.next as? UITabBarController {
let tabViewControllers = vc.viewControllers?.filter({$0.isViewLoaded && $0.view.window == nil }) ?? [UIViewController]()
// detect navigation controller in the hidden tab bar view controllers
let vc1 = tabViewControllers.compactMap({$0 as? UINavigationController})
vc1.forEach { (vc) in
let nvNavVC = vc.viewControllers.filter({$0.isViewLoaded && $0.view.window == nil })
let navVCViews = nvNavVC.compactMap({$0.view})
notVisibleViews.append(contentsOf: navVCViews)
// detect child vc in not visible vc in the nav controller
let childInNvNavVC = nvNavVC.compactMap({$0.children}).reduce([],+).compactMap({$0.view})
notVisibleViews.append(contentsOf: childInNvNavVC)
}
// ad non-navigation controller in the hidden tab bar view controllers
let tabVCViews = tabViewControllers.compactMap({($0 as? UINavigationController) == nil ? $0.view : nil})
notVisibleViews.append(contentsOf: tabVCViews)
}
}
subviews.append(contentsOf: notVisibleViews.removingDuplicates())
// *********** End looking for non-visible view into view controllers ***********
subviews.forEach({result.append(contentsOf: $0.allSubviews(of: type))})
return result.removingDuplicates()
}
extension Array where Element: Hashable {
func removingDuplicates() -> [Element] {
var dict = [Element: Bool]()
return filter { dict.updateValue(true, forKey: $0) == nil }
}
}
Sample usage:
let allButtons = keyWindow.allSubviews(of: UIButton.self)
Note: If a modal view controller is currently presented, the above script does not find views which are contained in the presentingViewController. (Can be expanded for that, but I could not find an elegant way to achieve it, as this code is already not elegant by itself :/ )
Probably is not common to have this need, but maybe helps someone out there :)

Get class name of UIViewController in swift

How do you get the class name of a UIViewController class in Swift?
In Objective-C, we can do something like this:
self.appDelegate = (shAppDelegate *)[[UIApplication sharedApplication] delegate];
UIViewController *last_screen = self.appDelegate.popScreens.lastObject ;
if(last_screen.class != self.navigationController.visibleViewController.class){
//.......
}
but in Swift I tried:
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let last_screen = appDelegate.popScreens?.lastObject as UIViewController
Can't do this.
if last_screen.class != self.navigationController.visibleViewController.class {
//....
}
no class method of UIViewController i.e last screen
To know your class name you can call something like this:
var className = NSStringFromClass(yourClass.classForCoder)
The cleanest way without needing to know the name of your class is like this.
let name = String(describing: type(of: self))
A simple way in swift 3 is to write the below code:
for instances:
let className = String(describing: self)
for classes:
let className = String(describing: YourViewController.self)
Expanding on juangdelvalle's answer.
I added this as an extension so that it's reusable and easier to call from any view controller. Also in some cases NSStringFromClass in Swift returns a string in the format like this:
< project name >.viewControllerClassName.
This extension property is modified to get rid of the project name prefix and return only the class name.
extension UIViewController {
var className: String {
NSStringFromClass(self.classForCoder).components(separatedBy: ".").last!
}
}
Swift 4
Suppose we have class with name HomeViewController. Then you can get name of class with the following code:
let class_name = "\(HomeViewController.classForCoder())"
The classForCoder() method returns AnyClass object (name of your class) which we convert to string for user.
Here is a swift3 version of isuru's answer.
extension UIViewController {
var className: String {
return NSStringFromClass(self.classForCoder).components(separatedBy: ".").last!;
}
}
Swift 5 solution:
extension NSObject {
var className: String {
return String(describing: type(of: self))
}
class var className: String {
return String(describing: self)
}
}
USAGE:
class TextFieldCell: UITableVIewCell {
}
class LoginViewController: UIViewController {
let cellClassName = TextFieldCell.className
}
The property is called dynamicType in Swift.
Use String.init(describing: self.classForCoder)
example:
let viewControllerName = String.init(describing: self.classForCoder)
print("ViewController Name: \(viewControllerName)")
How about:
extension NSObject {
static var stringFromType: String? {
return NSStringFromClass(self).components(separatedBy: ".").last
}
var stringFromInstance: String? {
return NSStringFromClass(type(of: self)).components(separatedBy: ".").last
}
}
We can also do: String(describing: Self.self) in Swift 5.1.

Swift language NSClassFromString

How to achieve reflection in Swift Language?
How can I instantiate a class
[[NSClassFromString(#"Foo") alloc] init];
You must put #objc(SwiftClassName) above your swift class.
Like:
#objc(SubClass)
class SubClass: SuperClass {...}
This is the way I init derived UIViewController by class name
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()
More information is here
In iOS 9
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Less hacky solution here: https://stackoverflow.com/a/32265287/308315
Note that Swift classes are namespaced now so instead of "MyViewController" it'd be "AppName.MyViewController"
Deprecated since XCode6-beta 6/7
Solution developed using XCode6-beta 3
Thanks to the answer of Edwin Vermeer I was able to build something to instantiate Swift classes into an Obj-C class by doing this:
// swift file
// extend the NSObject class
extension NSObject {
// create a static method to get a swift class for a string name
class func swiftClassFromString(className: String) -> AnyClass! {
// get the project name
if var appName: String? = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as String? {
// generate the full name of your class (take a look into your "YourProject-swift.h" file)
let classStringName = "_TtC\(appName!.utf16count)\(appName)\(countElements(className))\(className)"
// return the class!
return NSClassFromString(classStringName)
}
return nil;
}
}
// obj-c file
#import "YourProject-Swift.h"
- (void)aMethod {
Class class = NSClassFromString(key);
if (!class)
class = [NSObject swiftClassFromString:(key)];
// do something with the class
}
EDIT
You can also do it in pure obj-c:
- (Class)swiftClassFromString:(NSString *)className {
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleName"];
NSString *classStringName = [NSString stringWithFormat:#"_TtC%d%#%d%#", appName.length, appName, className.length, className];
return NSClassFromString(classStringName);
}
I hope this will help somebody !
UPDATE: Starting with beta 6 NSStringFromClass will return your bundle name plus class name separated by a dot. So it will be something like MyApp.MyClass
Swift classes will have a constructed internal name that is build up of the following parts:
It will start with _TtC,
followed by a number that is the length of your application name,
followed by your application name,
folowed by a number that is the length of your class name,
followed by your class name.
So your class name will be something like _TtC5MyApp7MyClass
You can get this name as a string by executing:
var classString = NSStringFromClass(self.dynamicType)
Update In Swift 3 this has changed to:
var classString = NSStringFromClass(type(of: self))
Using that string, you can create an instance of your Swift class by executing:
var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
It's almost the same
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Check this doc:
https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString
I was able to instantiate an object dynamically
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()
if let testObject = instance as? TestObject {
println("yes!")
}
I haven't found a way to create AnyClass from a String (without using Obj-C). I think they don't want you to do that because it basically breaks the type system.
For swift2, 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)
}
This will get you the name of the class that you want to instantiate. Then you can use Edwins answer to instantiate a new object of your class.
As of beta 6 _stdlib_getTypeName gets the mangled type name of a variable. Paste this into an empty playground:
import Foundation
class PureSwiftClass {
}
var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"
println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")
The output is:
TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS
Ewan Swick's blog entry helps to decipher these strings: http://www.eswick.com/2014/06/inside-swift/
e.g. _TtSi stands for Swift's internal Int type.
In Swift 2.0 (tested in the Xcode 7.01) _20150930
let vcName = "HomeTableViewController"
let ns = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String
// Convert string to class
let anyobjecType: AnyObject.Type = NSClassFromString(ns + "." + vcName)!
if anyobjecType is UIViewController.Type {
// vc is instance
let vc = (anyobjecType as! UIViewController.Type).init()
print(vc)
}
xcode 7 beta 5:
class MyClass {
required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
let object = classObject.init()
}
string from class
let classString = NSStringFromClass(TestViewController.self)
or
let classString = NSStringFromClass(TestViewController.classForCoder())
init a UIViewController class from string:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
I am using this category for Swift 3:
//
// String+AnyClass.swift
// Adminer
//
// Created by Ondrej Rafaj on 14/07/2017.
// Copyright © 2017 manGoweb UK Ltd. All rights reserved.
//
import Foundation
extension String {
func convertToClass<T>() -> T.Type? {
return StringClassConverter<T>.convert(string: self)
}
}
class StringClassConverter<T> {
static func convert(string className: String) -> T.Type? {
guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String else {
return nil
}
guard let aClass: T.Type = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
return nil
}
return aClass
}
}
The use would be:
func getViewController(fromString: String) -> UIViewController? {
guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
return nil
}
return viewController.init()
}
I think I'm right in saying that you can't, at least not with the current beta (2). Hopefully this is something that will change in future versions.
You can use NSClassFromString to get a variable of type AnyClass but there appears to be no way in Swift to instantiate it. You can use a bridge to Objective C and do it there or -- if it works in your case -- fall back to using a switch statement.
Apparently, it is not possible (anymore) to instantiate an object in Swift when the name of the class is only known at runtime. An Objective-C wrapper is possible for subclasses of NSObject.
At least you can instantiate an object of the same class as another object given at runtime without an Objective-C wrapper (using xCode Version 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
In Swift 2.0 (tested in the beta2 of Xcode 7) it works like this:
protocol Init {
init()
}
var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()
For sure the type coming from NSClassFromString have to implement this init protocol.
I expect it is clear, className is a String containing the Obj-C runtime name of the class which is by default NOT just "Foo", but this discussion is IMHO not the major topic of your question.
You need this protocol because be default all Swift classes don't implement an init method.
Looks like the correct incantation would be...
func newForName<T:NSObject>(p:String) -> T? {
var result:T? = nil
if let k:AnyClass = NSClassFromString(p) {
result = (k as! T).dynamicType.init()
}
return result
}
...where "p" stands for "packaged" – a distinct issue.
But the critical cast from AnyClass to T currently causes a compiler crash, so in the meantime one must bust initialization of k into a separate closure, which compiles fine.
I use different targets, and in this case the swift class is not found. You should replace CFBundleName with CFBundleExecutable. I also fixed the warnings:
- (Class)swiftClassFromString:(NSString *)className {
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleExecutable"];
NSString *classStringName = [NSString stringWithFormat:#"_TtC%lu%#%lu%#", (unsigned long)appName.length, appName, (unsigned long)className.length, className];
return NSClassFromString(classStringName);
}
Isn't the solution as simple as this?
// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)
// className = "MyApp.MyClass"
Also in Swift 2.0 (possibly before?) You can access the type directly with the dynamicType property
i.e.
class User {
required init() { // class must have an explicit required init()
}
var name: String = ""
}
let aUser = User()
aUser.name = "Tom"
print(aUser)
let bUser = aUser.dynamicType.init()
print(bUser)
Output
aUser: User = {
name = "Tom"
}
bUser: User = {
name = ""
}
Works for my use case
Try this.
let className: String = String(ControllerName.classForCoder())
print(className)
I have implemented like this,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Swift 5, easy to use, thanks to #Ondrej Rafaj's
Source code:
extension String {
fileprivate
func convertToClass<T>() -> T.Type? {
return StringClassConverter<T>.convert(string: self)
}
var controller: UIViewController?{
guard let viewController: UIViewController.Type = convertToClass() else {
return nil
}
return viewController.init()
}
}
class StringClassConverter<T> {
fileprivate
static func convert(string className: String) -> T.Type? {
guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String, let aClass = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
return nil
}
return aClass
}
}
Call like this:
guard let ctrl = "ViewCtrl".controller else {
return
}
// ctrl do sth
A page jump example shown here, the hope can help you!
let vc:UIViewController = (NSClassFromString("SwiftAutoCellHeight."+type) as! UIViewController.Type).init()
self.navigationController?.pushViewController(vc, animated: true)
// Click the Table response
tableView.deselectRow(at: indexPath, animated: true)
let sectionModel = models[(indexPath as NSIndexPath).section]
var className = sectionModel.rowsTargetControlerNames[(indexPath as NSIndexPath).row]
className = "GTMRefreshDemo.\(className)"
if let cls = NSClassFromString(className) as? UIViewController.Type {
let dvc = cls.init()
self.navigationController?.pushViewController(dvc, animated: true)
}
Swift3+
extension String {
var `class`: AnyClass? {
guard
let dict = Bundle.main.infoDictionary,
var appName = dict["CFBundleName"] as? String
else { return nil }
appName.replacingOccurrences(of: " ", with: "_")
let className = appName + "." + self
return NSClassFromString(className)
}
}
Here is a good example:
class EPRocks {
#require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);

Resources