WCSession sendMessage:replyHandler error code 7014 (WCErrorCodeDeliveryFailed)
Asked Answered
A

14

26

I have a Watch OS 2 application that communicates with the iOS app via WCSession method sendMessage:replyHandler:errorHandler:

The iOS application reply correctly but time to time I get the error with code 7014 of domain WCErrorDomain: "Payload could not be delivered"

It happens more often when the iOS application is not foreground.

I do not find any solution of this problem, I hope one of you know a solution to this problem

Aeroballistics answered 18/10, 2015 at 16:56 Comment(7)
When the error gets returned, does it have any underlying error (error.userInfo[NSUnderlyingErrorKey])? It'd be good to see the code that sends the message and the implementation of the delegate method that should be receiving it!Swane
Hi, did you fixed this issue?Shawn
Hi, no I'm still looking for a solutionAeroballistics
I have the same problem, no solution here as well. Must be a bug in WatchConnectivity...Madox
You never answered my questions above. Also, it's be good to see the class where you set up the WCSessionSwane
Were you able to fix this issue? Im getting this issue on Watch OS 3 beta 6. If you found a solution, Please helpNival
I'm using the XCODE 8 GM with SWIFT 3 and also I'm getting the 7014 error with code that was working in previous versions... thoughts?Plasmolysis
S
17

In my case, I had to implement both delegates:

  1. The one without any replyHandler

    func session(_ session: WCSession,
                     didReceiveMessage message: [String : Any])
    
  2. The one with replyHandler

    func session(_ session: WCSession,
                 didReceiveMessage message: [String : Any],
                 replyHandler: @escaping ([String : Any]) -> Void)
    

If you send a message without a replyHandler then the first delegate runs.
If you send a message with a replyHandler then the second delegate runs.


In some cases I was sending only a message, and in other cases I was sending a message and expecting a reply from the counterpart.
BUT... I had implemented only the second delegate -_-

Anyways, eventually to reduce duplicate code I implemented a common method and ended up with:

func session(_ session: WCSession,
             didReceiveMessage message: [String : Any]) {
    handleSession(session, 
                  didReceiveMessage: message)
}

func session(_ session: WCSession,
             didReceiveMessage message: [String : Any],
             replyHandler: @escaping ([String : Any]) -> Void) {
    handleSession(session, 
                  didReceiveMessage: message, 
                  replyHandler: replyHandler)
}

//Helper Method
func handleSession(_ session: WCSession,
                   didReceiveMessage message: [String : Any],
                   replyHandler: (([String : Any]) -> Void)? = nil) {
    //Common logic
}

Watch OS 4

Sihun answered 14/2, 2018 at 12:37 Comment(0)
O
13

For anyone having issues on iOS10 beta 6 and GM, and you are using Swift3, the solution is to change the delegate function header in the iOS app to the following:

    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {

Note the @escaping and the Any instead of the AnyObject type.

Ogrady answered 8/9, 2016 at 5:46 Comment(1)
You are a super hero!Overturf
S
5

Try this one, this fixed my issue. Inside the InterfaceController add the following methods for passing the data to phone.

-(void)sendDataToPhone:(NSDictionary* _Nonnull)dictData
{
    if(WCSession.isSupported){

        WCSession* session = WCSession.defaultSession;
        session.delegate = self;
        [session activateSession];

        if(session.reachable)
        {
            [session sendMessage:dictData replyHandler: ^(NSDictionary<NSString *,id> * __nonnull replyMessage) {

                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@".....replyHandler called --- %@",replyMessage);
                    // Play a sound in watch
                    [[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess];
                });
            }
                    errorHandler:^(NSError * __nonnull error) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            NSLog(@"Error = %@",error.localizedDescription);
                        });
                    }
             ];
        }
        else
            NSLog(@"Session Not reachable");
    }
    else
        NSLog(@"Session Not Supported");
}



#pragma mark - Standard WatchKit delegate

-(void)sessionWatchStateDidChange:(nonnull WCSession *)session
{
    if(WCSession.isSupported){
        WCSession* session = WCSession.defaultSession;
        session.delegate = self;
        [session activateSession];

    }
}

In the phone side, add the following codes for receiving the data from watch.

Add the following in didFinishLaunchingWithOptions.

// Allocating WCSession inorder to communicate back to watch.
    if(WCSession.isSupported){
        WCSession* session = WCSession.defaultSession;
        session.delegate = self;
        [session activateSession];
    }

Now add the WCSessionDelegate.

#pragma mark - WCSession Delegate

- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message replyHandler:(void(^)(NSDictionary<NSString *, id> *replyMessage))replyHandler
{
    if(message){
        NSData *receivedData = [message objectForKey:@"AudioData"];
        NSDictionary* response = @{@"response" : [NSString stringWithFormat:@"Data length: %lu",(unsigned long)receivedData.length]} ;
        replyHandler(response);
    }
}


#pragma mark - Standard WatchKit Delegate

