<?xml version="1.0" encoding="UTF-8"?>
<!--Generated by Site-Server v@build.version@ (http://www.squarespace.com) on Fri, 10 Apr 2026 17:18:01 GMT
--><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://www.rssboard.org/media-rss" version="2.0"><channel><title>WatchKit Developer Blog - Sneaky Crab</title><link>https://www.sneakycrab.com/blog/</link><lastBuildDate>Tue, 04 Aug 2015 21:05:43 +0000</lastBuildDate><language>en-US</language><generator>Site-Server v@build.version@ (http://www.squarespace.com)</generator><description><![CDATA[<p>A blog by the developers of Sneaky Crab, discussing the cutting edge of WatchKit development, tips, code examples and more.</p>]]></description><item><title>watchOS 1 quickie -- debugging reloadRootControllersWithNames errors</title><dc:creator>Justin Ng</dc:creator><pubDate>Tue, 04 Aug 2015 21:05:42 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/8/4/watchos-1-quickie-debugging-reloadrootcontrollerswithnames-errors</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:55c12707e4b0fa221024779a</guid><description><![CDATA[<p>If you've ever used the API <code>reloadRootControllersWithNames(names: [AnyObject], contexts: [AnyObject]?)</code> in any non-trivial way, you're likely to encounter the frustration of getting this error spamming your logs and causing a lot of issues:</p>

<p><code>*********** ERROR -[SPRemoteInterface _interfaceControllerClientIDForControllerID:] clientIdentifier for interfaceControllerID:114D0003 not found</code></p>

<p>Generally this doesn't seem all too helpful. What is <code>interfaceControllerID:114D0003</code> anyway?</p>

<p>It turns out this is the ID in a private property called <code>_viewControllerID</code>. There is a little bit of code you can add to your <code>willActivate()</code> to see what the <code>_viewControllerID</code> is for each of your pages.</p>

  
    <pre><code class="language-swift">    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        if let vcID = self.valueForKey("_viewControllerID") as? NSString {
            println("Page One: \(vcID)")
        }
    }</code></pre>
  

<p>With a little bit of key-value coding trickery, we can get access to the private variable. Knowing exactly which <code>WKInterfaceController</code> is misbehaving really saves time when you're hunting down to clear out all references to the old controller before blowing it away with the <code>reloadRootControllersWithNames</code> call.</p>

