Showing posts with label iOS. Show all posts
Showing posts with label iOS. Show all posts

Tuesday, April 19, 2016

Watch tutorial 6: Watch Connectivity - Application Context

This post is part of a set of short tutorials on Watch. If you want to see the previous post. In this tutorial, you're going to see how you can communicate between your Watch and your iOS app using Application Context.

Get starter project

In case you missed Watch tutorial 5: Watch Connectivity - Direct Message, here are the instructions how to get the starter project. Clone and get the initial project by running:
git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step5
open DoItCoach.xcodeproj

Send Application context from Phone

In DoItCoach/DetailedTaskViewController.swift, search for the method timerStarted(_:), and add one line of code in [1]:
func sendTaskToAppleWatch(task: TaskActivity) {
  if WCSession.defaultSession().paired && delegate.session.watchAppInstalled {    // [1]
    try! delegate.session.updateApplicationContext(["task": task.toDictionary()]) // [2]
  }
}
[1]: Before sending to the Watch, as a best practice, check is the Watch is paired and the app in installed on the Watch. No need to do a context update when it's doomed to failure.
[2]: You send the Context update. Here your don't try catch, but you could do it and display the error.

You need to import WatchConnectivity to make Xcode happy.
Still in DoItCoach/DetailedTaskViewController.swift call sendTaskToAppleWatch(_:) in timerStarted(_:) as done in [1] (Note all the rest of the method is unchanged):
@objc public func timerStarted(note: NSNotification) {
  if let userInfo = note.object, 
     let taskFromNotification = userInfo["task"] as? TaskActivity 
     where taskFromNotification.name == self.task.name {
     if let sender = userInfo["sender"] as? String 
         where sender == "ios" {
         task.start()
         sendTaskToAppleWatch(task) // [1]
     }
     saveTasks()
     self.startButton.setTitle("Stop", forState: .Normal)
     self.startButton.setTitle("Stop", forState: .Selected)
     self.circleView.animateCircle(0, color: taskFromNotification.type.color, 
                                   duration: taskFromNotification.duration)
  }
  print("iOS app::TimerStarted::note::\(note)")
}

Receive Message in Watch app

In DoItCoach WatchKit Extension/ExtensionDelegate.swift, at the end of the class definition, add the following extension declaration:
// MARK: WCSessionDelegate
extension ExtensionDelegate: WCSessionDelegate {
  func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
    if let task = applicationContext["task"] as? [String : AnyObject] { // [1]
      if let name = task["name"] as? String,
         let startDate = task["startDate"] as? Double {
         let tasksFound = TasksManager.instance.tasks?.filter{$0.name == name} // [2]
         let task: TaskActivity?
         if let tasksFound = tasksFound where tasksFound.count > 0 {
           task = tasksFound[0] as TaskActivity
           task?.startDate = NSDate(timeIntervalSinceReferenceDate: startDate)  // [3]
           dispatch_async(dispatch_get_main_queue()) {  // [4]
             NSNotificationCenter.defaultCenter().postNotificationName("CurrentTaskStarted", 
                                                                       object: ["task":task!])
           }
         }
       }
     }
  }
}
[1]: You get the dictionary definition of the task that was started on the iPhone.
[2]: You find its matching Task object in the list of tasks in the Watch.
[3]: You assign the startDate defined on the iOS app.
[4]: You make sure you go to UI thread to send a notification for the Watch to refresh its display.

Refreshing Watch display

In DoItCoach WatchKit Extension/InterfaceController.swift in awakeWithContext(_:), add one line of code [1] to register to the event CurrentTaskStarted:
override func awakeWithContext(context: AnyObject?) {
  super.awakeWithContext(context)
  NSNotificationCenter.defaultCenter()  // [1]
                      .addObserver(self, 
                                   selector: #selector(InterfaceController.taskStarted(_:)), 
                                   name: "CurrentTaskStarted", 
                                   object: nil)
  display(TasksManager.instance.currentTask)
}
Still in DoItCoach WatchKit Extension/InterfaceController.swift implement the following methods to respond to the NSNotificationCenter event:
func taskStarted(note: NSNotification) { 
  if let userInfo = note.object,  
     let taskFromNotification = userInfo["task"] as? TaskActivity,
     let current = TasksManager.instance.currentTask
     where taskFromNotification.name == current.name { 
    replayAnimation(taskFromNotification)           // [1]
  }
}
    
func replayAnimation(task: TaskActivity) {
  if let startDate = task.startDate  {
    let timeElapsed = NSDate().timeIntervalSinceDate(startDate) 
    let diff = timeElapsed < 0 ? abs(timeElapsed) : timeElapsed
    let imageRangeRemaining = (diff)*90/task.duration   // [2]
    self.group.setBackgroundImageNamed("Time")
    self.group.startAnimatingWithImagesInRange(NSMakeRange(Int(imageRangeRemaining), 90), 
               duration: task.duration - diff, repeatCount: 1) // [3]
  }
}
[1]: For the current task, replay the animation.
[2]: Calculate how much is images is already started. You will have a short delay since the task was started in the iPhone and you received it on the Watch.
[3]: As you've seen in Tutorial3: Animation, launch the animation.

Build and Run

You can now start a task from your phone. The careful reader that you are, will notice that once a task started from the phone is completed, it is not refreshed on the Watch app. That brings us to the next section, let's talk about your challenges.

Challenges left to do

Your mission, should you choose to accept it is:
  • make the task list refreshed on the Watch when a task started from your phone get completed
  • remove the bootstrap code in TaskManager.swift. All tasks should be persisted to the iPhone (all the persistence code is already written for you in Task.swift). When the iPhone app launch send the list of tasks to Watch. Whenever a task is added on the phone, send the list of tasks to the watch.
  • make the animation carries on where it should be when the Watch app go background and foreground again.

Get final project

If you want to check the final project, here are the instructions how to get it.
cd DoItCoach
git checkout step6
open DoItCoach.xcodeproj
Or if you want to get the final project with all the challenges implemented:
cd DoItCoach
git checkout master
open DoItCoach.xcodeproj

What's next?

With this tutorial, you saw how you can send update application context messages from your Watch to your phone. Since you know how to communicate between your app and your watch, you're all ready to make great apps!

Watch tutorial 5: Watch Connectivity - Direct Message

This post is part of a set of short tutorials on Watch. If you want to see the previous post. In this tutorial, you're going to see how you can communicate between your Watch and your iOS app.

How does my Watch talk to my phone, and vice versa?

WatchConnectivity framework provides different options for implementing a bi-directional communication between WatchKit and iOS apps.
  • Application Context Mode: allows exchange of data serialized in a dictionary object from one app and another. The transfer is done in the background. The messages are queued and delivered to the receiving app via a delegate method. One specificity of Application Context mode is that only the latest update is sent (ie: older data is overwritten by the new data). This is perfect if the receiving app only need the latest state.
  • User Information transfer mode is similar to application context mode. It is also a background mode, message get queued and unlike application context all messages will be sent once the destination app is available.
  • Interactive messaging mode sends messages (serialized in dictionary) immediately to the receiving app. The receiving app is notified of the message arrival via a delegate method call.
Whether you send a message from your iOS app or from your Watch app, the method to call is the same on both devices. Similarly when you receive a remote call the delegate method to use is the same. You'll get the "déjà vu" feeling when developing with WatchConnectivity especially for bi-directional messages.

Although, there is a symmetry of usage of WatchConnectivity framework, choosing which option to use (queued messages vs direct messages) really depends on your use case and where do you send it from. Time to dig into the nitty-gritty of Direct Messages.

Get starter project

In case you missed Watch tutorial 4: Animation, here are the instructions how to get the starter project. Clone and get the initial project by running:
git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step4
open DoItCoach.xcodeproj

The Use Case

Let's start using WatchConnectivity with this use case in mind. You want to start the task on your Watch and be able to see it as started on your iPhone. From Apple Documentation:

Calling this method from your WatchKit extension while it is active and running wakes up the corresponding iOS app in the background and makes it reachable. Calling this method from your iOS app does not wake up the corresponding WatchKit extension. If you call this method and the counterpart is unreachable (or becomes unreachable before the message is delivered), the errorHandler block is executed with an appropriate error. The errorHandler block may also be called if the message parameter contains non property list data types.

