Passing data back from watchOS interfaceController using block and segue
Passing data back and forth between interfaceControllers is not so simple. There is segue process in WatchKit but the first problem is that there is no prepareForSegue and you couldn't reach segue's destinationViewController, so you couldn't inject stuffs easily to the new controller (WatchOS 3 - 4).
In the backward direction there is no exit so you couldn't reach the unwind segue.
Another problem is that these solutions try to update the data and the user interface of the of the first interfaceController in the willActivate method which is fired any time the watch screen awake - so quite frequently - and this could cause problems and it's complicate.
The programming practice is mainly using delegate and injecting self using the context of the segue, as the above answers describe.
But using delegate is a little bit complicate so I use blocks which is more contemporary and I think better and more elegant.
Let's see how:
First let's prepare the segue in the Interface Builder of the Apple Watch's storyboard, just connect a button with another interfaceController pushing Ctrl button and name the segue.

then in the .h file of the source interfaceController lets's name it SourceInterfaceController.h declare a property for the block:
@property (nonatomic, strong) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
then use contextForSegueWithIdentifier: to transfer the block or any other data to the destination interfaceController using the segueIdentifier if you have more segues.
This Apple method actually use a (id)context as a return object which could be any object and the destination interfaceController's awakeWithContext:(id)context method will use it when the the interfaceController launches.
So let's declare the block in SourceInterfaceController.m then pass it to the context:
- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier {
__unsafe_unretained typeof(self) weakSelf = self;
if ([segueIdentifier isEqualToString:@"MySegue"]) {
self.initNewSessionBlock = ^BOOL (NSDictionary *mySegueDict, NSError *error)
{
[weakSelf initNewSession];
NSLog(@"message from destination IC: %@", realTimeDict[@"messageBack"]);
return YES;
};
return self.initNewSessionBlock;
}
else if ([segueIdentifier isEqualToString:@"MyOtherSegue"]) {
self.otherBlock = ^BOOL (NSString *myText, NSError *error)
{
//Do what you like
return YES;
};
return self.otherBlock;
}
else {
return nil;
}
}
If you'd like to transfer any more data than just the block with the context to the destination interfaceController, just wrap them in a NSDictionary.
In the destination interfaceController name it DestinationInterfaceController.h let's declare another property to store the block using any name but the same variable declaration
@property (copy) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
then fetch the block from the context in DestinationInterfaceController.m:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.initNewSessionBlock = context;
}
Later in DestinationInterfaceController.m just trigger the block, for example in an action method with a button:
- (IBAction)initNewSessionAction:(id)sender {
NSError *error = nil;
NSDictionary *realTimeDict = @{@"messageBack" : @"Greetings from the destination interfaceController"};
BOOL success = self.initNewSessionBlock(realTimeDict, error);
if (success) {
[self popController];
}
}
The block will be executed any method of the source interfaceController using the data in the scope of the destination interfaceController, so you can send data back to the destination sourceController.
You can pop the interfaceController using popController if everything is ok and the block return yes as a BOOL.
Note: Of course you can use any kind of segue whether it's a push or modal and you can use pushControllerWithName:context: too to
trigger the segue, and you can use this method's context in the same way.