Swift - Generic Functions - swift

The Goal
I want to call the instantiateViewControllerWithIdentifier method on a Storyboard to get a view controller. But I want to do it with generics for code reuse and simplicity.
The Non-Compiling Code
let viewController = StoryboardHelper.viewControllerFromStoryboard<ChooseProviderViewController>()
class func viewControllerFromStoryboard<T>() -> T {
let storyboard = mainStoryboard()
let viewController = storyboard.instantiateViewControllerWithIdentifier(resolveViewController(T)) as T
return viewController
}
private class func resolveViewController<T>() -> String {
if T is ChooseProviderViewController {
return chooseProviderViewControllerIdentifier;
}
return ""
}
Now, the above code is what I have at the moment, and how I would like the API to be. It would be nice to say "StoryboardHelper, here is the type of the view controller I want, go get the view controller instance for me".
So, in the end, I would like some way to do this:
let instanceOfXViewController = StoryboardHelper.viewControllerFromStoryboard<XViewController>()
let instanceOfYViewController = StoryboardHelper.viewControllerFromStoryboard<YViewController>()

I have a answer for you but it will only work with Swift 1.2 (+), in 1.1 it will return a intance of the wrong (super) type. I changed it to a more general situation/naming but it is effectly the same problem. First since you only want to manage UIViewControllers you should restrict T. Like <T:UIViewController> . Secondly you should/can pass the Type as an argument and create an instance of it.
import Foundation
class Helper {
class func instanceFromT<T:R>(T.Type) -> T {
return T.self()
}
}
class R {
required init () {
}
func me() -> String {
return "me is R"
}
}
class B : R {
override func me() -> String {
return "me is B"
}
}
class A : R {
override func me() -> String {
return "me is A"
}
}
let my = Helper.instanceFromT(A.self)
println(my.me()) // "me is A"

Related

NSClassFromString from myClass [duplicate]

This question already has an answer here:
NSClassFromString returning nil for nested class
(1 answer)
Closed 3 years ago.
I have my custom class in extension UIImage like this
extension UIImage {
class AssetItem: NSObject {
}
}
when I try get class from NSClassFromString like this
NSClassFromString("UIImage.AssetItem")
I receive nil. How I can do it? I need get it class from String.
actually I try to do a tree with names, for can get exec like it NavigationBar.additionMenu and receive string "NavigationBar.additionMenu".
protocol EIAssetRoot {
}
protocol EIAssetFolder {
static func name(_ key: String) -> String
}
extension EIAssetFolder {
static func name(_ key: String = #function) -> String {
let full = "\(String(reflecting: self)).\(key)"
let lastSpace = full.components(separatedBy: ":").last ?? full
let components = lastSpace.components(separatedBy: ".")
var rootComponents = components
var rootFounding = false
repeat {
rootComponents = rootComponents.dropLast()
let name = rootComponents.joined(separator: ".")
if let anyClass = NSClassFromString(name) {
if anyClass is EIAssetRoot {
rootFounding = true
}
}
} while rootComponents.count > 0 && rootFounding == false
let keyComponents = components.dropFirst(rootComponents.count)
let name = keyComponents.joined(separator: ".")
return name
}
}
extension UIImage {
#objc class AssetItem: NSObject, EIAssetRoot {
class NavigationBar: EIAssetFolder {
static var additionMenu: String { get { return name() } }
static var save: String { get { return name() } }
static let toLeft: String { get { return name() } }
static func toRight: String { get { return name() } }
}
}
}
this I try build string from class name and trunc first part to class EIAssetRoot
I don't like doing in by enum because it looks like .NavigationBar(.Menu(.SecondMenu(.additionMenu))) too meny ()
You need to "unhide" AssetItem by moving it out of the UIImage extension and up to the top level. Objective-C can never "see" a nested type like UIImage.AssetItem — and NSClassFromString is Objective-C / Cocoa.
However, it would be even better to ask yourself why you need this. In Swift, an attempt to use NSClassFromString is first and foremost a Bad Smell. You are probably trying to do something here that can be done in a correct Swifty way, without involving Objective-C Cocoa at the back end. But you have not revealed what it is.
You will have to access the class by an explicitly defined name like this. These symbols are defined in the global scope in Objective-C (so no .'s are allowed in the name):
extension UIImage {
#objc(UIImageAssetItem)
class AssetItem: NSObject {
}
}
let cls = NSClassFromString("UIImageAssetItem")

Calling a function/passing data from outside a TableViewController (and others) in Swift

In my app I have one screen divided between two ViewControllers - LadderViewController and GameHistoryTableViewController, which lies in a container. I want user to be able to filter the data in the table by tapping on something in the LadderView. I tried to solve this using delegates:
LadderViewController:
delegate = GameHistoryTableViewController()
func imageTapped(imageIndex: Int) {
delegate?.selectedHeroNumber(imageIndex)
}
GameHistoryTableViewController: (conforms to the delegate protocol and implemets a function from it)
func selectedHeroNumber(heroNumber: Int) {
let filteredGames = filterGamesFromHeroNumber(heroNumber)
tableDataSource = filteredGames
self.tableView.reloadData()
}
That doesn't work, though, because the delegate I declare in LadderViewConroller is another instance of GameHistoryTableViewController, not the (to the user) shown one. I don't know how to access the "visible" instance (table) of GameHistoryTableViewController though... So, how should be delegating used here? Or should I use another approach (and if so, what kind)? I basically need to change the table's data source according to on what the user taps, one can say "from outside" (dataSource is a property in my GameHistoryTableViewController class).
Here is an example with delegation like you want to do. It's a better solution than singleton in this case ;)
declare a new protocol call HeroInfo:
protocol HeroInfo: class {
func selectedHeroNumber(heroNumber: Int);
}
LadderViewController:
//create the delegation
weak var delegate:HeroInfo?
func imageTapped(imageIndex: Int) {
//call the delegate method
delegate?.selectedHeroNumber(imageIndex)
}
GameHistoryTableViewController:
// Here get the protocol HeroInfo inheritance
class userTableViewController: UITableViewController, HeroInfo {
override func viewDidLoad() {
super.viewDidLoad()
//Here get your Ladder view in a splitView
if let split = self.splitViewController {
let controllers = split.viewControllers
self.ladderViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? ladderViewController
//register it to delegate
self.ladderViewController?.delegate = self
}
}
...
// Here is your method of your protocol that you must conform to
func selectedHeroNumber(heroNumber: Int) {
let filteredGames = filterGamesFromHeroNumber(heroNumber)
tableDataSource = filteredGames
self.tableView.reloadData()
}
...
}
There are a few ways to achieve this, I have a similar setup for which I use a model class with a singleton to store the relevant data.
For instance you could have the following
class dataModel {
static let sharedInstance = dataModel()
private var _heroNumber = Int()
private init() {}
var heroNumber: Int = {
return _heroNumber
}
func setHero(hero: Int) -> Int {
return _heroNumber
}
}
}
You can then can access this model from each of your controllers using dataModel.sharedInstance.heroNumber etc...

