watchOS

watchOS 2 Tutorial: Transferring images using transferFile (Watch Connectivity #3)

Onto part 3 of my Watch Connectivity series. We’ll be looking at the transferFile API today – in particular, we’ll be transferring an image from the watch to the phone, but the same API can be used to transfer data and other media files. This will likely be the last of the watch connectivity series – I’m looking to explore ClockKit in the future and I would love to hear any other suggestions for tutorial ideas.

dog transfer

Anyway, let’s get started. Here are the final projects in both Swift and Objective-C.

We’ll be using this adorable picture of Ghost and Boo, two of my favorite dogs in the world <3 (check out their Instagram!)dogs

Create a new watch application project (iOS app with WatchKit app) and drag and drop this picture into the watch extension folder (but not the assets folder). We will not be using the image assets folder in this case since we cannot access the file directly when it is in that folder. Most circumstances will probably call for data/files to be loaded using a network request so this is really just a shortcut to getting the media file and not what developers will likely be doing regularly.

Interface.storyboard

We’ll start with the watch storyboard. This one is fairly straightforward. Drag in a button and change the label to “Send dogs” as shown below.

watch interface

Open up the split view editor and control-drag an IBAction outlet into the InterfaceController.swift/InterfaceController.m files with sendDogs as the function name. Close the split view and focus on the InterfaceController.swift/InterfaceController.m next.

InterfaceController.swift/InterfaceController.m

First, we’ll setup Watch Connectivity. Import Watch Connectivity, add the WCSessionDelegate and initialize the session. You can see how to do this below:

//Swift

import WatchKit
import Foundation
import WatchConnectivity

class InterfaceController: WKInterfaceController, WCSessionDelegate {
    private let session : WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
    
    override init() {
        super.init()
        
        session?.delegate = self
        session?.activateSession()
    }
    
    @IBAction func sendDogs() {
    }
}

//Objective-C

#import "InterfaceController.h"
#import <WatchConnectivity/WatchConnectivity.h>

@interface InterfaceController() 
@property (weak, nonatomic) WCSession *session;
@end

@implementation InterfaceController

-(instancetype)init {
    self = [super init];
    
    if (self) {
        if ([WCSession isSupported]) {
            self.session = [WCSession defaultSession];
            self.session.delegate = self;
            [self.session activateSession];
        }
    }
    
    return self;
}

- (IBAction)sendDogs {
}

Like the rest of the Watch Connectivity APIs, we must first check if a WCSession is supported before we can initialize and activate it. Next, let’s fill in sendDogs.

//Swift
@IBAction func sendDogs() {
    let filePath = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("dogs", ofType: "png")!)
    self.session?.transferFile(filePath, metadata: nil)
}
//Objective-C
- (IBAction)sendDogs {
    NSURL *filePath = [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:@"dogs" ofType:@"png"]];
    [self.session transferFile:filePath metadata:nil];
}

Here, we’re loading the file path of the dogs image that we dragged into the watchkit extension folder and using the transferFile API to send it through to the parent app on the phone. You can also send a dictionary of metadata through with your file, in case you have any other information needed, but we don’t in this case.

One last thing to add is a way to verify that your file transfer went through successfully. transferFile will notify didFinishFileTransfer when a file transfer has gone through and whether or not there’s an error. It’s optional to implement, but it is especially helpful with testing the transfer and in the case where you want to notify the user if something goes wrong. Add this method to your InterfaceController file:

//Swift
func session(session: WCSession, didFinishFileTransfer fileTransfer: WCSessionFileTransfer, error: NSError?) {
    print("error: ", error)
}
//Objective-C
-(void)session:(WCSession *)session didFinishFileTransfer:(WCSessionFileTransfer *)fileTransfer error:(NSError *)error {
    NSLog(@"error: %@", error);
}

We’ll move to the iOS app next.

Main.storyboard

Add two things to your View Controller interface – an image view and a label that says “loading…” Center both of these elements in the view.

main storyboardIn order to show a correctly proportioned image, we’ll want to change the mode of the image view to Aspect Fit. This will allow the image to maintain its proportions while maximizing the size of the image in the given space (otherwise, you may see some distortion if you leave the mode in its default Scale to Fill mode).

aspect fit

That’s it for the storyboard. Pretty simple interface for this one also. Open up the split view and drag two outlets from interface builder to ViewController.swift/ViewController.m for both the image view and the loading label. I’ve named them imageView and loadingLabel respectively.

ViewController.swift/ViewController.m

Within your ViewController, we’ll want to create another WCSession so that we can receive the file on this device. Similar to what we did in the watch extension, we’ll want to import watch connectivity, add the delegate, and create and activate a session. It should look something like this:

//Swift
import UIKit
import WatchConnectivity

class ViewController: UIViewController, WCSessionDelegate {
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var loadingLabel: UILabel!
    
    private let session : WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        session?.delegate = self
        session?.activateSession()
    }
}
//Objective-C
#import "ViewController.h"
#import <WatchConnectivity/WatchConnectivity.h>

@interface ViewController () 
@property (weak, nonatomic) IBOutlet UILabel *loadingLabel;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) WCSession *session;
@end

@implementation ViewController

-(void)viewDidLoad {
    if ([WCSession isSupported]) {
        self.session = [WCSession defaultSession];
        self.session.delegate = self;
        [self.session activateSession];
    }
}

The last thing we need to do is to add the receiver for the file transfer. This is done through didReceiveFile.

//Swift
func session(session: WCSession, didReceiveFile file: WCSessionFile) {
    dispatch_async(dispatch_get_main_queue()) {
        self.loadingLabel.hidden = true;
        let imageData = NSData.init(contentsOfURL: file.fileURL)
        self.imageView.image = UIImage(data: imageData!)
    }
}
//Objective-C
-(void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.loadingLabel.hidden = YES;
        NSData *imageData = [[NSData alloc] initWithContentsOfURL:file.fileURL];
        self.imageView.image = [[UIImage alloc] initWithData:imageData];
    });
}

Here, we’re hiding the loading label when we get the file so that it doesn’t show on the screen any longer. After that, we’re converting the file data back into an image by pulling the image data out of the fileURL and setting that as the image to be displayed. That’s it! Run the app and you should see two happy faces smiling back at you 🙂

Please leave any questions and comments below.

5 thoughts on “watchOS 2 Tutorial: Transferring images using transferFile (Watch Connectivity #3)”

  1. Very good tutorial but i m struggeling to undestand how to initialise watch app with data already stored in my ios app.Such as display my user’s email and have it display on the watch.

Leave a Reply

Your email address will not be published. Required fields are marked *