If I call sendMessage(_:replyHandler:) from my Watch, it has the ability to wake-up my iOS app. How cool!

I prefer to use Direct Messages for actions from the watch -> iPhone. The other way around iPhone -> Watch is less useful, as the chances that your Watch app is active when you send DM from your phone is slim.

Direct Message from the Watch to your Phone are useful to say "hi phone, go fetch me these resources", but bear in mind, they are not queued, so if your phone is switch off or out of range, they fail and will not be delivered.

As a rule of thumb, when synchronizing data use Application Context or UserInfo. We could have used ApplicationContext, but we'll design DoITCoach Watch App to be a companion app of the watch. All the states are persisted on the Phone.

Initialise WCSession

WCSession.defaultSession() returns a singleton object. You still need to define which object will handle delegate methods and activate the session.

Where?

For the Watch app, the best place to do it is ExtensionDelegate.swift as this is the place where the life cycle of the app takes place.

How?

In DoItCoach WatchKit App Extension/ExtensionDelegate.swift, add the import:
import WatchConnectivity
var session : WCSession!
func applicationDidFinishLaunching() {
  // Perform any final initialization of your application.
  if (WCSession.isSupported()) {
    session = WCSession.defaultSession()
    session.delegate = self
    session.activateSession()
  }
}
The compiler is now complaining because your class has to implement WCSessionDelegate. In DoItCoach WatchKit App Extension/ExtensionDelegate.swift afte3r the calss definition, add the extension declaration:

extension ExtensionDelegate: WCSessionDelegate {}

Send DM from Watch to Phone

In DoItCoach Watch Extension/InterfaceController.swift add the method to do the send:
func sendToPhone(task: TaskActivity) {
  let applicationData = ["task": task.toDictionary()]
  if session.reachable { // [1]
    session.sendMessage(applicationData, replyHandler: {(dict: [String : AnyObject]) -> Void in
      // handle reply from iPhone app here
      print("iOS APP KNOWS Watch \(dict)")
    }, errorHandler: {(error) -> Void in
      // catch any errors here
      print("OOPs... Watch \(error)")
    })
  } 
}
At the beginning of the file add an import WatchConnectivity.

[1]: As a best practice, you can check the app on iOS device is reachable so you don't waste a call.

Still in DoItCoach Watch Extension/InterfaceController.swift call sendToPhone(_:) in onStartButton() as done in [1] (Note all the onStartButton is unchanged):
@IBAction func onStartButton() {
  guard let currentTask = TasksManager.instance.currentTask else {return} 
  if !currentTask.isStarted() { 
    let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
    timer.setDate(duration)
    // Timer fired
    NSTimer.scheduledTimerWithTimeInterval(currentTask.duration,
                                           target: self,
                                           selector: #selector(NSTimer.fire),
                                           userInfo: nil,
                                           repeats: false) 
    timer.start() 
    // Animate
    group.setBackgroundImageNamed("Time")
    group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentTask.duration, repeatCount: 1)
    currentTask.start()
    startButtonImage.setHidden(true) 
    timer.setHidden(false) 
    taskNameLabel.setText(currentTask.name)
    sendToPhone(currentTask) // [1]
  }
}
You also need to send a message to your phone once the task is finished in [1]:
func fire() {
  timer.stop()
  startButtonImage.setHidden(false)
  timer.setHidden(true)
  guard let current = tasksMgr.currentTask else {return}
  print("FIRE: \(current.name)")
  current.stop()
  group.stopAnimating()
  // init for next
  group.setBackgroundImageNamed("Time0")
  display(tasksMgr.currentTask)
  sendToPhone(current) // [1]
}

Receive Message in iOS app


Where?

For the iOS app, the best place to do it is AppDelegate.swift as this is the place where the life cycle of the app takes place. You want to be able to receive direct message even when your iOS app is not started.

How?

var session : WCSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  if (WCSession.isSupported()) {
    session = WCSession.defaultSession()
    session.delegate = self
    session.activateSession()
  }
  return true
}
Don't forget to import WatchConnectivity.
Déjà vu feeling?
;)

Delegate implementation

// MARK: WCSessionDelegate
extension AppDelegate: WCSessionDelegate {
  func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
    print("RECEIVED ON IOS: \(message)")
    dispatch_async(dispatch_get_main_queue()) { // [1]
      if let taskMessage = message["task"] as? [String : AnyObject] {
        if let taskName = taskMessage["name"] as? String {
          let tasksFiltered = TasksManager.instance.tasks?.filter {$0.name == taskName}
          guard let tasks = tasksFiltered else {return}
          let task = tasks[0]                   // [2]
          if task.isStarted() {
            replyHandler(["taskId": task.name, "status": "already started"])
            return
          }
          if task.endDate != nil {
            replyHandler(["taskId": task.name, "status": "already finished"])
            return
          }
          if let endDate = taskMessage["endDate"] as? Double {
            task.endDate = NSDate(timeIntervalSinceReferenceDate: endDate)
            replyHandler(["taskId": task.name, "status": "finished ok"])
            NSNotificationCenter.defaultCenter().postNotificationName("TimerFired", // [3]
                                                 object: ["task":self])
          } else if let startDate = taskMessage["startDate"] as? Double {
              task.startDate = NSDate(timeIntervalSinceReferenceDate: startDate)
              replyHandler(["taskId": task.name, "status": "started ok"])
          }
          saveTasks()    // [4]
        }
      }
    }
  }
}
[1]: You need to dispatch to main thread as eventually we want to refresh the UITableView in the UI queue.
[2]: You get the task name from the dictionary. You find the matching task in iOS app (task name is used as an identifier).
[3]: You set either startDate or endDate on the task itself. When you end the task, you need to issue an event so that UITableView get refreshed.
[4]: You save all tasks.

Get final project

If you want to check the final project, here are the instructions how to get it.
cd DoItCoach
git checkout step5
open DoItCoach.xcodeproj


Build and Run

Before launching the app, delete any previous version of DoItCoach on your Phone.




What's next?

With this tutorial, you saw how you can send direct messages from your phone to your watch. As you've seen, you can do a lot with direct message: wake up an iOS app but there are still cases where your message won't reach your phone. When it comes to synchronise states between AppleWatch and its iPhone companion app, Application Context or User Info transfer mode are much more suitable. See Watch tutorial 6: Watch Connectivity (Application Context) to learn more.

Watch tutorial 4: Animation

This post is part of a set of short tutorials on Watch. If you want to see the previous post. In this tutorial, you're going to create ring animation that looks like the one in the Activity app.



The different types of animations

Like we've seen with Layout, animations on the AppleWatch are very simple. There are two main types of animations in WatchKit: property animations and animated images.
  • Properties animation is limited to some properties of of UI elements like: width/height, alpha, background color, inset.
  • Animated images is simply a set of images. When you run them quickly, your eyes see them as animated: the basic of cartoon animation :)

Build your images

When I first started on AppleWatch, I searched for this cool ring (that is used in Activity app) in the UI control list without success. It is not part of the start ui controls. You can do such animation but you've got to build your own imagines.

After googling, I found this interesting project: RadialChartImageGenerator which also comes with some online tooling available to generate the images. Ah the joy of open source! Let's use the tool to generate our images.

  • Go to RadialChartImageGenerator online tool
  • Select the single Arc
  • For Current and Max value select 90
  • Select the color that match task color. You can start with dark blue and go clearer. (See image below for color reference)
  • At the bottom, untick Show Text and Subtext. You only need a empty ring set of images. As you deal with timer programmatically.
  • Hit Generate Images button, the images are downloaded in you download folder


Get starter project

In case you missed Watch tutorial 3: Layout, here are the instructions how to get the starter project. Clone and get the initial project by running:
git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step3
open DoItCoach.xcodeproj

Add images to Xcode project

From the previous tutorial, you should have the animation images already populated in HOME_DIR/DoItCoach/DoItCoach WatchKit App/Assets.xcassets folder.

Optionally if you want to add your own ring generated images:
  • Create a new folder in Asserts.xcassets, name it TimeSpent,
  • copy your images here. You should get something like this.

Animate your images

You can animate all object that conform to WKImageAnimatable.
You are going to animate the background image of the main Group.

