Tutorial: Building an Apple Watch App
The new Apple Watch is going to be released soon, we are going to take a little time to build a quick app that displays the North American time zones. This app will allow the user to swipe between the timezones so they can easily know what time it is anywhere in Canada and the US.
This app is built on top of the Tutorial: Today Widget in Swift tutorial, since in that app we also made a timezone view. Since the Today Widget and the Apple Watch apps are very similar because they are both built using extensions to an existing app it should make it a little easier to build.
What is a Watch App?
If you don’t know much about the framework of what makes up a watch app, you should first read Get Started with Apple Watch and WatchKit. This will give you a great introduction to what a Watch App is, and how glances and notifications work with the watch.
Setup the Application Extension
- Select File > New > Project. Select the Single view application template, and call it WatchApp.
- Add the Application Extension Target. Select File > New > Target…. Then in the left pane, select Apple Watch and under that choose WatchKit App.
Set the language to Swift if is isn’t already and check both Include Notification Scene and Include Glance Scene are not checked. Click Finish and Xcode will set up the target and the files needed for the Watch interface.
- A message will popup asking if you want to activate the “WatchApp WatchKit App” scheme. Click Activate.
- Adding a WatchKit App target to your Xcode project creates two new executables and updates your project’s build dependencies. Building your iOS app builds all three executables (the iOS app, WatchKit extension, and WatchKit app) and packages them together.
The WatchKit app is packaged inside your WatchKit extension, which is in turn packaged inside your iOS app. When the user installs your iOS app on an iPhone, the system prompts the user to install your WatchKit app if there is a paired Apple Watch available.
Create the Watch App View
The terminology for Watch Apps are a little different than iOS apps, instead of calling them “views”, Apple uses the term “Interface”. WatchKit apps do not use the same layout model as iOS apps. When assembling the scenes of your WatchKit app interface, you do not create view hierarchies by placing elements arbitrarily in the available space. Instead, as you add elements to your scene, Xcode arranges the items for you, stacking them vertically on different lines. At runtime, Apple Watch takes those elements and lays them out for you based on the available space.
When creating your interfaces in Xcode, let objects resize themselves to fit the available space whenever possible. App interfaces should be able to run both display sizes of Apple Watch. Letting the system resize objects to fit the available space minimizes the amount of custom code you have to write for each device
- We are going to start by adding a label into the Watch App Interface, in Interfaces the label type is WKInterfaceLabel. This one is the name of the time zone for the time we are going to display. Drag the label out from the Objects inspector just like you would in an iOS app. Also be sure to align the label text to center so it looks nice, also set the Position – Horizontal to “Center” as well.
- Next we need to add another label for the time. We’ll use a standard label so we can easily change the date per time zone> Watch Kit comes with its own Date label called WKInterfaceDate (but that only displays the current date and time). This can be customized, for our purposes we only want to see the time, not the date, so change the Date format to “None” and customize the font to “System” with a size of 30, and make it “Bold” so the time stands out, also set the Position – Horizontal to “Center” and Vertical to “Center”.
Xcode supports customizing your interface for the different sizes of Apple Watch. The changes you make in the storyboard editor by default apply to all sizes of Apple Watch, but you can customize your storyboard scenes as needed for different devices. For example, you might make minor adjustments to the spacing and layout of items or specify different images for different device sizes.
- Now that we have our UI defined, let’s connect the IBOutlets with the code in the “InterfaceController.swift” file. You can do this many ways, just like an iOS app. Either Control-drag the UI object from the storyboard into the InterfaceController.swift or define the code and link up using the Connections Inspector.
Here is the code for the two objects we are linking:// // InterfaceController.swift // WatchApp WatchKit Extension // // Created by Brian Coleman on 2015-04-17. // Copyright (c) 2015 Brian Coleman. All rights reserved. // import WatchKit import Foundation class InterfaceController: WKInterfaceController { @IBOutlet var timeInterfaceDate : WKInterfaceDate? = WKInterfaceDate() override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Configure interface objects here. } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } }
Watch App Navigation
For WatchKit apps with more than one screen of content, you must choose a technique for navigating between those screens. WatchKit apps support two navigation styles, which are mutually exclusive:
- Page based. This style is suited for apps with simple data models where the data on each page is not closely related to the data on any other page. A page-based interface contains two or more independent interface controllers, only one of which is displayed at any given time. At runtime, the user navigates between interface controllers by swiping left or right on the screen. A dot indicator control at the bottom of the screen indicates the user’s current position among the pages.
- Hierarchical. This style is suited for apps with more complex data models or apps whose data is more hierarchical. A hierarchical interface always starts with a single root interface controller. In that interface controller, you provide controls that, when tapped, push new interface controllers onto the screen.
Although you cannot mix page-based and hierarchical navigation styles in your app, you can supplement these base navigation styles with modal presentations. Modal presentations are a way to interrupt the current user workflow to request input or display information. You can present interface controllers modally from both page-based and hierarchical apps. The modal presentation itself can consist of a single screen or multiple screens arranged in a page-based layout.
For our project we are going to use a page based navigation so the user can swipe between the four time zones.
You configure a page-based interface in your app’s storyboard by creating a next-page segue from one interface controller to the next.
To create a next-page segue between interface controllers:
- In your storyboard, add interface controllers for each of the pages in your interface. Let’s add one for each of our time zones.
- Control-click your app’s main interface controller, and drag the segue line to another interface controller scene.
- The second interface controller should highlight, indicating that a segue is possible.
- Release the mouse button.
- Select “next page” from the relationship segue panel.
- Using the same technique, create segues from each interface controller to the next.
- The order in which you create your segues defines the order of the pages in your interface.
Add in Live Dynamic Data
We are almost done making our watch app, let’s add in the clocks to finish it up. The updateClocks() method is called every second to keep the time up to date.
// // InterfaceController.swift // WatchApp WatchKit Extension // // Created by Brian Coleman on 2015-04-17. // Copyright (c) 2015 Brian Coleman. All rights reserved. // import WatchKit import Foundation class InterfaceController: WKInterfaceController { @IBOutlet var easternLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var centralLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var mountainLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var pacificLabel : WKInterfaceLabel? = WKInterfaceLabel() override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Create a timer to refresh the time every second var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateClocks"), userInfo: nil, repeats: true) timer.fire() } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } func updateClocks() { var time: NSDate = NSDate() let formatter:NSDateFormatter = NSDateFormatter(); var timeZone = NSTimeZone(name: "UTC") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" var formattedString = formatter.stringFromDate(time) var formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) timeZone = NSTimeZone(name: "US/Eastern") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.easternLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Pacific") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.pacificLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Mountain") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.mountainLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Central") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.centralLabel?.setText(formatDateString) } }
Test Your Watch App
- When you run the app you’ll see a blank view since our app doesn’t do anything except create the Watch App and the watch is shown in an External Display.
- Build and Run your app against the Watch App target.
- Then, switch over to your simulator and check that you have the External Display set for the watch. iOS Simulator > Hardware > External Displays (try both sizes to see how it looks).
- You should see your app in the Apple Watch simulator. Swipe left to see the other time zones.
You can grab the full source code for this tutorial. Note: Built for Xcode 6.3 (Swift 1.2).
If you would like to continue on and add a Glance to your watch app, read Tutorial: Building a Apple Watch Glance or add a Dynamic Notification Interface, read Tutorial: Building a Apple Watch Notification
Tutorial: Building an Apple Watch Glance
A glance is a supplemental way for the user to view important information from your app. Not all apps need a glance. A glance provides immediately relevant information in a timely manner. For example, the glance for a calendar app might show information about the user’s next meeting, whereas the glance for an airline app might display gate information for an upcoming flight. WatchKit apps may have only one glance interface. Do not add more than one glance interface controller to your app’s storyboard.
To build a Glance you need to already have a Watch App, if you’d like to learn how to do that, read
Tutorial: Building an Apple Watch App In that tutorial we walk through building an app that shows the time across all four Canadian and U.S. timezones.
Glance Guidelines
Xcode provides fixed layouts for arranging the contents of your glance. After choosing a layout that works for your content, use the following guidelines to fill in that content:
- Design your glance to convey information quickly. Do not display a wall of text. Make appropriate use of graphics, colors, and animation to convey information.
- Focus on the most important data. A glance is not a replacement for your WatchKit app. Just as your WatchKit app is a trimmed down version of its containing iOS app, a glance is a trimmed down version of your WatchKit app.
- Do not include interactive controls in your glance interface. Interactive controls include buttons, switches, sliders, and menus.
- Avoid tables and maps in your glance interface. Although they are not prohibited, the limited space makes tables and maps less useful.
- Be timely with the information you display. Use all available resources, including time and location to provide information that matters to the user. And remember to update your glance to account for changes that occur between the time your interface controller is initialized and the time it is displayed to the user.
- Use the system font for all text. To use custom fonts in your glance, you must render that text into an image and display the image.
- Because an app has only one glance interface controller, that one controller must be able to display the data you want.
For our app we’re going to add a Glance that contains all timezones for the user to view at once.
Add a Glance
- Begin by adding all of the labels needed for the Interface.
- Now that we have our UI defined, let’s connect the IBOutlets with the code in the “GlanceController.swift” file. You can do this many ways, just like an iOS app. Either Control-drag the UI object from the storyboard into the GlanceController.swift or define the code and link up using the Connections Inspector.
- After the UI is all hooked up, let’s add some code as we did for the Watch app so our times will update every second.
// // GlanceController.swift // WatchApp WatchKit Extension // // Created by Brian Coleman on 2015-04-17. // Copyright (c) 2015 Brian Coleman. All rights reserved. // import WatchKit import Foundation class GlanceController: WKInterfaceController { @IBOutlet var easternLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var centralLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var mountainLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var pacificLabel : WKInterfaceLabel? = WKInterfaceLabel() override func awakeWithContext(context: AnyObject?) { super.awakeWithContext(context) // Create a timer to refresh the time every second var timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("updateClocks"), userInfo: nil, repeats: true) timer.fire() } override func willActivate() { // This method is called when watch view controller is about to be visible to user super.willActivate() } override func didDeactivate() { // This method is called when watch view controller is no longer visible super.didDeactivate() } func updateClocks() { var time: NSDate = NSDate() let formatter:NSDateFormatter = NSDateFormatter(); var timeZone = NSTimeZone(name: "UTC") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" var formattedString = formatter.stringFromDate(time) var formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) timeZone = NSTimeZone(name: "US/Eastern") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.easternLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Pacific") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.pacificLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Mountain") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.mountainLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Central") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.centralLabel?.setText(formatDateString) } }
Test Your Glance
- When you run the app you’ll see a blank view since our app doesn’t do anything except create the Watch App and the watch is shown in an External Display.
- Build and Run your app against the Glance target.
- Then, switch over to your simulator and check that you have the External Display set for the watch. iOS Simulator > Hardware > External Displays (try both sizes to see how it looks).
- You should see your app in the Glance in the Watch simulator.
You can grab the full source code for this tutorial. Note: Built for Xcode 6.3 (Swift 1.2).
If you would like to continue on and add a Dynamic Notification Interface to your Watch App, read Tutorial: Building a Apple Watch Notification
Tutorial: Building an Apple Watch Notification
Notifications on Apple Watch facilitate quick, lightweight interactions for local and remote notifications. These interactions occur in two stages, which are managed by the short-look and long-look interfaces. The short-look interface appears when a local or remote notification first arrives. A short look presents a discreet, minimal amount of information to the user—preserving a degree of privacy. If the wearer’s wrist is lowered, the short-look interface disappears. The long-look interface appears when the wearer’s wrist remains raised or when the wearer taps the short-look interface. It provides more detailed information and more functionality—and it must be actively dismissed by the wearer.
Static vs Dynamic Interfaces
The custom long-look notification interface consists of two separate interfaces: one static and one dynamic. The static interface is required and is a simple way to display the notification’s alert message and any static images and text that you configure at design time. The dynamic interface is optional and gives you a way to customize the display of your notification’s content.
Use the static notification interface to define a simple version of your custom notification interface. The purpose of a static interface is to provide a fallback interface in the event that your WatchKit extension is unable to configure the dynamic interface in a timely manner.
A dynamic notification interface lets you provide a more enriched notification experience for the user. With a dynamic interface, you can display more than just the alert message. You can incorporate additional information, configure more than one label, display dynamically generated content, and so on.
To implement a dynamic notification interface, you must create a custom WKUserNotificationInterfaceController subclass. How you implement that subclass determines what information is displayed in the notification interface.
We’ll cover how to customize a dynamic notification, since for the static ones, “it just works”.
To build a Dynamic Notification you need to already have a Watch App, if you’d like to learn how to do that, read
Tutorial: Building an Apple Watch App In that tutorial we walk through building an app that shows the time across all four Canadian and U.S. timezones.
Customize a Dynamic Notification
- Begin by adding all of the labels needed for the Interface within the Storyboard under the Dynamic Notification Interface.
- Drag in a Group object from the Object Library for each one of the timezones.
- Now that we have our UI defined, let’s connect the IBOutlets with the code in the “NotificationController.swift” file. You can do this many ways, just like an iOS app. Either Control-drag the UI object from the storyboard into the NotificationController.swift or define the code and link up using the Connections Inspector.
- After the UI is all hooked up, let’s add some code for the NotificationController.swift so when we receive a notification we’ll show the current timezone times to the user.
- The following is to define our labels that will be in the notification. Don’t forget to add the alertLabel and bodyLabel or else the user will not see the pay load for the remote notification sent to them.
@IBOutlet var easternLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var centralLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var mountainLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var pacificLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var alertLabel : WKInterfaceLabel? = WKInterfaceLabel() @IBOutlet var bodyLabel : WKInterfaceLabel? = WKInterfaceLabel()
- Next are the methods for handling the local and remote notifications. You need to add code in here for the Dynamic Interface to be enabled, if you leave it blank you will get the Static Interface for the notification with the title displayed.
override func didReceiveLocalNotification(localNotification: UILocalNotification, withCompletion completionHandler: ((WKUserNotificationInterfaceType) -> Void)) { // This method is called when a local notification needs to be presented. // Implement it if you use a dynamic notification interface. // Populate your dynamic notification interface as quickly as possible. self.alertLabel?.setText(localNotification.alertTitle) self.updateClocks() // After populating your dynamic notification interface call the completion block. completionHandler(.Custom) } override func didReceiveRemoteNotification(remoteNotification: [NSObject : AnyObject], withCompletion completionHandler: ((WKUserNotificationInterfaceType) -> Void)) { self.updateClocks() if let remoteaps:NSDictionary = remoteNotification["aps"] as? NSDictionary{ if let remoteAlert:NSDictionary = remoteaps["alert"] as? NSDictionary{ handleNotification( remoteAlert ); } } completionHandler(.Custom) } func handleNotification( alert : AnyObject? ){ if let alert: AnyObject = alert, let remotetitle = alert["title"] as? String{ println( "didReceiveRemoteNotification::remoteNotification.alert \(remotetitle)" ) self.alertLabel!.setText(remotetitle); } if let alert: AnyObject = alert, let remotebody = alert["body"] as? String{ //println( "didReceiveRemoteNotification::remoteNotification.alert \(remotetitle)" ) self.bodyLabel!.setText(remotebody); } }
- Lastly below is the method to return the time per timezone to be displayed in the notification.
func updateClocks() { var time: NSDate = NSDate() let formatter:NSDateFormatter = NSDateFormatter(); var timeZone = NSTimeZone(name: "UTC") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" var formattedString = formatter.stringFromDate(time) var formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) timeZone = NSTimeZone(name: "US/Eastern") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.easternLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Pacific") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.pacificLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Mountain") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.mountainLabel?.setText(formatDateString) timeZone = NSTimeZone(name: "US/Central") formatter.timeZone = timeZone formatter.dateFormat = "h:mm a" formattedString = formatter.stringFromDate(time) formatDateString = formattedString.stringByReplacingOccurrencesOfString(" p", withString: "PM", options: nil, range: nil) formattedString = formattedString.stringByReplacingOccurrencesOfString(" a", withString: "AM", options: nil, range: nil) self.centralLabel?.setText(formatDateString) }
Test Your Notification
- When you run the app you’ll see a blank view since our app doesn’t do anything except create the Watch App and the watch is shown in an External Display.
- Build and Run your app against the Notification target.
- Then, switch over to your simulator and check that you have the External Display set for the watch. iOS Simulator > Hardware > External Displays (try both sizes to see how it looks).
- You should see your app in the Notification in the Watch simulator. Since it’s in the simulator it will provide some sample payload “Optional title” and “Test message” to be displayed.
You can grab the full source code for this tutorial. Note: Built for Xcode 6.3 (Swift 1.2).
Tutorial: Upgrading to Swift 1.2
Apple has just released it’s new and improved version of Swift. They have been listening to a lot of developer feedback and continue to make Swift better and easier to use. If you upgraded to the Xcode 6.3 and ran your project you’ll notice a lot of new errors, hopefully the details below will make it easier to upgrade your project.
What’s New in Swift 1.2
You’ll now notice that Xcode is really really fast again. Those of you who work on large scale projects will notice how fast it compiles now. In Xcode 6.2 our current project took 1:45 – 2:00 minutes to compile, making incremental changes infuriating, now it takes seconds to compile, saving so much time.
- Incremental builds — Source files that haven’t changed will no longer be re-compiled by default, which will significantly improve build times for most common cases. Larger structural changes to your code may still require multiple files to be rebuilt.
- Faster executables — Debug builds produce binaries that run considerably faster, and new optimizations deliver even better Release build performance.
Other than speed you’ll notice its a lot more reliable and easier to write code in Swift.
- Better compiler diagnostics — Clearer error and warning messages, along with new Fix-its, make it easier to write proper Swift 1.2 code.
- Stability improvements — The most common compiler crashes have been fixed. You should also see fewer SourceKit warnings within the Xcode editor.
Convert to the Latest Swift Syntax
The first step you may want to try is to let Xcode take care of as much as possible itself.
From the menu bar in Xcode > Edit > Convert > To Swift 1.2
A little instructions and warning that this will not fix all of the issues in your project for Swift 1.2.
Select your target and then it’s the same as how refactoring works – Xcode will work away and then come back with a preview of the code that needs to be changed.
You’ll see the old code and new code side-by-side with the changes needed.
Down Casting Improvements
as! for failable casts — Casts that can fail at runtime are now expressed with the new as! operator to make their potential for runtime failure clear to readers and maintainers of your code.
You’ll need to use the as! to convert between types, which used to work before.
var appVersion = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as String
Apple wants you to be more explicit with optional vs. non-optional type conversions
ViewController.swift:61:105: 'AnyObject?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
Use as! instead of as
var appVersion = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as! String
let Constants
let constants are now more powerful and consistent — The new rule is that a let constant must be initialized before use (like a var), and that it may only be initialized, not reassigned or mutated after initialization.
Take enables patterns like this:
let x : SomeThing if condition { x = foo() } else { x = bar() } use(x)
This formerly required the use of a var even though there is no mutation taking place. Properties have been folded into this model to simplify their semantics in initializers as well.
if let Improvements
Prior to Swift 1.2 you could only optionally bind one at a time, now you can unwrap multiple optionals at once, as well as include intervening boolean conditions. This lets you express conditional control flow without lots of if – else nesting needed.
Here’s an example of if-let in a single statement:
if let language = swift?.language where language.type == kSwift, let objC = language.old? { doSomething() }
New Set Data Type
New native Set data structure — An unordered collection of unique elements that bridges with NSSet and provides value semantics like Array and Dictionary.
just like String, Array, and Dictionary are bridged to their corresponding Objective-C classes, the new Set data type is bridged to Objective-C’s NSSet class. Sets are generic collections so you need to provide the type of object to store; however, they store unique elements so you won’t see any duplicates.
var languages = Set() languages.insert("Objective-C") languages.insert("Swift") languages.insert("PHP") languages.insert("Perl") if languages.isEmpty { println("I don't know any computer languages.") } else { if languages.contains("Objective-C") || languages.contains("Swift") { println("I know how to make iOS apps.") } }
Tutorial: NSDate in Swift
NSDate objects represent a single point in time. NSDate is a class cluster; its single public superclass, NSDate, declares the programmatic interface for specific and relative time values. The objects you create using NSDate are referred to as date objects.
NSDate is an abstract class that provides behavior for creating dates, comparing dates, representing dates, computing intervals, and similar functionality. NSDate presents a programmatic interface through which suitable date objects are requested and returned. Date objects returned from NSDate are lightweight and immutable since they represent an invariant point in time.
Using NSDate
In objective-c, the following code results in the UTC date time information using the date API.
NSDate *currentUTCDate = [NSDate date]
In Swift however,
let date = NSDate()
Results in local date and time.
UTC vs Local Time
NSDate is a specific point in time without a time zone. Think of it as the number of seconds that have passed since a reference date. How many seconds have passed in one time zone vs. another since a particular reference date? The answer is the same.
Depending on how you output that date (including looking at the debugger), you may get an answer in a different time zone.
If they ran at the same moment, the values of these are the same. They’re both the number of seconds since the reference date, which may be formatted on output to UTC or local time. Within the date variable, they’re both UTC.
To explain this, we can use a NSDateFormatter in a playground:
import UIKit let date = NSDate(); // "Apr 1, 2015, 8:53 AM" <-- local without seconds var formatter = NSDateFormatter(); formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"; let defaultTimeZoneStr = formatter.stringFromDate(date); // "2015-04-01 08:52:00 -0400" <-- same date, local, but with seconds formatter.timeZone = NSTimeZone(abbreviation: "UTC"); let utcTimeZoneStr = formatter.stringFromDate(date); // "2015-04-01 12:52:00 +0000" <-- same date, now in UTC
Compare NSDate
If you need to compare two dates, you can use the method below. This example shows comparing the current date against an endDate. It will give you all possible results so you can handle each situation.
// Date comparision to compare current date and end date. var dateComparisionResult:NSComparisonResult = NSDate().compare(endDate) if dateComparisionResult == NSComparisonResult.OrderedAscending { // Current date is smaller than end date. } else if dateComparisionResult == NSComparisonResult.OrderedDescending { // Current date is greater than end date. } else if dateComparisionResult == NSComparisonResult.OrderedSame { // Current date and end date are same. }
There are several useful methods in NSCalendar in iOS 8.0+:
startOfDayForDate, isDateInToday, isDateInYesterday, isDateInTomorrow
And even to compare days:
func isDate(date1: NSDate!, inSameDayAsDate date2: NSDate!) -> Bool
To ignore the time element you can use this:
var toDay = NSCalendar.currentCalendar().startOfDayForDate(NSDate())
Formatting NSDate
If you have a string and want to convert it to an NSDate.
var dataString = "April 1, 2015" as String var dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "MM-dd-yyyy" dateFormatter.timeZone = NSTimeZone.localTimeZone() // convert string into date let dateValue = dateFormatter.dateFromString(dataString) as NSDate! println(dateValue)
This might help you also formatting your dates:
Convert Unix TimeStamp to NSDate in Swift
If you have a timestamp "/Date(1427909016000-0800)" and you need to convert it to an NSDate, you can use the following extension. This will also convert it to your local time. The first part "1427909016000" is the time since the Unix epoch in milliseconds, and the second part "-0800" is a time zone specification.
extension NSDate { convenience init?(jsonDate: String) { let prefix = "/Date(" let suffix = ")/" let scanner = NSScanner(string: jsonDate) // Check prefix: if scanner.scanString(prefix, intoString: nil) { // Read milliseconds part: var milliseconds : Int64 = 0 if scanner.scanLongLong(&milliseconds) { // Milliseconds to seconds: var timeStamp = NSTimeInterval(milliseconds)/1000.0 // Read optional timezone part: var timeZoneOffset : Int = 0 if scanner.scanInteger(&timeZoneOffset) { let hours = timeZoneOffset / 100 let minutes = timeZoneOffset % 100 // Adjust timestamp according to timezone: timeStamp += NSTimeInterval(3600 * hours + 60 * minutes) } // Check suffix: if scanner.scanString(suffix, intoString: nil) { // Success! Create NSDate and return. self.init(timeIntervalSince1970: timeStamp) return } } } // Wrong format, return nil. (The compiler requires us to // do an initialization first.) self.init(timeIntervalSince1970: 0) return nil } }
Here's an example of it in use:
if let theDate = NSDate(jsonDate: "/Date(1427909016000-0800)/") { println(theDate) } else { println("wrong format") }
Result
2015-04-01 09:23:36 +0000