-(void)sessionWatchStateDidChange:(nonnull WCSession *)session
{
    if(WCSession.isSupported){
        WCSession* session = WCSession.defaultSession;
        session.delegate = self;
        [session activateSession];

        if(session.reachable){
            NSLog(@"session.reachable");
        }

        if(session.paired){
            if(session.isWatchAppInstalled){

                if(session.watchDirectoryURL != nil){

                }
            }
        }
    }
}

Hope this helps you :)

Shawn answered 22/10, 2015 at 7:3 Comment(0)
Q
3

Sorry I'dont have enough reputation to comment answers. My issue got resolved with Peter Robert's answer: With Swift 3 WCErrorCodeDeliveryFailed appeared and the solution was simply changing AnyObject to Any on the replyHandlers.

    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
//code
 replyHandler (answer as [String : Any])
}
Quyenr answered 17/12, 2016 at 10:9 Comment(0)
K
1

I was experiencing the same and moving WCSession initialization (setting delegate and activating it) later in the app lifecycle fixed the issue.

I had WCSession activation in app delegates didFinishLaunching and having it there broke the communication. Moving WCSession intialization later in the app made comms work again.

Kristankriste answered 13/6, 2016 at 4:49 Comment(0)
S
1

In my case I put WCSessionDelegate(iOS side) in a separate class and initialize it as local variable. Changing it to global instance variable solved the issue.

So my iOS Code was:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
     SessionHandler()
}

Changed to below to get it working:

var handler: SessionHandler!

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
     handler = SessionHandler()
}
Symposium answered 24/10, 2018 at 21:54 Comment(0)
M
0

Working on an app and have the exact same behavior. I feel fairly sure that I have looked everywhere in my code and found nothing wrong. My best guess is that this must be a bug with WatchConnectivity.

My current error handler workaround simply tries to reload data on this particular error. Not very beautiful, but it works ok.

You might want to try something similar?

func messageErrorHandler(error: NSError) {
  isLoading = false
  print("Error Code: \(error.code)\n\(error.localizedDescription)")

  // TODO: WTF?. Check future releases for fix on error 7014, and remove this...
  if error.code == 7014 {
    // Retry after 1.5 seconds...
    retryTimer = NSTimer.scheduledTimerWithTimeInterval(
      NSTimeInterval(1.5), target: self, selector: "reloadData", userInfo: nil, repeats: false)
    return
  }

  displayError("\(error.localizedDescription) (\(error.code))",
    message: "\(error.localizedDescription)")
}

UPDATE:

For anyone working with WatchConnectivity; I need to have a similar "hack" for testing the session.reachable variable.

I have noticed that my app manages to send a message before the session becomes reachable. So I simply try to reload data (re-send the message) a couple of times before actually telling the user their phone is out of reach.

UPDATE 2: The above example is using .sessionWatchStateDidChange(), so the issue is not that .sendMessage() is triggered too early because of not waiting for connection ack. This must be a bug as it is not happening every time, it just freaks out like 1 per 100 messages.

Madox answered 12/12, 2015 at 16:23 Comment(2)
Why not use the delegate callback informing you that reachable changed or KVO on the reachable property to know when to try the send again?Swane
I am using it, the code snippet above is just my error hander for '.sendMessageData()'Madox
A
0

You may need to (check and) implement that your WCSession delegate implemented the following method. I got this error due to missing the implementation.

- (void)session:(WCSession * _Nonnull)session
didReceiveMessage:(NSDictionary<NSString *, id> * _Nonnull)replyMessage
   replyHandler:(void (^ _Nonnull)(NSDictionary<NSString *, id> * _Nonnull replyMessage))replyHandler
{
    NSLog(@"Received. %@", replyMessage);
    [self processResponse:replyMessage];
}
Angelicaangelico answered 18/1, 2016 at 15:23 Comment(0)
H
0

Check the delegate connected correct ?

 WCSession* session = WCSession.defaultSession;
 session.delegate = self;
 [session activateSession];

Note : Verify session.delegate = self; set to self.

Hofmann answered 2/6, 2016 at 6:40 Comment(0)
S
0

I have found that putting the reply code as the first thing to run fixes this issue (possible being caused by timing out?).

func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: ([String : Any]) -> Void) {
    print("Message - received")

    //Send reply
    let data = ["receivedData" : true]
    replyHandler(data as [String : AnyObject])

}
Subedit answered 8/9, 2016 at 0:32 Comment(0)
R
0

In Swift 3 I solved implementing didReceiveMessage with this signature:

func session(_ session: WCSession, didReceiveMessage message: [String : Any],
             replyHandler: @escaping ([String : Any]) -> Void)
Renelle answered 21/11, 2016 at 21:54 Comment(0)
S
0

Be sure that your Session is always active. I for example had an other View which was part of the testing and then returned on the initially View and was wondering why the Session wasn't active anymore.

