Subscribe for new changes in Swift and iOS app development.
The latest version of Swift 1.2 was released today as part of the Xcode 6.3 Beta. You can start using it today, and you'll get access to some awesome new features.
Apple wrote a and also posted it's Download Xcode 6.3 Beta from Apple's Xcode page and make sure you review the changes.
In this post I wanted to comment on a few features that I'm excited about and how you can start using them. Further in the post I'll show you how to convert a small project from Swift 1.1 to 1.2 (manually), and then I'll talk about the great Swift Migrator.
Swift 1.2 breaks a few things in your code. The language changed, so you'll have to change your Xcode projects to adapt.
New Features
1. The Set data structure is available
I'm really excited about the Set data structure, as it was missing (and I missed it from Objective-C).
Set is similar to , except that it will make sure you only have a single unique item in the collection. It's great if you want to restrict duplicates for an algorithm or logic in your app. You can also use the powerful set logic, which lets you combine sets through addition or subtraction to create subsets.
Here's a short code snippet to show off creating a set using an Array as input. Alternatively you can add each element individually using the insert() method.
var cardSuits = Set(["Diamonds", "Spades", "Hearts", "Clubs"])
println("Cards: \(cardSuits)")
// Cards: ["Diamonds", "Clubs", "Spades", "Hearts"]
cardSuits.insert("Diamonds") // no error, but no insert happens
println("Cards: \(cardSuits)")
// Cards: ["Diamonds", "Clubs", "Spades", "Hearts"]
cardSuits.insert("Rocks") // adds new suit
println("Cards: \(cardSuits)")
// Cards: ["Rocks", "Diamonds", "Clubs", "Spades", "Hearts"]
Set documentation
There is hardly any documentation on how to use the Set collection right now, but you can get the Set methods by Command + left-clicking on the word Set in Xcode (after writing a line creating a set variable). That should take you to the Swift code, which has all the methods available and some sparse comments.
2. "if let" improvements - no more "pyramid of doom"
This one is fantastic. If you've written any kind of user input validation or JSON/XML data parsing you are going to love it.
Before I would either nest a ton of if/else statements, or I would use optionals and then test all the optional values in one if/else statement. The more values you need to test, validate, or compare the more { } curly brackets you're going to be adding. It can then become a little mind-numbing as you try to figure out what block { } you are in, and where another one ends (pro-tip: double click the { to find the corresponding }).
if let name = validateName(nameTextField.text) {
if !name.isEmpty {
if let age = validateAge(ageTextField.text) {
if age > 13 {
// create a new user ...
}
}
}
}
In Swift 1.2 you can combine multiple "if let" values in a single if statement. Plus you get to use where!
if let name = validateName(nameTextField.text),
age = validateAge(ageTextField.text)
where age > 13 && !name.isEmpty {
// create a new user ...
}
This change alone is worth the broken code that you'll have to update.
I always felt there was a disconnect between the if/else statements and the switch statements in Swift. Now you can combine multiple statements into a single expression, and you can use the where clause to further limit conditions into a single readable statement.
I much prefer this shorter statement because it keeps the information local, as opposed to putting it in nested if/else statements.
One Caveat - error messaging in the UI
While I do like the concise nature of the statement. Depending on the type of app you might need to provide understandable error messages to the end user.
If you are going to handle any error logic you will probably need to leverage semi-nested if/else statements, or you could do all your error checking in a separate method (i.e. validate methods). That way you can be sure to properly warn a user that they didn't add their name to a required field in your signup form.
My test driven philosophy would urge you to not make a method do multiple things, but my shipping apps philosophy would recommend doing the naive approach (just get it working and ship it to the App Store).
I'd rather see you shipping apps than obsessing over every single code design decision (and not shipping anything).
Broken code: Swift 1.2 breaks existing projects
The biggest takeaways I found when testing out my latest Swift game project were related to types, casting, and converting between Objective-C types (NSString, NSArray, and NSDictionary) to Swift types (String, Array, Dictionary).
Download my iPhone game Xcode project if you want to follow along. It's written for Swift 1.1 and Xcode 6.1. You can learn more about the game in my Swift iPhone game course.
1. NSString isn't a String anymore
You won't get automatic conversion from NSString to String anymore, but it does work in the opposite direction. Apple says it simplifies things, but I'm not sure what that means. Simplifies for them, or simplifies for the programmer using Swift?
To me it seems more complicated, since it'll be another edge case you need to keep track of where the relationship isn't bi-directional. For example in my default Game project, Apple's code unarchiving the Sprite Kit .sks file breaks. They pass a NSString to a method that takes a String.
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
Error: Apple is forcing you to add additional type information when converting between Objective-C and Swift
GameViewController.swift:14:61: 'NSString' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
Warning: The auto fix didn't work because the parameter expects an optional value.
GameViewController.swift:14:58: Treating a forced downcast to 'String' as optional will never produce 'nil'
Looking at the documentation for , you can see that pathForResource() has an the optional string type: String? as it's parameter.
func pathForResource(name: String?, ofType ext: String?) -> String?
Fix: Change the as! to an as?
Using the as? operator will make it so that if the conversion fails, it'll return nil. And that is what the pathForResource() method expects.
As the Swift language changes you'll run into situations like these. Apple hasn't quite settled yet, so that some code is going to have to change to accommodate new features in the language.
if let path = NSBundle.mainBundle().pathForResource(file as? String, ofType: "sks") {
2. Down casting (i.e.: type conversions) has changed a bit.
You'll need to use the as! to convert between types, which used to work before.
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
Error: Apple wants you to be more explicit with optional vs. non-optional type conversions
GameViewController.swift:19:82: 'AnyObject?' is not convertible to 'GameScene'; did you mean to use 'as!' to force downcast?
Fix: Change then ending to use as! instead of as
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as! GameScene
3. Method signatures have been Swift'ified
With the addition of the new Set class, your touch events methods in both UIControl subclasses or in Sprite Kit SKScene subclasses will have to be updated. There are other APIs in Cocoa/Cocoa Touch that have also been updated. You'll want to look at the full list to see what changed () and ().
You'll find a lot of method names have changed with the addition of the Set collection class in Swift. Additionally many methods are also using String instead of NSString!, which is a big win for Swift!
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
Error: The type has changed from NSSet to Set
GameScene.swift: Overriding method with selector 'touchesBegan:withEvent:' has incompatible type '(NSSet, UIEvent) -> ()'
Fix: You'll need to update your method signature (i.e.: the entire first line) to the following:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
I'm not sure why it doesn't have the method signature using UITouch. I'd love it more if it used Set
Error: Set doesn't have the method anyObject()
if let touch = touches.anyObject() as UITouch? {
Now that you have a Swift Set that has typed objects you need to update your cast if you use one with the touches parameter.
Fix: Use one of the new methods of Set like first or last, which return optional NSObject values
There's a couple gotchas with this conversion. anyObject() was a method, but first is a property. Firstly, you'll call them differently (i.e: no parenthesis).
Secondly You can't use as? UITouch and as UITouch? interchangeably anymore with Swift 1.2 (not sure if this is a bug or intended feature).
if let touch = touches.first as? UITouch {
Swift Migrator: Convert to Swift 1.2
Apple did provide a menu option to do the conversion for you. It works ok, but it's not going to do everything for you.
From the menu bar in Xcode > Edit > Convert > To Swift 1.2
Gotchas
1. It won't catch issues related to APIs changing types (anything that used NSSet to Set
2. It didn't seem to change things the way I would. Scroll up to see what I recommend as a fix to the code issues in the conversion as a starting point.
3. The recommend quick fixes are not always right, so you'll have to consult the web or .
Conclusion
It's going to be a little bit of work to convert your project. I think you'd spend at least 30-60 minutes working through your project. Any subsequent projects will get easier as you learn what you need to look out for in your older Swift 1.1 code.
From my own experience I find that I have to fiddle with the code (massage it until it works). After I figured out the different edge cases it was easier to convert.
I'm looking forward to when Xcode 6.3 comes out of beta with Swift 1.2!
Connect
- Subscribe to my iOS mailing list if you found this interesting.
- Let me know how your .
- Learn more about making iPhone games.