Brian Coleman

  • Home
  • ABOUT
  • SERVICES
  • PORTFOLIO
  • BLOG
  • Contact

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

  1. Select File > New > Project. Select the Single view application template, and call it WatchApp.
    Screen Shot 2015-04-17 at 8.49.00 AM
    Screen Shot 2015-04-17 at 8.49.16 AM

  2. Add the Application Extension Target. Select File > New > Target…. Then in the left pane, select Apple Watch and under that choose WatchKit App.
    Screen Shot 2015-04-17 at 8.56.49 AM
    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.
    Screen Shot 2015-04-17 at 9.00.58 AM

  3. A message will popup asking if you want to activate the “WatchApp WatchKit App” scheme. Click Activate.
    Screen Shot 2015-04-17 at 9.19.04 AM

  4. 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.
    Screen Shot 2015-04-17 at 9.27.10 AM
    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.
    target_structure_2x

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.

storyboard_layout_2x

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

  1. 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.

    Screen Shot 2015-04-17 at 9.43.41 AM

  2. 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”.

    Screen Shot 2015-04-17 at 9.54.57 AM

    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.

  3. 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:

  1. In your storyboard, add interface controllers for each of the pages in your interface. Let’s add one for each of our time zones.
    Screen Shot 2015-04-17 at 10.58.52 AM

  2. Control-click your app’s main interface controller, and drag the segue line to another interface controller scene.
  3. The second interface controller should highlight, indicating that a segue is possible.
  4. Release the mouse button.
    Screen Shot 2015-04-17 at 11.01.16 AM

  5. Select “next page” from the relationship segue panel.
  6. Using the same technique, create segues from each interface controller to the next.
    Screen Shot 2015-04-17 at 11.02.27 AM

  7. 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

  1. 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.
  2. Build and Run your app against the Watch App target.
    Screen Shot 2015-04-17 at 1.44.56 PM

  3. 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).
    Screen Shot 2015-04-17 at 1.44.26 PM

  4. You should see your app in the Apple Watch simulator. Swipe left to see the other time zones.
    Screen Shot 2015-04-17 at 1.49.09 PM

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

April 20, 2015 Swift, Tutorials, Watchios8, swift, tutorial, watch

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

  1. Begin by adding all of the labels needed for the Interface.
    Screen Shot 2015-04-20 at 8.37.23 AM

  2. 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.
  3. 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

  1. 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.
  2. Build and Run your app against the Glance target.
    Screen Shot 2015-04-20 at 8.47.53 AM

  3. 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).
    Screen Shot 2015-04-17 at 1.44.26 PM

  4. You should see your app in the Glance in the Watch simulator.
    Screen Shot 2015-04-20 at 8.48.58 AM

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

April 20, 2015 Swift, Tutorials, Watchios8, swift, tutorial, watch

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.

notification_static_dynamic_2x

notification_process_2x

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

  1. Begin by adding all of the labels needed for the Interface within the Storyboard under the Dynamic Notification Interface.
  2. Drag in a Group object from the Object Library for each one of the timezones.
    Screen Shot 2015-04-20 at 3.55.05 PM

  3. 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.
  4. 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.
  5. 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()
    
  6. 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);
            }
        }
    
  7. 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

    1. 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.
    2. Build and Run your app against the Notification target.
      Screen Shot 2015-04-20 at 4.00.35 PM

    3. 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).
      Screen Shot 2015-04-17 at 1.44.26 PM

    4. 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.
      Screen Shot 2015-04-20 at 4.02.20 PM

    You can grab the full source code for this tutorial. Note: Built for Xcode 6.3 (Swift 1.2).

April 20, 2015 Swift, Tutorials, Watchios8, swift, tutorial, watch

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

Screen Shot 2015-04-13 at 9.52.36 AM

A little instructions and warning that this will not fix all of the issues in your project for Swift 1.2.

Screen Shot 2015-04-13 at 9.52.51 AM

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.

Screen Shot 2015-04-13 at 9.53.00 AM

You’ll see the old code and new code side-by-side with the changes needed.

Screen Shot 2015-04-13 at 10.13.30 AM

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.")
    }
}
April 13, 2015 Swift, Tutorials, Uncategorizedios8, swift, tutorial

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:
Sg0tZ

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
April 1, 2015 Swift, Tutorialsios8, swift, tutorial
Page 3 of 19«12345...10...»Last »
ABOUT
Brian is a Lead iOS/tvOS Developer from Toronto with over 18 years of multifaceted experience including development, design, business analysis and project management.

FOLLOW ME
    
Email Subscription
Sign up for my newsletter to receive the latest news and tutorials posted.

Enter your email address:

2023 © Brian Coleman