Why is downcasting required after instantiateViewController(withIdentifier:) in Swift - ios

Why do we need to downcast using the keyword as after instantiating a view controller with UIStoryboard's instantiateViewController(withIdentifier:) method in order for the view controller's properties to be accessible? The UIStoryboard method instantiateViewController(withIdentifier:) already returns a UIViewController and knows which class based on the Storyboard ID or this is what I assume happens but not totally true.
The following code works and compiles but I want to understand why. If I were building this based on the documentation, I wouldn't have assumed downcasting would be necessary so I'm trying to figure out what part I haven't learned or understood in regards to types and/or objects being returned from functions.
func test_TableViewIsNotNilOnViewDidLoad() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(
withIdentifier: "ItemListViewController")
let sut = viewController as! ItemListViewController
_ = sut.view
XCTAssertNotNil(sut.tableView)
}

Because storyboard.instantiateViewController... always returns a UIViewController (the base class for your specific subclass) and thus cannot know implementation details specific to your subclass.
The method mentioned above doesn't infer your sub-class type based on the storyboard id, this is something you do in your code when downcasting (see here).
func instantiateViewController(withIdentifier identifier: String) -> UIViewController
So it works because you get an UIViewController from the method above and then you force downcast it to your ItemListViewController (it always works because you defined ItemListViewController as an UIViewController subclass).
PS. I'm not sure I've understood your question though, this seems pretty straightforward.

knows which class based on the Storyboard ID
This is completely incorrect. The Storyboard ID is a string. In your case it happens to be a static string, but this method doesn't require that. It could be computed at run time (and I've personally written code that does that). Your string happens to match the classname, but there's no requirement that this be true. The identifier can be any string at all. And the storyboard isn't part of the compilation process. The storyboard could easily be changed between the time the code is compiled and the time it's run such that the object in question has a different type.
Since there isn't enough information to compute the class at compile time, the compiler requires that you explicitly promise that it's going to work out and to decide what to do if it fails. Since you use as!, you're saying "please crash if this turns out to be wrong."

This is partially due to polymorphism. Take for example the following code:
class Person {
let name: String
}
class Student: Person {
let schoolName: String
}
func createRandomPerson(tag: Int) -> Person {
if tag == 1 { return Person() }
else { return Student() }
}
let p = createRandomPerson(2)
Depending on the value of the tag parameter you'll get either a Person or a Student.
Now if you pass 2 to the function, then you can be sure that createRandomPerson will return a Student instance, but you will still need to downcast as there are scenarios where createRandomPerson will return an instance of the base class.
Similar with the storyboard, you know that if you pass the correct identifier you'll get the view controller you expect, however since the storyboard can create virtually any instance of UIViewController subclasses (or even UIViewController) the function in discussion has the return type set to UIViewController.
And similarly, if you do your math wrong - i.e. you pass 1 to createRandomPerson(), or wrong identifier to the storyboard, then you won't receive what you expect to.

You maybe need to do some background reading on object oriented programming in general to help you understand. Key concepts are class, instance/object, inheritance, and polymorphism.
storyboard.instantiateViewController() will create an instance of ItemListViewController but it is being returned as UIViewController. If this part is difficult to understand then this is where you need objected oriented knowledge background.
In OO languges, like C++ for example, an instance of a class (also known as an object) can be referenced by a pointer to its parent (or grandparent or great grand parent etc.) class. In the majority of literature and tutorials on object orientation the concept on inheritance and casting and polymorphism is always illustrated using pointers to base classes and derived objects and so on.
However with Swift, pointers aren't exposed in the way there are in many other OO languages.
In your code, and explaining this in a simplified way and as if it was C++ or a similar OO language as that is how most tutorials on OO explain things:
let viewController = storyboard.instantiateViewController(
withIdentifier: "ItemListViewController")
viewController is a "pointer" of class type UIViewController which is pointing to an object of type ItemListViewController.
The compiler sees the pointer viewController as being of type UIViewController and therefore it does not know about any of the specific methods or properties of ItemListViewController unless you explicitly do the cast so that it knows about them.

Related

What are the disadvantages of passing data between ViewControllers using Global Variables?

