The_iOS_Apprentice_v3.3_Changes_for_Swift_1.2
Transcription
The_iOS_Apprentice_v3.3_Changes_for_Swift_1.2
Changes for Swift 1.2 and Xcode 6.3 Swift 1.2 In April 2015, Apple released Xcode 6.3, which includes Swift version 1.2. This new version of Swift introduces a number of changes to the language that are not backwards compatible. As a result, the projects from previous releases of The iOS Apprentice for Xcode 6.2 and earlier will give errors when you try to compile them with Xcode 6.3. This document lists what changes you need to make to the source code in order to build the projects without errors. For a complete overview of what’s new in Swift 1.2, visit the following link: http://www.raywenderlich.com/95181/whats-new-in-swift-1-2 New type casting operator: as! The biggest change to Swift that causes existing code to break is the new as! type cast. This replaces as in many cases. For example: cell.viewWithTag(1000) as UILabel is now written as: cell.viewWithTag(1000) as! UILabel The previous version of Swift had just two type cast operators, as and as?. Swift 1.2 adds a new one, as!, with an exclamation point at the end. The difference between the three casts is as follows: as is a type cast that will always work. Swift knows enough about all the types involved to guarantee this. This kind of cast is quite rare. as? is an optional cast. If you write x as? Y, you’re trying to cast the variable x to an object of type Y. There are two reasons why this may fail: x is nil or the type of The iOS Apprentice (Third Edition) StoreSearch x isn’t compatible with type Y. Because as? is an optional cast, you can catch these situations with if let. as! is the new kid on the block. You need this kind of cast because the iOS APIs often return AnyObject types, and Swift can’t draw any conclusions from these vague AnyObjects at the moment it is compiling your app. It won’t know what type the object is really supposed to be until you run the app. With x as! Y you’re telling Swift that it’s OK to perform this cast, even though Swift can’t verify that x is truly compatible with type Y. So you’re asking Swift to take a leap of faith. For example, if you know that the AnyObject you’re dealing with is really a UITableViewCell, then it’s perfectly fine to write x as! UITableViewCell. Of course, if you’re not careful and the objects you’re casting don’t have the type you expect them to have, using as! will crash the app. C’est la vie. ;-) Tutorial 1: Getting Started No changes to the source code are needed for this tutorial. Tutorial 2: Checklists All the as type casts occurring in this tutorial are now as!, with an exclamation mark at the end. Tutorial 3: MyLocations Almost all the as type casts are now written as! in this tutorial. There are two exceptions to this change, both in LocationDetailsViewController.swift: 1) Inside imagePickerController(didFinishPickingMediaWithInfo), the line that reads the UIImage from the info dictionary currently is: image = info[UIImagePickerControllerEditedImage] as UIImage? This should become: image = info[UIImagePickerControllerEditedImage] as? UIImage You could change this to as! UIImage?, but using the optional as? cast is simpler and does the same thing. 3 The iOS Apprentice (Third Edition) StoreSearch 2) In textView(shouldChangeTextInRange, replacementText), the following cast should remain unchanged: descriptionText = (textView.text as NSString).stringByReplacing... The type of textView.text is String and this is “bridged” with NSString, so this kind of cast can never fail. That’s why you don’t need as! or as? here. Fixed a crashing bug with editing locations from the Map screen To reproduce this bug, launch the app and immediately switch to the Map tab. Tap a location to edit it and change its description. After you press Done, the app crashes with the message, “fatal error: unexpectedly found nil while unwrapping an Optional value”. (Note: This crash does not happen when you change the location’s category, only the description.) The fix: In LocationsViewController.swift, in the extension for the NSFetchedResultsControllerDelegate, the code in case .Update should be: case .Update: println("*** NSFetchedResultsChangeUpdate (object)") if let cell = tableView.cellForRowAtIndexPath(indexPath!) as? LocationCell { let location = controller.objectAtIndexPath(indexPath!) as! Location cell.configureForLocation(location) } This puts the call to tableView.cellForRowAtIndexPath() inside an if let statement, so that the rest of the code is skipped when this returns nil. In case you’re interested, this bug is actually the result of the workaround for another bug. (Don’t you just love programming?) The section on using NSFetchedResultsController describes a problem with Core Data – the “FATAL ERROR: The persistent cache of section information does not match the current configuration” crash. One proposed solution to prevent this from occurring was to add the following line to AppDelegate.swift: let forceTheViewToLoad = locationsViewController.view That solved the Core Data caching problem, but it also introduced this sneaky new bug into the Map screen. Let this be a lesson that even a fairly simple app such as this can have all kinds of complicated bugs hidden inside it. ;-) 4 The iOS Apprentice (Third Edition) StoreSearch Tutorial 4: StoreSearch Almost all the as type casts are now written as! in this tutorial, but pay attention to the following. String and NSString As of Swift 1.2, NSString objects are no longer automatically converted to Swift’s native String type. This affects the JSON parsing code. For example, the following no longer works: searchResult.name = dictionary["trackName"] as NSString The type of searchResult.name is String, but the value on the right-hand side of the = sign is an NSString. You should now write this as follows: searchResult.name = dictionary["trackName"] as! String This converts the value from dictionary["trackName"], which is an AnyObject, directly to a String. (Of course, this only works if the AnyObject really is a string to begin with; if not, the as! operator will crash the app.) Xcode suggests that you fix this code as: searchResult.name = dictionary["trackName"] as! NSString as String That accomplishes the same; first it tries to cast the AnyObject to an NSString, and then it converts that NSString to a native Swift string. The first cast may fail if dictionary["trackName"] contains something else than a string, but the cast from NSString to String will always succeed. In previous Swift versions, this conversion was done automatically for you by the compiler, but as of Swift 1.2 you have to be explicit about it. To summarize, simply replace all occurrences of as NSString by as! String, and all occurrences of as? NSString by as? String. Double and NSNumber In the JSON parsing code, replace the following code: if let price = dictionary["trackPrice"] as? NSNumber { searchResult.price = Double(price) } by this: if let price = dictionary["trackPrice"] as? Double { 5 The iOS Apprentice (Third Edition) StoreSearch searchResult.price = price } It is no longer necessary to cast to NSNumber first and then convert to Double; you can do this in one step now. The end. 6