WatchKit Extension and iOS App File and Data Sharing - A Tutorial

From Techotopia
Jump to: navigation, search
PreviousTable of ContentsNext
Sharing Data Between a WatchKit App and the Containing iOS AppConfiguring Preferences with the WatchKit Settings Bundle


Purchase the fully updated watchOS 2/Swift 2 edition of this 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 eBook Buy Print


The objective of this chapter is to put the theory from the previous chapter into practice by creating a project that makes use of app groups to implement a simple example of sharing both file content and default data between a WatchKit extension and the containing iOS app.




About the App Group Sharing Example

The project created in this chapter will consist of an iOS app in which the user can enter text and change the setting of a switch control. On selection of a “save” button, the text entered by the user will be saved to a file in a shared container and the current switch setting stored using shared user defaults.

The companion WatchKit app will present a Label and a Switch object configured to display the same text and switch setting as those entered by the user on the iOS app. Changes made to the Switch object in the WatchKit app will also be stored and used by the iOS app.

Creating the Sharing Project

Start Xcode and create a new iOS project. On the template screen choose the Application option located under iOS in the left hand panel and select Single View Application. Click Next, set the product name to SharingData, enter your company identifier and make sure that the Devices menu is set to Universal. Before clicking Next, change the Language menu to Swift. On the final screen, choose a location in which to store the project files and click on Create to proceed to the main Xcode project window.


Designing the iOS App User Interface

Locate and select the Main.storyboard file in the Project Navigator panel and drag and drop a Text View, Switch and Button object onto the scene canvas. Configure and position the views so that the layout of the user interface matches that of Figure 15-1:


The layout of the parent iOS app

Figure 15-1


Having designed the layout, click on the white background of the scene so that no views are selected. Display the Resolve Auto Layout Issues menu and select the Reset to Suggested Constraints option listed under All Views in View Controller as shown in Figure 15-2:


Setting layout constraints

Figure 15-2


Finally, double click on the Text View object so that the Latin text highlights before tapping the keyboard delete key to remove the sample text.

Connecting Actions and Outlets

With the scene still displayed in Interface Builder, open the Assistant Editor and verify that it is displaying the content of the ViewController.swift file. Ctrl-click on the Text View object in the scene and drag the resulting line to a position beneath the class declaration line in the ViewController.swift file. Release the line and establish an outlet connection named textView.

Repeat the above steps to establish an outlet from the Switch object named mySwitch. Finally, Ctrl-click on the Button view, drag to a position beneath the viewDidLoad method and establish an action connection to a method named saveData. On completion of these steps, the ViewController.swift file should read as follows:

import UIKit

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    @IBOutlet weak var mySwitch: UISwitch!
    @IBOutlet weak var textView: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func saveData(sender: AnyObject) {
    }
.
.
.
}

Creating the App Group

In order for the iOS app and WatchKit extension to be able to share files and data they must both be enrolled as members of the same app group. To do this, begin by selecting the SharingData target located at the top of the project navigator and clicking on the Capabilities tab in the main panel. Within the Capabilities panel, locate the App Groups section and move the switch to the On position. When prompted, select an Apple Developer account to be associated with the app group.

When app groups have been enabled in the Capabilities screen, any existing app groups associated with your Apple developer account will be listed.

To add a new app group to your account, simply click on the + button and enter the new app group name, for example:

group.com.ebookfrenzy.SharingData

Add the current app to the newly added app group by enabling the checkbox next to the group name.

Performing Initialization Tasks

When the iOS app launches it will need to obtain a reference to the default file manager and then use that to get the URL of the shared container. Using this URL, the full path to the file that will be shared between the iOS app and the WatchKit extension will need to be constructed. These tasks can be performed in the viewDidLoad method as follows (noting that the app group identifier will need to be changed to match the one you created in the previous section):

class ViewController: UIViewController {

    @IBOutlet weak var mySwitch: UISwitch!
    @IBOutlet weak var textView: UITextView!

    var sharedFilePath: String?
    var sharedDefaults: NSUserDefaults?
    let fileManager = NSFileManager.defaultManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        let sharedContainer = fileManager
		.containerURLForSecurityApplicationGroupIdentifier(
			"<YOUR APP GROUP IDENTIFIER HERE>")
        
        let dirPath = sharedContainer?.path
        sharedFilePath = dirPath?.stringByAppendingPathComponent(
					"sharedtext.doc")
    }
.
.
.
}

Next, the viewDidLoad method needs to check if the sharedtext.doc file already exists in the shared container and, if so, read the content of the file into a data buffer before displaying it to the user via the Text View:

