An Introduction to Watch Connectivity in watchOS 2

From Techotopia
Revision as of 19:55, 27 October 2016 by Neil (Talk | contribs) (Text replacement - "<table border="0" cellspacing="0">" to "<table border="0" cellspacing="0" width="100%">")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
PreviousTable of ContentsNext
Handling User Input in a watchOS 2 WatchKit AppA watchOS 2 WatchConnectivity Messaging Tutorial


Purchase the full edition of this watchOS 2 App Development Essentials book in eBook ($12.99) or Print ($27.99) format
watchOS 2 App Development Essentials Print and eBook (ePub/PDF/Kindle) editions contain 35 chapters.

Buy Print


When watchOS 1 was introduced it provided a minimal level of support for the implementation of communication between an iOS app running on an iPhone and the corresponding WatchKit app running on a paired Apple Watch. The shortcomings of watchOS 1 have been addressed in watchOS 2 with the introduction of the WatchConnectivity framework.

As will be outlined in this chapter, the WatchConnectivity framework provides a range of options for implementing bi-directional communication between WatchKit and iOS apps including the transfer of files and data and the exchange of interactive messages.


Contents


Watch Connectivity Communication Options

Watch Connectivity provides four different ways in which an iOS app and the corresponding WatchKit app can communicate:

Application Context Mode

Application context mode allows small amounts of data contained in a dictionary object to be passed between one app and another. The data is transferred in the background and delivered to the receiving app via a delegate method the next time the receiving app runs. The time at which the background transfer occurs is dependent upon the operating system based on factors such as battery status and device activity.

If more recent data is sent by the sending app before the background transfer has taken place, the older data is overwritten by the new data. This ensures that when the transfer takes place, only the most recent data will be delivered to the receiving app.


User Information Transfer Mode

User information transfer mode is similar to application context mode in that it facilitates the transfer of small amounts of dictionary based data between apps. As with application context mode, transfers are performed in the background subject to timing decided by the system. Unlike application context mode, however, user information transfers are placed in a delivery queue so that each message will be received by the destination app.

File Transfer Mode

As the name suggest, file transfer mode allows files to be transferred between apps. Transfers are performed in the background and placed in a temporary directory on the destination device from which they must be moved to avoid subsequent deletion by the system.

Interactive Messaging Mode

Interactive messaging mode allows messages to be sent immediately to the receiving app. The receiving app is notified of the message arrival via a delegate method call and passed the message. Messages are delivered in the order in which they are sent.

Before sending an interactive message, the sending app must first check that the destination app is reachable. For a WatchKit app to be reachable it must be installed and currently running on the Apple Watch device. Since the iOS app is launched automatically in the background when an interactive message arrives, it is considered to be reachable even when not currently running.

Interactive messages provide the option to pass through a reply handler which may subsequently be called by the receiving app and used to return data to the sending app.

Interactive messaging supports the transfer of both Dictionary-based data and NSData objects.

WatchConnectivity Session Creation

Regardless of the type of communication to be used, a number of session initialization tasks must be performed prior to making any send requests. It only makes sense to perform these steps, however, if the device on which the app is running supports Watch Connectivity (Watch Connectivity is not, for example, supported when an app is running on an iPad).

Whether or not the device on which an app is running supports Watch Connectivity can be identified by calling the isSupported method of the WCSession class as follows:

import WatchConnectivity
.
.
.
if (WCSession.isSupported()) {
	// Watch Connectivity is supported
}

If Watch Connectivity is supported, the next steps are to obtain a reference to the default connectivity session for the app, designate a class instance to act as the session delegate and then activate the session:

import WatchConnectivity
.
.
.
if (WCSession.isSupported()) {
    let session = WCSession.defaultSession()
    session.delegate = self
    session.activateSession()
}

It is important to note that the class instance designated as the session delegate must conform to the WCSessionDelegate protocol and that it is the methods within this class that will be called by the system to notify the app of incoming WatchConnectivity content and messages.

A key point of which to be aware is that the above initialization steps must be performed on both the iOS and WatchKit app in order for communication to take place.

