Typecasting for ViewControllers in swift - swift

I’m quite new to iOS development and swift.
I’m seeing something like this quite often
let vc = window?.rootViewController as! MainViewController
Would someone mind explaining type casting and what the purpose of this is to me? I’ve been trying to find other questions or just a basic explanation of the purpose - but I’m not having much luck.
Thanks

The code you posted is a crash in the making. The as construct casts a variable of a general type to a more specific type. If it can fail, you need to add a ? (as?) or an exclamation point (as!) The form with an exclamation point is known as a "force cast". It tries to cast the object to a different type, and will crash if the cast fails. Thus it is a bad idea and should be avoided.
Better to use code like this:
func someFunc() {
guard let vc = window?.rootViewController as? MainViewController else {
return
}
//Code that depends on vc being cast to type MainViewController
}
(Read up on the guard statement in the Apple Swift iBook for more information.)
Even this code is better than what you posted:
func someFunc() {
guard let vc = window?.rootViewController as? MainViewController else {
fatalError("Unable to cast rootViewController to type MainViewController")
}
//Code that depends on vc being cast to type MainViewController
}
The second version will still crash, but will display a very clear message in the console when it does crash, so you can tell what went wrong.

Related

What is the purpose of a line of code that says: '_ = viewController.view'?

What is the purpose of the following line of code? It doesn't seem to do anything. If it were in Playground or in a print statement it would at least show something.
_ = masterVC.view
It's in Apple's sample code at Sharing CloudKit Data with Other iCloud Users.
Here's the complete significant code that contains that line of code:
if let masterVC = masterNC?.viewControllers.first as? ZoneViewController {
      _ = masterVC.view
      start ? masterVC.spinner.startAnimating() : masterVC.spinner.stopAnimating()
}
Let's see the doc of view from UIViewController:
If you access this property when its value is nil, the view controller automatically calls the loadView() method and returns the resulting view.
Seeing the project code: masterVC.spinner, is a lazy var that in viewDidLoad() will be added as a subview of its UITableView.
Why do this then?
Because when you do:
let someVC = SomeViewController()
Its IBOutlet and its view hasn't been loaded yet. You can reproduce it with getting having a IBOutlet on that VC, or some subview that will be added to the view in viewDidLoad(). Try to access the property, it will be nil for the IBOutlet (and usually crash with if it's declared forced unwrapped), and subview.superview will be nil for the other one.
That's also why when you use UIStoryboardSegue, in prepare(for:sender:), when you do:
if let nextVC = segue.destination as? SomeViewController() {
nextVC.labelIBOulet.text = "someTextToPass" //It will crash for the same reason
}
There, it's rather common to pass the model (here a simple String) instead of setting the value of the UILabel.
I personally prefer calling loadViewIfNeeded() instead, in my opinion it's less strange that invoking the view and ignore the returned value.

Can't cast from RLMObject to a subclass of Object?

Song is a subclass of RLMObject (typealias Object) and is used throughout my app, including in searchViewController(_:cellForObject:atIndexPath:)
let song = object as! Song
But in my prepare(for segue:) method (below), when I try to perform the same downcast, the compiler says "Cast from 'RLMObject' to unrelated type 'Song' always fails."
if let row = tableView.indexPathForSelectedRow?.row {
YpbApp.currentRequest?.songObject = results!.object(at: UInt(row)) as? Song
}
This doesn't makes sense, what's wrong here?
RLMObject is not a typealias for Object; they are different classes entirely that have different interfaces. It sounds like you are trying to mix the Swift and Objective-C APIs, which is not supported.

Is force cast really bad and should always avoid it?