override func viewDidLoad() {
    super.viewDidLoad()

    let sharedContainer = fileManager
		.containerURLForSecurityApplicationGroupIdentifier(
			"<YOUR APP GROUP IDENTIFIER HERE>")
        
    let dirPath = sharedContainer?.path
    sharedFilePath = dirPath?.stringByAppendingPathComponent(
			"sharedtext.doc")

    if fileManager.fileExistsAtPath(sharedFilePath!) {
        let databuffer = fileManager.contentsAtPath(sharedFilePath!)
        textView.text = NSString(data: databuffer!, 
		encoding: NSUTF8StringEncoding) as! String
    }
}

Finally, the method needs to check for the presence of a user default value for the key “switch” and, in the event that a default exists, set the state of the Switch view accordingly:

override func viewDidLoad() {
    super.viewDidLoad()

    let sharedContainer = fileManager
		.containerURLForSecurityApplicationGroupIdentifier(
			"<YOUR APP GROUP IDENTIFIER HERE>")
        
    let dirPath = sharedContainer?.path
    sharedFilePath = dirPath?.stringByAppendingPathComponent(
			"sharedtext.doc")

    if fileManager.fileExistsAtPath(sharedFilePath!) {
        let databuffer = fileManager.contentsAtPath(sharedFilePath!)
        textView.text = NSString(data: databuffer!, 
		encoding: NSUTF8StringEncoding)
    }

    sharedDefaults = NSUserDefaults(
	suiteName: "<YOUR APP GROUP IDENTIFIER HERE>")

    let switchSetting = sharedDefaults?.boolForKey("switch")

    if let setting = switchSetting {
        mySwitch.on = setting
    }
}

Purchase the fully updated watchOS 2/Swift 2 edition of this 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 eBook Buy Print

Saving the Data

The next step in implementing the iOS app is to write the code for the saveData method. Locate this method in the ViewController.swift file and modify it to read as follows:

@IBAction func saveData(sender: AnyObject) {
    let databuffer = (textView.text 
		as NSString).dataUsingEncoding(NSUTF8StringEncoding)

    if fileManager.fileExistsAtPath(sharedFilePath!) {
        databuffer?.writeToFile(sharedFilePath!, atomically: true)
    }  else {
        fileManager.createFileAtPath(sharedFilePath!, contents: databuffer, attributes: nil)
    }
    sharedDefaults?.setBool(mySwitch.on, forKey: "switch")
}

The method begins by encoding the text entered into the Text View into an NSData object and writing that data to the shared file. Next, the switch setting is saved to the shared defaults storage as a Boolean value.

Compile and run the application. Once running, enter some text into the Text View and change the setting of the Switch view. Stop the app from the Xcode toolbar and then re-launch it. On restarting, both the text and switch mode should have been preserved.

With the iOS app functioning as expected the next step is to add and implement the WatchKit app.

Adding the WatchKit App Target

Within Xcode, select the File -> New -> Target… menu option. In the target template dialog, select the Apple Watch option listed beneath the iOS heading. In the main panel, select the WatchKit App icon and click on Next. On the subsequent screen, turn off the Include Glance Scene and Include Notification Scene options before clicking on the Finish button.

As soon as the extension target has been created, a new panel will appear requesting permission to activate the new scheme for the extension target. Activate this scheme now by clicking on the Activate button in the request panel.

Adding the WatchKit App to the App Group

With the iOS app added to the app group, the WatchKit extension must also be added as a member of the same group in order to gain access to the shared container. To access the capability settings for the WatchKit extension, use the menu located in the top left-hand corner of the Capabilities panel as indicated in Figure 15-3:


The Xcode Capabilities target menu

Figure 15-3


When clicked, this menu will present a list of targets contained within the current project as shown in Figure 14 5, one of which will be the SharingData WatchKit Extension. Select this option and repeat the steps followed for the iOS app to enable membership in the same app group as that configured for the iOS app.

Designing the WatchKit App Scene

Select the Interface.storyboard file located under SharingData WatchKit App so that the storyboard loads into Interface Builder. Drag and drop a Label and a Switch from the Object Library onto the scene. Select the Label object and use the Attributes Inspector to change the Horizontal position property to Center. On completion of these steps, the scene layout should match that shown in Figure 15-4:

The WatchKit app layout

Figure 15-4


The final task is to allow the label to display more than a single line of text. With the Label object selected, increase the Lines property to 8.

Adding the WatchKit App Actions and Outlets