Obtaining Session State Information

Once a session is active, a variety of properties can be used within the iOS app to obtain additional information relating to the session. It is possible, for example, to verify that the iPhone is currently paired with an Apple Watch:

if session.paired == true {
    // Devices are paired
}

It is also possible to check from within the iOS app whether the companion WatchKit app is installed on the paired Apple Watch device:

if session.watchAppInstalled == true {
    // WatchKit app is installed
}

Two methods may also be implemented within the session delegate class to receive notification of when either the session state or the reachability of an app changes during a communication session:

func sessionReachabilityDidChange(session: WCSession) {
    // Handle change in reachability
}

func sessionWatchStateDidChange(session: WCSession) {
    // Handle session state change
}

When using interactive messaging mode, it is important to verify that the corresponding app is reachable before attempting to send a message:

if session.reachable == true {
    // App is reachable
}

When working with clock face complications, it is also possible to verify whether the complication associated with the WatchKit app is installed in the user’s clock face by checking the complicationEnabled property:

if session.complicationEnabled == true {
    // Complication is enabled
}

The topic of complications will be covered in greater detail beginning with the chapter entitled An Overview of watchOS 2 ClockKit and Apple Watch Complications.

Purchase the full edition of this watchOS 2 App Development Essentials book in eBook ($12.99) or Print ($27.99) format
watchOS 2 App Development Essentials Print and eBook (ePub/PDF/Kindle) editions contain 35 chapters.

Buy Print

The watchDirectoryURL Property

For as long as the WatchKit app is installed on the Apple Watch and the watchAppInstalled session property returns a true value, the watchDirectoryURL session property will contain the path to the watch directory. This is a container on the watch into which can be stored any data needed by the current installation instance of the WatchKit app such as preferences or files waiting to be transferred. The directory will remain in place until the user removes the WatchKit app from the Apple Watch device at which point the directory and all its content will be deleted from the device.

Sending and Receiving Application Context Data

Once the Watch Connectivity session has been established by both the iOS and WatchKit apps, application context data may be packaged into a dictionary object and transferred via a call to the updateApplicationContext method of the default session as demonstrated in the following code fragment:

do {
  let session = WCSession.defaultSession()
  let context = ["FlightTime" : "Delayed"]
  try session.updateApplicationContext(context)

} catch let error as NSError {
    print("Error: \(error.description)")
}

The receiving app will be notified of the arrival of a context update via a call to the session:didReceiveApplicationContext method of the object designated as the delegate during the session initialization process. If the destination app is already running, the method will be called as soon as the transfer is complete. In the event that the app is not running when the context data is transferred, the method will be called next time the app is launched by the user. Passed through as a parameter to this method is the dictionary object containing the context data:

func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
    print("Received context")
    print(applicationContext["FlightTime"])
}

Sending and Receiving User Information Data

User information data is transferred using the transferUserInfo method of the default session instance as follows:

let session = WCSession.defaultSession()
let userInfo = ["NewMessage" : msgTitle]
session.transferUserInfo(userInfo)

The receiving app will be notified of the arrival of user info data via a call to the session:didReceiveUserInfo method of the object designated as the delegate during the session initialization process. If the destination app is already running, the method will be called as soon as the transfer is complete. In the event that the app is not running when the context data is transferred the method will be called next time the app is launched by the user. Passed through as a parameter to this method is the dictionary object containing the user info data. If multiple user info transfers are pending the method is called once for each delivery:

func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
    print("Received user info")
    print(userInfo["NewMessage"])
}

As previously outlined, multiple user info transfers are placed in a queue ready for transfer and then transmitted at a time decided by the system. Details of any transfers that have yet to be completed may be obtained at any time from within the sending app via a call to the outstandingUserInfoTransfers session method as follows:

let pending = session.outstandingUserInfoTransfers() 

A list of pending transfers is returned in the form of an array of WCUserInfoTransfer objects containing the data being transferred and a Boolean value indicating whether the transfer is currently in progress. A call to the cancel method of an object will remove that object from the transfer queue.

Transferring Files