In DoItCoach WachKit Extension, go to InterfaceController.swift, go to the onStartButton and add the following 2 lines of code:
group.setBackgroundImageNamed("Time")
group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentActivity.duration, repeatCount: 1)
The final method should look like:
@IBAction func onStartButton() {
  guard let currentTask = TasksManager.instance.currentTask else {return}
  if !currentTask.isStarted() {
    let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
    timer.setDate(duration)
    timer.start()
    currentTask.start()
    group.setBackgroundImageNamed("Time")
    group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentTask.duration, repeatCount: 1)
    startButtonImage.setHidden(true)
    timer.setHidden(false)
    taskNameLabel.setText(currentTask.name)
  }
}
Well done! You get your timer animation done!

Go to next task

Once the timer is fired, your Watch should display the next task on the list.

The UI Timer object does not provide a fire method. So to trigger an event once the the task is done, you have to add a NSTimer object.
@IBAction func onStartButton() {
   guard let currentTask = TasksManager.instance.currentTask else {return} 
   if !currentTask.isStarted() { 
      let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
      timer.setDate(duration)
      // Timer to fire event
      NSTimer.scheduledTimerWithTimeInterval(currentTask.duration,
                                             target: self,
                                             selector: #selector(NSTimer.fire),
                                             userInfo: nil,
                                             repeats: false) // [2]
      timer.start() 
      // Animate
      group.setBackgroundImageNamed("Time")
      group.startAnimatingWithImagesInRange(NSMakeRange(0, 90), duration: currentTask.duration, repeatCount: 1)
      currentTask.start()
      startButtonImage.setHidden(true) 
      timer.setHidden(false) 
      taskNameLabel.setText(currentTask.name)
   }
}
func fire() {  // [2]
  timer.stop()
  startButtonImage.setHidden(false)
  timer.setHidden(true)
  guard let current = TasksManager.instance.currentTask else {return}
  current.stop()
  group.stopAnimating()
  display(TasksManager.instance.currentTask)
}

func display(task: Task?) {
  guard let task = task else {
    taskNameLabel.setText("NOTHING TO DO :)")
    timer.setHidden(true)
    startButtonImage.setHidden(true)
    return
  }
  group.setBackgroundImageNamed("Time0") // [3]
  taskNameLabel.setText(task.name)
}
  • [1]: start an timer that is used to refresh UI display. Note the the Timer UI component can not be associated to a fires method.
  • [2]: in the fire method, you need to display start button, hide timer info, stop the animation and display the next task.
  • [3]: to initialise the Group background image to the initial image
Hooray, your timer starts, animate and go to the next task! Victory.

Animation and Watch App life cycle

For the need of testing DoItCoach, we made the timer duration to 10 seconds. In real life, the timer would be of 25 mins. Your AppleWatch won't say in foreground the whole duration of the task. You need to take care of replaying the animation in willActivate() in InterfaceController.swift. You will have to calculate the remaining time and make it match the image number for your animation. This is your challenge!

Get final project

If you want to check the final project, here are the instructions how to get it.
cd DoItCoach
git checkout step4
open DoItCoach.xcodeproj

What's next?

With this first introduction tutorial, you saw how you can do animation that look like the Activity app on your AppleWatch app. In the iOS app we can also see task in progress by selection the task in the table view. what about if the task has been started with the watch. wouldn't be nice to see it running on both AppleWatch and iOs app?

This is time to talk about WatchConnectivy! See Watch tutorial 5: Watch Connectivity (Direct Message)

Monday, April 18, 2016

Watch tutorial 3: Layout

This post is part of a set of short tutorials on Watch. If you want to see the previous post. In this tutorial, you're going to layout the screen needed to start the task from your Watch.

Are you a AutoLayout Guru?

Good. From Apple documentation:

Watch apps do not use the same layout model used by iOS apps. When assembling the scenes for your Watch app interface, Xcode arranges items for you, stacking them vertically on different lines. At runtime, Apple Watch takes those elements and lays them out based on the available space.

For Watch, you won't use AutoLayout!!!

AppleWatch Layout is much basic and therefore much easier to use :)

You must use storyboards to design your interfaces. Remember that storyboard is part of the WatchKit app bundle. Everything is laid down in storyboard, you won't be able to access a position coordinate at runtime. To me, Watch layout looks much more like box driven ie: CSS-like rather than constraints based Layout ie: iOS-like.

Get starter project

In case you missed Watch tutorial 2: Watch Architecture, here are the instructions how to get the starter project. Clone and get the initial project by running:
git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step2
open DoItCoach.xcodeproj

Laying out first screen


Add image resource

Download the Asset.assets folder from the final repo. In Finder copy paste Assets.xcassets folder into your project Watch App folder ie: /DoItCoach/DoItCoach WatchKit App/.

In DoItCoach WatchKit App in Project Navigator, select Assets.xcassets, should see the new images:


You're done with the images, let's go to DoItCoach WatchKit App's storyboard.

Start button layout

To be able to start the timer for a task,
  • Go to DoItCoach WatchKit App/Interface.storyboard
  • In the bottom right hand side Object Library search for a Group UI control.
  • Drag and drop the Group onto your main screen
  • In Attributes Inspector, choose Horizontal: Center, Vertical: Center, in Background select Time12 image, in Height select 0.9 to make sure the circle has no distortion.
  • In Object Library search for a Button UI control.
  • Drag and drop the Group onto your newly created group
  • In Attributes Inspector, select Content and assign the value Group.
  • In left hand side scene view, select the newly appeared Group below the Button, set its Height to Relative to Container
  • In Object Library search for a Image UI control.
  • In Attributes Inspector, select Image and assign the value Start. Choose Horizontal: Center, Vertical: Center.
  • In Object Library search for a Timer UI control.
  • Drag and drop the Timer onto your Button group. Oops! what is happening, you timer is out of screen. Remember, by default Group have a horizontal layout, go to the Group under your Button and change Layout to Vertical. You can now see your timer element. For your timer, choose Horizontal: Center, Vertical: Center, Hidden: true, Units: Second, Minute checked, Text Color: Green, Front: system, Ultra thin, 27.
Build and run.
Et voila!



Add outlets

To link UI controls to your Swift code, same technique as for iOS app: use outlet and actions.
  • Control drag Label to InterfaceController.swift, select outlet, name it taskNameLabel
  • Control drag Group (the top level one) to InterfaceController.swift, select outlet, name it group
  • Control drag Button to InterfaceController.swift, select outlet, name it startButton
  • Control drag Image underButton Group to InterfaceController.swift, select outlet, name it startButtonImage
  • Control drag Timer to InterfaceController.swift, select outlet, name it timer

Add Shared business model

To add Task business model to WatchKit App Extension, go to Build phases, in Compiled Sources, add Task.swift and TasksManager.swift.

In TasksManager.swift, replace the empty init() by the following one to bootstrap some values into your AppWatch:
public init() {
  self.tasks = [TaskActivity(name: "Task1", manager: self), TaskActivity(name: "Task", manager: self)]
}

App life cycle

In InterfaceController.swift, add display(task:) method as below:
func display(task: Task?) {
  guard let task = task else { // [1]
    taskNameLabel.setText("NOTHING TO DO :)")
    timer.setHidden(true)
    startButtonImage.setHidden(true)
    return
  }
  taskNameLabel.setText(task.name) // [2]
}
  • [1]: if there are no task available display in the task label: nothing to do and hide all other components
  • [2]: otherwise for a new task, display the task's name
In InterfaceController.swift, in awakeWithContext(context:) add a call to display method as below:
override func awakeWithContext(context: AnyObject?) {
  super.awakeWithContext(context)
  display(TasksManager.instance.currentTask)
}

Add action to Start

In InterfaceController.swift, in awakeWithContext(context:) add a call to display method as below:
@IBAction func onStartButton() {
  guard let currentTask = TasksManager.instance.currentTask else {return} // [1]
  if !currentTask.isStarted() { // [2]
    let duration = NSDate(timeIntervalSinceNow: currentTask.duration)
    timer.setDate(duration)
    timer.start() // [3]
    currentTask.start()
    startButtonImage.setHidden(true) // [4]
    timer.setHidden(false) // [5]
    taskNameLabel.setText(currentTask.name)
  }
}
  • [1]: if there are no task return
  • [2]: otherwise if the task is not already started
  • [3]: start it
  • [4]: hide start button
  • [5]: show timer