<p>Hope this quick tip helps all of you too.</p>]]></description></item><item><title>Haptic feedback with the Taptic Engine - WKInterfaceDevice and WKHapticType in WatchKit and watchOS 2</title><dc:creator>Michael Gu</dc:creator><pubDate>Mon, 22 Jun 2015 23:17:21 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/6/22/haptic-feedback-with-the-taptic-engine-in-watchkit-and-watchos-2-wkinterfacedevice-and-wkhaptic</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:558871cde4b04baa030fb016</guid><description><![CDATA[<p>The Taptic Engine is a game-changing piece of hardware that Apple has added to the Apple Watch. One of the annoyances of Android Wear and Pebble is that the notification vibrations are not very subtle, and everyone nearby knows that your watch demands attention. The Taptic Engine allows you to send taps to your users wrist, feeling much like someone tapping their finger on you, as a subtle and discreet to communication information for users that they can choose to ignore or receive without bothering those around them.</p>

<h2 id="sowhattheheckisahapticandhowisitdifferentfromataptic">So what the heck is a Haptic and how is it different from a Taptic?</h2>

<p><a href="https://en.wikipedia.org/wiki/Haptic_technology">Haptic Feedback</a> used in technology typically refers to any kind of vibration or force used in input devices on the user. Pressing a button on a smooth glass screen just doesn't feel as satisfying as clicking a physical button on a mouse. That's because the mouse button depresses and then gives a 'click' feeling vibrating into your finger to indicate the button has been pressed. What's nice about this is that you can know when a mouse button has been successfully pressed without looking at it. </p>

<p>Compare this to a button on an iOS App, where you have to visually look where you are aiming your finger, and rely on visual feedback that the button has been pressed correctly. Haptic feedback refers to the class of technology to simulate physical feedback in a device, like the vibrations of a Playstation Controller or the taps on an Apple Watch.</p>

<p>The Taptic Engine is Apple's marketing name for the piece of hardware that can produce Haptic feedback by way of the feeling of taps on your wrist in response to different actions in the hardware. It can notify you that you scrolled to the end of a list, it can make it feel clicky as you click through the items of a <a href="http://www.sneakycrab.com/blog/2015/6/12/wkinterfacepicker-in-watchkit-20-using-the-digital-crown">WKInterfacePicker</a> list, or even indicate on your driving directions if it's time to turn left or right with different feeling taps so you don't even have to look at the Watch screen to know which way to turn.</p>

<h1 id="codingyourownhaptics">Coding Your Own Haptics</h1>

<p>The API is exceedingly simple, with just a quick API call to <code>playHaptic()</code>:</p>

  
    <pre><code class="language-swift">WKInterfaceDevice.currentDevice().playHaptic(.Click)</code></pre>
  

<p>The <code>playHaptic()</code> API takes in values from the <code>WKHapticType</code> enum.</p>

  
    <pre><code class="language-swift">enum WKHapticType : Int {
    case Notification
    case DirectionUp
    case DirectionDown
    case Success
    case Failure
    case Retry
    case Start
    case Stop
    case Click
}</code></pre>
  

<p>Apple currently doesn't allow for defining custom Haptic events, so whatever you want to do on your app, you'll have to do it with one of these nine Haptic types.</p><p>It's pretty important to understand the feeling, sound and intention of each of these types, so we'll cover each in detail.</p><h1 id="wkhaptictypeindetail"><code>WKHapticType</code> in Detail</h1><h2 id="notification"><code>.Notification</code></h2><p><strong>Sound</strong>: Chime <br>
<strong>Haptic</strong>: Tap-Tap-Vibrate</p><p>The <code>.Notification</code> type is intended for drawing the user's attention when something significant or out of the ordinary has occurred.</p><h2 id="directionup"><code>.DirectionUp</code></h2><p><strong>Sound</strong>: Increasing Pitch <br>
<strong>Haptic</strong>: Tap-Tap</p><p>The <code>.DirectionUp</code> type is intended for indicating a significant increase value threshold has been crossed such as when moving up a list.</p><h2 id="directiondown"><code>.DirectionDown</code></h2><p><strong>Sound</strong>: Decreasing Pitch <br>
<strong>Haptic</strong>: Tap-Tap</p><p>The <code>.DirectionDown</code> type is intended for indicating a significant decrease value threshold has been crossed, such as when moving down a list.</p><h2 id="success"><code>.Success</code></h2><p><strong>Sound</strong>: Confirmation Ding <br>
<strong>Haptic</strong>: Tap-Tap-Tap</p><p>The <code>.Success</code> type is intended as a confirmation tone to indicate some action has been completed successfully.</p><h2 id="failure"><code>.Failure</code></h2><p><strong>Sound</strong>: Failure Ding <br>
<strong>Haptic</strong>: Long Vibrate</p><p>The <code>.Failure</code> type is intended as a failure tone to indicate some action has not been completed successfully.</p><h2 id="retry"><code>.Retry</code></h2><p><strong>Sound</strong>: Quick Ding-Ding-Ding <br>
<strong>Haptic</strong>: Long Vibrate</p><p>The <code>.Retry</code> type is intended as a gentle tone to indicate some action has not been completed successfully but the user has an opportunity to retry. Typically you should display some UI with this tone giving the user an opportunity to retry their failed action.</p><h2 id="start"><code>.Start</code></h2><p><strong>Sound</strong>: Long Ding <br>
<strong>Haptic</strong>: Long Tap</p><p>The <code>.Start</code> type is intended to indicate the start of an activity, such when a timer begins.</p><h2 id="end"><code>.End</code></h2><p><strong>Sound</strong>: Long Ding-Long Ding <br>
<strong>Haptic</strong>: Long Tap-Long Tap</p><p>The <code>.End</code> type is intended to indicate the end of an activity, such when a timer has ended.</p><h2 id="click"><code>.Click</code></h2><p><strong>Sound</strong>: Very soft click <br>
<strong>Haptic</strong>: Light tap</p><p>The <code>.Click</code> type is intended to indicate a clicking sound, like when a dial is clicking.</p>











































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png" data-image-dimensions="484x873" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=1000w" width="484" height="873" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1435014050323-0P0KOR758WMHPD4GFHT1/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Haptic Demo sample provided on GitHub</p>
          </figcaption>
        
      
        </figure>
      

    
  


  


<h1 id="tapticengineconsiderations">Taptic Engine Considerations</h1>

<p>The Taptic Engine cannot overlap Haptics, and there is a delay between each one that can be played. So for example, let's say you wanted to play a <code>.Click</code> as you move through each item in a list. If you move quickly through the list, a lot of <code>.Click</code> haptics will be ignored, and your user will find it not feeling quite right when the taps fall out of sync from your UI.</p>

<p>Make sure that when you are designing your application, only use haptics on rare and significant events to both conserve battery as well as feel strange if they are asked to overlap and you end up losing feedback commands that you intended.</p>

<h1 id="tryingitout">Trying It Out</h1>

<p>We've provided sample code for an app to try out each of the haptic types on the <a href="https://github.com/sneakycrab/hapticdemo">Sneaky Crab GitHub</a>. Unfortunately, the current version of the watchOS Simulator does not support sounds, so you'll have to install this on your iOS 9/watchOS 2 hardware to try it out.</p>

<p><em>Remember that it is currently impossible to reverse a watchOS 2 update, so make sure that you only update to watchOS 2 for test devices and not your main Apple Watch, especially considering that watchOS 2 beta 1 is extremely unstable!</em></p>

<h1 id="wrappingup">Wrapping Up</h1>

<p>Apple provides a very simple API for providing haptic feedback to a user through the excellent Taptic Engine. Staying consistent with the intended meanings of the different types will allow a user to learn and understand what you are trying to convey without having to relearn anything while using your application.</p>

<p>How have you implemented Haptic feedback in your Apple Watch apps?</p>]]></description></item><item><title>WKInterfacePicker in WatchKit 2.0 - Using The Digital Crown</title><dc:creator>Michael Gu</dc:creator><pubDate>Fri, 12 Jun 2015 22:46:20 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/6/12/wkinterfacepicker-in-watchkit-20-using-the-digital-crown</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:557b0d90e4b04cad1c0b7885</guid><description><![CDATA[<p>Apple has provided a new <code>WKInterfaceObject</code> called <code>WKInterfacePicker</code>. It allows you to select an item from a list, similar to the <code>UIPickerView</code> you're already familiar with in iOS 8. You can use it on a list of text, images, and the items are selected by using the Digital Crown.</p>

<h2 id="pickerstyles">Picker Styles</h2>

<p>There are three main styles you can choose from: List, Stack and Sequence.</p>











































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=1000w" width="272" height="340" sizes="(max-width: 640px) 100vw, (max-width: 767px) 33.33333333333333vw, 33.33333333333333vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434144101612-OHQPY83DOMXLX63HS1P5/image-asset.gif?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>List Style Picker</p>
          </figcaption>
        
      
        </figure>
      

    
  


  













































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=1000w" width="272" height="340" sizes="(max-width: 640px) 100vw, (max-width: 767px) 33.33333333333333vw, 33.33333333333333vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145082583-QL95QE5TFVVEKPA9CP6Z/image-asset.gif?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Stack Style Picker</p>
          </figcaption>
        
      
        </figure>
      

    
  


  













































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=1000w" width="272" height="340" sizes="(max-width: 640px) 100vw, (max-width: 767px) 33.33333333333333vw, 33.33333333333333vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434145828398-0GW8JPZEQ10JUQNJ0YV4/image-asset.gif?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Sequence Style Picker (Images from&nbsp;<a href="http://hmaidasani.github.io/RadialChartImageGenerator/">RadialChartImageGenerator</a>)</p>
          </figcaption>
        
      
        </figure>
      

    
  


  


<p>The List style is very similar the the <code>UIPickerView</code> in iOS, where you use the crown to select a sequential list of items. The items can be images and text, just text or just images.</p>

<p>The Stack Style animates the items like a stack of cards, providing an attractive way to select an item.</p>

<p>The Sequence Style replaces each image, allowing you to quickly flip between your choices, and also can be used to animate progress bars.</p>

<h2 id="focusstyles">Focus Styles</h2>

<p>For each style, you can indicate a focus style, which indicates how you want watchOS to display which picker is in focus. If there is only one picker on the screen, you may want to use None. If you have multiple pickers, you need to indicate which control is in focus and will be affected by the Digital Crown. For example, when customizing the clock face, Outline focus styles are used to indicate which complication is in focus to be adjusted by the Digital Crown.</p>

<p>Each picker item can be annotated by a caption, which can be used to indicate the group or secondary information about the item that will be displayed while picking. The caption can be useful if you are picking images, for example.</p>











































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=1000w" width="272" height="340" sizes="(max-width: 640px) 100vw, (max-width: 767px) 33.33333333333333vw, 33.33333333333333vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146539267-TFC7KTCYHF3LKG0LOXW9/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Focus Style: None</p>
          </figcaption>
        
      
        </figure>
      

    
  


  













































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=1000w" width="272" height="340" sizes="(max-width: 640px) 100vw, (max-width: 767px) 33.33333333333333vw, 33.33333333333333vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146504649-5XJQ351IJIYXIN6YQBIO/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Focus Style: Outline</p>
          </figcaption>
        
      
        </figure>
      

    
  


  













































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=1000w" width="272" height="340" sizes="(max-width: 640px) 100vw, (max-width: 767px) 33.33333333333333vw, 33.33333333333333vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434146531300-3YE9TSPDNL519V4X5YLQ/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Focus Style: Outline With Caption</p>
          </figcaption>
        
      
        </figure>
      

    
  


  





  <h2>Coding</h2><p>Open the Storyboard for your WatchKit App, and drag a Picker into your InterfaceController.</p>


































































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png" data-image-dimensions="960x442" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=1000w" width="960" height="442" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1434147089963-7VBRY88EWZ788PJY51TJ/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
      
        </figure>
      

    
  


  





  <p>Choose the Style and Focus Style. The Indicator allows you to toggle whether the scroll guide appears beside the Digital Crown. This can be useful for styles like Stack, where it may not be clear to the user where in the list they have scrolled to.</p><p>Connect your picker to your interface controller twice, one for the outlet and one for the action.</p>
























  
    <pre><code class="language-swift">class InterfaceController: WKInterfaceController {
    @IBOutlet var itemPicker: WKInterfacePicker!

    @IBAction func pickerSelectedItemChanged(value: Int) {
    }
}
</code></pre>
  

<p>We're going to use the <code>IBOutlet</code> to set up the picker with initial values, and the <code>IBAction</code> to react to changes in the picker.</p>

<p>Here's the code for a picker populated with some text:</p>

  
    <pre><code class="language-swift">class ListPickerInterfaceController: WKInterfaceController {
    @IBOutlet var itemPicker: WKInterfacePicker!

    var foodList: [(String, String)] = [
        ("Broccoli", "Gross"),
        ("Brussel Sprouts", "Gross"),
        ("Soup", "Delicious"),
        ("Steak", "Delicious"),
        ("Ramen", "Delicious"),
        ("Pizza", "Delicious") ]

    override func willActivate() {
        super.willActivate()

        let pickerItems: [WKPickerItem] = foodList.map {
            let pickerItem = WKPickerItem()
            pickerItem.title = $0.0
            pickerItem.caption = $0.1
            return pickerItem
        }
        itemPicker.setItems(pickerItems)
    }

    @IBAction func pickerSelectedItemChanged(value: Int) {
        NSLog("List Picker: \(foodList[value].0) selected")
    }
}</code></pre>
  

<p>Modifying this to support images like in the Stack picker is very simple. Just use the new <code>WKImage</code> and pass those in. If you wanted to create a picker with images <code>frame1.png</code> to <code>frame5.png</code>:</p>

  
    <pre><code class="language-swift">class StackPickerInterfaceController : WKInterfaceController {
    @IBOutlet var itemPicker: WKInterfacePicker!

    var items: [String]! = nil
    
    override func willActivate() {
        super.willActivate()

        items = (1...5).map { "frame\($0).png" }
        
        let pickerItems: [WKPickerItem] = items.map {
            let pickerItem = WKPickerItem()
            pickerItem.contentImage = WKImage(imageName: $0)
            return pickerItem
        }
        itemPicker.setItems(pickerItems)
        itemPicker.focusForCrownInput()
    }

    @IBAction func pickerSelectedItemChanged(value: Int) {
        NSLog("Stack Picker: \(items[value]) selected.")
    }
}</code></pre>
  

<p>Finally, you'll find that a Sequence picker is set up exactly like the Stack picker:</p>

  
    <pre><code class="language-swift">class SequencePickerInterfaceController : WKInterfaceController {
    @IBOutlet var itemPicker: WKInterfacePicker!

    override func willActivate() {
        super.willActivate()

        let pickerItems: [WKPickerItem] = (0...100).map {
            let pickerItem = WKPickerItem()
            pickerItem.contentImage = WKImage(imageName: "picker\($0).png")
            return pickerItem
        }
        itemPicker.setItems(pickerItems)
    }
    
    @IBAction func pickerSelectedItemChanged(value: Int) {
        NSLog("Sequence Picker: \(value) selected.")
    }
}</code></pre>
  

<h2 id="wrappingup">Wrapping Up</h2>

<p>All of the sample code for this project is available in the <a href="https://github.com/sneakycrab/pickerdemo">Sneaky Crab GitHub</a>. If you're having trouble, make sure to download it and try out the sample code.</p>

<p><code>WKInterfacePicker</code> provides a simple way to do very attractive and easy to use picking using the Digital Crown in watchOS 2.</p>

<p>What cool interactions have you designed for your watch apps?</p>]]></description></item><item><title>Writing a WatchKit Complication in watchOS 2</title><dc:creator>Michael Gu</dc:creator><pubDate>Thu, 11 Jun 2015 04:13:08 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/6/10/writing-your-own-watchkit-complications</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:5578b7bbe4b08ce360da09a2</guid><description><![CDATA[<p>One of the exciting new additions to the WatchKit Framework in watchOS 2 is the ability to add custom complications to the clock faces provided by Apple. We've written a quick guide on how to add custom Complications to your watch app.</p>

<h2 id="implementclkcomplicationdatasource">Implement CLKComplicationDataSource</h2>

<p>All of the magic happens in <code>CLKComplicationDataSource</code>. Create a new class on your WatchKit Extension target that implements this delegate. Since every delegate method is required, we can start by adding the skeleton of every method in the delegate.</p>

  
    <pre><code class="language-swift">import ClockKit

class Cowmplication: NSObject, CLKComplicationDataSource {
    
    func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
        handler(nil)      
    }

    func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
        handler(nil)
    }
    
    func getPrivacyBehaviorForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationPrivacyBehavior) -> Void) {
        handler(CLKComplicationPrivacyBehavior.ShowOnLockScreen)
    }
    
    func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimelineEntry?) -> Void) {
        handler(nil)
    }
    
    func getTimelineEntriesForComplication(complication: CLKComplication, beforeDate date: NSDate, limit: Int, withHandler handler: ([CLKComplicationTimelineEntry]?) -> Void) {
        handler(nil)
    }
  
    func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: ([CLKComplicationTimelineEntry]?) -> Void) {
        handler([])
    }

    func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
        handler([CLKComplicationTimeTravelDirections.None])        
    }
    
    func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(NSDate())
    }
    
    func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
        handler(NSDate())
    }
}</code></pre>
  