I am new to Swift, coming from C and Java background.
I am trying to pass a struct (called 'Player') object from one view controller to another, and as i learned one proper way to do is first defining it in the target VC like:
class TargetVC: UIViewController {
var playerVar: Player!
}
And then assigning it in prepare function inside source VC:
if let targetVC = segue.destination as? TargetVC
{
targetVC.playerVar = myPlayer
}
But i thinked about simply using a global variable to pass the Player object to the target view controller by defining it like:
var myPlayer = Player() // Global Variable
class SourceVC: UIViewController {
}
Then reaching it in target VC:
class TargetVC: UIViewController {
myPlayer.name = // Stuff
}
Would this method cause any problems in runtime or does it have any disadvantages? If yes, what are them?
Thanks in advance.
Passing data gives to you to conform programming principles to build a good classes, even in your example. It makes classes more extendable and reusable in many cases.
Global var gives some advantages to reach needed data, but you have to care about reference cycles, memory managing, handle threads, queues and blocks, etc.
You can also read out about singletones, static vars,... – where you can get some data from class scope vars.
One disadvantage is, as the name suggests, everything has access to global variables, even things that have no reason to see, much less modify, that data.

Passing information between controllers in a Swifty (/protocol) way?

I am trying to pass information from controller A to controller B. The thing is, I want:
Concise: To minimize the autocomplete from XCode to some key information. I want to know in a easy way the exact parameters required before pushing the controller on the stack.
Avoid segues. From my understanding, segues create a lot of tight coupling in the storyboard. I don't want to be dependent of the storyboard to pass information. (Each time I want to switch controller A to another, I have to go to the storyboard to make some changes). I might split my app in several storyboards at some point, and segues can be quite annoying to deal with.
Beautiful: Maybe Swift can provide a Swifty solution I have not thought about.
What I have been trying to accomplish at first was to push controller as protocol. Even if it not possible, let me explain:
Pushing a controller as a protocol would allow me to have exact visibility on my attributes.
There is no tight coupling related to the storyboard
Many controllers (A, C, D) might want to push a B controller, I might give them each one of them a different protocol to push controller B. Maybe B could appear under different circumstances.
At first, my code was looking like that (the storyboard extension works):
if let myBCustomVC = storyboard.instanciateControllerWithIdentifier(MyBCustomVC.self) as? myVCustomFromAProtocol{
myBCustomVC.AToB = self.data.count
self.navigationController?.pushViewController(myBCustomVC, animated: true)
}
protocol myVCustomFromAProtocol {
var AToB: Int {get set}
}
The thing is, I cannot push a view controller downcast to a protocol. I had to come by an ugly UINavigationController extension. Here is the full result.
if let myBCustomVC = storyboard.instanciateControllerWithIdentifier(MyBCustomVC.self) as? myVCustomFromAProtocol{
myBCustomVC.AToB = self.data.count
self.navigationController?.pushViewController(vcToPush: myBCustomVC, animated: true)
}
extension UINavigationController{
func pushViewController(vcToPush: Any, animated: Bool){
if let vc = vcToPush as? UIViewController{
self.pushViewController(vc, animated: animated)
}
}
}
Let's face it, while achieving my first two goals, I have downcasted an Any to a UIViewController, woosh.
Is there any way to avoid tight coupling and push controllers on the stack in a beautiful way while keeping a limited visibility on the parameters to pass from the first view controller(A) to the second(B). What are your thoughts? Why wouldn't I want to do that?
I'd approach this from the other side (i.e. the side of the controller you are presenting).
You could do something like creating a Presenter protocol.
protocol Presenter {
func present(inContext context: UIViewController)
// possibly with some data in it too (if it's across all instances)...
var count: Int {get set}
}
And have some sort of factory struct on top of each...
struct BVCPresenter: Presenter {
var count: Int = 0
func present(inContext context: UIViewController) {
// create the BViewController here
let controller = ...
context.push(controller, animated: true)
}
}
Or something along those lines. It really depends on the use cases and the data being passed around etc...
So now A can do...
someGenericPresenter.present(inContext: navigationController)
The generic presenter can be passed in as a property to A or can be passed into a function as a parameter etc...
or something.
You could even create a "FlowController" that manages these transitions and data for you...
self.flowController.presentB()
The flowController then knows what B needs and how and where to present it and populate it with data. The individual view controllers don't actually need to know anything about each other.
I don't think there is a solution for this that would suit all cases. It requires a lot of thought and design. But there are many options. Think about what you do need between the view controllers and work from that.
Also, don't be too worried about creating something that "just works" even if it isn't textbook generic, "Swifty", elegant, beautiful code. Having code that works is much better than having a beautifully designed system that doesn't.
:)
With the help of Fogmeister I came up with this answer. It has several advantages and disadvantages over the use of a Presenter Object that inherits from a protocol.
Solution: Use a static factory method (or several depending of the use case) located in the controller to be instantiated.
Advantages:
Using a static factory method enforces to pass parameters that the actual controller know to be required for his own initialization.
No structure involved (less complexity?)
The constant use of a static factory method becomes natural over time. You know when looking at a controller you must implement the method to call it, and you know the parameters required. You do not have to worry which parameters must be implemented to push the controller, just implement the function. Using a presenter, you do not have this knowledge as the unwrapped parameters can be numerous.
The use of a Presenter is closer to MVP and gets away from the dependency injection model advocated by Apple (MVC)
Disadvantages:
The use of the Presenter pattern suggested by Fogmeister uses a protocol. The protocol gives limited visibility to controller A (and all controllers that want to push a controller B).
The use of the Presenter pattern gives more modularity and reduces tight coupling. Controller A has no need to know anything about controller B. The replacement of controller B to another controller C can be done without affecting all the controllers that were going to B (can be quite useful if you are restructuring your application or in some use cases).
Here is my current implementation in my class:
In controller B (static function):
static func prepareController(originController: UIViewController, AToB: Int) -> bVC? {
if let bVC = originController.storyboard?.instanciateControllerWithIdentifier(self) as? bVC{
bVC.AToB = AToB
return bVC
}
else{
return nil
}
}
In controller A (to push the controller on the stack):
if let bVC = bVC(originController: self, AToB: *someValue*){
self.navigationController?.pushViewController(bVC, animated: true)
}
Solution has to be taken with a grain of salt, let me know what you think #Fogmeister?
Both solutions can be combined. Using a protocoled structure that would use the static method provided by the controller. The thing is it can quickly become overly complex for people you introduce to the project. That's why I am sticking to my solution at the moment. I am looking at frameworks for dependency injection though.