- (void)willActivate {
// This method is called when watch view controller is about to be visible to user
[super willActivate];

//Setup WCSession
if ([WCSession isSupported]) {
    [[WCSession defaultSession] setDelegate:self];
    [[WCSession defaultSession] activateSession];
}}

Above did it for me. Had it first placed in the awakeWithContext, stupid me....

Salty answered 12/2, 2017 at 18:33 Comment(0)
H
0

This scenario will cover several use case. Please take look at these steps , it help me a lot.

1 - Understand that each device must have their own WCSession instance configured and with the appropriate delegates configured.

2 - implement WCSessionDelegate only at one single place on each device, ej. on iOS app on the AppDelegate, on watchOS on ExtensionDelegate. This is very important because with the appropriate WCSession configured on watchOS but on iPhone implementing on two different place, ej. on app delegate and then on the first viewcontorllweer of the app, (on my case ) lead to unstable behaviour and thats the main reason why sometimes the iOS app stop responding when message received from watch.

3 - reactivating the session is advisable only do it on the Host App. This is an example of my iOS App with only one WCSessionDelegate. (AppDelegate )


#pragma mark - WCSessionDelegate

- (void)session:(WCSession *)session activationDidCompleteWithState:(WCSessionActivationState)activationState error:(NSError *)error{

    if( activationState == WCSessionActivationStateActivated) {
        NSLog(@"iPhone WKit session Activated");
    }else if (activationState == WCSessionActivationStateInactive) {
        NSLog(@"iPhone WKit Inactive");
    }else if (activationState == WCSessionActivationStateNotActivated) {
        NSLog(@"iPhone WKit NotActivated");
    }
}



- (void)sessionDidBecomeInactive:(WCSession *)session{
    /*
     The session calls this method when it detects that the user has switched to a different Apple Watch. While in the inactive state, the session delivers any pending data to your delegate object and prevents you from initiating any new data transfers. After the last transfer finishes, the session moves to the deactivated state
     */
    NSLog(@"sessionDidBecomeInactive");

    if (session.hasContentPending) {
        NSLog(@"inactive w/ pending content");
    }
}




- (void)sessionDidDeactivate:(WCSession *)session{
    // Begin the activation process for the new Apple Watch.
    [[WCSession defaultSession] activateSession];

    //perform any final cleanup tasks related to closing out the previous session.
}





- (void)sessionReachabilityDidChange:(WCSession *)session{
    NSLog(@"sessionReachabilityDidChange");
}

last thing, write the appropriate method signature, if you need a reply sending data from watch , take the method signature who have reply:... According to apple the following methods


sendMessage:replyHandler:errorHandler:, sendMessageData:replyHandler:errorHandler:, and transferCurrentComplicationUserInfo: 

has a higher priority and is transmitted right away. All messages received by your app are delivered to the session delegate serially on a background thread.

So do not waste time dispatching the reply object on mainQueue on the iOS appDelegate, wait till you have the response on your watchOS back and change it to main thread to update your UI accordingly.

Hexapartite answered 4/8, 2017 at 11:21 Comment(1)
As per your point number 3: reactivating the session is advisable only do it on the Host App. How to deactivate the session? Something I am facing issue when I send message from watch to iPhone and expect the call back from iPhone app to watch is works but sometimes don’t.Stockist
E
0

For anyone still having this issue, if none of the above solutions helped you, perhaps you have a similar issue to what I had.

My delegates were setup correctly and my messages were being passed along with the proper casting.

My issue ended up being related to the way I was creating my watch communication delegate objects and I think the main problem was having multiple instances of these objects being passed around.

I ended up created a watch communication singleton and using that for communication, which fixed my issue.

Here's a sample of the WatchCommunication singleton ( doesn't contain my full login / logout code since that is project specific ):

import Foundation
import WatchConnectivity

class WatchCommunication: NSObject, WCSessionDelegate {
    static let shared = WatchCommunication()
    var watchSession: WCSession!
    
    override init() {
        super.init()
        
        if (WCSession.isSupported()) {
            print ("iOS WCSession.isSupported()")
            if (watchSession == nil) {
                print ("iOS watchSession == nil")
                watchSession = WCSession.default
                watchSession!.delegate = self
                watchSession!.activate()
            }
        }
    }
    
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print ("iOS activationDidCompleteWith")
    }
    
    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        print ("iOS didReceiveMessage")
    }
    
    func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
        print ("iOS didReceiveMessage with replyhandler ")

        // DO SOMETHING HERE THEN REPLY

        replyHandler(message as [String: Any])        
    }
    
    func sessionDidBecomeInactive(_ session: WCSession) {
        print ("sessionDidBecomeInactive")
    }
    
    func sessionDidDeactivate(_ session: WCSession) {
        print ("sessionDidDeactivate")
    }

    func doLogin() {
        // Do something for login
    }
}

For example when my phone logs in and I want to tell my watch, I just call:

WatchCommunication.shared.doLogin()
Edita answered 6/3 at 19:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.