<p>You never need to create an instance of this class, and Apple will handle instantiating it using the default constructor.</p>

<h2 id="understandingcomplicationfamilies">Understanding Complication Families</h2>

<p>There are 5 families of complications that we need to become familiar with in the <a href="https://developer.apple.com/library/prerelease/watchos/documentation/ClockKit/Reference/CLKComplication_class/index.html#//apple_ref/c/tdef/CLKComplicationFamily"><code>CLKComplicationFamily</code></a> enum. From left to right, here are images of <code>ModularSmall</code>, <code>ModularLarge</code>, <code>UtilitarianSmall</code>, <code>UtilitarianLarge</code>, <code>CircularSmall</code>.</p>



  

  



  
    
      

        

        

        
          
            
              
                
                <a role="presentation" aria-label="ModularSmall" class="
                    image-slide-anchor
                    
                    content-fit
                  "
                >
                  
                  <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433981880986-N8I5EETWDFNLYSVIBH5P/modularsmall.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="ModularSmall" data-load="false" data-image-id="5578d3b8e4b0c9bb376ec835" data-type="image" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433981880986-N8I5EETWDFNLYSVIBH5P/modularsmall.png?format=1000w" /><br>
                </a>
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                <a role="presentation" aria-label="ModularLarge" class="
                    image-slide-anchor
                    
                    content-fit
                  "
                >
                  
                  <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433981923939-NK6I6RY3MBJ1032AX04G/modularlarge.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="ModularLarge" data-load="false" data-image-id="5578d3e3e4b06e95d2919752" data-type="image" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433981923939-NK6I6RY3MBJ1032AX04G/modularlarge.png?format=1000w" /><br>
                </a>
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                <a role="presentation" aria-label="UtilitarianSmall" class="
                    image-slide-anchor
                    
                    content-fit
                  "
                >
                  
                  <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433982009146-00T3ITECHRXIN9NTOK1G/Simulator+Screen+Shot+Jun+10%2C+2015%2C+5.19.51+PM.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="UtilitarianSmall" data-load="false" data-image-id="5578d439e4b0c89ed7226dbc" data-type="image" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433982009146-00T3ITECHRXIN9NTOK1G/Simulator+Screen+Shot+Jun+10%2C+2015%2C+5.19.51+PM.png?format=1000w" /><br>
                </a>
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                <a role="presentation" aria-label="UtilitarianLarge" class="
                    image-slide-anchor
                    
                    content-fit
                  "
                >
                  
                  <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433982048012-646TIFZHUU6YUGVVKA1M/Simulator+Screen+Shot+Jun+10%2C+2015%2C+5.20.45+PM.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="UtilitarianLarge" data-load="false" data-image-id="5578d460e4b0a20ad9434de7" data-type="image" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433982048012-646TIFZHUU6YUGVVKA1M/Simulator+Screen+Shot+Jun+10%2C+2015%2C+5.20.45+PM.png?format=1000w" /><br>
                </a>
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                <a role="presentation" aria-label="CircularSmall" class="
                    image-slide-anchor
                    
                    content-fit
                  "
                >
                  
                  <img class="thumb-image" elementtiming="system-gallery-block-grid" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433982131769-733ZK1SY1CTXSTIOC7O1/Simulator+Screen+Shot+Jun+10%2C+2015%2C+5.22.08+PM.png" data-image-dimensions="272x340" data-image-focal-point="0.5,0.5" alt="CircularSmall" data-load="false" data-image-id="5578d4b3e4b08ce360daa68d" data-type="image" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433982131769-733ZK1SY1CTXSTIOC7O1/Simulator+Screen+Shot+Jun+10%2C+2015%2C+5.22.08+PM.png?format=1000w" /><br>
                </a>
                
              
            
          

          
        

      
    
  

  
    
    
    
     
  










