Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialNadaa Taiyab
2,074 PointsDowncasting
Could you explain the concept of downcasting and this section of code:
if segue.identifier == "showPlaylistDetail" {
let playlistDetailController = segue.destinationViewController as PlayListDetailViewController
playlistDetailController.segueLabelText = "Yay you pressed me!"
in a little bit more detail? This is a new concept that has not been covered before. I could not quite understand the documentation on it either.
Your videos are generally very helpful, but sometimes you skip over new concepts too quickly.
Christopher St. George
2,606 PointsDowncasting is important for gaining access to information that you would otherwise be unable to access in an object that has been Upcast.
Upcasting is the process of referencing a child class object as if it were one of its parent classes,
for instance:
class Shape {
}
class Circle: Shape {
let radius: Double
init(radius r: Double) {
self.radius = r
}
}
class Square: Shape {
let width: Double
init(width w: Double) {
self.width = w
}
}
// upcasting shapes to put in this Shape-class-only array
let shapeArray: [Shape] = [ Square(width: 2.0), Circle(radius: 3.5) ]
// now let's print out the shapes' areas!
for shape in shapeArray {
var area: Double
if let circle = shape as? Circle {
area = circle.radius * circle.radius * 3.14159
println(area)
} else if let square = shape as? Square {
area = square.width * square.width
println(area)
}
}
// notice we *downcast* to access the variables that we needed to find the shapes' areas.
// without downcasting we would not have been able to access them (the variables)
// note also that you can only upcast to a parent class (or protocol) of the object you are upcasting
// also note that you can only downcast to a child of a parent class/protocol, and only if the object was originally upcast from it or through it (you cannot downcast to something that the object does not have information for... for instance a Shape cannot be downcast to a UIViewController... it doesn't have much, if any, of the same information within it, and more importantly, it is not even related)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// notice segue: UIStoryboardSegue in the function parameters above
// if you command + click UIStoryboardSegue you open up the file containing the UIStoryboardSegue class code.
// note that the initializer for UIStoryboardSegue clearly shows the destinationViewController is of type UIViewController
// if you look further down in the class, var destinationViewController is actually saved as type AnyObject
if segue.identifier == "showPlaylistDetail" {
let playlistDetailController = segue.destinationViewController as? PlaylistDetailViewController
playlistDetailController. segueLabelText = "You pressed the button!"
// notice playlistDetailController. segueLabelText in the line above
// the AnyObject protocol does not have a variable called "segueLabelText"
// the UIViewController class does not have a variable called "segueLabelText"
// the PlaylistDetailViewController class that we created *does* have var segueLabelText.
// we know that the "showPlaylistDetail" segue pushes a view controller of type
// PlaylistDetailViewController onto the navigation stack. We programmed it to do that.
// because of that, we know that if segue.identifier == "showPlaylistDetail",
// then the destinationViewController for THIS segue MUST be of type PlaylistDetailViewController.
// we need to downcast to let the compiler know that the object that segue.destinationViewController references is specifically a PlaylistDetailViewController and therefore has the variable segueLabelText.
}
}
10 Answers
Shawn Bolour
2,423 PointsI had the same problem understanding this concept. The best explanation I got was from the following link:
Christopher Mayfield
19,928 PointsThanks Shawn Bolour , that really helped.
Victor Miclovich
9,108 PointsQuick updates with the latest xcode
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showPlaylistDetail"{
// downcasting operation
let playlistDetailController = segue.destinationViewController as! PlayListDetailViewController
playlistDetailController.segueLabelText = "Yay, you pressed the button"
}
}
Downcasting will happen with as!
Christopher St. George
2,606 PointsThank-you Victor for updating this.
As of Swift 1.2 the as operator is now as!
The 'bang' [ ! ] has been placed at the end to more clearly indicate that you are unwrapping an optional that could be a nil value. (i.e. the object you are downcasting might not be able to be downcast to the class/protocol you are attempting to downcast it to).
See this post in Apple's Swift Developer Blog
Antoine Guenet
2,843 PointsChristopher's answer looks great but I still don't understand... The concept seems clear : downcasting is a tool we use to access scoped information (not sure about the terminology here but nevermind) : a variable in a function from outside that function for instance ? Or in this case a variable in a view from a different view. But what does that "as" mean and how do you use it ? It's all a bit confusing... Thanks for your help anyway!
Christopher St. George
2,606 PointsHere is another attempt at an example of downcasting/upcasting
class BaseClass {
var baseClassVariable: String = "I am the baseClassVariable"
}
class ChildClass: BaseClass {
var childClassVariable: String = "I am the childClassVariable"
}
class ChildOfChildClass: ChildClass { // this is also a descendant of BaseClass, because ChildClass is a descendent if BaseClass (think family trees)
var childOfChildClassVariable: String = "I am the childOfChildClassVariable"
}
class RandomClass {
var randomClassVariable: String = "I am the randomClassVariable"
}
let baseClassObject = BaseClass()
let childClassObject = ChildClass()
let grandchildClassObject = ChildOfChildClass()
let randomClassObject = RandomClass()
// you can test making variables that upcast or downcast the above objects.
var testBaseToChildDowncast = baseClassObject as? ChildClass // this will be 'nil' because baseClassObject never was any more specific than 'BaseClass'... so you cannot downcast it.
var testGrandchildToBaseUpcast = grandchildClassObject as BaseClass // this will NOT be nil, ChildOfChildClass is related to BaseClass and is more specific than BaseClass, so you can make it less specific by labeling it as a 'BaseClass' object.
// xcode will tell you to remove the optional operator (?) from after the 'as' because you can always upcast to a related class (you can describe it as something less specific), whereas you cannot always make something more specific (downcasting)
var testOtherBaseToChildDowncast = testGrandchildToBaseUpcast as? ChildClass // this will NOT be nil.
// Even though testGrandchildToBaseUpcast is a BaseClass object, it was originally a ChildOfChildClass object therefore you CAN downcast it into being a ChildClass object (ChildClass is in between BaseClass and ChildOfChildClass on the family tree!)
// take a look at what printing these objects to the console does!
println(testBaseToChildDowncast) // "nil", as expected
println(testGrandchildToBaseUpcast) // ""__lldb_expr_2.ChildOfChildClass""
println(testOtherBaseToChildDowncast)
// xcode will have issues if you try to cast an object to something that it isn't even remotely related to
// for instance, if you cast randomClassObject to a ChildOfChildClassObject, any playground file will begin giving you a nasty error
// e.g.: var randomToGrandchild = randomClassObject as? ChildOfChildClassObject
John Stephanites
2,007 PointsChristopher that is the answer I was looking for thank you very much. I would like the videos to address these kind of things more but at the same time I do realize that it is probably not possible because then the videos would be way to long. I guess that is a great reason to visit the forums! I think this is starting to seep in some and that this series will have to be watched a few times! Thanks for the reply!
John Stephanites
2,007 PointsThanks for the answers! I think the hardest part of all this is I can't get past how many things actually go into making an app. I don't think I was prepared for that. I thought it wasn't so involved and that is what's tripping me up. All the actions and code that one needs to know seems daunting and I don't know how one remembers the best way to do what needs to be done. I know you can google and look at the reference material but that to me seems only the tip of the iceberg and if you search enough you seem to always find a better way. I guess you gotta want it which I do so I will keep trudging along till I get it through this thick skull! Thanks for putting up with all the questions!
Nadaa Taiyab
2,074 PointsThank you Christopher! I am processing what you have written. Will get back to you if I have more questions.
Christopher St. George
2,606 PointsNo problem, definitely let me know if there is anything you are not totally clear on.
Antoine Guenet
2,843 PointsGreat, thanks Christopher! This is extremely helpful, finally it all seems clear to me!
Only just a couple of little questions.
- In your example, why does anything return anything else than nil since your classes were not set to return anything ?
- Why is testOtherBaseToChildDowncast not nil ? Hasn't it lost the properties of ChildOfChildClass when we upcasted it to BaseClass, therefore making it irrelevant that it ever was a ChildOfChildClass object ?
Thanks, this is the last step into making this perfectly clear in my head. You already connected most dots that weren't thus far :-)
Cheers,
Antoine
Mohsen Lotfi
2,694 PointsAntoine, I am also learning this and I can say things get complicated pretty quickly. I'd defiantly recommend copy pasting the syntax above and also struct Playlist and struct MusicLibrary into playground. this way you can observe returned value and better understand what happens in the background.
good luck
John Stephanites
2,007 PointsI am lost with this. I don't get why you would want to do this i the first place. The tutorial that this comes from had the opportunity to just enter the text "Yay you pressed the Button!" in the field provided. Why not just do that?
Christopher St. George
2,606 PointsJohn, the reason you would want to do this is more clear further along in the Playlist App tutorial, however let me see if I can clear up your confusion right here.
Simply entering the text "Yay you pressed the Button!" would be one way of setting the text for the label, but what if pressing that button were not the only way to transition over to a playListDetailViewController?
Imagine you added a second button as well, just like you did for the first button (except with a different name and location in the view)
Let us say that you set the text for the first button to "Button1" and the second to "Button2", and that you wanted the text of the label in the PlayListDetailViewController's view to display which button you actually pressed.
If you press Button1, you want to set "Yay you pressed Button1!" as the text for the label in playListDetailViewController If you press Button2, you want to set "Yay you pressed Button2!" as the text for the label in playListDetailViewController
It is probably easiest to accomplish this by simply checking which button is the 'sender' in the 'prepareForSegue' function
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showPlaylistDetail" {
let buttonTitle: String? = (sender as? UIButton)?.titleLabel?.text
if buttonTitle == "Button1" {
playlistDetailController.segueLabelText = "Yay you pressed Button1!"
} else if buttonTitle == "Button2" {
playlistDetailController.segueLabelText = "Yay you pressed Button2!"
} else {
// this else block is executed if buttonTitle is nil or some String value we weren't expecting.
}
}
}
// also notice how the 'sender' needed to be downcast from 'AnyObject' to 'UIButton'.
// this is because the 'sender' is one of those buttons
// if sender is not a 'UIButton' then the label text would not be set because 'buttonTitle: String?' would have a value of 'nil'
Anthony Albertorio
22,624 PointsNope, not defined as clearly as I would hope. Can someone explain downcasting and upcasting as if they are explaining it to a 5 year old? Please limit your assumptions and pretend that I know nothing of this.
Thanks
Anthony Albertorio
22,624 PointsNope, not defined as clearly as I would hope. Can someone explain downcasting and upcasting as if they are explaining it to a 5 year old? Please limit your assumptions and pretend that I know nothing of this.
Thanks
Steve Hyun
1,798 PointsSteve Hyun
1,798 PointsI agree. I've had minor trouble up to this point but this video was very confusing.