I have implemented a very simple iOS + WatchOS app project, where I test how one can communicate with the other. I will paste the code below, but the idea is really simple. Each app has one single screen with a button and a label. Tapping the button will send a message to the counterpart indicating the timestamp when the message was generated.
If I run the app on the simulators, everything works fine: messages are sent and received correctly on both the iPhone and the Watch. (You can find a reference gif on imgur.com/ + o1ZQTLp).
The problem occurs when I try to run the same apps on my physical devices. Session is activated successfully but messages aren't sent. If I debug the code, I even see the WCSession.isReachable value set to true.
When debugging the WatchKit app, I see errorHandler is called on the func sendMessage(), and the error states:
WatchConnectivity session on paired device is not reachable. However, the errorHandler isn't called from the iPhone app.
Details of my devices:
iOS version: 14.0.1
WatchOS version: 7.0.1
I tested this same code before installing the 7.0.1 WatchOS and it worked without any problems, so I wonder if the update introduced some error on the WatchConnectivity framework.
Code on the iPhone app:
import UIKit
import WatchConnectivity
class ViewController: UIViewController {
		@IBOutlet weak var messageLabel: UILabel!
		fileprivate var wcSession: WCSession!
		override func viewDidLoad() {
				super.viewDidLoad()
				wcSession = WCSession.default
				wcSession.delegate = self
				wcSession.activate()
		}
		@IBAction func sendMessageAction(_ sender: Any) {
				let message = [
						"content": "Message sent from iPhone on \(Date())."
				]
				wcSession.sendMessage(message, replyHandler: nil, errorHandler: { error in
						print("Error when sending message: \(error.localizedDescription)")
				})
		}
}
extension ViewController: WCSessionDelegate {
		func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
				switch activationState {
				case .activated:
						print("WCSession activated successfully")
				case .inactive:
						print("Unable to activate the WCSession. Error: \(error?.localizedDescription ?? "--")")
				case .notActivated:
						print("Unexpected .notActivated state received after trying to activate the WCSession")
				@unknown default:
						print("Unexpected state received after trying to activate the WCSession")
				}
		}
		func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
				guard let content = message["content"] as? String else { return }
				DispatchQueue.main.async {
						self.messageLabel.text = content
				}
		}
		func sessionDidBecomeInactive(_ session: WCSession) {
		}
		func sessionDidDeactivate(_ session: WCSession) {
		}
}
Code on the Watch Kit app:
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController {
		@IBOutlet weak var messageLabel: WKInterfaceLabel!
		fileprivate var wcSession: WCSession!
		override func awake(withContext context: Any?) {
				wcSession = WCSession.default
				wcSession.delegate = self
				wcSession.activate()
		}
		@IBAction func sendMessageAction() {
				let message = [
						"content": "Message sent from Watch on \(Date())."
				]
				wcSession.sendMessage(message, replyHandler: nil, errorHandler: { error in
						print("Error when sending message: \(error.localizedDescription)")
				})
		}
}
extension InterfaceController: WCSessionDelegate {
		func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
				switch activationState {
				case .activated:
						print("WCSession activated successfully")
				case .inactive:
						print("Unable to activate the WCSession. Error: \(error?.localizedDescription ?? "--")")
				case .notActivated:
						print("Unexpected .notActivated state received after trying to activate the WCSession")
				@unknown default:
						print("Unexpected state received after trying to activate the WCSession")
				}
		}
		func session(_ session: WCSession, didReceiveMessage message: [String: Any]) {
				guard let content = message["content"] as? String else { return }
				messageLabel.setText(content)
		}
}