Get final project

If you want to check the final project, here are the instructions how to get it.
cd DoItCoach
git checkout step3
open DoItCoach.xcodeproj

What's next?

With this tutorial, you saw how you can layout your first AppleWatch screen, how you connect your UI element to the code to add dynamic effect on your screen. Now it is time to talk about animation: how do you make the ring show timer progress?

See Watch tutorial 4: Animation

Watch tutorial 2: Watch Architecture

This post is the second post of a set of short tutorials on Watch. If you want to see the previous post. In this tutorials, you're going to build your first AppleWatch app: DoItCoach.

Let's start by talking about what is an Watch app...

How does a watch app work?

First thing to know about AppleWatch app is that is always come bundles with its companion iOS app. It's the same idea as the App extensions introduced in iOS8, where you have a main iOS app and you can add extensions. Those extensions are embedded into Today, Shared (depending on their type) component. Like Extension, for AppleWatch app, you create an app project which comes with several build targets.

A Watch app consists of two separate bundles that work together.
  • WatchKit App: contains the storyboards and resource files needed to display your interface.
  • WatchKit Extension: contains the code needed for your native AppleWatch app. This is the part to get compiled and the binaries get transferred to your watch.

Side note: WatchOS vs watchOS2

As a side note, I think it's interesting to look back and know the differences between watchOS (the first version released in April 2015) and watchOS2 (released in September 2015). An image is worth a thousand worlds:


Now In watchOS 2, the extension runs on the user’s Apple Watch instead of on the user’s iPhone, as was the case in watchOS 1. This is the fundamental change for watchOS2: you can run watch native apps. The separation into Watch app / Watch Extension makes even more sense in the context of WatchOS1 as the binaries were deployed in different physical targets. In watchOS2, there is still the distinction of Watch app (storyboard, resources) and Watch extension (code binaries) although both are deployed natively to the AppleWatch.

The separation between WatchKit App and WatchKit Extension also means that the app's user interface is static and can't be changed at runtime. Adding or removing elements, for example, isn't possible. You can show and hide user interface elements though (I'll tell you more about that in Layout tutorial). This is done this way to save Watch resources so that it doesn't drain the battery.

Having the code binaries deployed natively makes your apps launch quicker, and be far more responsive as you remove the bluetooth latency. It also changes drastically the way you communicate/synchronize data between AppleWatch and its companion app. I tell you more about that in WatchConnectivity tutorial.

You now need to share a common business model between you iOS app and your Watch app. For that purpose, I like to separate the business model in a Shared group. This group is included in both iOS and Watch Extension target so it gets compiled and deployed on both.

Shared business model

Before you start coding,have a look at the shared business model. This model is used in the iOS app and will also be used in the Watch to represent a Task. Looking at the Task protocol:
public protocol Task: CustomStringConvertible {
  var name: String {get}
  var duration: NSTimeInterval {get}
  var startDate: NSDate? {get set}
  var endDate: NSDate? {get set}
  var timer: NSTimer? {get set}
  var type: TaskType {get set}
  func start()
  func stop()
...
}
We see a Task has a name, a duration, a startDate and endDate and two methods to start and stop the Task.

Ready for some code?
3, 2, 1... Go

Get starter project

In case you missed Watch tutorial 1: Which app?, here are the instructions how to get the starter project. Clone and get the initial project by running:
git clone https://github.com/corinnekrych/DoItCoach.git
cd DoItCoach
git checkout step1
open DoItCoach.xcodeproj

Create your Watch targets

To add an AppleWatch deployment target, in Xcode:
  • Go to File -> New -> Target...
  • Under watchOS, select Application tab and then choose WatchKit app
  • In product name enter DoItCoach WatchKit App
  • Untick all include scene, hit Finish button
  • Click yes when Xcode prompts you to activate Apple Watch schema
If prompted: Activate “DoItCoach WatchKit App” scheme?, answer yes.



You should now be able to see your the new target: DoItcoach Watch App, Xcode should have created its matching schema:



You've just created your first Watch app, but if you run the appleWatch screen is all black :(

Let's add a label

In Xcode:
  • Go to newly created Group named DoItCoach Watch App
  • Select Interface.storyboard, in the bottom right hand side Object Library search for a Label UI control.
  • Drag and drop the label onto your main screen
  • In Attributes Inspector:
    • in Alignment section, select Horizontal: center
    • change Text Color to Blue
    • in Font select System, UltraLight 17

Build and Run


To run in the simulator

Select DoItCoach WatchKit App schema with iPhone6sPlus + AppleWatch - 42 mm as targeted simulators.
The command should start both simulators and launch the iOS app and the AppleWatch app. In Xcode, in the left hand side Debug Navigator, you can see debug information for each app.



Note: Sometimes, Xcode failed to attache the debug process of the iOS app, you can do it manually:
  • either by selecting the schema that is not launched and run it again.
  • or by manually attaching the iOS app debug process to Xcode. It quite simple and I think this blog post explained it well

To run on Watch

