{"id":994,"date":"2014-11-29T21:28:25","date_gmt":"2014-11-30T03:28:25","guid":{"rendered":"http:\/\/dev.iachieved.it\/iachievedit\/?p=994"},"modified":"2014-12-07T14:09:39","modified_gmt":"2014-12-07T20:09:39","slug":"building-a-apple-watch-for-location-aware-weather","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/building-a-apple-watch-for-location-aware-weather\/","title":{"rendered":"Building an Apple Watch App for Location-Aware Weather"},"content":{"rendered":"<p><b>Editor&#8217;s Note<\/b>:  This is a tutorial on how to use iOS 8.2 with Xcode 6.2 to develop an Apple Watch app.  It is <i>not<\/i> an exhaustive overview of WatchKit.  I recommend reading the following first in this order:<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.apple.com\/library\/prerelease\/ios\/documentation\/General\/Conceptual\/WatchKitProgrammingGuide\/index.html\">Apple WatchKit Programming Guide<\/a>\n<li><a href=\"http:\/\/www.raywenderlich.com\/89473\/watchkit-initial-impressions\">WatchKit Initial Impressions<\/a>\n<li><a href=\"http:\/\/www.raywenderlich.com\/89562\/watchkit-tutorial-with-swift-getting-started\">Getting Started With WatchKit<\/a>\n<\/ul>\n<p>By the time you&#8217;ve finished reading these tutorials you should have a basic understanding of the architecture of a WatchKit app and the application lifecycle of a WatchKit Extension (the app component that runs on your iPhone).<\/p>\n<p>While I&#8217;ve tried to provide plenty of step-by-step detail, this tutorial does make an assumption that you have a basic understanding of iOS application development and are comfortable navigating in Xcode.  You will of course require an iOS developer&#8217;s account to obtain Xcode 6.2 and the iOS 8.2 simulator included.<\/p>\n<h2>A Current Conditions WatchKit App<\/h2>\n<h2>Starter Application<\/h2>\n<p>It should have been clear from reading the <a href=\"https:\/\/developer.apple.com\/library\/prerelease\/ios\/documentation\/General\/Conceptual\/WatchKitProgrammingGuide\/index.html\">Apple WatchKit Programming Guide<\/a> that your Watch app is an <i>extension<\/i> of an existing iPhone application.  That is, there is no such thing as an Apple Watch-only application.  One of the examples given on Apple&#8217;s website is that of an American Airlines watch app that shows flight updates, boarding notifications, etc.  The American Airlines watch app cannot be used without downloading and installing the <a href=\"https:\/\/itunes.apple.com\/us\/app\/american-airlines\/id382698565?mt=8\">iPhone app<\/a>, as that is where the code for the Watch app will reside.<\/p>\n<p>Rather than walk through how to create an application from scratch we&#8217;ll begin with a <i>starter application<\/i> and add an Apple Watch app to it.  Ours is a basic weather app that provides a brief look at the weather in your current location.  To keep things simple we&#8217;ll provide:<\/p>\n<ul>\n<li>the name of the current location (for example, New Orleans, LA)\n<li>a weather icon depicting the current conditions\n<li>a brief description of the current conditions\n<li>the current temperature\n<\/ul>\n<p>Even though this is a simple app in its functionality, it does provide a good example of how we will leverage third-party SDKs and the CoreLocation framework to provide location-aware weather information to an Apple Watch app.  <b>Warning:<\/b>  If you thought this was going to be a &#8220;How do I write a simple WatchKit app in 10 minutes?&#8221; tutorial, try out the <a href=\"http:\/\/www.raywenderlich.com\/89562\/watchkit-tutorial-with-swift-getting-started\">Bitcoin Price Tracker<\/a> app first.  Ours is a bit more involved.<\/p>\n<p>Download the starter application from <a href=\"https:\/\/bitbucket.org\/joeiachievedit\/aerisweather\/get\/starter.zip\">BitBucket<\/a> and open the project in Xcode.<\/p>\n<p>To get started with the app you&#8217;ll need to change your <b>aerisweather<\/b> target <b>bundle identifier<\/b>.  In this example we&#8217;ve changed it to <code>com.yourcompany.aerisweather<\/code>.  <\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_change_bundle_identifier.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_change_bundle_identifier.png\" alt=\"watch_tut_change_bundle_identifier\" width=\"748\" height=\"196\" class=\"alignnone size-full wp-image-1024\" \/><\/a><\/p>\n<p>We will be using the <a href=\"http:\/\/www.hamweather.com\/products\/aeris-api\/\">Aeris Weather API<\/a> from HAMweather.com, so you will need to register for the service and create some API keys.  You may want to review our <a href=\"http:\/\/dev.iachieved.it\/iachievedit\/?p=934\">previous post<\/a> on signing up.<\/p>\n<p>Create a new application in the <a href=\"http:\/\/www.hamweather.com\/account\/content\/p\/id\/2\/\">HAMweather portal<\/a> by filling out your <b>Application Name<\/b> and <b>Application Domain\/Identifier<\/b>.  <\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_api_key.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_api_key.png\" alt=\"watch_tut_api_key\" width=\"599\" height=\"282\" class=\"alignnone size-full wp-image-1041\" \/><\/a><\/p>\n<p>Click Register to register your application and receive your API keys.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_successful_registration.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_successful_registration.png\" alt=\"watch_tut_successful_registration\" width=\"634\" height=\"239\" class=\"alignnone size-full wp-image-1033\" \/><\/a><\/p>\n<p>Configure your keys by creating a <i>property list<\/i> file called <code>ApiKeys.plist<\/code>.  This can easily be done through Xcode with <b>File &#8211; New &#8211; File<\/b> selecting the iOS Resource item and then Property List.  Again, name it <code>ApiKeys<\/code>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_apikeys_save.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_apikeys_save.png\" alt=\"watch_tut_apikeys_save\" width=\"713\" height=\"315\" class=\"alignnone size-full wp-image-1022\" \/><\/a><\/p>\n<p>If Xcode has two <code>ApiKeys.plist<\/code> files listed in the project navigator, select one of them, right-click, and Delete and choose <b>Remove Reference<\/b> (not Trash!).<\/p>\n<p>Add two items to the property list file,<code>AERIS_CLIENT_ID<\/code> and <code>AERIS_APP_SECRET<\/code>, and set them to the values of the keys that were created when you registered your application in the Aeris portal.<\/p>\n<p>Stop and build the iOS application at this point <i>in the iOS simulator<\/i> for either the iPhone 5, 5s, 6, or 6 Plus.  If Xcode complains about the lack of a provisioning profile click the <b>Fix Issue<\/b> button to generate an appropriate provisioning profile. <\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_fix_pp.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_fix_pp.png\" alt=\"watch_tut_fix_pp\" width=\"595\" height=\"268\" class=\"alignnone size-full wp-image-1046\" \/><\/a><\/p>\n<p>The simulator should prompt you requesting location access.  Click <b>Allow<\/b>.  Hopefully you see something like this in your simulator.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_first_app_run.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_first_app_run.png\" alt=\"watch_tut_first_app_run\" width=\"377\" height=\"689\" class=\"alignnone size-full wp-image-1026\" \/><\/a><\/p>\n<h3>Simulating Location<\/h3>\n<p>We are using significant location change monitoring which relies primarily on recognizing locations of Wifi networks and cell towers rather than GPS.  We use this rather than GPS because we don&#8217;t require the same level of accuracy for providing the current weather.  Of course in large urban areas where cities blend together your app may show you to be in Dallas when in reality you are in Richardson.  To simulate significant location changes in the simulator select the <b>Debug<\/b> menu and choose <b>Location &#8211; Freeway Drive<\/b>.  At this point you can stop the application.  As long as the <b>Freeway Drive<\/b> location simulation is running we will have data for us in our Watch app.<\/p>\n<p><b>Note:<\/b>  If the app is not receiving location updates (and correspondingly you see a screen that has default label text), go to <b>Hardware &#8211; Location<\/b> and select <b>None<\/b>.  Then, go back to <b>Hardware &#8211; Location<\/b> and select <b>Freeway Drive<\/b>.<\/p>\n<h3>Adding the Watch App<\/h3>\n<p>Let&#8217;s add a watch app to our weather app!  I assume you have the <b>aerisweather<\/b> project open and you&#8217;ve already added API keys for Aeris and have run the starter app in the simulator.  If you haven&#8217;t, do so, the remainder of the tutorial builds upon these steps.<\/p>\n<p>Now, to add a Watch App target in Xcode go to <b>File &#8211; New &#8211; Target<\/b> and select <b>Apple Watch<\/b>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_watchapp_target.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_watchapp_target.png\" alt=\"watch_tut_watchapp_target\" width=\"733\" height=\"432\" class=\"alignnone size-full wp-image-1001\" \/><\/a><\/p>\n<p>Take a moment to note what the <b>Watch App<\/b> template does:  <i>This template builds a Watch App with an associated WatchKit app extension.<\/i>  It bears repeating that there are <i>two<\/i> items about to be added:  the watch app itself which runs on the Apple Watch and the watchkit app extension which runs on the iPhone.<\/p>\n<p>Click <b>Next<\/b>.  When choosing your target options make sure for this tutorial to uncheck <b>Include Notification Scene<\/b> and <b>Include Glance Scene<\/b>.  We will not be including either a notification or glance scene in this tutorial.  Also ensure your <b>Language<\/b> is set to <b>Swift<\/b>.  Click <b>Finish<\/b>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_choose_options.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_choose_options.png\" alt=\"watch_tut_choose_options\" width=\"733\" height=\"433\" class=\"alignnone size-full wp-image-1002\" \/><\/a><\/p>\n<p>Notice on this screen also there is a <i>new<\/i> bundle identifier.  If you used <code>com.yourcompany.aerisweather<\/code> for your bundle identifier above, the new identifier for the watch app will be <code>com.yourcompany.aerisweather.watchapp<\/code>.  As you will see below there is also a <i>third<\/i> bundle identifier for the app extension named <code>com.yourcompany.aerisweather.watchkitextension<\/code>.<\/p>\n<p>Again, <i>two new targets<\/i> have been added, along with a new scheme.  The targets are the <i>watch app extension<\/i> and <i>watch app<\/i>, and the scheme is <b>aerisweather Watch App<\/b>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_new_targets.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_new_targets.png\" alt=\"watch_tut_new_targets\" width=\"486\" height=\"334\" class=\"alignnone size-full wp-image-1003\" \/><\/a><\/p>\n<p>When first working with WatchKit it was confusing understanding how to simulate the Watch.  The answer is by using the new scheme Xcode created, in our case it is named <b>aerisweather Watch App<\/b>.  You must use the simulator for now; that is, you cannot run the app and app extension on your physical iPhone and simulate the watch.  <\/p>\n<p>Let&#8217;s lay out our watch interface using the Xcode storyboard created for us.  Go to the file <b>Interface.storyboard<\/b> in the <b>aerisweather Watch App<\/b> folder.  Like our iPhone app we will have a label for our last known location, a weather icon, current conditions label, temperature label, and at the bottom a last updated string.  Here is our layout:<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_layout.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_layout.png\" alt=\"watch_tut_layout\" width=\"572\" height=\"375\" class=\"alignnone size-full wp-image-1004\" \/><\/a><\/p>\n<p>Laying items out on a watch interface is a bit easier than for an iOS device.  For one reason we don&#8217;t have to use constraints!  There are a few things to note.  First, our layout was created by adding, in this order:<\/p>\n<ul>\n<li>Label with a horizontal position of center\n<li>Group\n<li>Image inside the group with a horizontal position of Left and size of 64&#215;64\n<li>Label inside the group with a horizontal position of Right\n<li>Label with a horizontal position of center\n<li>Label with a horizontal position of center\n<\/ul>\n<p>Second, we are using <b>Avenir Next<\/b> for our font, and have set the <b>currentTemperature<\/b> label to 30-pt, and the <b>lastUpdated<\/b> label to 10-pt.  The remaining labels are 16-pt.<\/p>\n<p>We now need to wire our elements up to the <code>InterfaceController<\/code>.  Notice that the controller is in our <i>extension<\/i> and not in the Watch app itself!  But if you click on Interface.storyboard and then the Assistant Editor icon (the joined circles), Xcode will place the storyboard and controller side-by-side in the editor.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_interfacecontroller.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_interfacecontroller.png\" alt=\"watch_tut_interfacecontroller\" width=\"1142\" height=\"494\" class=\"alignnone size-full wp-image-1006\" \/><\/a><\/p>\n<p>Just as with building iOS applications, simply CTRL-click from the UI element and drag over to the InterfaceController class to insert the Outlet.  To be consistent with our iPhone interface we&#8217;ll name these:<\/p>\n<ul>\n<li><code>currentLocationLabel<\/code>\n<li><code>currentWeatherIcon<\/code>\n<li><code>currentTemperatureLabel<\/code>\n<li><code>currentConditionsLabel<\/code>\n<li><code>lastUpdatedLabel<\/code>\n<\/ul>\n<p>After adding our IBOutlets <\/p>\n<pre class=\"lang:swift decode:true \" >\r\n  @IBOutlet weak var currentLocationLabel: WKInterfaceLabel!\r\n  @IBOutlet weak var currentWeatherIcon: WKInterfaceImage!\r\n  @IBOutlet weak var currentTemperatureLabel: WKInterfaceLabel!\r\n  @IBOutlet weak var currentConditionsLabel: WKInterfaceLabel!\r\n  @IBOutlet weak var lastUpdatedLabel: WKInterfaceLabel!\r\n<\/pre>\n<p>It is possible to run the watch app at this point and see the labels displayed.  To run the app select the <b>aerisweather Watch App<\/b> scheme and click Run.  To <i>see<\/i> the Apple Watch display go to the simulator and in the <b>Hardware<\/b> menu find <b>External Displays<\/b> and select one of the two Apple Watch displays.<\/p>\n<p><b>Note:<\/b>  I&#8217;ve frequently had trouble with the simulator and watch kit extensions running properly.  If you see in your Xcode debug navigator <code>waiting to attach<\/code> to your process and it never attaches, simply stop the application and run again.  One more than one occasion I&#8217;ve had to restart the simulator altogether.<\/p>\n<h3>App Groups<\/h3>\n<p>To display the weather on the Apple Watch we&#8217;re going to need to fetch it from Aeris, but to fetch it from Aeris we&#8217;re going to need to obtain the last known location.  <\/p>\n<p>We need to stress that our app extension does <i>not<\/i> have access to iOS CoreLocation, thus it cannot retrieve the location and update the weather.  Why?  Because Apple warned us not to use it.  From Apple&#8217;s WatchKit guidelines:  &#8220;<i>Avoid using technologies that request user permission, like Core Location. Using the technology from your WatchKit extension could involve displaying an unexpected prompt on the user\u2019s iPhone the first time you make the request. Worse, it could happen at a time when the iPhone is in the user\u2019s pocket and not visible.<\/i>&#8221;  So there you go.<\/p>\n<p>To obtain the location we leverage sharing application data between our iOS application and our app extension.  Select your project in the project navigator and select the <b>aerisweather WatchKit Extension<\/b> target.  Then select the <b>Capabilities<\/b> page and scroll down to <b>App Groups<\/b>.  Enable it and select <code>groups.aerisweather<\/code>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_enable_app_groups.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_enable_app_groups.png\" alt=\"watch_tut_enable_app_groups\" width=\"1086\" height=\"384\" class=\"alignnone size-full wp-image-1005\" \/><\/a><\/p>\n<p>Now, let&#8217;s add some code!  Open <code>InterfaceController.swift<\/code> and add the following to the <code>willActivate<\/code> function:<\/p>\n<pre class=\"lang:swift decode:true \" >\r\n    let url = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier(\"group.aerisweather\")\r\n    let file = url!.URLByAppendingPathComponent(\"aerisweather.plist\")\r\n    \r\n    if NSFileManager.defaultManager().fileExistsAtPath(file.path!) {\r\n      \r\n      let dict = NSDictionary(contentsOfURL: file) as Dictionary<String,AnyObject>\r\n      let city          = dict[\"city\"]! as String\r\n      let state         = dict[\"state\"]! as String\r\n      let country       = dict[\"country\"]! as String\r\n      let lastUpdatedAt = dict[\"timestamp\"]! as NSDate\r\n      \r\n      println(\"Last location:  \\(city), \\(state), at \\(lastUpdatedAt)\")\r\n    } else {\r\n      println(\"No last location\")\r\n    }\r\n<\/pre>\n<p>Run your watch app again and you should see in your console log something like:<\/p>\n<pre class=\"lang:default highlight:0 decode:true \" >2014-11-29 11:14:51.067 aerisweather WatchKit Extension[16728:1012693] &lt;aerisweather_WatchKit_Extension.InterfaceController: 0x7fa25141f1c0&gt; init\r\n2014-11-29 11:14:51.083 aerisweather WatchKit Extension[16728:1012693] &lt;aerisweather_WatchKit_Extension.InterfaceController: 0x7fa25141f1c0&gt; will activate\r\nLast location:  Los Altos Hills, CA, at 2014-11-29 17:14:32 +0000<\/pre>\n<p>If the log instead says <code>No last location<\/code> you haven&#8217;t launched and run your iOS application.  Remember, our watch application (and its corresponding extension) does <i>not<\/i> run CoreLocation, so it cannot directly obtain the user&#8217;s location.  Only the iOS application requests and receives location information.<\/p>\n<p>What exactly did we just do?  By enabling app groups we are allowing our iOS application and our watchkit extension to share information.  When our iOS application receives a new location from CoreLocation it reverse-geocodes the coordinate and then <i>writes<\/i> that information to a file in the app group <code>group.aerisweather<\/code>.  When you launch your watch app it <i>reads<\/i> that information from the file in the app group.  Check out the <code>CoreLocationController<\/code>&#8216;s <code>didUpdateLocations<\/code> function to see where the information is being written.<\/p>\n<p>Note that we <i>must<\/i> enable <b>App Groups<\/b> in each app that is granted permission to read and write in the shared <code>group.aerisweather<\/code> group.<\/p>\n<h3>Adding Aeris<\/h3>\n<p>Now, let&#8217;s get to adding Aeris to our watch app.  To be more precise, we are adding Aeris to the watchkit app extension, as no Aeris code runs on the watch itself.  <i>As such<\/i>, and this is key, we need to register <i>another<\/i> application in the HAMweather.com portal.  Because we will be executing Aeris routines inside an application with a different bundle ID (remember, our extension has a bundle identifier of <code>com.yourcompany.aerisweather.watchkitextension<\/code> we need to register it.  <\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_watchkitextension_register.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_watchkitextension_register.png\" alt=\"watch_tut_watchkitextension_register\" width=\"597\" height=\"279\" class=\"alignnone size-full wp-image-1031\" \/><\/a><\/p>\n<p>Add your new secret to the <code>ApiKeys.plist<\/code> file as <code>AERIS_EXTAPP_SECRET<\/code>.  In the property list you should now have three different keys:  <code>AERIS_CLIENT_ID<\/code> (which is shared by all the applications in your account), <code>AERIS_APP_SECRET<\/code> and <code>AERIS_EXTAPP_SECRET<\/code>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_allkeys.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_allkeys.png\" alt=\"watch_tut_allkeys\" width=\"664\" height=\"137\" class=\"alignnone size-full wp-image-1034\" \/><\/a><\/p>\n<p>Next we need to configure our Objective-C bridging header for the watchkit extension target.  If you&#8217;ve been working with Swift and Objective-C in the same projects you should be accustomed to this step by now.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_add_bridging.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_add_bridging.png\" alt=\"watch_tut_add_bridging\" width=\"936\" height=\"224\" class=\"alignnone size-full wp-image-1007\" \/><\/a><\/p>\n<p>Set the bridging header to <code>$(SRCROOT)\/bridgingHeader.h<\/code> (you will see it is actually already a part of the overall project since we use it for our iOS application as well).<\/p>\n<p>Now let&#8217;s add the <code>Aeris.framework<\/code> itself.  Select the <code>aerisweather WatchKit Extension<\/code> target and then <b>Build Phases<\/b>.  Click the disclosure triangle for <b>Link Binary With Libraries<\/b> and click the + icon.  When presented with the <b>Choose frameworks and libraries to add<\/b> dialog, click <b>Add Other<\/b>.  Locate the <b>Aeris.framework<\/b> item in your project directory, select it, and click Open.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_addaerisframework.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_addaerisframework.png\" alt=\"watch_tut_addaerisframework\" width=\"716\" height=\"455\" class=\"alignnone size-full wp-image-1028\" \/><\/a><\/p>\n<p>We&#8217;ll be using the <code>ApiKeys.swift<\/code> in the watch kit extension, so we need to add it to the compile sources.  You should already be in the <b>Build Phases<\/b> panel for the watch kit extension, so click the disclosure triangle for <b>Compile Sources<\/b> and then click the + icon.  Find the <code>ApiKeys.swift<\/code> file, select it, and click Add.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_addapikeys_compile.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_addapikeys_compile.png\" alt=\"watch_tut_addapikeys_compile\" width=\"768\" height=\"480\" class=\"alignnone size-full wp-image-1029\" \/><\/a><\/p>\n<p>Now, add the ApiKeys.plist to the <b>Copy Bundle Resources<\/b> list.  In the same area, <b>Build Settings<\/b>, disclose the <b>Copy Bundle Resources<\/b> area, click +, and add the <code>ApiKeys.plist<\/code>.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_addapikeysplist.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_addapikeysplist.png\" alt=\"watch_tut_addapikeysplist\" width=\"690\" height=\"139\" class=\"alignnone size-full wp-image-1008\" \/><\/a><\/p>\n<p>The Aeris framework also requires us to add <code>-ObjC<\/code> to <b>Other Linker Flags<\/b> in the <b>Build Settings<\/b> (if you don&#8217;t you&#8217;ll get a nasty crash with <code>-[__NSCFString awf_URLEncodedString]: unrecognized selector sent to instance<\/code>).  Go ahead and add it.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_other_linker.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_other_linker.png\" alt=\"watch_tut_other_linker\" width=\"933\" height=\"367\" class=\"alignnone size-full wp-image-1009\" \/><\/a><\/p>\n<h3>Adding AFNetworking<\/h3>\n<p>We are going to compile our AFNetworking code into our target (rather than using a pre-compiled framework), so we need to add all of the required AFNetworking files to our <b>Compile Sources<\/b>.  Go back to <b>Compile Sources<\/b> in the <b>Build Settings<\/b> panel for the extension target, click the + icon, and find the <code>AFNetworking<\/code> folder.  Select each of the <code>.m<\/code> files in the <code>AFNetworking<\/code> <i>and<\/i> <code>UIKit+AFNetworking<\/code> folders and add them.  This can be done quickly by holding down the Option key while selecting each file.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_add_afnetworking.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_add_afnetworking.png\" alt=\"watch_tut_add_afnetworking\" width=\"405\" height=\"462\" class=\"alignnone size-full wp-image-1055\" \/><\/a><\/p>\n<p>There are iOS routines that are <i>not available<\/i> to app extensions.  In particular <code>sharedApplication<\/code> is referenced in AFNetworking code and it is not available.  If you tried to compile now, you will see an error like <code>'sharedApplication' is unavailable: not available on iOS (App Extension)<\/code>.  The AFNetworking authors recognized this issue and added a flag <code>AF_APP_EXTENSIONS<\/code> that can be defined to not call on unavailable routines.  For an example see <a href=\"https:\/\/github.com\/AFNetworking\/AFNetworking\/issues\/2321\">Github<\/a>, and here is a screenshot of what the error looks like:<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_sharedapp_na.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_sharedapp_na.png\" alt=\"watch_tut_sharedapp_na\" width=\"262\" height=\"284\" class=\"alignnone size-full wp-image-1057\" \/><\/a><\/p>\n<p>To set the <code>AF_APP_EXTENSIONS<\/code> define go to your <b>aerisweather WatchKit Extension<\/b> <b>Build Settings<\/b> page and type <b>preprocessor<\/b> in the search box and locate the section headed <b>Preprocessing<\/b>.  We will be building the <b>Debug<\/b> configuration in this tutorial so add <code>AF_APP_EXTENSIONS=1<\/code> underneath the <code>DEBUG=1<\/code> define.<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_afappextensions.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_afappextensions.png\" alt=\"watch_tut_afappextensions\" width=\"937\" height=\"603\" class=\"alignnone size-full wp-image-1010\" \/><\/a><\/p>\n<p>Rebuilding at this point will still result in an error regarding iOS App Extensions and <code>sharedApplication<\/code> because a routine inside the Aeris SDK uses <code>AFNetworkActivitityIndicatorManager<\/code>.  The offending routine in <code>AFNetworkActivityIndicatorManager.m<\/code> is <\/p>\n<pre class=\"lang:swift decode:true \" >\r\n- (void)updateNetworkActivityIndicatorVisibility {\r\n    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]];\r\n}\r\n<\/pre>\n<p>To resolve the issue simply wrap the contents of the method with <code>#if !defined(AF_APP_EXTENSIONS)<\/code> and <code>#endif<\/code>:<\/p>\n<pre class=\"lang:swift decode:true \" >\r\n- (void)updateNetworkActivityIndicatorVisibility {\r\n#if !defined(AF_APP_EXTENSIONS)\r\n    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]];\r\n#endif\r\n}\r\n<\/pre>\n<p>One more file needs to be touched:  <code>UIAlertView+AFNetworking.m<\/code>.  There are calls to <code>UIAlertView<\/code> in the methods <code>showAlertViewForTaskWithErrorOnCompletion<\/code> and <code>showAlertViewForRequestOperationWithErrorOnCompletion<\/code>.  Wrap both calls to the UIAlertView with <code>#if !defined(AF_APP_EXTENSIONS)<\/code> and corresponding <code>#endif<\/code>.<\/p>\n<p>Take a moment to compile and run your watch app:  there should be no unresolved errors at this point!<\/p>\n<h2>Updating <code>willActivate<\/code><\/h2>\n<p>Now that we have everything configured properly for using Aeris, let&#8217;s fill out our routine to include the following in the <code>willActivate<\/code> routine <i>after<\/i> we receive our location.  This block of code goes immediately following the line <code>        println(\"Last location:  \\(city), \\(state), at \\(lastUpdatedAt)\")<\/code> in <code>InterfaceController.swift<\/code>.<\/p>\n<pre class=\"lang:swift decode:true \" >\r\n      \/* Initialize Aeris *\/\r\n      let aerisConsumerId =     valueForAPIKey(keyname: \"AERIS_CLIENT_ID\")\r\n      let aerisConsumerSecret = valueForAPIKey(keyname: \"AERIS_EXTAPP_SECRET\")\r\n      AerisEngine.engineWithKey(aerisConsumerId, secret: aerisConsumerSecret)\r\n      \r\n      \/* Use Aeris to obtain current weather for last known location *\/\r\n      let place = AWFPlace(city: city, state:state, country:country)\r\n      let loader = AWFObservationsLoader()\r\n      \r\n      loader.getObservationForPlace(place, options: nil, completion: { (observations, error) -> Void in\r\n        \r\n        if observations.count > 0 {\r\n          \r\n          let observation = observations[0] as AWFObservation\r\n          \r\n          self.currentWeatherIcon.setImageNamed(observation.icon)\r\n          self.currentConditionsLabel.setText(observation.weather)\r\n          self.currentLocationLabel.setText(\"\\(city), \\(state)\")\r\n          self.currentTemperatureLabel.setText(\"\\(observation.tempF)\u00b0\")\r\n          \r\n          let dateFormatter = NSDateFormatter()\r\n          let formattedDate = NSDateFormatter.localizedStringFromDate(lastUpdatedAt, dateStyle: .ShortStyle,\r\n            timeStyle: .ShortStyle)\r\n          self.lastUpdatedLabel.setText(formattedDate)\r\n          \r\n        } else {\r\n          println(\"No observations\")\r\n          if let e = error {\r\n            println(\"error:  \\(e)\")\r\n          }\r\n        }\r\n      })\r\n<\/pre>\n<p>The full <code>willActivate<\/code> routine should now look like this:<\/p>\n<pre class=\"lang:swift decode:true \" >\r\n    override func willActivate() {\r\n        \/\/ This method is called when watch view controller is about to be visible to user\r\n        super.willActivate()\r\n      \r\n      let url = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier(\"group.aerisweather\")\r\n      let file = url!.URLByAppendingPathComponent(\"aerisweather.plist\")\r\n      \r\n      if NSFileManager.defaultManager().fileExistsAtPath(file.path!) {\r\n        \r\n        let dict = NSDictionary(contentsOfURL: file) as Dictionary<String,AnyObject>\r\n        let city          = dict[\"city\"]! as String\r\n        let state         = dict[\"state\"]! as String\r\n        let country       = dict[\"country\"]! as String\r\n        let lastUpdatedAt = dict[\"timestamp\"]! as NSDate\r\n        \r\n        println(\"Last location:  \\(city), \\(state), at \\(lastUpdatedAt)\")\r\n        \r\n        \/* Initialize Aeris *\/\r\n        let aerisConsumerId =     valueForAPIKey(keyname: \"AERIS_CLIENT_ID\")\r\n        let aerisConsumerSecret = valueForAPIKey(keyname: \"AERIS_EXTAPP_SECRET\")\r\n        AerisEngine.engineWithKey(aerisConsumerId, secret: aerisConsumerSecret)\r\n        \r\n        \/* Use Aeris to obtain current weather for last known location *\/\r\n        let place = AWFPlace(city: city, state:state, country:country)\r\n        let loader = AWFObservationsLoader()\r\n        \r\n        loader.getObservationForPlace(place, options: nil, completion: { (observations, error) -> Void in\r\n          \r\n          if observations.count > 0 {\r\n            \r\n            let observation = observations[0] as AWFObservation\r\n            \r\n            self.currentWeatherIcon.setImageNamed(observation.icon)\r\n            self.currentConditionsLabel.setText(observation.weather)\r\n            self.currentLocationLabel.setText(\"\\(city), \\(state)\")\r\n            self.currentTemperatureLabel.setText(\"\\(observation.tempF)\u00b0\")\r\n            \r\n            let dateFormatter = NSDateFormatter()\r\n            let formattedDate = NSDateFormatter.localizedStringFromDate(lastUpdatedAt, dateStyle: .ShortStyle,\r\n              timeStyle: .ShortStyle)\r\n            self.lastUpdatedLabel.setText(formattedDate)\r\n            \r\n          } else {\r\n            println(\"No observations\")\r\n            if let e = error {\r\n              println(\"error:  \\(e)\")\r\n            }\r\n          }\r\n        })\r\n\r\n      } else {\r\n        println(\"No last location\")\r\n      }\r\n      \r\n    }\r\n<\/pre>\n<h2>Running the Watch App<\/h2>\n<p>Running now you should see something like this in the logs:<\/p>\n<pre>\r\n2014-11-29 09:14:51.445 aerisweather WatchKit Extension[14329:845403] <aerisweather_WatchKit_Extension.InterfaceController: 0x79e38cd0> init\r\nLast location:  San Francisco, CA, at 2014-11-30 02:31:04 +0000\r\n2014-11-29 09:14:52.677 aerisweather WatchKit Extension[14329:845506] Unable to find image named \"cloudyn.png\" on Watch\r\n<\/pre>\n<p>and our corresponding watch app will show:<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_no_icons.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/watch_tut_no_icons.png\" alt=\"watch_tut_no_icons\" width=\"273\" height=\"364\" class=\"alignnone size-full wp-image-1059\" \/><\/a><\/p>\n<p>Oops!  We need to add some assets to our app!  Our iOS application currently contains a folder named <code>Images.xcassets<\/code> with all of our weather icons.  We&#8217;ll leverage this and include them into our watch app.  To do so right-click on <b>Images.xcassets<\/b> in the <b>aerisweather<\/b> folder and select <b>Show in Finder<\/b>.  <\/p>\n<p>Now select your <b>Images.xcassets<\/b> icon in the <b>aerisweather Watch App<\/b> folder (there are three folders named Images.xcassets in your project, make sure and select the right one here!) and then drag-and-drop the contents of the folder <i>in the Finder<\/i> into Xcode in the icon pane of your <code>Images.xcassets<\/code>.  To be clear on this one:  our existing iOS application contains all of the icons we need, but we need them on our watch as well.  Using <b>Show in Finder<\/b> we expose the contents of our <b>Images.xcassets<\/b> so we can drag-and-drop the resources into the <i>Watch App&#8217;s<\/i> <b>Images.xcassets<\/b>!<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/weather_tut_drag_and_drop.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/weather_tut_drag_and_drop.png\" alt=\"weather_tut_drag_and_drop\" width=\"1199\" height=\"859\" class=\"alignnone size-full wp-image-1011\" \/><\/a><\/p>\n<p>Run the watch app again and you should see something like:<\/p>\n<p><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/tut_apple_watch_finished.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2014\/11\/tut_apple_watch_finished.png\" alt=\"tut_apple_watch_finished\" width=\"273\" height=\"364\" class=\"alignnone size-full wp-image-1044\" \/><\/a><\/p>\n<h2>Get the Code on BitBucket<\/h2>\n<p>BitBucket contains two versions of the application:  <\/p>\n<ul>\n<li>The <a href=\"https:\/\/bitbucket.org\/joeiachievedit\/aerisweather\/get\/starter.zip\">starter application<\/a> which does not include the Apple Watch target.  Download this file if you want to go through the above tutorial from start to finish.\n<li>The <a href=\"https:\/\/bitbucket.org\/joeiachievedit\/aerisweather\/get\/applewatch.zip\">Apple Watch application<\/a> which includes both the iPhone and Apple Watch apps.  Download this file if you want to see the completed Apple Watch application.\n<\/ul>\n<p><b>Warning!<\/b>  Neither version comes with the <code>ApiKeys.plist<\/code> file which contains the Aeris SDK API keys.  You <i>have<\/i> to create this file yourself and obtain your own API keys from <a href=\"https:\/\/hamweather.com\/\">HAMweather.com<\/a>!<\/p>\n<p>You can also obtain fork us on BitBucket, just go to the <a href=\"https:\/\/bitbucket.org\/joeiachievedit\/aerisweather\">main project page<\/a>.<\/p>\n<h2>Final Thoughts<\/h2>\n<p>It&#8217;s been fun putting together this tutorial on creating an Apple Watch app.  If you install and run the aerisweather app on your iPhone it does take a toll on the battery over the course of the day using the significant location change monitoring feature of CoreLocation.  I&#8217;m a bit disappointed in that, but have become somewhat accustomed to recharging the iPhone every day.  It will be interesting what types of applications are available for Apple Watch on launch day and what iOS features they take advantage of!<\/p>\n<h2>Questions or Comments<\/h2>\n<p>I&#8217;ve been through the tutorial a number of times starting with the aptly named starter application.  If you have any difficulty going through the tutorial please send a tweet to @iachievedit!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Editor&#8217;s Note: This is a tutorial on how to use iOS 8.2 with Xcode 6.2 to develop an Apple Watch app. It is not an exhaustive overview of WatchKit. I recommend reading the following first in this order: Apple WatchKit Programming Guide WatchKit Initial Impressions Getting Started With WatchKit By the time you&#8217;ve finished reading [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,16,2],"tags":[],"class_list":["post-994","post","type-post","status-publish","format-standard","hentry","category-apple","category-apple-watch","category-xcodetips"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/994"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=994"}],"version-history":[{"count":48,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/994\/revisions"}],"predecessor-version":[{"id":1123,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/994\/revisions\/1123"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=994"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=994"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=994"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}