Display the Assistant Editor and verify that it is displaying the InterfaceController.swift file. Establish outlet connections from the Label and Switch objects named watchLabel and watchSwitch respectively. Also establish an action connection on the Switch object so that a method named switchChanged is called when the user changes the switch setting. On completion of these steps the InterfaceController.swift file should read as follows:

import WatchKit
import Foundation

class InterfaceController: WKInterfaceController {

    @IBOutlet weak var watchLabel: WKInterfaceLabel!
    @IBOutlet weak var watchSwitch: WKInterfaceSwitch!

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        // Configure interface objects here.
    }

    @IBAction func switchChanged(value: Bool) {
    }
.
.
}

Performing the WatchKit App Initialization

The WatchKit extension will need to perform many of the same tasks as the iOS app in order to load the text from the shared file and identify the current switch setting. These tasks can best be performed in the awakeWithContext method. Locate this method in the InterfaceController.swift file and modify it to read the content of the sharedtext.doc file and display it on the Label object (noting that the app group identifier will once again need to be changed to match the one you created).

class InterfaceController: WKInterfaceController {

    @IBOutlet weak var watchLabel: WKInterfaceLabel!
    @IBOutlet weak var watchSwitch: WKInterfaceSwitch!

    var sharedDefaults: NSUserDefaults?

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        let fileManager = NSFileManager.defaultManager()

        let sharedContainer = fileManager
           .containerURLForSecurityApplicationGroupIdentifier(
                "<YOUR APP GROUP IDENTIFIER HERE>")

        let dirPath = sharedContainer?.path
        let sharedFilePath = 
            dirPath?.stringByAppendingPathComponent("sharedtext.doc")

        if fileManager.fileExistsAtPath(sharedFilePath!) {
            let databuffer = 
		   fileManager.contentsAtPath(sharedFilePath!)
            watchLabel.setText(NSString(data: databuffer!, 
		   encoding: NSUTF8StringEncoding) as? String)
        }
    }
.
.
}

Next, add the code to access the shared user defaults using the “switch” key to obtain the Boolean switch setting value:

class InterfaceController: WKInterfaceController {

    @IBOutlet weak var watchLabel: WKInterfaceLabel!
    @IBOutlet weak var watchSwitch: WKInterfaceSwitch!

    var sharedDefaults: NSUserDefaults?

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)

        let fileManager = NSFileManager.defaultManager()

        let sharedContainer = fileManager
           .containerURLForSecurityApplicationGroupIdentifier(
                "group.com.payloadmedia.SharingData")

        let dirPath = sharedContainer?.path
        let sharedFilePath = 
            dirPath?.stringByAppendingPathComponent("sharedtext.doc")

        if fileManager.fileExistsAtPath(sharedFilePath!) {
            let databuffer = 
		   fileManager.contentsAtPath(sharedFilePath!)

            watchLabel.setText(NSString(data: databuffer!, 
		   encoding: NSUTF8StringEncoding) as? String)
        }

        sharedDefaults = NSUserDefaults(suiteName: 
		"<YOUR APP GROUP IDENTIFIER HERE>")

        if let setting = sharedDefaults?.boolForKey("switch") {
            watchSwitch.setOn(setting)
        }
    }
.
.
}

Implementing the switchChanged Method

The final task required to complete the project is to add the code to the switchChanged method so that changes to the switch are stored in the shared user defaults. Remaining in the InterfaceController.swift file, locate the stub method created by the Assistant Editor and modify it so that it reads as follows:

@IBAction func switchChanged(value: Bool) {
    sharedDefaults?.setBool(value, forKey: "switch")
}

When the user changes the switch setting this method is called and passed as a parameter a Boolean value indicating the new switch state. The code added to the method stores this new value into the shared defaults storage using the “switch” key.

Testing the Project

Use the run target menu in the Xcode toolbar to select the SharingData iOS app target together with a suitable device or emulator as shown in Figure 15-5:


Selecting the run target

Figure 15-5

When the iOS app launches, enter new text into the Text View, change the switch setting and click the Save button. Change the run target menu to SharingData WatchKit App and click the run button to invoke the WatchKit app. When the app appears it should do so displaying the text entered into the iOS app and the current switch state. Within the WatchKit app, change the switch setting, re-launch the iOS app and verify that the switch state change has been picked up by the switch view.

Summary

This chapter has put the theory of the previous chapter into practice through the creation of a project that utilizes app groups to share file and user defaults data between an iOS app and a WatchKit extension.


Purchase the fully updated watchOS 2/Swift 2 edition of this 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 eBook Buy Print



PreviousTable of ContentsNext
Sharing Data Between a WatchKit App and the Containing iOS AppConfiguring Preferences with the WatchKit Settings Bundle