custom Swift function has errors - swift

I have formulated the function below so that I can re-use it in my ios app. However, I am unable to build my app because my function below has errors indicated to me but I cannot see what is wrong with it. The function is intended to move the user to a new page in the ios app. Please can someone advise?
func goToPage(goto storyBoardId: String, ofType typeUIViewController: UIViewController.Type) -> UIViewController {
let storyBoard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let newPage = storyBoard.instantiateViewController(withIdentifier: storyBoardId) as! typeUIViewController // Error: use of undeclared type 'typeUIViewController'
self.present(newPage, animated: true, completion: nil)
return newPage
}

Your function expects a return value of type UIViewController and you return nothing. So either return an instance you create (if you need one). Or remove return value.
There's a slightly modified variation of your function with generics, which does just what you want. The #discardableResult word before the function tells the compiler that the result can be omitted.
#discardableResult
func goToPage<T>(goto storyBoardId: String,
ofType typeUIViewController: T.Type) -> T
where T: UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let newPage = storyboard.instantiateViewController(withIdentifier: storyBoardId) as! T
self.present(newPage, animated: true, completion: nil)
return newPage
}
USAGE
// Ignore return value
goToPage(goto: "Page", ofType: ViewController.self)
// Preserve return value:
// Thanks to generics, page and page2 types are inferred by the compiler
// page is CustomController and page2 is LoginController
// and you can access corresponding interface:
var page = goToPage(goto: "Page", ofType: CustomController.self)
var page2 = goToPage(goto: "Page", ofType: LoginController.self)
Update
I see that Honey suggests the right idea, but the problem with typecasting still persists. The reason of that is that the compiler has no idea what kind of type typeUIViewController is. In fact, it is not actually even a type, it's only an inner name of the variable. And there is no way compiler could infer the type of it (to use with as operator). Hence, one of the proper ways to achieve what you are trying is by using generics. Consider generic T as a pattern which meets certain condition.