<p>From within these families, to populate data, you implement different complication templates related to each family. For example, <code>CLKComplicationFamily.CircularSmall</code> can use the following templates:</p>

<ul>
<li><code>CLKComplicationTemplateCircularSmallRingText</code></li>
<li><code>CLKComplicationTemplateCircularSmallRingImage</code></li>
<li><code>CLKComplicationTemplateCircularSmallStackText</code></li>
<li><code>CLKComplicationTemplateCircularSmallStackImage</code></li>
</ul>

<p>There are many templates available. Take a look at the list of subclasses to <code>CLKComplicationTemplate</code> in the <a href="https://developer.apple.com/library/prerelease/watchos/documentation/ClockKit/Reference/ClockKit_framework/index.html#//apple_ref/doc/uid/TP40016082">ClockKit Framework Reference</a> to see all of the options. Clicking in on a template you can see Apple's visual diagram of how the information in each template is presented.</p>

<h2 id="configureinfoplist">Configure <code>Info.plist</code></h2>

<p>Go to your targets and select your WatchKit Extension target. Under the General tab, set the Data Source Class to the class delegate we created above prefixed with <code>$(PRODUCT_MODULE_NAME)</code>. For example, since our example class was <code>Cowmplication</code>, we put <code>$(PRODUCT_MODULE_NAME).Cowmplication</code> for Data Source Class.</p>

<p>Next, check off which complication families you want to support. Most likely you'd want to support all families, but we're just going to implement <code>CircularSmall</code> for this example.</p>











































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png" data-image-dimensions="563x240" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=1000w" width="563" height="240" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433989392168-BA5A0RH71EVA9YZBSU0E/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
      
        </figure>
      

    
  


  


<h2 id="setprivacybehavior">Set Privacy Behavior</h2>

<p>You can choose to show or hide your complication data if the watch is locked, especially if you are displaying more private or sensitive information by passing either <code>CLKComplicationPrivacyBehavior.ShowOnLockScreen</code> or <code>CLKComplicationPrivacyBehavior.HideOnLockScreen</code> to the handler of <code>getPrivacyBehaviorForComplication()</code>.</p>

  
    <pre><code class="language-swift">    func getPrivacyBehaviorForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationPrivacyBehavior) -> Void) {
        handler(CLKComplicationPrivacyBehavior.ShowOnLockScreen)
    }</code></pre>
  

<h2 id="setrefreshfrequency">Set Refresh Frequency</h2>

<p>Implement <code>getNextRequestedUpdateDateWithHandler()</code> delegate method to tell the watch how often to refresh the complication data. Apple recommends choosing hourly or even an entire day, and providing as much information as possible in a single update cycle with your complication. This will avoid unnecessary battery life drains.</p>

<p>The API gives you a handler you need to call, passing in the date of the next update.</p>

  
    <pre><code class="language-swift">    func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
        // Update hourly
        handler(NSDate(timeIntervalSinceNow: 60*60))
    }</code></pre>
  

<h2 id="implementplaceholdertemplates">Implement Placeholder Templates</h2>

<p>If data is not populated, especially when customizing the clock face, the OS will show a placeholder for your complication.</p>

<p>You set this up with the <code>getPlaceholderTemplateForComplication()</code> delegate method. It's important to know that this method is called only once, during the installation of your app and the placeholder is cached, so you won't be able to customize this later on.</p>

  
    <pre><code class="language-swift">    func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
        var template: CLKComplicationTemplate? = nil
        switch complication.family {
        case .ModularSmall:
            template = nil
        case .ModularLarge:
            template = nil
        case .UtilitarianSmall:
            template = nil
        case .UtilitarianLarge:
            template = nil
        case .CircularSmall:
            let modularTemplate = CLKComplicationTemplateCircularSmallRingText()
            modularTemplate.textProvider = CLKSimpleTextProvider(text: "--")
            modularTemplate.fillFraction = 0.7
            modularTemplate.ringStyle = CLKComplicationRingStyle.Closed
            template = modularTemplate
        }
        handler(template)
    }</code></pre>
  

<p>In this example code, we've only implemented the delegate method for <code>.CircularSmall</code>, but in your apps, you'd likely want to configure the look for most or all of the complication types.</p>

<p>For the placeholder, you are not supposed to populate with example data, which is why we put <code>"--"</code> for the text, following what Apple's stock complications do when they put their own placeholders.</p>

<h2 id="populateyourcomplicationwithrealdata">Populate Your Complication With Real Data</h2>

<p>Next, we'll actually implement the delegate method that will provide real data for the complication. Again, while in the code snippet we only implement <code>.CircularSmall</code>, you would build multiple templates for all of the different complication families you support.</p>

  
    <pre><code class="language-swift">    func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimelineEntry?) -> Void) {

        if complication.family == .CircularSmall {
            let template = CLKComplicationTemplateCircularSmallRingText()
            template.textProvider = CLKSimpleTextProvider(text: "\(getCurrentHealth())")
            template.fillFraction = Float(getCurrentHealth()) / 10.0
            template.ringStyle = CLKComplicationRingStyle.Closed

            let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: template)
            handler(timelineEntry)
        } else {
            handler(nil)
        }
    }</code></pre>
  

<p>This introduces the concept of a <code>CLKComplicationTimelineEntry</code>, which is a container that pairs a date with a complication template. <code>getCurrentTimelineEntryForComplication</code> is used to populate the current complication data, which is why we use <code>NSDate()</code> to indicate the current date and time.</p>

<h2 id="refreshingthecomplicationfromyourapp">Refreshing The Complication From Your App</h2>

<p>It's a very common scenario that while the user is using your app, you'll want to update your complication as you know the data is stale or incorrect. You can use the <code>CLKComplicationServer</code> singleton to trigger updates to your complications.</p>

  
    <pre><code class="language-swift">        let complicationServer = CLKComplicationServer.sharedInstance()
        for complication in complicationServer.activeComplications {
            complicationServer.reloadTimelineForComplication(complication)
        }</code></pre>
  


<p>Apple has indicated that <code>reloadTimelineForComplication()</code> is rate limited to preserve battery life. If a complication exceeds a daily limit, it will ignore calls to refresh for the remainder of that day.</p>
<h2 id="buildruntest">Build, Run &amp; Test</h2>











































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                <img data-stretch="false" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png" data-image-dimensions="312x390" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=1000w" width="312" height="390" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1433994723619-CTZB1UM8Z6S1XXEXMO2A/image-asset.png?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>The completed Complication.</p>
          </figcaption>
        
      
        </figure>
      

    
  


  