How to use the object oriented language Swift the right way?

I'm new in Swift and even in object oriented programming languages itself. So my question is, how to use this very extensive language Swift the right way? I give an example to verify my problem:
Let's say I have got two classes:
Class ScanForBluetoth{} //Handles all parts to scan for BT devices
class ScanForDevices: UIViewController, CBCentralManagerDelegate , CBPeripheralDelegate, UITableViewDelegate,UITableViewDataSource{}
Class Bluetooth{} //Handles only the Bluetooth parts with it's delegates
class Bluetooth: ScanForDevices{}
Now, I would like to implement all my used delegates and other Bluetooth specific functions into the Bluetooth class. BUT I need some objects (for example of the CBCentralManagerDelegate) in my ScanForDevices class, too. So, I have to implement all my delegates in my "mother" class ScanForDevices although I only need some properties. SO if I implement the delegates, I have to be conform with the protocol and must implement ALL my needed delegate functions... At the end I have implemented all my delegate functions in ScanForDevices and then override them in Bluetooth. But I don't think that my way is the best way to realize this problem...
Thanks for reading!
Firstly, I would like to point out that your naming conventions are really off. In object oriented programming, you want your class names to be objects (nouns). You named your classes by what they were doing, rather than what they are. A better name choice for your classes would be something like BluetoothDeviceScanner, rather than scan for devices, and BluetoothManager rather than the non-explicit "bluetooth".
Secondly, what you have done is subclassed the bluetooth class to scan for devices class, which causes it to inherit all the functionality of its class. This really doesn't make any sense. Subclassing is used to create an object based on a parent object, while these two objects handle two totally different things, and then you're planning on overriding the functions anyway. Instead of that, you should just include the protocols that you need in the bluetooth class separately. Keep the functionality of the two classes separated as much as possible.
Thirdly, you should separate your view controller functionality from the scanning functionality. What I mean is the "ScanForDevices" object's job is to scan for devices, so it shouldn't also have the job of controlling a view... I would remove the UIViewController protocol and introduce a new view controller class, and within that class you can have a property that is assigned the "ScanForDevices" object, at which point the devices can be scanned for within the viewcontroller, but the scanning functionality is contained within a single object (which is best practice).
EDIT
All you need to do to "connect" the data is have your BluetoothManager and BluetoothScanner objects is have them available as a property within whatever view controller you need them. So, in the viewcontroller declare some properties, I usually do it with optionals so that I don't have to worry about initializing the properties (This means you need to unwrap the variables before using them).
In your ViewController...
var bluetoothScanner: BluetoothScanner?
var bluetoothManager: BluetoothManager?
override func viewDidLoad() {
super.viewDidLoad()
bluetoothScanner = BluetoothScanner(init parameters)
bluetoothManager = BluetoothManager(init parameters)
}
You're objects are now "connected" in the sense that you have access to them and all their properties/methods in the viewcontroller. Now that I think about it, you don't even need to have both objects on this level. You can store the BluetoothScanner as a property of the Bluetooth manager, at which point you would only need to use a BluetoothManager object to handle all your bluetooth needs on the view controller level.
Init Methods
//init method that takes 2 parameters, a string and a uiviewcontroller.
init(param1: String, param2: UIViewController) {
//Struct init code using parameters
self.name = param1
self.viewController = param2
}
//init method that takes no parameters, but still initializes the same properties.
init() {
self.name = "YungGun"
self.viewController = UIViewController()
}
Keep in mind these initialization methods are made up and have nothing to do with your problem at hand, I was attempting to illustrate that this is where you define the parameters needed to initialize the struct. The same parameters in the parenthesis must be passed when creating an instance of the struct.