Files are transferred using the transferFile method of the default session and are placed in a queue until the transfer is completed in the background. The transferFile method takes as parameters the URL of the file to be transferred together with an optional dictionary containing metadata. The following code, for example, initiates the transfer of an image file:

let session = WCSession.defaultSession()
let url = NSBundle.mainBundle().URLForResource("myimage", 
		withExtension: "png")

session.transferFile(url!, metadata: nil)

As with user info data mode, if more than one transfer is initiated the transfers are queued by the system. Once a transfer is complete the session:didReceiveFile method of the session delegate is called and passed a WCSessionFile object containing the URL and metadata for the file. If the app is not running when the transfer completes the method is called the next time the app is launched.

The transferred file is placed in the Document/Inbox folder of the destination app and will be deleted once the delegate method returns. As such it is essential that the transferred file be moved to a permanent location as soon as the destination app receives notification of transfer completion. The following sample implementation of the session:didReceiveFile delegate method moves the transferred file to the Document directory:

func session(session: WCSession, didReceiveFile file: WCSessionFile) {
        
    let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, 
		.UserDomainMask, true)
    let docsDir = dirPaths[0] as String
    let filemgr = NSFileManager.defaultManager()

    do {
        try filemgr.moveItemAtPath(file.fileURL.path!, 
		toPath: docsDir + "/myimage.png")
    } catch let error as NSError {
        print("Error moving file: \(error.description)")
    }
}

An array of WCSessionFileTransfer file transfer objects that have yet to be completed may be obtained at any time from within the sending app via a call to the outstandingUserInfoTransfers session method as follows:

let pending = session.outstandingFileTransfers()

The information available within each WCSessionFileTransfer object in the array includes access to the file awaiting transfer and a property indicating whether or not the transfer is currently in progress. A call to the cancel method of such an object will end the transfer and remove the file from the transfer queue.

Sending and Receiving Interactive Messages

Interactive messages can take the form of a dictionary object using the sendMessage method, or an NSData object using the SendMessageData method. In addition to the message content, these methods may also be passed an optional replyHandler reference. This takes the form of a closure containing the code to be called by the receiving app providing a convenient mechanism by which data can be returned to the sending app. The option is also available to pass through an error handler which can be used by the receiving app to return error information. Before attempting to send a message from the iOS app to the WatchKit app, the code should always first check that the destination app is reachable.

The following code demonstrates the use of the sendMessage method, in this case passing through both reply and error handlers:

if WCSession.defaultSession().reachable == true {

    let requestValues = ["command" : "start"]

    let session = WCSession.defaultSession()

    session.sendMessage(requestValues, replyHandler: { reply in

           // Code to handle reply here

        }, errorHandler: { error in

           // Code to handle error here
    })
}

When a message is received, either the session:didReceiveMessage method or the session:didReceiveMessageData method of the session delegate will be called. It is important to note that there are two forms of these methods, one which expects a reply handler and one which does not. Be sure to implement the correct one within the session delegate depending on whether or not the sent message includes a reply handler. The following example method implementation expects the received message to contain a reply handler reference which it subsequently calls to return data to the sending app:

func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {

    var replyValues = ["status" : "playing"] // Data to be returned

    
    // Code to process message here
    
    replyHandler(replyValues) // Return data to sending app
}

Summary

Watch Connectivity is a new framework included with the watchOS 2 SDK designed to enable bi-directional communication between an iOS app and the corresponding WatchKit app. Watch Connectivity supports the background transfer of data and files back and forth between the two apps and also the implementation of interactive messaging between apps.

Having provided an overview of Watch Connectivity, the next chapter (entitled A watchOS 2 WatchConnectivity Messaging Tutorial) will provide a practical example of the use of interactive messaging.


Purchase the full edition of this watchOS 2 App Development Essentials book in eBook ($12.99) or Print ($27.99) format
watchOS 2 App Development Essentials Print and eBook (ePub/PDF/Kindle) editions contain 35 chapters.

Buy Print



PreviousTable of ContentsNext
Handling User Input in a watchOS 2 WatchKit AppA watchOS 2 WatchConnectivity Messaging Tutorial