You need to change UIViewController to UIViewController.Type. For more see here
because a parameter of UIViewController can accept a UIViewController instance e.g. UIViewController(). However you need to get its type information (you don't need an instance), therefore it has to be a parameter of type UIViewController.Type so the value you pass can be something like SomeUIViewControllerSubclass.self which is NOT an instance...
So you have to do this:
func goToPage(goto storyBoardId: String, ofType typeUIViewController: UIViewController.Type) -> UIViewController {
let storyBoard: UIStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let newPage = storyBoard.instantiateViewController(withIdentifier: storyBoardId) as! typeUIViewController
self.present(newPage, animated: true, completion: nil)
}

Related

How to access UITextfield placeholder in Unit Test?

How can I access the placeholder value of a UITextfield inside XCTestCase? Currently I'm getting an error 'Ambigous use of placeholder'.
import XCTest
#testable import Project
class Tests: XCTestCase {
func testExample() {
let login = UIStoryboard(name: Storyboards.Authentication, bundle: nil).instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
let _ = login.view
XCTAssertEqual("Email ID", login.emailTextField.placeholder)
}
}
Not sure what type you have in LoginViewController.
From my understanding, you have some custom type. I guess you have the same property declared placeholder in your custom type.
If this is the case, you just suggest to Swift what exactly type you need.
I guess for testing you need UITextField.
let textField = UITextField() //works fine, direct type
XCTAssertNotNil(textField.placeholder)
let typed = (login.emailTextField as? UITextField)
XCTAssertNotNil(typed.placeholder)

Quicklook always displays "no file to preview" error (url is valid)

I'm trying to use QuickLookController subclass as a child controller, setting its view as a subview in the parent. However, it always displays "no file to preview" message in the opening window. URL in the data source is valid, but the controller is never trying to get it! func previewItemAt index is never invoked!
func "numberOfPreviewItems" invokes always.
Please, help!
I get it. driven by example in article https://williamboles.me/hosting-viewcontrollers-in-cells/ I loaded my controller from bundle:
static func createFromStoryBoard() -> PreviewControler {
let storyboard = UIStoryboard(name: "PreviewControler", bundle: Bundle(for: PreviewControler.self))
guard let viewController = storyboard.instantiateViewController(withIdentifier: "PreviewControler") as? PreviewControler else {
fatalError("PreviewControler should be present in storyboard")
}
return viewController
}
But QuickLook controller must be created with it's constructor, so change to
let viewController = PreviewController()
solved the problem. Now all is fine.

call uiactivityviewcontroller with spritekit/skscene

I'm trying to add a share button to social media from within my game to share a highscore. I can't seem to figure it out, and from various other answers, I've arrived at this code (which throws a NSInternalInconsistencyException). Any ideas?
let savedScore = UserDefaults.standard.value(forKey: "HighestScore") as! Int
let textToShare = "My highscore on Panda Pong is \(savedScore)! Can you beat that?"
let objectsToShare = [textToShare]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
activityVC.excludedActivityTypes = [UIActivityType.airDrop, UIActivityType.addToReadingList]
let vc = UIViewController(nibName: "testview", bundle: nil) as UIViewController
vc.present(activityVC, animated:true, completion:nil)
The correct code is:
let vc = self.view!.window!.rootViewController!
vc.present(activityVC, animated:true, completion: nil)
Not exactly sure why, but messed around with some syntax and arrived at this answer.
You can do it this way in spritekit
...
let rootViewController = view?.window?.rootViewController
rootViewController?.present(activityVC, animated:true, completion:nil)
Instead of trying to instantiate a new ViewController just use the rootViewController (GameViewController).
I would also reccomend to not do things such as
let savedScore = UserDefaults.standard.value(forKey: "HighestScore") as! Int
with force casting the user defaults value (as! Int). You will crash if there is no value yet so change it to this
let savedScore = UserDefaults.standard.value(forKey: "HighestScore") as? Int ?? 0
You now safely check if the UserDefaults value exists and is an Int (as? Int) and if not it will create a new default value (?? 0).
Furthermore try to put your UserDefaults String keys ("HighestScore") into a constant property so you avoid typos.
enum UserDefaultsKey: String {
case highscore = "HighestScore"
}
and than use it like so
UserDefaults.standard.value(forKey: UserDefaultsKey.highscore.rawValue)
Hope this helps

Why do I need to force the type using this Swift generic function?

I had some repetitive UIViewController boiler-plate scattered around that I wanted to encapsulate, so I defined this generic UIViewController extension method:
extension UIViewController {
func instantiateChildViewController<T: UIViewController>(
storyboardName: String? = nil,
identifier: String? = nil
) -> T {
let storyboard: UIStoryboard!
if let name = storyboardName {
storyboard = UIStoryboard(name: name, bundle: nil)
}
else {
storyboard = UIStoryboard(name: "\(T.self)", bundle: nil)
}
let vc: T!
if let identifier = identifier {
vc = storyboard.instantiateViewController(withIdentifier: identifier) as! T
}
else {
vc = storyboard.instantiateInitialViewController()! as! T
}
self.addChildViewController(vc)
self.view.addSubview(vc.view)
return vc
}
}
However, when I use this extension like so:
class ChildViewController: UIViewController { /*...*/ }
class ParentViewController: UIViewController {
private var childVC: ChildViewController!
//...
func setupSomeStuff() {
self.childVC = self.instantiateChildViewController() //<-- Compiler error
let vc: ChildViewController = self.instantiateChildViewController() //<-- Compiles!
self.childVC = vc
}
}
I get the compiler error Cannot assign value of UIViewController to type ChildViewController! on the line with the comment above. However, if I use an intermediate variable that I explicitly give a type to it works.
Is this a Swift bug? (Xcode 8.1) My interpretation of how generics work is that in this case T should equal the more specific ChildViewController, not the less constrained UIViewController. I get the same issue if I defined childVC as private var childVC: ChildViewController?, the only work-around I've found is the local variable, which obviously makes the extension less compelling, or to do an explicit cast like:
self.childVC = self.instantiateChildViewController() as ChildViewController
I've seen this too. I think there's some weird behavior around Optionals the compiler isn't dealing with as expected.
If you change the return value of the function to an optional value it should work without a problem.
func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T!
or
func instantiateChildViewController<T: UIViewController>(//whateverParams) -> T?
Also, your childVC should be a var rather than a let if you're going to set it anyplace other than an initializer

Class casting dynamically in swift

I am trying to dyanmically cast to a class in Swift. Is this possible? Here is the code I am trying to use:
let stringClass: AnyClass = NSString.self
let anyObject: AnyObject = "foo"
let string = anyObject as! stringClass
The code fails to compile at the cast. Is this possible and if so, why is the right syntax?
Real use case
Here is the real issue. I am attempting to refactor this code:
switch (value) {
case "valueOne":
viewController = storyboard.instantiateViewController(withIdentifier: "foo") as! FirstViewController
case "valueTwo":
viewController = storyboard.instantiateViewController(withIdentifier: "bar") as! SecondViewController
default:
return nil
}
into:
let controllersDictionary: [String: (String, UIViewController.Type)] = [
"valueOne" : ("bar", FirstViewController.self),
"valueTwo" : ("foo", SecondViewController.self)
]
let tuple = controllersDictionary[value]!
let identifier = tuple.0
let cast = tuple.1
let viewController = storyboard.instantiateViewController(withIdentifier: identifier) as! cast
I'm not sure exactly what you're trying to achieve, but here's a working version of your example:
func cast<T>(value: Any, to type: T) -> T? {
return castedValue as? T
}
let inputValue: Any = "this is a test"
let inputType = String.self()
let casted = cast(value: inputValue, to: inputType)
print(casted)
I'm not seeing what the cast at this point is for. You can write:
let controllersDictionary: [String: String] = [
"valueOne" : "bar",
"valueTwo" : "foo"
]
let identifier = controllersDictionary[value]!
let viewController = storyboard.instantiateViewController(withIdentifier: identifier)
The cast does nothing for you in the code that you have shown. viewController is typed as UIViewController, but it is the correct underlying view controller subclass thanks to polymorphism; whatever the class is in the storyboard, that's the class of this instance.
The only time you need to cast down is when you have to message an instance with a message belonging only to the subclass, and you have not shown any such need at this point in your code.
While there are/will be ways to make this kind of thing work, the Swifty solution (IMO) is to have your desired classes adhere to a protocol that defines the shared behavior you're trying to use, or simply use a super class they have in common
This allows the dynamism requried (in most cases at least) while still allowing the compile-time checks that prevent run time errors.
For your example,
protocol Stringable {
func toString() -> String
}
extension String: Stringable {
func toString() -> String {
return self
}
}
let thing = "foo"
let anything: Any = thing
let test: String? = (anything as? Stringable)?.toString()
Note that this requires "Any" rather than "AnyObject" since you need to cast to a protocol
Since you mentioned ViewControllers, I thought this might help:
static func createViewController<T: UIViewController>(storyboard: String, scene: String) -> T? {
return UIStoryboard(name: storyboard, bundle: nil).instantiateViewControllerWithIdentifier(scene) as? T
}
The x as! Y.Type syntax only works when Y is explicitly stated.
So, the compiler wants x as! NSString. The compiler crash is a bug, I suggest that you file a report.
And just think about it for a second, how would that even help you? stringClass is of type AnyClass, and you're force casting an AnyObject which already conforms to AnyClass. You should cast when you have a static type in mind, because casting doesn't really make any sense otherwise.
If you want to check, however, whether your object's class is a subclass of a particular type, you'd use:
x is Y.Type
Its possible so long as you can provide "a hint" to the compiler about the type of... T. So in the example below one must use : String?.
func cast<T>(_ value: Any) -> T? {
return value as? T
}
let inputValue: Any = "this is a test"
let casted: String? = cast(inputValue)
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
Why Swift doesn't just allow us to let casted = cast<String>(inputValue) I'll never know.
One annoying scenerio is when your func has no return value. Then its not always straightford to provide the necessary "hint". Lets look at this example...
func asyncCast<T>(_ value: Any, completion: (T?) -> Void) {
completion(value as? T)
}
The following client code DOES NOT COMPILE. It gives a "Generic parameter 'T' could not be inferred" error.
let inputValue: Any = "this is a test"
asyncCast(inputValue) { casted in
print(casted)
print(type(of: casted))
}
But you can solve this by providing a "hint" to compiler as follows:
asyncCast(inputValue) { (casted: String?) in
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>
}