<p>If you didn't make any typos, you should be able to now test &amp; run this on the simulator or on a device. If you are having trouble finding it, make sure that you are looking for it on the right clock face, since not all clock faces support all families of complications.</p><h2 id="timetravel">Time Travel</h2><p>Improving the code to implement Time Travel is relatively straightforward. First, update <code>getSupportedTimeTravelDirectionsForComplication()</code> to indicate if your complication supports values into the future or the past. For example, a stocks complication would only make sense to show values in the past, while a weather complication could show values in the past and the future.</p>

  
    <pre><code class="language-swift">    func getSupportedTimeTravelDirectionsForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTimeTravelDirections) -> Void) {
        handler([.Backward, .Forward])
    }</code></pre>
  

<p>Next, implement <code>getTimelineEntriesForComplication(complication:beforeDate:limit:withHandler:)</code> and <code>getTimelineEntriesForComplication(complication:afterDate:limit:withHandler:)</code> which is similar to <code>getCurrentTimelineEntryForComplication()</code> except that you pass in an array of <code>CLKComplicationTimelineEntry</code> objects. Don't return more objects than limit, and make sure your dates are in sequential order and all occur before or after the passed in dates.</p>

<p>Finally, implement <code>getTimelineStartDateForComplication()</code> and <code>getTimelineEndDateForComplication()</code> to indicate the ranges of time travel you support.</p>

<h2 id="wrappingup">Wrapping Up</h2>

<p>You can check out the completed project code on our <a href="https://github.com/sneakycrab/cowmplication">Sneaky Crab GitHub</a> so you can quickly get going.</p>

<p>Apple has provided a very simple and powerful API to convey simple, glance-able information right on the clock face for app developers.</p>

<p>Tell us about the cool complications that you come up with in the comment section below! </p>
<h1 id="update62215watchos2beta2bugs">Update 6/22/15: watchOS 2 beta 2 bugs</h1>

<p>If you're having trouble with Complications when using beta 2, be aware of the known issues and workarounds from the <a href="https://developer.apple.com/library/prerelease/watchos/releasenotes/General/RN-watchOSSDK-2.0/index.html">watchOS 2 Release Notes</a> from Apple.</p>

<h2 id="knownissues">Known Issues</h2>