Correct way to access commonly used code blocks from single class at Swift

I want to implement a helper class for some constant values and also commonly used code blocks. Between below usages, which one is the correct way?
Defining them as static let values
Defining them as class functions
class Constants
{
// 1: defining them as static let values
static let storyboardA = UIStoryboard(name: "StoryboardA", bundle: nil)
static let storyboardB = UIStoryboard(name: "StoryboardB", bundle: nil)
static let rootVC = UIApplication.sharedApplication().delegate?.window!!.rootViewController
// 2: OR defining them as class functions
class func getStoryboardA() -> UIStoryboard {
return UIStoryboard(name: "storyboardA", bundle: nil)
}
class func getStoryboardB() -> UIStoryboard {
return UIStoryboard(name: "StoryboardB", bundle: nil)
}
class func getRootVC() -> UIViewController? {
return UIApplication.sharedApplication().delegate?.window!!.rootViewController
}
}
Your examples do different things (instantiating a new one instance vs. using the same over and over again). I'd go with the methods, and cache reused objects within private variables.
I'd highly recommend against using this approach, though. It looks tempting at first, but it brings huge costs along with it in the long wrong. It makes for very tight code coupling and poor code reuse. Testing will be much harder.
Your helper class will get bigger and bigger, and it doesn't have a single "topic". It's responsible for the most different things within your app. It's unlikely that you need the storyboard for one part in your app in many other parts etc.
Most of the time, if you need to access those things all over the place your app design would profit from some refactoring. For example, you should rarely if ever need to access the application delegate. It's a convenient "reference point" that's easily available to pester it with code that really does not belong their (been there, done it, learned my lesson).
A slightly more sound approach is to make individual helper classes and throw them into the using classes, and I wouldn't make their members class methods either.

Double question mark, How does it work?

I am learning Swift and, as part of the process, trying to figure out what exactly is going on here. I have a custom segue where I want to place my modal view controller dismissing transition. What used to be in objective-c as:
UIViewController *sourceViewController = self.sourceViewController;
[sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
self is an instance of UIStoryboardSegue.
I translated this snippet in Swift as:
self.sourceViewController.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
getting this error from the compiler:
'UIViewController?' does not have a member named
'dismissViewControllerAnimated'
Now, by documentation, the presentingViewController method looks like this:
var presentingViewController: UIViewController? { get }
From what I understood by the Swift language documentation, ? should unwrap the value, if any. In this case the view controller. The unexplained fact is: if I put a double question mark, it compiles and it works:
self.sourceViewController.presentingViewController??.dismissViewControllerAnimated(true, completion: nil)
Can someone tell me what I am missing? What should that do?
The extra ? required is due sourceViewController returning an AnyObject instead of a UIViewController. This is a flaw in the API conversion from Objective-C (in which such property returns a rather meaningless id). It's still an on-going process that started with iOS 8 beta 5, and apparently those API have not been fixed yet.
If you provide an appropriate cast, it will work as expected
(self.sourceViewController as UIViewController).presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
Now, why do we need an extra ? when dealing with AnyObject?
AnyObject can represent any object type, pretty much as id does in Objective-C. So at compile-time you can invoke any existing method on it, for example sourceViewController.
When you do so, it triggers an implicit downcast from AnyObject to UIViewController and according to the official guide:
As with all downcasts in Swift, casting from AnyObject to a more specific object type is not guaranteed to succeed and therefore returns an optional value
So when you do
self.sourceViewController.presentingViewController??
it implicitly translates to something like
let source: UIViewController? = self.sourceViewController as? UIViewController
let presenting: UIViewController? = source?.presentingViewController
and that's why you need two ?: one for resolving the downcast and one for the presentingViewController.
Finally, always according to the documentation:
Of course, if you are certain of the type of the object (and know that it is not nil), you can force the invocation with the as operator.
which is exactly my proposed solution above.

Resources