I started to use swiftLint and noticed one of the best practices for Swift is to avoid force cast. However I used it a lot when handling tableView, collectionView for cells :
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as! MyOffersViewCell
If this is not the best practice, what's the right way to handle this? I guess I can use if let with as?, but does that mean for else condition I will need to return an empty cell? Is that acceptable?
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellID, forIndexPath: indexPath) as? MyOffersViewCell {
// code
} else {
// code
}
This question is probably opinion based, so take my answer with a grain of salt, but I wouldn't say that force downcast is always bad; you just need to consider the semantics and how that applies in a given situation.
as! SomeClass is a contract, it basically says "I guarantee that this thing is an instance of SomeClass". If it turns out that it isn't SomeClass then an exception will be thrown because you violated the contract.
You need to consider the context in which you are using this contract and what appropriate action you could take if you didn't use the force downcast.
In the example you give, if dequeueReusableCellWithIdentifier doesn't give you a MyOffersViewCell then you have probably misconfigured something to do with the cell reuse identifier and an exception will help you find that issue.
If you used a conditional downcast then you are going to get nil and have to handle that somehow - Log a message? Throw an exception? It certainly represents an unrecoverable error and something that you want to find during development; you wouldn't expect to have to handle this after release. Your code isn't going to suddenly start returning different types of cells. If you just let the code crash on the force downcast it will point straight to the line where the issue occurred.
Now, consider a case where you are accessing some JSON retrieved from a web service. There could be a change in the web service that is beyond your control so handling this more gracefully might be nice. Your app may not be able to function but at least you can show an alert rather than simply crashing:
BAD - Crashes if JSON isn't an array
let someArray=myJSON as! NSArray
...
Better - Handle invalid JSON with an alert
guard let someArray=myJSON as? NSArray else {
// Display a UIAlertController telling the user to check for an updated app..
return
}
Update
After using Swiftlint for a while, I am now a total convert to the Zero Force-Unwrapping Cult (in line with #Kevin's comment below).
There really isn't any situation where you need to force-unwrap an optional that you can't use if let..., guard let... else, or switch... case let... instead.
So, nowadays I would do this:
for media in mediaArray {
if let song = media as? Song {
// use Song class's methods and properties on song...
} else if let movie = media as? Movie {
// use Movie class's methods and properties on movie...
}
}
...or, if you prefer the elegance and safety of an exhaustive switch statement over a bug-prone chain of if/elses, then:
switch media {
case let song as Song:
// use Song class's methods and properties on song...
case let movie as Movie:
// use Movie class's methods and properties on movie...
default:
// Deal with any other type as you see fit...
}
...or better, use flatMap() to turn mediaArray into two (possibly empty) typed arrays of types [Song] and [Movie] respectively. But that is outside the scope of the question (force-unwrap)...
Additionally, I won't force unwrap even when dequeuing table view cells. If the dequeued cell cannot be cast to the appropriate UITableViewCell subclass, that means there is something wrong with my storyboards, so it's not some runtime condition I can recover from (rather, a develop-time error that must be detected and fixed) so I bail with fatalError().
Original Answer (for the record)
In addition to Paulw11's answer, this pattern is completely valid, safe and useful sometimes:
if myObject is String {
let myString = myObject as! String
}
Consider the example given by Apple: an array of Media instances, that can contain either Song or Movie objects (both subclasses of Media):
let mediaArray = [Media]()
// (populate...)
for media in mediaArray {
if media is Song {
let song = media as! Song
// use Song class's methods and properties on song...
}
else if media is Movie {
let movie = media as! Movie
// use Movie class's methods and properties on movie...
}
Others have written about a more general case, but I want to give my solution to this exact case:
guard let cell = tableView.dequeueReusableCell(
withIdentifier: PropertyTableViewCell.reuseIdentifier,
for: indexPath) as? PropertyTableViewCell
else {
fatalError("DequeueReusableCell failed while casting")
}
Basically, wrap it around a guard statement and cast it optionally with as?.
"Force Cast" has its place, when you know that what you're casting to is of that type for example.
Say we know that myView has a subview that is a UILabel with the tag 1, we can go ahead and force down cast from UIView to UILabel safety:
myLabel = myView.viewWithTag(1) as! UILabel
Alternatively, the safer option is to use a guard.
guard let myLabel = myView.viewWithTag(1) as? UILabel else {
... //ABORT MISSION
}
The latter is safer as it obviously handles any bad cases but the former, is easier. So really it comes down to personal preference, considering whether its something that might be changed in the future or if you're not certain whether what you are unwrapping will be what you want to cast it to then in that situation a guard would always be the right choice.
To summarise: If you know exactly what it will be then you can force cast otherwise if theres the slightest chance it might be something else use a guard
As described in some casting discussions, forcing the cast for tableView.dequeueReusableCell is ok and can/should be done.
As answered on the Swiftlint Github site you can use a simple way to turn it off for the table cell forced cast.
Link to Swiftlink issue 145
// swiftlint:disable force_cast
let cell = tableView.dequeueReusableCell(withIdentifier: "cellOnOff", for: indexPath) as! SettingsCellOnOff
// swiftlint:enable force_cast
When you are working with your types and are sure that they have an expected type and always have values, it should force cast. If your apps crash you can easily find out you have a mistake on which part of UI, Dequeuing Cell, ...
But when you are going to cast types that you don't know that is it always the same type?
Or is that always have value?
You should avoid force unwrap
Like JSON that comes from a server that you aren't sure what type is that or one of that keys have value or not
Sorry for my bad English I’m trying to improve myself
Good luck🤞🏻
In cases where you are really sure the object should be of the specified type it would be OK to down cast. However, I use the following global function in those cases to get a more meaningful result in the logs which is in my eyes a better approach:
public func castSafely<T>(_ object: Any, expectedType: T.Type) -> T {
guard let typedObject = object as? T else {
fatalError("Expected object: \(object) to be of type: \(expectedType)")
}
return typedObject
}
Example usage:
class AnalysisViewController: UIViewController {
var analysisView: AnalysisView {
return castSafely(self.view, expectedType: AnalysisView.self)
}
override func loadView() {
view = AnalysisView()
}
}

instantiateViewControllerWithIdentifier is crashing (found nil)

When I try to instantiateViewControllerWithIdentifier on an iPhone it is crashing the app, although this works fine on the ios simulator. The code I have used is:
let questionsGameVC: QuestionsGame = self.storyboard?.instantiateViewControllerWithIdentifier("Questions") as! QuestionsGame
The error it is saying is
fatal error: unexpectedly found nil while unwrapping an Optional value
Can someone add anything to what is going wrong?
There are lots of places this could go wrong. I would put a breakpoint at that line and look at the state of the system, but debugging swift is still not in a great state. One alternative is to break apart that line of code and test all the pieces. Something like this:
if let storyboard = self.storyboard {
if let viewController = storyboard.instantiateViewControllerWithIdentifier("Questions") {
if let questionGame = viewController as? QuestionGame {
println("Success!")
} else {
println("Question game is the wrong type in the storyboard")
}
} else {
println("There is no view controller with the identifier Questions")
}
} else {
println("The storyboard is nil")
}
Whatever gets printed at the end should give you a better idea of where the problem is. Most often I have seen misspelled identifiers or situations where the class of the view controller in the storyboard has not been changed from UIViewController to the custom type.

AppDelegate, DidFinishLaunchingWithOptions, ImplicitlyUnwrappedOptional What does it mean?

The app build successfully with no error in debugging area, but immediately stops and bring me to this;
I'm not sure what to make of the error. I can only guess its in the AppDelegate.swift, somewhere along DidFinishLanchingWithOptions.
Does anyone know how to solve this error?
Sorry, not smart enough to figure out this probably trivial error to you guys
UPDATE:
I've tried the suggestion by user mstysf below, but the same problem occur? Am i doing it wrong or missing something?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
if let navigationController = self.window?.rootViewController as? UINavigationController {
let challengesController = navigationController.topViewController as ChallengesViewController
unarchiveDataSource()
if let dataSource = challengeDataSource {
challengesController.challengeDataSource = dataSource
} else {
loadDefaultChallenges()
challengesController.challengeDataSource = challengeDataSource
}
}
return true
}
Does anyone know what is wrong? Thanks again, any help is appreciated.
It means the navigationController is nil. You should handle with it like this:
if let navigationController = self.window?.rootViewController as? UINavigationController {
// do your work here
}
It builds successfully because compiler trusts you navigationController won't be nil and does not check it. That's the point of implicitly unwrapped optional type. In the runtime if it is nil then your app crashes. That's the danger of implicitly unwrapped optionals.