<ul>
<li><p>Complications are disabled across launches of Simulator. <br>
<strong>Workaround</strong>: After enabling a complication in the Watch Simulator you need to lock the watch sim, using Sim Menu &gt; Hardware &gt; Lock (or Command-L), to have the complication still be enabled after quitting Simulator and relaunching.</p></li>
<li><p>Location request dialog text is jumbled in Simulator.</p></li>
<li><code>CLKImageProvider</code> objects do not currently honor the <code>foregroundImage</code> property.</li>
<li>The <code>CLKComplicationRingStyle</code> property is currently not honored on any <code>CLKComplicationTemplate</code>.</li>
<li>The <code>CLKRelativeDateStyleOffset</code> enumeration of <code>CLKRelativeDateStyle</code> is not honored for use in <code>CLKRelativeDateTextProvider</code>: It appears as <code>CLKRelativeDateStyleNatural</code>.</li>
<li>The <code>CLKComplicationPrivacyBehavior</code> on <code>CLKComplicationDataSource</code> is not currently honored.</li>
</ul>]]></description></item><item><title>watchOS 2.0 tl;dr</title><dc:creator>Michael Gu</dc:creator><pubDate>Tue, 09 Jun 2015 01:43:27 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/6/8/watchos-20-tldr</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:5576291ce4b0ba7a8708fbb8</guid><description><![CDATA[<p>Apple announced a major update to watchOS 2 today that will be available with iOS 9 this fall. We've gone through the official Apple documentation, all of the headers and code provided in Xcode 7, and summarized everything for you so you can quickly skim what's coming up.</p>

<h1 id="thewatchkitextensionnowrunsontheapplewatch">The WatchKit Extension now runs on the Apple Watch</h1>

<p>In watchOS 1, the WatchKit extension ran as an extension of your iOS App. In watchOS 2, it runs natively on the Apple Watch. This is great because you get native performance without the slow back-and-forth communication over Bluetooth for every UI interaction.</p>

<p>There is a downside though–current WatchKit extensions enjoy the use of every iOS API, whereas the new watch OS 2 apps have access to a <a href="https://developer.apple.com/library/prerelease/watchos/documentation/General/Conceptual/AppleWatch2TransitionGuide/LeverageSystemTechnologies.html#//apple_ref/doc/uid/TP40015234-CH8-SW1">subset of APIs</a> available only to WatchKit. For example, if your extension depended on CloudKit, you can't use it directly on the Watch and will have to contact the parent iOS app if you want to continue using that functionality.</p>

<h1 id="watchconnectivityisforsynchronizingtheiphoneandwatch">Watch Connectivity is for synchronizing the iPhone and Watch</h1>

<p>Now that the watch app runs on the actual watch device, synchronizing data and state between the iPhone and watch is a little trickier. Apple has added <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchConnectivity/Reference/WatchConnectivity_framework/index.html#//apple_ref/doc/uid/TP40015269">Watch Connectivity</a> with the <code>WCSession</code> class to facilitate communication between the two devices. </p>

<h1 id="youbundlewatchos12appsinthesameapp">You bundle watchOS 1 &amp; 2 apps in the same app</h1>

<p>Apple will allow you to continue to bundle the old WatchKit extension code for watchOS 1 users in addition to shipping new code for watchOS 2. If you do this, sharing code might be tricky as different APIs are available between the two and it doesn't appear as though embedded frameworks will work on the watch.</p>

<h1 id="wkextensiondelegateistheappdelegateforwatchkit">WKExtensionDelegate is the AppDelegate for WatchKit</h1>

<p>One of the annoying things in watchOS 1 was how difficult it was to track app lifecycle changes. Now you simply implement <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKExtensionDelegate_protocol/index.html#//apple_ref/occ/intf/WKExtensionDelegate"><code>WKExtensionDelegate</code></a> similar to how you implement <code>AppDelegate</code> on iOS. There's a simplified lifecycle, with three states: Not Running, Inactive and Active.</p>

<h1 id="youcanaddcomplicationsforyourapptothewatchface">You can add Complications for your app to the watch face</h1>

<p>If you implement <a href="https://developer.apple.com/library/prerelease/watchos/documentation/ClockKit/Reference/CLKComplicationDataSource_protocol/index.html#//apple_ref/occ/intf/CLKComplicationDataSource"><code>CLKComplicationDataSource</code></a> from ClockKit, you can set up complication information. You provide a timeline, where you describe the various changes to your complication throughout the day, as well as an interval for when ClockKit should refresh your complication. There is also a high-priority push-notification that you can use meant for updating the Complication in real time.</p>

<p>Check out our guide on <a href="http://www.sneakycrab.com/blog/2015/6/10/writing-your-own-watchkit-complications">Writing a WatchKit Complication</a>.</p>

<h1 id="youcanplayvideoandaudiocontentonthewatch">You can play Video and Audio content on the watch </h1>

<p>For embedded video and audio in your UI, use <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKInterfaceMovie_class/index.html#//apple_ref/occ/cl/WKInterfaceMovie"><code>WKInterfaceMovie</code></a>. To present this in a new controller, use <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKInterfaceController_class/index.html#//apple_ref/occ/instm/WKInterfaceController/presentMediaPlayerControllerWithURL:options:completion:"><code>presentMediaPlayerControllerWithURL:options:completion:</code></a>. If you want to play extended content that continues playing even when your app is inactive, use <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKAudioFilePlayer_class/index.html#//apple_ref/occ/cl/WKAudioFilePlayer"><code>WKAudioFilePlayer</code></a> and set up the audio value in <code>UIBackgroundModes</code> in your <code>Info.plist</code>. </p>

<p>Audio plays directly on the watch, prioritizing the Bluetooth speaker, but will also use the built-in speaker if a Bluetooth speaker is not available.</p>

<h1 id="youcanrecordaudiowiththemicrophone">You can record audio with the microphone</h1>

<p>It seems you can only do it through Apple's stock audio recording controller, calling <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKInterfaceController_class/index.html#//apple_ref/occ/instm/WKInterfaceController/presentAudioRecordingControllerWithOutputURL:preset:maximumDuration:actionTitle:completion:"><code>presentAudioRecordingControllerWithOutputURL:preset:maximumDuration:actionTitle:completion:</code></a>. There doesn't seem to be a way to record audio and process realtime audio or record audio inline or using a custom controller or UI.</p>

<h1 id="youcanusethetapticenginetoprovidehapticfeedback">You can use the Taptic Engine to provide haptic feedback</h1>

<p><a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKInterfaceDevice_class/index.html#//apple_ref/doc/uid/TP40014996-CH1-SW16"><code>WKInterfaceDevice.playHaptic()</code></a> will provide haptic feedback. You can pick from one of the choices in the <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKInterfaceDevice_class/index.html#//apple_ref/c/tdef/WKHapticType"><code>WKHapticType</code> enum</a>. We didn't find a way in the documentation or header files to define custom haptic feedback.</p>

<p>Check out our guide on <a href="http://www.sneakycrab.com/blog/2015/6/22/haptic-feedback-with-the-taptic-engine-in-watchkit-and-watchos-2-wkinterfacedevice-and-wkhaptic">Writing Haptic feedback on the Taptic Engine</a>.</p>

<h1 id="digitalcrownaccess">Digital Crown Access</h1>

<p>The <a href="https://developer.apple.com/library/prerelease/watchos/documentation/WatchKit/Reference/WKInterfacePicker_class/index.html#//apple_ref/occ/cl/WKInterfacePicker"><code>WKInterfacePicker</code></a> handles digital crown access. You can have a List UI, a stack of cards that animate, or an image sequence that responds to the digital crown. We didn't find a way to read raw delta values from the crown in the current API, which may be useful for game developers. </p>

<p>Check out our guide on coding your own <a href="http://www.sneakycrab.com/blog/2015/6/12/wkinterfacepicker-in-watchkit-20-using-the-digital-crown"><code>WKInterfacePicker</code>: Using the Digital Crown</a>.</p>

<h1 id="accesstosensors">Access to Sensors</h1>

<p>Opening up Xcode 7, we were able to <code>import CoreMotion</code> and instantiate a <code>CMMotionManager</code> in a test WatchKit Extension project. This appears to work exactly like in iOS. We assume that heart rate and step data are also available through the HealthKit framework.</p>

<h1 id="healthkithomekitpasskitcorelocationcoregraphics">HealthKit, HomeKit, PassKit, CoreLocation, CoreGraphics</h1>

<p>In Xcode 7, we were able to import and instantiate objects from all of these libraries from within our WatchKit extension project, so we assume that these work just as they do from iOS. CoreAnimation did not appear to be available.</p>

<h1 id="xcodesimulator">Xcode Simulator</h1>

<p>The Xcode simulator now fully supports the watch. This means that you can test glances, notifications, watch complications and so on. You can also debug and run the watch and device simulators simultaneously which was not possible in watchOS 1 simulators.</p>

<p>In addition, the Hardware menu has added "Simulate Touch Pressure" with an option for Shallow Press and Deep Press. Scrolling your mouse-wheel simulates digital crown movement, and pressing ⌘⇧H simulates pressing the crown in.</p>

<h1 id="tobecontinued">To be continued...</h1>

<p>As WWDC unfolds, we'll update the information here with corrections and the latest information as Apple provides it. All in all, watchOS 2 is an extremely exciting improvement for developers, and we can't wait to update Chairman Cow to the new features, and stay tuned as we are determined to bring you world class watchOS 2 apps and games this fall!</p>

<p>Edit: 6/8/15 Added information about recording audio/microphone.</p>]]></description></item><item><title>Speeding up slow app install times when debugging on a real Apple Watch</title><dc:creator>Michael Gu</dc:creator><pubDate>Fri, 29 May 2015 07:58:47 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/5/28/speeding-up-slow-install-times-when-debugging-on-a-real-apple-watch</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:55680323e4b0618e504d1a7c</guid><description><![CDATA[<p>Fast Compile&nbsp;➡ Install&nbsp;➡ Run times are essential for developer productivity, especially when learning a new SDK. Given how new WatchKit is and the&nbsp;sparse detail of the&nbsp;official documentation, the only real way to really understand the call patterns and behaviors of WatchOS and WatchKit is to write lots of sample code and trace or debug it.</p><p>One of the most maddening things is how long it takes to install an app to a real Watch. This was particularly frustrating lately for two reasons:</p><ol><li>The simulator, while helpful, sometimes has different bugs or behaviors from the Watch itself.</li><li>Apple has not released an updated version of Xcode with a WatchOS 1.0.1 simulator, so testing new behaviors such as the change to <a href="http://www.sneakycrab.com/blog/2015/5/26/wkinterfacecontroller-lifecycle-in-watchos-101">WKInterfaceController didAppear() caching</a>&nbsp;requires testing on a real Watch.</li></ol><p>Let's take a journey to find the fastest way to get from building to having a debugger attached to the Watch app and save all of our sanity.</p><p>The most expensive part is installation, so we profiled modifying a few variables to see what made the biggest impact for install time of our app.&nbsp;We tested installing the app over Bluetooth and also forced app install over Wi-Fi, and also tried to see if having the Apple Watch App open, on the app detail and not asleep mattered. This was tested on Xcode 6.3.2, iOS 8.2 and WatchOS 1.0.1.</p>
























  
    
  <table>
  <tr>
    <th>Time</th>
  	<th>Wireless</th>
  	<th>Apple Watch App</th>
  </tr>
  <tr>
    <td>4:23</td>
    <td>Bluetooth</td>
    <td>Background</td>
  </tr>
  <tr>
    <td>3:37</td>
    <td>Bluetooth</td>
    <td>Background</td>
  </tr>
  <tr>
    <td>2:39</td>
    <td>Bluetooth</td>
    <td>Background</td>
  </tr>
  <tr>
    <td>2:00</td>
    <td>Bluetooth</td>
    <td>Background</td>
  </tr>
  <tr class="decent">
    <td>1:26</td>
    <td>Bluetooth</td>
    <td>Foreground</td>
  </tr>
  <tr class="decent">
    <td>1:12</td>
    <td>Bluetooth</td>
    <td>Foreground</td>
  </tr>
  <tr class="best">
    <td>1:06</td>
    <td>Wi-Fi</td>
    <td>Background</td>
  </tr>
  <tr class="best">
    <td>1:01</td>
    <td>Wi-Fi</td>
    <td>Foreground</td>
  </tr>
  <tr class="best">
    <td>1:00</td>
    <td>Wi-Fi</td>
    <td>Foreground</td>
  </tr>
  <tr class="best">
    <td>1:00</td>
    <td>Wi-Fi</td>
    <td>Background</td>
  </tr>
</table>

  












































  

    
  
    

      

      
        <figure class="
              sqs-block-image-figure
              intrinsic
            "
        >
          
        
        

        
          
            
              
              
          
            
                
                
                
                
                
                
                
                <img data-stretch="true" data-image="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg" data-image-dimensions="1242x2208" data-image-focal-point="0.5,0.5" alt="" data-load="false" elementtiming="system-image-block" src="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=1000w" width="1242" height="2208" sizes="(max-width: 640px) 100vw, (max-width: 767px) 100vw, 100vw" onload="this.classList.add(&quot;loaded&quot;)" srcset="https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=100w 100w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=300w 300w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=500w 500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=750w 750w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=1000w 1000w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=1500w 1500w, https://images.squarespace-cdn.com/content/v1/552351f8e4b0ea389c393627/1432883171895-SHQTS2TF4OH830OLBP39/image-asset.jpeg?format=2500w 2500w" loading="lazy" decoding="async" data-loader="sqs">

            
          
        
            
          
        

        
          
          <figcaption class="image-caption-wrapper">
            <p>Bluetooth installs were faster if the Apple Watch App was in the foreground.</p>
          </figcaption>
        
      
        </figure>
      

    
  


  





  <p>From our admittedly unscientific tests, we can draw two conclusions:</p><ol><li>Bluetooth installs seem to be significantly faster if the Apple Watch App is in the foreground</li><li>Wi-Fi was fast in every test.</li></ol><p>Due to the consistency of the Wi-Fi approach, I'll focus on that here.</p><h2>Setup</h2><p>Make sure that your Apple Watch is set up to use Wi-Fi.</p><ul><li>The Apple Watch can only use 2.4 Ghz Wi-Fi networks, so if your Wi-Fi is 5 Ghz and you can't enable 2.4 Ghz, you're out of luck.</li><li>The phone will automatically share Wi-Fi credentials with your watch.</li><li>You can test if it is working by turning off Bluetooth on your phone, and seeing if you can still use Siri or make a phone call with your watch.</li></ul><p>Make sure your Apple Watch is paired with your iPhone, and it's identifier has been added to your Devices section in Certificates, Identifiers &amp; Profiles in developer.apple.com.</p><h2>Fastest Build -&gt; Install -&gt; Run -&gt; Debug Cycle</h2><ol><li><strong>Turn off Bluetooth on your phone.</strong> The fastest way is to swipe up control center and toggle it there. You'll get a dialog nagging you to turn it back on<span>–</span>hit cancel. We do this first to make sure that the system doesn't accidentally start installing the app over Bluetooth. If you turn off Bluetooth after it began installing over Bluetooth, we've found it hard to convince the phone to continue installing over Wi-Fi. It seems to prefer the network type it began the install.</li><li><strong>Hit stop in Xcode until it greys out.</strong>&nbsp;We've had lots of problems with Xcode attached to different old&nbsp;processes that cause problems later on. We recommend ensuring you're in a clean state.</li><li><strong>Choose the iOS base scheme in Xcode, and your device</strong>. If you build and run the WatchKit Extension scheme here, Xcode will time out trying to attach while you're waiting for it to install on the Watch which is more dialogs for you to click on.</li><li><strong>Click the Xcode play button to begin build and installation to your phone.</strong></li><li><strong>When the iOS App starts running, multitask back to&nbsp;the Apple Watch App, open the page for your App.&nbsp;</strong>While this doesn't seem to speed up Wi-Fi installs, the system seems to notice faster that it needs to install an updated watch app than when you aren't on this screen. Obviously, make sure Show App on Apple Watch is enabled.</li><li><strong>Your app should install to device in about 1 minute.</strong></li><li><strong>Turn on Bluetooth.</strong> You can't launch or debug your app over Wi-Fi, so this must be done. Again, swiping up control center is the fastest way to toggle Bluetooth.</li><li><strong>Back in Xcode, press </strong><strong>⌘6 to open the Debug Navigator</strong>&nbsp;<strong>&nbsp;</strong></li><li><strong>Hit stop again, switch to the WatchKit Extension&nbsp;Scheme in Xcode.</strong> Now we're going to attach debugging.</li><li><strong>Hit play in Xcode, when it is ready, you'll see in Debug Navigator show the extension as&nbsp;"Waiting to Attach".</strong></li><li><strong>Manually open the app on your watch, and you'll see Xcode magically attach to the process.</strong></li></ol><p>Phew. That's a lot of steps. But we got pretty fast at it while we were trying to debug issues in our Chairman Cow game with WatchOS 1.0.1, and it's better than waiting 4 minutes per build for the app to install.</p><p>This is the best method we found. What are your tips and tricks for efficient Build ➡&nbsp;Run ➡&nbsp;Debug on a real watch? Please tell us if you have found a better way!</p>]]></description></item><item><title>Dealing With WKInterfaceController Life Cycle Changes in WatchOS 1.0.1</title><dc:creator>Michael Gu</dc:creator><pubDate>Tue, 26 May 2015 10:45:17 +0000</pubDate><link>https://www.sneakycrab.com/blog/2015/5/26/wkinterfacecontroller-lifecycle-in-watchos-101</link><guid isPermaLink="false">552351f8e4b0ea389c393627:5564269ee4b0c0ea1b06cf5e:556426bae4b0cc18bc6e9db1</guid><description><![CDATA[<p>In an effort to reduce loading times for 3rd party watch apps, for a page-based interface, Apple has changed the calling pattern for <code>willActivate()</code> and <code>didDeactivate()</code>. In essence, WatchOS will more aggressively call <code>willActivate()</code> on your <code>WKInterfaceController</code> even if it is not visible to cache the view so it can be displayed immediately when you swipe over to it.</p>