When you want to run your app on Watch, plug your phone to a USB port, and keep you watch close by. You can install the app on your phone:
  • either by selecting DoItCoach WatchKit App schema with your iPhone and Paired Watch. This way, you can install the app in debug mode.
  • or by selecting by selecting DoItCoach schema with your iPhone selected. Open Watch app, in General -> App Install section, make sure Automatic App Install is checked. Once the app is installed on your iPhone, its Watch app will be automatically installed on your Watch. With this approach you won't be able to debug your Watch app but the install might be quicker.


  • Get final project

    If you want to check the final project, here are the instructions how to get it.
    cd DoItCoach
    git checkout step2
    open DoItCoach.xcodeproj
    


    What's next?

    With this first introduction tutorial, you saw how the Watch app is architectured:

    To sum up, an AppleWatch project is typically build three main parts: iOS app (iOS storyboard, iOS code), Watch app (watch storyboard), Watch extension (Watch code).

    You also created you first AppleWatch target, see how to build and run the apps on simulators or iPhone/paired Watch. You are now ready to add more UI controls on your watch screen. See Watch tutorial 3: Layout.

    Watch tutorial 1: Which app?

    This post is the first post of a set of short tutorials on Watch. In these step by step tutorials, you're going to build your first AppleWatch app. Yay!

    I'll guide you through. No prior knowledge on watchOS2 is required, but some basic iOS development skills (storyboards usage) and Swift language knowledge are assumed.

    When designing for an AppleWatch, you should keep the features simple. Bear in mind they'll have to work at on a 312 pixels wide by 390 pixels tall screen for a 42 mn watch. Don't try to fit too many features with a complexe screen hierarchy. Also, remember that your watch app comes with its iPhone companion app. Therefore, you don't need to fit all the features in the watch extension: a well chosen subset will do well.

    Let's start by talking about the app, you're going to build...

    DoIt Coach




    Have you ever wonder how to get more things done during the day, how to stay focus on your tasks? After all, getting your job done efficiently gives you more free time ;)

    Based on a well known time management technique, with DoItCoach, you break your day in small tasks interlaced with small breaks. DoItCoach's main goal is to be more efficient and stay healthy.

    Start the day, planning the list of task to be done. For the planification use the iPhone app. Add one task followed by one break. After a 3 of those add a longer break.

    Let's spice it up: since you want to stay fit in your life, you're going to try to do something physical during your breaks. Shorter breaks could be perfect for some weight lifting or curls ;) while longer breaks could be used for outdoor walk or short run.

    The iOS app

    Since the goal of this tutorial is about Watch app, you'll start with an initial project. All source code is available on github DoItCoach project for the final project.

    Starter project

    Clone and get the initial project by running those git commands:
    git clone https://github.com/corinnekrych/DoItCoach.git
    git checkout step1
    open DoItCoach.xcodeproj
    

    Build and Run

    open DoItCoach.xcodeproj
    
    Run the project in Xcode.

    You can add new task, move them and start the first task in the list. Once completed, the task is moved at the bottom of the list and a new one is available for you to start.

    What's next?

    With this first introduction tutorial, you saw how the iOS app DoItCoach worked. It's now your turn to work: let's add the AppleWatch target. See Watch tutorial 2: Watch Architecture

    Thursday, April 23, 2015

    How well does Swift play with iOS7?

    Swift was created with the Objective-C interoperability in mind. It's easy to get why, Swift playing nicely with Objective-C, was required in order to use existing cocoa API. At first, when trying interoperability, I mostly used Objective-C libs in my Swift apps. But, as I progress in my Swift immersion, I soon write reusable Swift code.

    Apple stated it from day one:
    • you can also use Swift code from Objective-C app
    • as Swift applications compile into standard binaries plus some Xcode bundling Swift bits in your app, you can run Swift code on iOS 7.
    You can run Swift code in iOS7 BUT there are several paths to drill down…

    Do you want to run a Swift app on iOS7?


    Let's talk about runtime


    How does iOS7 understand Swift? Does iOS7 operating system includes Swift support?

    Nope! It’s the other way around. Application with Swift code bundles Swift specific standard libs.

    From Colemancda's blog post:
    "With Swift, Apple has changed how standard libraries are shipped. With Objective-C, all of the standard libraries, system frameworks, and the runtime itself, were shipped with the OS. With Swift, Apple wanted the ability to quickly deprecate parts of the Swift Standard Library and also add new features. While these changes do break apps at the source code level, it would be a huge problem if shipped apps started to break because the standard library they are linked against has an incompatible API. Apple’s solution to the problem is to ship a specific version of the standard library with your app."

    Besides, reading Swift blog post about Compatibility, I found that this statement is interesting: "When the binary interface stabilizes in a year or two, the Swift runtime will become part of the host OS and this limitation will no longer exist."

    iOS8 brings a shinny new langage support: Swift but, the other correlated important change that happens is the way libraries are packaged. Running Swift on iOS7 also brings the question of how well Swift/Objective-C go together.

    Let's talk about Objective-C / Swift impedance


    So Swift code can be run even when called from Objective-C. Swift is a strongly type-safe language whereas Objective-C is dynamic by essence. It sometimes brings some blurry runtime behaviour (either crash or nothing happen) to watch out for when writing Swift code that aims to run on both Objective-C and Swift:

    • Swift pure object are not supported: you need to add @objc or inherit from NSObject if your class is visible from Objective-C.
    • Pay special attention to optional. I recommend this stackoverflow post for more reading.
    • Same goes when optionally casting.
    • Don’t use iOS8 api: of course… it seems obvious. But it's easy to forget tough and then you run into runtime exception - I say it from experience :))
    • Some enum support is available in Objective-C since Swift1.2.
    etc... I will go in more details in a later blog post.

    An interesting open source library which used the Swift first approach (code written in Swift first but compatible with Objective-C) is Quick. Most of the code is written in Swift some adapters in Objective-C are required when Swift paradigm won't fit (note: Quick and Nimble are DSL for BDD testing, DSL doe uses langage paradigm a lot).

    Let's see an example


    Here is an experiment I did: Run an HelloWorld app written in Swift on iOS7. That app registers to UnifiedPush Server. For this first experiment, let's just have one application with all the source code bundled together.

    You can clone the Xcode6.3 code source:
    git clone https://github.com/corinnekrych/unified-push-helloworld.git
    cd unified-push-helloworld
    git checkout ios7.experiment
    open HelloWorldSwift.xcodeproj
    
    and run it.

    To run the app you will need a device with iOS7 installed because push notification can not be run from simulator. Also make sure the UPS instance is live on OpenShift. Alternatively if my OpenShift instance is not running, create your own server following the UPS guide.

    Run the app on device. Go to UPS console, login with admin/admin. Go to "send message" right hand tab, and send a message. Your message should be displayed in the list of messages.

    Now what about if we want to extract the code related to the UPS registration in an external lib?

    Do you want to run Swift libs linked to Swift app on iOS7?


    Dynamic framework


    Swift libraries can only packaged using dynamic framework (sometimes called cocoa touch framework or embedded framework or bundled framework). Although dynamic frameworks are new to iOS8, they used to be used in OSX though for a while.

    With Swift, you can’t package your Swift libs statically because static libs would lead to multiple runtimes in the final executable. We’re back to the point we discussed earlier in …: With Swift evolves quickly and ship its a specific version of the standard library with your app.

    So you need to copy/paste your lib source code in your final app?

    Cocoapods to the rescue


    Or use cocoapods 0.36+ with the use_frameworks! option. I recommend you to read the excellent article from Marius: CocoaPods 0.36 - Framework and Swift Support. Behind the scene, cocoapods ensures all dependant libraries are bundled together with the same set of dylibs, which are embedded into the Frameworks subdirectory of the application bundle.

    Using cocoapods brings an easy tooling to support dynamic framework with Swift.

    Let's see an example


    Let's take an simple app ChuckNorrisJoke (Yes! Chuck Norris is in the place) from aerogear-ios-cookbook written in Swift and let's use aerogear-ios-http (Swift too) an run the app on iOS7.

    Originally aerogear-ios-http was designed with minimal deployment target to 8.0, in this experimental branch, I'm going to lower the deployment target to 7.0 and adjust some of the Swift code to fit iOS7.

    git clone https://github.com/corinnekrych/aerogear-ios-cookbook-1
    git checkout ios7.support
    cd aerogear-ios-cookbook/ChuckNorrisJokes
    pod install
    open ChuckNorrisJokes.xcworkspace
    
    Run on iOS7 device or on iOS7 simulator and enjoy chuck Norris humour :)

    Take away


    As we've seen, swift code can run on iOS7 and iOS8 but comes with some compromises:
    • writing code that comply with both Objective-C and Swift.
    • dynamic framework packaging. Using cocoapods takes some of the burden away.
    • last but not least, it certainly requires some extra testing as most of the errors will happen at runtime.
    Swift is moving fast, and as we've seen the latest version (Swift 1.2 with iOS that ships with iOS8.3) brings improvement for compatibility with Objective-C (enum case). Interoperability is key to achieve developer's Nirvana of "easy maintenance": write once, deploy on both iOS7 and iOS8.

    Thursday, February 26, 2015

    Even more fun with playground in Xcode 6.3

    I've just installed Xcode 6.3, it brings us even more fun with playground!

    Last summer, I blogged about playground and how you can use them to do great interactive tutorial. With playgrounds... It's love at first sight. ❤ ❤ ❤ ❤

    I think they are great learning tools. Apple's Guided tour made them popular from day 1.

    I even use them in my lib repositories to demo how to use an API. For that simply, create a workspace with your framework code and attach a playground file. See playground has never been so fun for more details and check out Alamofire lib usage of playground.

    When I first gave a presentation on Swift, I decided to write it with playground of cource :P
    But, how to write your own guided tour?

    At first, there was HTML...


    Playgrounds are directory that can contain: resources (images, html), swift source (.swift file) and a description file (contents.xcplayground) to help rendering.



    You define HTML page in Documentation folder, Swift source file directly under playground folder. Then using contents.xcplayground descriptive file you associate the different fragments together. As you're working with CSS, you can also customize you're own CSS. Don't specify too much the size etc... let Xcode preferences deal with that.

    With Xcode 6.2, it renders as:



    The annoying part, is when you open your playground and start changing the source file section, Xcode will generate a new file number: section-1.swift will become section-2.swift and so on...
    Slightly annoying, I have to confess.

    Then markdown-to-playground processing


    Then emerged swift-playground-builder an open source project which takes a markdown input and generates a playground out of it. And that indeed, makes your life easier, you don't have to switch between source code and documentation file. But...

    I'm afraid there is a 'but'. The main drawback is: as you write your tutorial you can't check your Swift syntax. You're in markdown file!

    I personally prefer to stick to real source file and html.

    To end up with markdown everywhere!




    With markdown directly in swift source code, you can write source code and tutorial text at the same time by using special comment ```//:``` :
    //: ### Immutability
    //: ```let``` for constants => cannot change once initialized
    //: ```var``` for variable => can be changed and can be optional. let name = "julie" let age = 18 println("Hello my name is \(name) and I am \(age) years old")
    And it renders as:



    The only slight 'but' here is about refreshing...
    Difficult to write your comment in Xcode directly and have them refreshed. I usually work with Xcode opened for rendering and another editor for editing, triggering refreshing but switching Xcode current file. Caution when modifying contents.xcplayground, Xcode is picky on this and may get upset (yeah! good old Xcode crash are not gone!)

    Writing interactive tutorial is really easy with Xcode 6.3. Follow the links if you want to see the source code of the Swift tutorial I've talked about in xcode 6.1 format or with the latest 6.3 format.
    Happy Swifting!

    Monday, January 12, 2015

    Sharing Keychain access in a Share Extension

    I've been wanted to do a blog post on how to achieve SSO on iOS using sharing Keychain for a bit...

    And at the same time, I also wanted to try app extension very badly. So in an attempt to get the best of the two worlds, let's talk about writing a share extension to an app which need to store OAuth2 access token in a secure manner. We'll see how to share Keychain content through group-id between an app and its extension.

    Remember Shoot'nShare app?
    A simple app that takes pictures and allows you to share them with Facebook, GoogleDrive or even your own Keycloak backend. If we want to learn more about it, visit previous blog posts: To simplify, in this blog post we will focus on sharing to Google Drive only. As a pre-requisite, let's start creating a Google project.

    OAuth2 Google Set up


    If you want to create a google project to use for uploading files to Google Drive, follow the steps below:
    • Have a Google account
    • Go to Google cloud console, create a new project
    • Go to APIs & auth menu, then select APIs and turn on Drive API
    • Always in APIs & auth menu, select Credentials and hit create new client id button Select iOS client and enter your bundle id.
    • NOTES: Enter a correct bundle id as it will be use in URL schema to specify the callback URL. Please use your own unique BUNDLE_ID with format like org.YOUR_DOMAIN.Shoot replacing YOUR_DOMAIN with your actual domain.
    Once completed you will have your information displayed as below:

    Now that we've got your google project set up, let's add an Share Extension to Shoot app and see what's involved.

    Share Extension


    What it is?

    An App extension add feature to an existing application. There are several types of extensions. The one we're interested in today is the share extensions. As the name says it all, this extension lets you share content with the external world. By default Xcode template will inherit from SLComposeServiceViewController. Therefore when hitting share button, a pop-up appears to send a message with image. Before iOS8, only a handset of providers were available to share content with. Those providers were defined directly in the operating system directly so the list was not flexible at all. Those days are over (yay!), you can now share with your favourite or even your own social networks directly from Photos app. This is exactly what we're going to do: let's share to GoogleDrive from Photos app via Shoot'nShare app.

    One important thing to bear in mind extensions are not deployed by themselves. They must be packaged within a container app. Concretely in Xcode extensions are extension target within you container app.

    Let's see an example

    1. Get the project
    Code source can be found in aerogear-ios-cookbook app.extension branch. Clone the repo and select the correct branch:
    git clone git@github.com:aerogear/aerogear-ios-cookbook.git
    git checkout AGIOS-224.shoot-extension
    
    2. Define you own bundle_id
    To be able to work with extension you need to enable App Groups. App Groups are closely linked to bundle identifiers. So let's change the BUNDLE_ID of the project to match your name. Select the Shoot project in the Project Navigator, and then select the Shoot target from the list of targets. On the General tab, update the Bundle Identifier to org.YOUR_DOMAIN.Shoot replacing YOUR_DOMAIN with your actual domain. Do the same for the extension target: select the Shoot project in the Project Navigator and then select the ShootExt target. On the General tab, update the Bundle Identifier to org.YOUR_DOMAIN.Shoot.ShootExt replacing YOUR_DOMAIN with your actual domain.

    3. Configure App Group for Shoot target
    In order for Shoot'nShare to share content with its extension, you’ll need to set up an App Group. App Groups allow access to group containers that are shared amongst related apps, or in this case your container app and extension. Select the Shoot project, switch to the Capabilities tab and enable App Groups by flicking the switch. Add a new group, name it group.org.YOUR_DOMAIN.Shoot, again replacing YOUR_DOMAIN with your actual domain.

    4. Configure your App Group for ShootExt target
    Open the Capabilities tab and enable App Groups. Select the group you created when setting up the Shoot project. The App Group simply allows both the extension and container app to share files. This is important because of the way files are uploaded when using the extension. Before uploading, image files are saved to the shared container. Then, they are scheduled for upload via a background task.

    Sharing Keychain


    With the same idea of sharing group between apps (or app and extension) to be able to have a common space for saving files, we can use Keychain group so that app and extension can share Keychain items. In our case we want a common space for Shoot app and Shoot Ext to share OAuth2 access token.

    1. Configure Keychain Sharing for Shoot target
    In order for Shoot'nShare to share access tokens with its extensions, you’ll need to set up a Keychain Sharing Group. Select the Shoot project in the Project Navigator, and then select the Shoot target from the list of targets. Now switch to the Capabilities tab and enable Keychain Sharing by flicking the switch. Add a new group, name it org.YOUR_DOMAIN.Shoot, again replacing YOUR_DOMAIN with your actual domain.

    2. Configure Keychain Sharing for ShootExt target
    Select the Shoot project in the Project Navigator and then select the ShootExt target. Open the Capabilities tab and enable Keychain Sharing. Select the group you created when setting up the Shoot project.

    3. Configure Shoot App code
    In Shoot/ViewController.swift modify:
    @IBAction func shareWithGoogleDrive() {
            let googleConfig = GoogleConfig(
                clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
                scopes:["https://www.googleapis.com/auth/drive"])
            let ssoKeychainGroup = "YOUR_APP_ID_PREFIX.org.YOUR_DOMAIN.Shoot"
    ...
    
    where YOUR_APP_ID_PREFIX is a unique alphanumeric identifier, you can view it on dev center:
    and org.YOUR_DOMAIN.Shoot is you BUNDLE_ID.

    4. Configure ShootExt code
    In ShootExt/ViewController.swift modify:
    let ssoKeychainGroup = "357BX7TCT5.org.corinne.Shoot"
        let appGroup = "group.org.corinne.Shoot"
    
        override func didSelectPost() {      
            // We can not use googleconfig as per default it take your ext bundle id, here we want to takes shoot app bundle id for redirect_uri
            let googleConfig = Config(base: "https://accounts.google.com",
                    authzEndpoint: "o/oauth2/auth",
                    redirectURL: "org.YOUR_DOMAIN.Shoot:/oauth2Callback",
                    accessTokenEndpoint: "o/oauth2/token",
                    clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
                    refreshTokenEndpoint: "o/oauth2/token",
                    revokeTokenEndpoint: "rest/revoke",
                    scopes:["https://www.googleapis.com/auth/drive"])"
    ...
    
    Replace:
    the constant ssoKeychainGroup with your YOUR_APP_ID_PREFIX + BUNDLE_ID.
    the constant appGroup with your App Group
    in google config, redirectURL should match your BUNDLE_ID

    5. Run the extension
    To run shoot extension, select ShootExt target and run it, select Photos app as host app.
    Select a photo, click on share button and select Shoot app. A Pop-up will appear, select send: you photo is uploaded on the background... and we're done. We've done all the configuration needed. Let's look at the code now.

    Spot the Difference


    Actually what we want to do from Share extension is basically the same as we do from Shoot'nShare app. But we do in an extension to allow us to do from Photos app. What about playing the difference game? What are the differences between uploading from Shoot'nShare app or uploading from ShootExt?

    1. you can not trigger the OAuth2 danse from the extension
    Extensions have limitations. Some API are not available. An app extension cannot access a sharedApplication object, and so cannot use any of the methods on that object. Difficult to trigger an external browser to launch the OAuth2 danse. Opening the container app in case no access tokens is available could be an alternative... However this alternative is offered only for today widget extension...

    Indeed depending on extension type, some actions are allowed or forbidden. For example quoting apple doc : "only a today widget (and no other app extension type) can ask the system to open its containing app by calling the openURL:completionHandler: method of the NSExtensionContext class."

    With our ShootExt, it would have been handy to be able to open Shoot'nShare app if no access token is available in the shared keychain. As our extension is a share extension, this is not available. As a result, we take as a pre-requisite that the end user has already shared a photo from Shoot'nShare app before using the extension. To do so we override OAuth2Module's requestAuthorizationCode method:
    public class OAuth2ModuleExtension: OAuth2Module {
        // For extension we do not want to be redirected to browser to authenticate
        // As a pre-requisite we should have a valid access_token stored in Keychain
        override public func requestAuthorizationCode(completionHandler: (AnyObject?, NSError?) -> Void) {
            completionHandler("NO_TOKEN", nil)
        }
    }
    
    In case there is no token, we will return an error message to the end user asking him to use Shoot'nShare first.

    2. you use the same redirect-uri OAuth2 for both extension and app
    To do so, in ShootExt/ViewController.swift we can not use GoogleConfig class, we'll have to use Config class ans spscify shoot'nShare's redirect url as shown below:
    let googleConfig = Config(base: "https://accounts.google.com",
                    authzEndpoint: "o/oauth2/auth",
                    redirectURL: "org.YOUR_DOMAIN.Shoot:/oauth2Callback",
                    accessTokenEndpoint: "o/oauth2/token",
                    clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
                    refreshTokenEndpoint: "o/oauth2/token",
                    revokeTokenEndpoint: "rest/revoke",
                    scopes:["https://www.googleapis.com/auth/drive"])
    


    3. you need to save in the Keychain using group-id
    When we first save the access token in Shoot'nShare app, we need to specified the group-id, in Shoot/Viewcontroller.swift, we modify shareWithGoogleDrive method to accomodate it: In line7-10 we create a TrustedPersistantOAuth2Session object with a keychain group-id:
     @IBAction func shareWithGoogleDrive() {
             let googleConfig = GoogleConfig(
                clientId: "YOUR_GOOGLE_APP_ID.apps.googleusercontent.com",
                scopes:["https://www.googleapis.com/auth/drive"])
            let ssoKeychainGroup = "YOUR_APP_ID_PREFIX.org.YOUR_DOMAIN.Shoot"
            // We specify the keychain groupId, should be the same as the one used in Share extension
            let gdModule = OAuth2Module(config: googleConfig, 
                                        session: TrustedPersistantOAuth2Session(accountId: 
                                                   "ACCOUNT_FOR_CLIENTID_\(googleConfig.clientId)", 
                                                   groupId: ssoKeychainGroup))
            self.http.authzModule = gdModule
            self.performUpload("https://www.googleapis.com/upload/drive/v2/files", parameters: self.extractImageAsMultipartParams())
        }
    


    4. you upload your photo in the background
    Last but not least, when dealing with extension, remember that action will take place in the background! In our case we want to perform a multipart upload (we're using multipart Google endpoint) in the background. using aerogear-ios-http you can perform multipart background upload either using upload method with stream or file as shown line 28. It's also possible to use a POST method with multipart params (behind the scene a NSURSLSession upload is performed):
     override func didSelectPost() {
            let googleConfig = ....
            
            // Create a TrustedPersistantOAuth2Session with a groupId for keychain group sharing
            let gdModule = OAuth2ModuleExtension(config: googleConfig, session: 
    TrustedPersistantOAuth2Session(accountId: "ACCOUNT_FOR_CLIENTID_\(googleConfig.clientId)", 
    groupId: ssoKeychainGroup))
            
            self.http.authzModule = gdModule
            gdModule.requestAccess { (response: AnyObject?, error: NSError?) -> Void in
                var accessToken = response as? String
                if accessToken == "NO_TOKEN" {
                    println("You should go to Shoot app and grant oauth2 access")
                } else {
                    let imageURL = self.saveImage(self.imageToShare!, name: NSUUID().UUIDString)
                    // multipart upload
                    let multiPartData = MultiPartData(url: imageURL!,
                                mimeType: "image/jpg")
                    let parameters = ["file": multiPartData]
                    // multi-part upload could be achievd either with upload as a stream or using POST
                    self.http.upload("https://www.googleapis.com/upload/drive/v2/files", 
                             stream: NSInputStream(URL: imageURL!)!, 
                             parameters: parameters, 
                             method: .POST, 
                             progress: { (ar1:Int64, ar2:Int64, arr3:Int64) -> Void in
                        println("Uploading...")
                        }) { (response: AnyObject?, error: NSError?) -> Void in
                        println("Uploaded: \(response) \(error)")
                    }
    
                }
            }
        }
    


    Hope your find this blog post useful and remember: it's all about Sharing...
    Do not hesitate to share this link :)

    Thursday, November 13, 2014

    OAuth2 for Android and iOS with Keycloak

    Like Jo, our brave iOS developer, you might have bumped into OAuth2 when writing an app that posts messages on Facebook wall. All cool social apps need to go through OAuth2 or OpenID authentication and authorization. Why?
    Because so far it is the only broadly adopted open standard. So you might have heard : "It's complicated etc..". Not really if you use the right tools.

    Mary, a cool mobile developer too, knows all about OAuth2. Follow Jo and Mary in their coffee break chat to learn in 5 mins OAuth2 howtos.

    What's the problem?



    - So Mary, to start with what is the problem we're trying to solve with OAuth?

    - Easy. On one hand you have services… in the form of APIs. For example, we have twitter API to get list of followers, tweets etc… Those APIS handle your confidential data. As far as your data are protect by a login/password account, all is fine. But on the other hand, you have lots of apps that need to consume those services. The question is simple: do you trust them all to share your twitter login/password with them?

    - I don't want my password all over the Internet!!!

    - Therefore, use OAuth. OAuth2 allows users to grant third-party access to their web resources without sharing their passwords. We talk about a “delegated access" between mobile and user resources using a security token called an "access token".

    Different actors and grants



    - Mary, very often I hear about OAuth2 dance, who's involved? What is it?

    - Actually there are 4 actors: authorization server (see our police man), responsible of authentication and authorization by providing an access token. The resource server, responsible for serving resources checking if there a valid token. The resource-owner, the end-user of the app, it could be you. And the client, in our case the mobile app.

    - What is the dance all about?

    - The interactions between those actors is described in OAuth2 spec as grant flows. This is the dance. There are different grant flows to fit different client app needs. A mobile app has diff needs that a single page browser-based app. In the spec, there are 4 different flows, you can group them in 2 different families:
    3-legged flows where you grant permission from user. It includes "implicit grant" for browser based app not capable of keeping tokens secure and the "authorization code grant".
    2-legged flows where the credentials are given to the app. In "resources owner credentials", this is the login/password whereas "client credential grant" takes client_id and client_secret.

    - How do I know which one to use?

    - It actually all boils down to two questions: "Is your client app capable of securely storing access tokens?" and "What is your trust relationship with the client app?" In your case, Jo, you should go for "authorization code grant".

    Authorization Code Grant

    step0: registration


    - One of the pre-requisites you need to go through Jo, is to register your mobile app. For example, you go to Google cloud dev console, you create a project, fill in the form (add your redirect URI). And eventually you get a client_id and client/secret

    step1: authz code




    - With this client_id you’re all set to develop your OAuth2 app. Your Shoot'nShare app will send a request (including the client_id and the redirect_uri its scope) to the third-party service asking for an Authorization code. The app switches to external browser.

    - Do you have to switch context?

    - Actually, there are different approaches, you can also embed a web view in your app. It's better from a usability perspective. But from a security point of view it's seen as less secure, as your app sits between the login/password form and the provider.

    - With external browser, it's actually the real Facebook login page?

    - Exactly. The user logs in (if not already logged in), authz server shows a grant page to the end-user: Shoot’nShare would like to access yours contacts and photos Allow/Deny. If the user clicks on "Allow", the server redirects to the client using the redirect_uri (the one we talked during step0) and sends the Authorization Code to the mobile application.

    step2: exchange for access token


    - Now that we’ve got this temporary code we can go to the token endpoint to exchange it for a proper access token.

    step3: get resources


    - Using this token, our mobile app can access protected resources on server. Here you can do your upload!

    How to implement OAuth2 on iOS?

    - Mary, if I want to start coding the iOS version of Shoot'nShare just after our coffee, what are my options:

    - You could use Social.famework from Apple, it will fit well for Facebook and a couple of other providers but it’s limited to those providers defined in settings. Here you actually provide your login/password but you trust your OS don’t you?

    - ...

    - You could use Facebook sdk but same here, limited to Facebook. If you want to share your photo to Google+, you need to use Google sdk and so on… - But Mary, there must be an open source library to help us…

    - Yes! actually let me tell you more about AeroGear OAuth2 library... OAuth2 is one of the lib offers by AeroGear mobile suite. All client sdk are declined in iOS, Android, Windows(work in progress) for native paltforms. There is also a JavaScript sdk (and its Cordova plugins when needed). AeroGear is not only client SDKs, it also offers server bits like its UPS (UnifiedPush Server), if you want to send push notification to iOS/Android/Windows.



    Want to see it in action?

    - Let's see it in action, follow me in this screencast:


    OAuth2 Server Side

    - Mary, I try to foresee my product owner needs, what about if he wants an OAuth2 secured rest endpoint, server side? - No worries, I've got an answer for you and it's all Open Source: Keycloak

    Shootn'Share demo

    - Let's see it in action, follow me in this screencast:



    Any feedback please drop us a line on AeroGear mailing list or contact Keycloak mailing list for more in-depth question on OAuth2/SSO server.

    Happy OAuth2!

    Thursday, October 16, 2014

    AeroGear with Keycloak, OAuth2 friends for iOS apps... running with Swift

    You might have bumped into OAuth2 when writing an app that posts messages on Facebook wall. All cool social apps need to go through OAuth2 or OpenID authentication and authorization. Why?
    Because so far it is the only broadly adopted open standard. So you might have heard : "It's complicated etc..". Not really if you use the right tools.

    Want to see it in action?



    Shootn'Share demo

    You want to take cool photos and share them with friends using GoogleDrive or Facebook account? With Shoot'nShare you can take pictures, browse your camera roll, pick a photo and share it! Photos get uploaded to your GoogleDrive or Facebook wall. You can also run this demo with its associated Keycloak backend and upload photo to your own social network :]

    The purpose of this blog is not to show you how to take picture in iOS, so let's work on existing Shoot'nShare. Just clone it from github:


    git clone https://github.com/aerogear/aerogear-ios-cookbook
    cd aerogear-ios-cookbook
    git checkout swift
    open Shoot/Shoot.xcodeproj

    Shoot'nShare project

    In this initial project you will find aerogear-ios-oauth2 as a source dependency in Shoot/libs/AeroGearOAuth2 the library we will use for OAuth2 on iOS.

    Google setup (optional)

    NOTES: This step is optional if your want to try the GoogleDrive app out of the box. You can reuse the Shoot client id for 'GoogleDrive'. However if you want to create your own app, you will have to go through your provider setup instruction. Here's how to do it for Google Drive.

    1. Have a Google account
    2. Go to Google cloud console, create a new project
    3. Go to APIs & auth menu, then select APIs and turn on Drive API
    4. Always in APIs & auth menu, select Credentials and hit create new client id button Select iOS client and enter your bundle id.

    NOTES: Enter a correct bundle id as it will be use in URL schema to specify the callback URL.

    Once completed, you will have your client id!

    Shoot'nShare redirect URI for Google

    Open Info.plist of your project as source code to see XML format and define
     CFBundleURLTypes
     
      
       CFBundleURLSchemes
       
             org.aerogear.Shoot
       
      
     
    
    This URL has to be unique (making it match your bundle id ensures unicity) and has to match OAuth2 server side configuration.


    Google sharing

    Let's start by implementing sharing with Google. In Shoot/shoot/ViewController.swift go to shareWithGoogleDrive:
    func shareWithGoogleDrive() {
            let googleConfig = GoogleConfig(
                clientId: "873670803862-g6pjsgt64gvp7r25edgf4154e8sld5nq.apps.googleusercontent.com",
                scopes:["https://www.googleapis.com/auth/drive"])
            
            let gdModule =  OAuth2Module(config: googleConfig)
            var http = Http()
            http.authzModule = gdModule
            
            gdModule.requestAccess { (response:AnyObject?, error:NSError?) -> Void in
                let filename = self.imageView.accessibilityIdentifier;
                let multiPartData = MultiPartData(data:UIImageJPEGRepresentation(self.imageView.image, 0.2),
                    name: "image",
                    filename: filename,
                    mimeType: "image/jpg")
                http.POST("https://www.googleapis.com/upload/drive/v2/files", parameters: ["data": multiPartData], completionHandler: {(response, error) in
                    if (error != nil) {
                        println("Error uploading file: \(error)")
                    } else {
                        println("Successfully uploaded: " + response!.description)
                    }
                })
            }
        }
    

    In line 3 and 4, we use the client id (associated to Shoot Google app) and we specify the scope, here we share with google drive.

    Line 6 and 7, we create an OAuth2 module. By default this module will create a TrustedSessionStorage to permanently store your tokens. Therefore every time your open your shoot app you can share photos without having to grant access everytime (see Notes below on iOS settings pre-requisites). You can choose a less secure MemorySessionStorage but each time you close your app and reopen it you will be prompted the first time to grant access.

    Line 7 we initialize an http object and inject it the OAuth2 module.

    In line 9, we actually request access. This method checks if the OAuth2Session (here stored in keychain) contains non-expired access token. If there is no token, it will go through the authorization code grant. If the token is expired (which happens every hour), a refreshed token will be asked transparently without any prompt.

    NOTES: System requirement iOS8. Because this demo securely stores OAuth2 tokens in your iOS keychain, we've chosen to use WhenPasscodeSet policy for TrustedSessionStorage as a result to run this app you need to have your passcode set. For more details see WhenPasscodeSet blog post and Keychain and WhenPasscodeSet blog post.

    Implicit http call

    An even easier way to go, is to use aerogear-ios-http implicit grant. In the previous example we explicitly call requestAccess method. In Shoot/shoot/ViewController.swift change shareWithGoogleDrive by:
    func shareWithGoogleDrive() {
           let googleConfig = GoogleConfig(
               clientId: "873670803862-g6pjsgt64gvp7r25edgf4154e8sld5nq.apps.googleusercontent.com",
               scopes:["https://www.googleapis.com/auth/drive"])
            let gdModule = AccountManager.addGoogleAccount(googleConfig)
            self.http.authzModule = gdModule
            self.http.POST("https://www.googleapis.com/upload/drive/v2/files", parameters:  self.extractImageAsMultipartParams(), completionHandler: {(response, error) in
                if (error != nil) {
                    self.presentAlert("Error", message: error!.localizedDescription)
                } else {
                    self.presentAlert("Success", message: "Successfully uploaded!")
                }
            })
       }
    

    In line 5, we use AccountManager to create an OAuth2 Module an factory method to create OAuth2 module.

    In line 7, we just post our image to Google without having ask for access. POST method underneath checks if an OAuth2 module is plugged to http and will make the right call for you:
  • either start authz code grant
  • or refresh access code if needed
  • or simply run the POST if all tokens are already available

  • OAuth2 with Keycloak

    Ready to build your own social network app, let's use Keycloak and build OAuth2 protected service...

    First of all, you can download Keycloak all appliance distribution.

    Then, clone Shoot backend repo:


    git clone https://github.com/aerogear/aerogear-backend-cookbook
    cd aerogear-backend-cookbook/Shoot


    Following README instructions, import shoot-realm into Keycloak, your admin console should look like:



    NOTE: Here too the redirect URI matches our bundle id. For Keycloak, you can put whatever you want but as we have Google config using bundle ID, let's reuse :)

    Similar to the other providers, you can create your Keycloak OAuth2 module using AccountManager, simply use addAccount with the class type of your OAuth2 module, as shown below:
    func shareWithKeycloak() {
            println("Perform photo upload with Keycloak")
            
            var keycloakConfig = Config(base: "http://localhost:8080/auth",
                authzEndpoint: "realms/shoot-realm/tokens/login",
                redirectURL: "org.aerogear.Shoot://oauth2Callback",
                accessTokenEndpoint: "realms/shoot-realm/tokens/access/codes",
                clientId: "shoot-third-party",
                refreshTokenEndpoint: "realms/shoot-realm/tokens/refresh",
                revokeTokenEndpoint: "realms/shoot-realm/tokens/logout")
    
            let gdModule = AccountManager.addAccount(keycloakConfig, moduleClass: KeycloakOAuth2Module.self)
            self.http.authzModule = gdModule
            self.performUpload("http://localhost:8080/shoot/rest/photos", parameters: self.extractImageAsMultipartParams())
        }

    And that's it! Go and check uploaded pictures in shoot web-app:



    Any feedback please drop us a line on AeroGear mailing list or contact Keycloak mailing list for more in-depth question on OAuth2/SSO server.

    Happy OAuth2!