Modifying a class to display specific output

I am new to Swift and am having a bit of trouble with this bit in particular. Attached is some code I need to run as part of a project. Details are at the end.
class Screen:DataSource {
var items:[String]=[]
func run(){
let lv = TableView()
items = ["John","Paul","George","Ringo"]
let ds = self
lv.dataSource=ds
lv.drawList()
}
}
class TableView {
// This class displays a list given a DataSource
var dataSource:DataSource?
init (){
self.dataSource=nil
}
func drawList(){
for(var i=0;i<dataSource!.getSize();i++) {
print("\(dataSource!.getItem(at:i))")
print("--------------------------")
}
}
}
protocol DataSource {
func getSize()->Int
func getItem(at pos:Int)->String
}
let screen = Screen()
screen.run()
Without changing the "run" function/method, I need to have it print this:
John
--------------------------
Paul
--------------------------
George
--------------------------
Ringo
--------------------------
I'm not sure what to modify in my Screen class. This is what I have so far:
class Screen: DataSource {
var items: [String]=[]   
func run() {       
let lv = TableView()       
items = ["John","Paul","George","Ringo"]       
let ds = self       
lv.dataSource=ds       
lv.drawList()   
}
//NEED HELP HERE
}
Any tips would be appreciated.
Use an extension to make Screen conform to DataSource:
class Screen {
var items:[String]=[]
func run(){
let lv = TableView()
items = ["John","Paul","George","Ringo"]
let ds = self
lv.dataSource=ds
lv.drawList()
}
}
extension Screen: DataSource {
func getSize() -> Int { return items.count }
func getItem(at index:Int) -> String { return items[index] }
}
You could also put the DataSource conformance and methods on the class itself, but doing it in an extension is a common style for organizing your class's members.

Swift.... Class method vs. Instance method