<p>While this is effective in reducing loading spinners that users will see, it can cause bugs based on some common assumptions that developers might make.</p>

<p>Let's first take a moment to understand deeply the change in the calling pattern. </p>

<p>In WatchOS 1.0, Apple would call <code>willActivate()</code> only just before the page is visible, and <code>didActivate()</code> only just as the page is losing visibility. This pattern could occur if the watch is going to sleep, or transitions between interface controllers.</p>

<p>Here's a trace of a page-based interface with 5 interface controllers.</p>

<h2 id="watchos10lifecycle">WatchOS 1.0 Life Cycle</h2>

  
    <pre class="source-code">00:50:07.583 page0: init()
00:50:07.585 page0: awakeWithContext()
00:50:07.780 page1: init()
00:50:07.780 page1: awakeWithContext()
00:50:07.994 page2: init()
00:50:07.995 page2: awakeWithContext()
00:50:08.190 page3: init()
00:50:08.190 page3: awakeWithContext()
00:50:08.317 page4: init()
00:50:08.317 page4: awakeWithContext()
00:50:08.514 page0: willActivate()

Swipe to next page (page1)

00:50:16.527 page1: willActivate()
00:50:16.721 page0: didDeactivate()

Swipe to next page (page2)

00:50:22.713 page2: willActivate()
00:50:22.909 page1: didDeactivate()</pre>
  

<p>Pretty straightforward. It will call <code>init()</code> ➡  <code>awakeWithContext()</code> on all pages, activate the first page (page0). On a swipe, it will first activate the next page, then deactivate the old page.</p>

<p>Let's look at a trace from WatchOS 1.0.1.</p>

<h2 id="watchos101lifecycle">WatchOS 1.0.1 Life Cycle</h2>

  
    <pre class="source-code">23:55:18.750 page0: init()
23:55:18.753 page0: awakeWithContext()
23:55:18.990 page1: init()
23:55:18.991 page1: awakeWithContext()
23:55:19.289 page2: init()
23:55:19.290 page2: awakeWithContext()
23:55:19.748 page3: init()
23:55:19.749 page3: awakeWithContext()
23:55:19.764 page4: init()
23:55:19.765 page4: awakeWithContext()
23:55:19.768 page0: willActivate()
23:55:22.751 page1: willActivate()
23:55:23.291 page1: didDeactivate()

Swipe to next page (page1)

23:55:43.953 page1: willActivate()
23:55:44.040 page0: didDeactivate()
23:55:44.853 page2: willActivate()
23:55:45.633 page2: didDeactivate()

