The highly anticipated Apple Watch opens up a rare opportunity to be one of the first developers on a new platform that is almost guaranteed to be a hit with consumers. Apple has recently released the WatchKit Beta, allowing developers to start making apps before the official hardware is released. One of the key features of WatchKit at the moment is its dependence on the host iOS app to do most of the data processing work. In this talk, Natasha of NatashaTheRobot.com shared advanced techniques for sharing data between your iOS app and your WatchKit Extension and how to best architect your project for this purpose. (The demo code from this talk can be found in full on GitHub.)
Overview (0:00)
When you’re developing for the Apple Watch, you want to keep mind the user on the go. They need information and they need it right now. How can you best architect your app to provide that information very easily without having the user do too much work? As you think about architecting your app, keep in mind how to make it most convenient for your user to live with the watch as more like a helping device, rather than something to keep staring at all the time.
The WatchKit SDK is still very basic, and the hardest part is getting information from your iPhone app to the watch and sharing data in between the two. Most of the code logic happens on the phone, and things on your watch will be very static. The basic architecture you’ll be working with is having most of the code in the WatchKit extension and having to share data between your devices.
NSUserDefaults (3:12)
NSUserDefaults is a quick way to share information. It’s good for just storing various small pieces of data, like usernames and profile information, which you can quickly access and populate. If you want to use UserDefaults, use it for something very static that the user doesn’t have to think about changing.
You’ll need to set up App Groups so that the devices can share information through a shared container. Make sure to do it both on the extension and your iOS target. You’re basically creating a little App Group identifier common to the devices so they can both access it. If you need to delete it, you can go through a similar process on developer.apple.com.
You use the defaults with the App Group name you’ve created, and you basically set the object for a specific key. On the iPhone, the user enters the text, clicks save, and the text is stored in the UserDefaults shared between the apps. On the watch up, you get the defaults from the App Group and then you can update your watch display.
// on the iPhone app
let defaults = NSUserDefaults(suiteName: "group.com.natashatherobot.userdefaults")
let key = "userInput"
override func viewDidLoad() {
super.viewDidLoad()
textLabel.text = defaults?.stringForKey(key) ?? "Type Something..."
}
@IBAction func onSaveTap(sender: AnyObject) {
let sharedText = textField.text
textLabel.text = sharedText
defaults?.setObject(sharedText, forKey: key)
defaults?.synchronize()
}
// WatchKit
class InterfaceController: WKInterfaceController {
@IBOutlet weak var textLabel: WKInterfaceLabel!
let defaults = NSUserDefaults(suiteName:
"group.com.natashatherobot.userdefaults")
var userInput: String? {
defaults?.synchronize()
return defaults?.stringForKey("userInput")
}
NSFileCoordinator (8:09)
For larger pieces of data, NSFileCoordinator is a way to manage files in a shared space that both the app and the watch extension can access. This is good if you have a finite list, and also works for images.
The following example is a very simple to-do list app, where you add a task in your phone and that data is transferred to the WatchKit extension and shows up on the watch. Your viewController needs to conform to the NSFile Presenter protocol, which is pretty simple. You have just two required methods, with other things that you’ll be able to do if you want. The FilePresenter protocol has a presented item URL, which is the part where you’re using your App Group identifier. With the URL, you’re creating a file in that directory where the file will be stored. You have an operation queue so you can have different multithreading if you wanted.
There’s also a delegate method, presentedItemDidChange, in FilePresenters that tells you if an item changed, so you can update your app without having the user press an update button anywhere. However, there’s a bug with NSFileCoordinator and NSFilePresenter that prevents it from being used in extensions. For more information, see this blogpost on Natasha’s site.
With the FileCoordinator, you write to a file in the to-do items array. You can archive and un-archive items to the array to write to and read from the file, and then you can populate a table with the items from the file. One thing to note is that if you have a delete function, where both your watch extension and iPhone app can modify the file, you might have issues with threading.
// iPhone app
private func saveTodoItem(todoItem :String) {
// write item into the todo items array
if let presentedItemURL = presentedItemURL {
fileCoordinator.coordinateWritingItemAtURL(presentedItemURL, options: nil, error: nil)
{ [unowned self] (newURL) -> Void in
self.todoItems.insert(todoItem, atIndex: 0)
let dataToSave = NSKeyedArchiver.archivedDataWithRootObject(self.todoItems)
let success = dataToSave.writeToURL(newURL, atomically: true)
}
}
}
// in the Watch
// MARK: Populate Table From File Coordinator
private func fetchTodoItems() {
let fileCoordinator = NSFileCoordinator()
if let presentedItemURL = presentedItemURL {
fileCoordinator.coordinateReadingItemAtURL(presentedItemURL, options: nil, error: nil)
{ [unowned self] (newURL) -> Void in
if let savedData = NSData(contentsOfURL: newURL) {
self.todoItems = NSKeyedUnarchiver.unarchiveObjectWithData(savedData) as [String]
self.populateTableWithTodoItems(self.todoItems)
}
}
}
}
Frameworks (14:01)
“If the code appears more than once, it probably belongs in a framework.”
-WWDC 2014, Building Modern Frameworks
Frameworks are great for business logic, Core Data, and reuseable UI components. As said at WWDC, you can put repeated code into a framework. In the FileCoordinator example, we had code that appeared twice when getting the file, reading it, and writing to it. That could be extracted into a framework. Creating a framework is really easy: you create a new target, select Cocoa Touch framework, and you name it. It will automatically link it for you in your iOS app, so don’t forget to link the framework in your WatchKit extension.
One key thing, especially for Swift, is to think of a framework as an API. It needs to be public, since it’s an interface that both the iOS app and the WatchKit extension are using. So, if you’re creating a class, make sure to include the “public” keyword. Then in both the phone and watch apps, you can import the framework to have access to whatever is public in there.
import WatchKit
import MySharedDataLayer
class InterfaceController: WKInterfaceController {
@IBOutlet weak var favoriteThingsLabel: WKInterfaceLabel!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let myFavoriteThings = MySharedData().myFavoriteThings
let favoriteThingsString = ", ".join(myFavoriteThings)
favoriteThingsLabel.setText("My favorite things are \(favoriteThingsString)")
}
}
Keychain Sharing (19:12)
Keychain sharing is really for more secure data. When you need a lot more security than UserDefaults provides, you can start using keychain sharing to keep your information secure and shareable across different extensions.
One big issue with WatchKit currently is that there’s no authentication mechanism. Apple has sample code for KeychainIteamWrapper, but the API is old enough that it doesn’t have ARC support. I’d recommend using this version, which has been ARCified and has a cleaner interace.
The key here is to instantiate a KeychainItemWrapper with an access group. This is a similar concept to App Groups, where you have that shared space between the devices. You’ll need the keychain in both the iOS and the WatchKit extension to access the user’s data. With the key-value store type of system, you set a username and password and create the same type of keychain item between the devices using the same identifier. In this example, just to show how it works, when the user puts in their username and password, the WatchKit extension will show that information.
// iPhone app
@IBAction func onSaveTap(sender: AnyObject) {
let username = usernameTextField.text
let password = passwordTextField.text
let keychainItem = KeychainItemWrapper(identifier: "SharingViaKeychain", accessGroup: "W6GNU64U6Q.com.natashatherobot.SharingViaKeychain")
// WatchKit extension
let keychainItem = KeychainItemWrapper(identifier: "SharingViaKeychain", accessGroup: "W6GNU64U6Q.com.natashatherobot.SharingViaKeychain")
let passwordData = keychainItem.objectForKey(kSecValueData) as NSData
let password = NSString(data: passwordData, encoding: NSUTF8StringEncoding)
let username = keychainItem.objectForKey(kSecAttrAccount) as? String
Q&A (24:52)
Q: If we have to make a call through the WatchKit extension in the iPhone app, do we still need to use shared credentials within that?
Natasha: It depends on how you want to structure it, but yes, you still need to have an app group to share keychain data with the actual iPhone app. If you want the extension to have access to the API toke stored in the keychain, it would have to be in the same app group as the iOS app for the keychain to work.
Q: Is any of the communication between the iPhone app and the Watch app going to be encrypted?
Natasha: I don’t know. Information is sent over Bluetooth, so I don’t know if Bluetooth LE is secure automatically. Ideally, you wouldn’t send the information to the watch. The extension itself wouldn’t be doing the API call.
Q: If Apple later provides a better API within the watch itself, is there any expectation that what we develop in the extension will work?
Natasha: I don’t know what Apple will do. There’s supposed to be a native SDK, but it should be very lightweight because the watch is tiny.
Q: Is there a way in which, if I tap something on the watch, it can invoke something and open up an app on the iPhone?
Natasha: Currently you cannot physically open the app, but you can send a quick burst of data. In your app delegate, there’s a new call “handleWatchKitExtension” where you can quickly send a little dictionary or string. It’s a very quick connection, and in the simulator it’s misleading because it works every time, but when it’s via Bluetooth, it might not always work.
Q: What are some of the first challenges that you encountered when you first started using WatchKit and what advice do you have for those of us who haven’t delved into it yet?
Natasha: The first thing I had a problem with was getting the simulator to run, because in the video they very briefly mention having to go to the hardware section and choosing to display the watch. Other things lke getting data was kind of the bigger challenge. I think the UI is pretty straightfoward, but very limited. For example, you can’t get the string from a label, you can only set the text on it.
Q: Do you have any experience with local notifications on WatchKit?
Natasha: I played with the remote and local notifications, but I haven’t heard it anyone has ever got the local ones to work.
Q: Do you know of any other shared containers that are evented?
Natasha: You can actually have different queues in the File Coordinator. There’s a lower level API for file sharing, which I haven’t really used, but you can even send bytes or data.
Q: Currently animation is basically flip card, so there’s a limit of 20 megabytes per application for images. When do you think we’ll get real animation support? Can you develop Swift-based applications that will on iPad, iPhone, and the watch?
Natasha: I can’t answer for Apple, but according to their WWDC video, you can even draw on the watch. I guess they’ll have to deliver if they showed it in their video. You can also make a universal app with the WatchKit extension, and all my code is in Swift so you can do it in Swift or Objective-C.
Q: What do we know about WatchKit and CoreData (or Realm)?
Natasha: It’s just an extension, so I would put it in a framework and then you can just use it like you normally would. I don’t know if there’s anything special that you’ll need to do. Tim: There’s process and multiprocess codes that can be a little tricky, but for the most part you can make it work. Realm is currently working on that.
Receive news and updates from Realm straight to your inbox