Thanks in advance for help!!
I'm trying to call a func from within my Class and I keep getting an error saying that:
Missing parameter for argument #1.............Read a few posts saying it's an instance vs class problem? I don't get it..I'm calling the method from within the Class??? There has to be an instance of the class if the method is being called????? right? Here is my code...Thanks
import Foundation
import Parse
class TestViewController {
let photos = getWallImages() //-----This is the line requesting an argument
func getWallImages() -> [WallPost] {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
return objects
println("We have \(objects.count)")
}
} else if let error = error {
println(error)
}
}
}
}
So the "crime" you are committing is the fact that the method is applied in an instance of the class and not as a class method. The function is expecting a self parameter (a reference to the instance). That explains the error message.
Now to fix that you have two quick options:
1. Make it a class function and call it that way too:
class TestViewController {
let photos = TestViewController.getWallImages()
class func getWallImages() -> [WallPost] {
// mumbo jumbo
}
}
This approach is problematic in case you would want to do some instance specific operations, because class func is static method and doesn't provide you with some of the object benefits.
2. Instantiate the object you are calling the method on:
class TestViewController {
let photos = TestViewController().getWallImages()
func getWallImages() -> [WallPost] {
// mumbo jumbo
}
}
This approach isn't correct with your given structure - it doesn't make sense to instantiate another view controller, but if you take the method and put it in a separate class, maybe it would then make sense.
Then of course you have multiple other ways of changing your code to make it work. Maybe you could initialize it with lazy parameter, maybe you could initialize it in the init method. Whatever suits you best. My answer is simply explaining where you've gone wrong.
There are a few ways you can set your property appropriately. You can make getWallImages() a type method:
class TestViewController {
let photos = TestViewController.getWallImages()
class func getWallImages() -> [WallPost] {
....
}
}
Or, you can keep your method an instance method and set your property upon initialization:
class TestViewController {
let photos: [WallPost]!
init() {
super.init()
photos = getWallImages()
}
func getWallImages() -> [WallPost] {
....
}
}
If you're asking a question you should reduce your code to a minimum, discarding unnecessary details.
You probably want something like this:
class MyClass {
let x = MyClass.getStuff()
static func getStuff() -> Int {
return 0
}
}
However your method getWallImages() can't do something like this, because it's returning the result asynchronous, which means you get the result much later after the function has returned.
You could do something like this though (this is how I'd be doing it):
class MyClass {
var x : Int? {
didSet {
if let x = x {
// Do something with x, here it's assigned
} else {
// x was set to nil, something failed
}
}
}
init() {
getStuffAsynchronous()
}
func getStuffAsynchronous() {
// Do your query stuff here, assign x to your objects to invoke the didSet
x = 0
// If it fails somehow, assign set x to nil
// if fail {
// x = nil
// }
}
}

Unexpected results from Swift is Operator

I'm trying to understand where this code went wrong given the code below.
In the code below, I'm trying to locate a UIViewController of a specific class in the UITabBarController's viewControllers property which is declared as:
var viewControllers: [AnyObject]?
So I'm defining two UIViewController subclasses and stuffing them to the viewControllers array and running two different methods to extract them "viewControllerInfo:" ""viewControllerInfo2".
Which both yield the same result.
My understanding is the:
if let x as? T will evaluate true and assign x as a "T" type if it is the same class.
Just as if x is T would.
Any idea why evaluation is behaving like this ?
class VC1: UIViewController {}
class VC2: UIViewController {}
let tabBar = UITabBarController()
tabBar.viewControllers = [VC1(), VC2()]
extension UITabBarController {
public func viewControllerInfo<T: UIViewController>(ofType: T.Type) -> (viewController: T,index: Int)? {
if let tabViewControllers = self.viewControllers{
for (idx, maybeVCTypeT) in enumerate(tabViewControllers) {
if let viewController = maybeVCTypeT as? T {
return (viewController, idx)
}
}
}
return nil
}
public func viewControllerInfo2<T: UIViewController>(ofType: T.Type) -> (viewController: T,index: Int)? {
if let tabViewControllers = self.viewControllers{
for (idx, maybeVCTypeT) in enumerate(tabViewControllers) {
if maybeVCTypeT is T {
return (maybeVCTypeT as! T, idx)
}
}
}
return nil
}
}
All the tests below will end up giving exactly the same result:
"<__lldb_expr_89.VC1: 0x7f85016079f0>"
if let (vc, idx) = tabBar.viewControllerInfo(VC1.self) {
println(vc)
}
if let (vc, idx) = tabBar.viewControllerInfo(VC2.self) {
println(vc)
}
if let (vc, idx) = tabBar.viewControllerInfo2(VC1.self) {
println(vc)
}
if let (vc, idx) = tabBar.viewControllerInfo2(VC2.self) {
println(vc)
}
I'm suspicious of the enumerate(x) since without it I'm getting expected results:
if testVC1 is VC2 {
println("\(testVC1) is \(VC2.self)")
}
the above code yields a warning:
Cast from 'VC1' to unrelated type 'VC2' always fails
Which is what i'm trying to achieve with enumerate...
***************** EDIT *****************
Actually my suspicion of enumerate is dissolved after running the following code which ran perfectly.
let myArray: [AnyObject] = [VC2(), VC1()]
for (idx, x) in enumerate(myArray) {
println(x)
if let xAsVC1 = x as? VC1 {
println("\(x) is supposed to be \(VC1.self)")
//"<__lldb_expr_155.VC1: 0x7fc12a9012f0> is supposed to be __lldb_expr_155.VC1"
}
if x is VC2 {
println("\(x) is supposed to be \(VC2.self)")
//"<__lldb_expr_155.VC2: 0x7fc12a900fd0> is supposed to be __lldb_expr_155.VC2"
}
}
This seems to be caused by the generic constraint, and I believe is a bug (http://www.openradar.me/22218124). Removing the generic constraint, or making it a non-ObjC class (such as AnyObject) seems to fix it:
public func viewControllerInfo<T>(ofType: T.Type) -> (viewController: T,index: Int)? {
You can also replace:
if maybeVCTypeT is T {
with:
if maybeVCTypeT.isKindOfClass(T) {
And that seems to work.