Swipe to next page (page2)

23:56:08.436 page2: willActivate()
23:56:08.552 page1: didDeactivate()
23:56:09.397 page3: willActivate()
23:56:10.115 page3: didDeactivate()</pre>
  

<p>We can see that the <code>init()</code> ➡ <code>awakeWithContext()</code> call pattern is the same, but as soon as page0 is activated, it immediately activates, then deactivates page1.</p>

<h1 id="commonissues">Common Issues</h1>

<p>While Apple's caching works well for populating the contents of the UI, but most issues revolve around doing any non-view configuration work in <code>willActivate()</code>. In particular, detecting visibility state change of the <code>WKInterfaceController</code>.</p>

<p>Here's a few scenarios that are more difficult in WatchOS 1.0.1:</p>

<p>Asynchronous updates kicked off by <code>willActivate()</code>. For example, <code>willActivate()</code> kicks off some network calls to populate data, and when it returns, <code>didDeactivate()</code> has already been called, so the updates will fail. </p>

<p>Knowing which interface controller is visible. For example, your <code>MMWormHole</code> is reporting a change on your iOS app, and you'd like to call a function on only the visible interface controller to refresh UI.</p>

<h1 id="workaroundssolutions">Workarounds/Solutions</h1>

<h2 id="asyncupdatessolutiontrackvisibilitywithinthewkinterfacecontroller">Async Updates Solution: Track Visibility Within the WKInterfaceController</h2>

<p>If we want to trigger event on first visibility, it is possible to track active state within the controller itself, and save any UI updates for when the controller is truly active.</p>

  
    <pre><code class="language-swift">class MyInterfaceController: WKInterfaceController {
    var isActive: Bool = false
    var pendingContext: AnyObject? = nil
    
    override func willActivate() {
        super.willActivate()

        isActive = true
        if let context: AnyObject = pendingContext {
            updateUI(context)
            pendingContext = nil
        } else {
            beginAsyncCallWithCallback(callback)
        }
    }
    
    override func didDeactivate() {
        super.didDeactivate()
        isActive = false
    }
    
    func callback(context: AnyObject) {
        if isActive {
            updateUI(context)
        } else {
            pendingContext = context
        }
    }
}</code></pre>
  

<h2 id="visiblecontrollersolution1assumethatvisibleisalwaysactivatedfirst">Visible Controller Solution 1: Assume that visible is always activated first.</h2>

<p>In the calling patterns of 1.0 and 1.0.1, the visible controller always gets <code>willActivate()</code> called first. If we assume that this will always hold true, we can definitively know which controller is visible.</p>

<p>Each time <code>willActivate()</code> is called, track it in an activeControllers dictionary along with the timestamp. Remove it when <code>didDeactivate()</code> gets called. When we want to know which controller is the visible one, return the controller with the smallest timestamp.</p>

  
    <pre><code class="language-swift">class RefreshableInterfaceController: WKInterfaceController {
    override func willActivate() {
        super.willActivate()
        // Do NOT call ControllerTracker.sharedInstance.visibleController() here
        // The previous page will not have called didDeactivate yet. 
        // Make sure to delay that call.
        ControllerTracker.sharedInstance.addActiveController(self)
    }
    
    override func didDeactivate() {
        super.didDeactivate()

        ControllerTracker.sharedInstance.removeActiveController(self)
    }
    
    func refreshUI() {
        // Refresh UI
    }
}

class ControllerTracker: NSObject {
    var activeControllers: [RefreshableInterfaceController : CFAbsoluteTime] = [:]
  
    func addActiveController(controller: RefreshableInterfaceController) {
        activeControllers[controller] = CFAbsoluteTimeGetCurrent()
    }
    
    func removeActiveController(controller: RefreshableInterfaceController) {
        activeControllers.removeValueForKey(controller)
    }
    
    func visibleController() -> RefreshableInterfaceController? {
        var earliestController: RefreshableInterfaceController? = nil
        var earliestTime: CFAbsoluteTime? = nil
        for (controller, timestamp) in activeControllers {
            var foundBetter = false
            if let time = earliestTime {
                if time > timestamp {
                    foundBetter = true
                }
            } else {
                foundBetter = true
            }
            if foundBetter {
                earliestController = controller
                earliestTime = timestamp
            }
        }
        return earliestController
    }
    
    class var sharedInstance: ControllerTracker {
        struct Singleton {
            static let instance = ControllerTracker()
        }
        return Singleton.instance
    }
    
    func resetControllers() {
        activeControllers.removeAll(keepCapacity: false)
        WKInterfaceController.reloadRootControllersWithNames(initialControllers, contexts: contexts)
    }

    func refreshVisibleController() {
        visibleController()?.refreshUI()
    }
}</code></pre>
  

<p>With this strategy, it's extremely important to clear the activeControllers before you call <code>reloadRootControllersWithNames</code> or you might orphan a controller that you need. See <code>resetControllers()</code> for example.</p>

<p>The flaw with this strategy is in the assumption, that Apple will always create the visible controller first before caching the next one. The API documentation makes no guarantees on call order, so this solution is definitely hacky. It's a "safe hack" though, since logically Apple would likely want to start loading the first visible controller before trying to cache others. There are lots of reasons, including threading races, as to why this assumption could be broken in the future.  </p>

<h2 id="visiblecontrollersolution2polluntilonlyonecontrolleractive">Visible Controller Solution 2: Poll until only one controller active.</h2>

<p>If we dislike the above hack, another option is to wait until our <code>activeControllers</code> dictionary resolves down to exactly one element. It works the same as the Solution 1 code snippet with just <code>visibleController()</code> and <code>refreshVisibleController()</code> changed.</p>

  
    <pre><code class="language-swift">class ControllerTracker: NSObject {
    func visibleController() -> RefreshableInterfaceController? {
        if activeControllers.count != 1 {
            return nil
        }
        return activeControllers.values[0]
    }
  
    func refreshVisibleController() {
        if let controller = visibleController() {
            controller.refreshUI()
        } else {
            NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "refreshVisibleController", userInfo: nil, repeats: false)
        }
    }
}</code></pre>
  

<p>The problem with this strategy is that polling causes additional lags and is generally messy control flow every time you want to take an action on the visible controller. The above solution also requires additional code to be production ready. This code will poll forever if <code>activeControllers</code> is empty. (e.g. the watch went to sleep)</p>

<h1 id="finalthoughts">Final Thoughts</h1>

<p>There is currently no way to detect if the watch is running WatchOS 1.0 or 1.0.1, so the code needs to be written in such a way that it works on all OS versions. It appears that Apple's intention is that <code>willActivate()</code> and <code>didDeactivate()</code> are intended just for setting up and tearing down views and no additional side effects should be occurring in the watch app. This is great in theory, but unfortunately there are no convenient method to actually know the difference between interface controller setup and the interface controller actually appearing. </p>

<p>The flow is really nice in iOS: <code>init ➡ viewDidLoad ➡ viewWillAppear ➡ viewDidAppear and viewWillDisappear ➡ viewDidDisappear ➡ viewDidUnload ➡ dealloc</code>. On the watch, perhaps this is overkill, and I can see them wanting to reduce communication over Bluetooth by reducing the number of events going over the air.</p>

<p>However the current state of the WatchKit API leads developers to a lot of hacks and workarounds when writing non-trivial code. Even if the watch UI is simple and clean, the underlying logic is sometimes necessarily complex to bring data from other sources cached and ready to go just-in-time. </p>

<p>Let's hope that improvements come in future updates.</p>]]></description></item></channel></rss>