SkillAgentSearch skills...

Communicator

Communication between iOS and watchOS apps just got a whole lot better.

Install / Use

/learn @KaneCheshire/Communicator
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Communicator

Introduction

Sending messages and data between watchOS and iOS apps is possible thanks to Apple's work on WatchConnectivity, however there are a lot of delegate callbacks to work with, some of the API calls are quite similar and it's not really clear which is needed and for what purpose.

Communicator tries to clear all this up, handles a lot of stuff for you, and it's extremely easy to use.

Communicator supports watch switching out-the-box, uses closures rather than delegate functions, and allows multiple places in your app to react to messages and events.

Quick start

Each app gets its own shared Communicator object to use which handles all the underlying session stuff:

Communicator.shared

Usage between the two platforms is essentially identical.

Here's how you send a simple message with Communicator:

let message = ImmediateMessage(identifier: "1234", content: ["messageKey" : "This is some message content!"])
Communicator.shared.send(message)

This will try to send a message to the counterpart immediately. If the receiving app is not appropriately reachable, the message sending will fail, but you can query this any time:

switch Communicator.shared.currentReachability {
  case .immediateMessaging: Communicator.shared.send(message)
  default: break
}

On the other device you register as an observer for new messages as early on as possible in your app's launch cycle:

ImmediateMessage.observe { message in
  guard message.identifier == "1234" else { return }
  print("Message received!", message)
}

You can observe these messages from anywhere in your app and filter out the ones you don't care about. Anything that can change or be received in Communicator, including Reachability and WatchState, is observable using the same syntax, just calling observe on the type you want to observe:

Reachability.observe { reachability in
  print("Reachability changed!", reachability)
}

Additionally, you can unobserve at any time:

let observation = Reachability.observe { _ in }
/// ...
Reachability.unobserve(observation)

Communicator can also transfer GuaranteedMessages, data Blobs and also sync Contexts.

GuaranteedMessages are similar to ImmediateMessages and InteractiveImmediateMessages, in that they have an identifier, but they don't support reply handlers and can be sent when the reachability state is at least .backgroundOnly, and will continue to transfer even if your app is terminated during transfer.

Blobs are perfect for sending larger amounts of data (WatchConnectivity will reject large data in any other message type), can be sent when the reachability state is at least .backgroundOnly, and will continue to transfer even if your app is terminated during transfer.

You can use a Context to keep things in sync between devices, which makes it perfect for preferences. Contexts are not suitable for messaging or sending large data. Sending or receiving a Context overwrites any previously sent Context, which you can query any time with Communicator.shared.mostRecentlySentContext and Communicator.shared.mostRecentlyReceivedContext

Lastly, you can update your watchOS complication from your iOS app by transferring a ComplicationInfo. You get a limited number of ComplicationInfo transfers a day, and you can easily query the remaining number of transfers available by getting the currentWatchState object.

If you have transfers available, your watch app is woken up in the background to process the ComplicationInfo.

NOTE: You app must have a complication added to the user's active watch face to be able to wake your watch up in the background, and the number of transfers available must not be 0.

Usage

Communicator

Each app has its own shared Communicator object which it should use to communicate with the counterpart app.

Communicator.shared

The APIs between iOS and watchOS are almost identical.

The first time you access the .shared instance, Communicator will do what it needs to in order to activate the underlying session and report any received messages/data etc.

This means you should access the shared instance as early on as possible in your app's lifecycle, but also observe any changes as soon as possible to avoid losing data:

Reachability.observe { reachability in
  // Handle reachability change
}
ImmediateMessage.observe { message in
  // Handle immediate message
}
GuaranteedMessage.observe { message in
  // Handle guaranteed message
}

NOTE: Observing any type will impliclty access the .shared instance, so you only need to observe things for Communicator to activate the underlying session.

Querying the current reachability

Before sending any messages or data you should check the current reachability of the counterpart app. This can change as the user switches watches, installs your app or backgrounds your app.

Additionally, since watchOS 6, it's possible to install a watch app without installing the iOS app, which Communicator takes into account.

You can query the current reachability at any time:

let reachability = Communicator.shared.currentReachability

You can also observe and react to reachability changes:

Reachability.observe { reachability in
  // Handle reachability change
}

Different types of communication require a different minimum level of reachability. I.e. ImmediateMessage and InteractiveImmediateMessage require .immediatelyReachable, but GuaranteedMessage, Blob, Context, and ComplicationInfo require at least .backgroundOnly (although can still be sent when .immediatelyReachable).

Querying the current activation state

You can query the current activation state of Communicator at any time:

let state = Communicator.shared.currentState

You can also observe state changes:

Communicator.State.observe { state in
 // Handle new state
}

The state can change as the user switches watches. Generally, you won't need to use this state and instead should query the reachability, which takes into account whether the counterpart app is currently installed.

Querying the current state of the counterpart device

You can query the state of the user's paired watch at any time:

let watchState = Communicator.shared.currentWatchState

You can also observe state changes:

WatchState.observe { state in
 // Handle new state
}

The watch state provides information like whether the watch is paired, your app is installed, a complication is added to the active watch face, and more.

Additionally, you can query the state of the iPhone from the watchOS app, since iOS 6 users can install your watch app without installing the iOS app:

let phoneState = Communicator.shared.currentPhoneState

And like all other states you can observe changes:

PhoneState.observe { state in
  // Handle new state
}

ImmediateMessage

An ImmediateMessage is a simple object comprising of an identifier string of your choosing, and a JSON dictionary as content.

The keys of the JSON dictionary must be strings, and the values must be plist-types. That means anything you can save to UserDefaults; String, Int, Data etc. You cannot send large amounts of data between devices using a ImmediateMessage because the system will reject it. Instead, use a Blob for sending large amounts of data.

This is how you create a simple ImmediateMessage:

let content: Content = ["TotalDistanceTravelled" : 10000.00]
let message = ImmediateMessage(identifier: "JourneyComplete", content: json)

And this is how you send it:

Communicator.shared.send(message) { error in
  // Handle error
}

This works well for rapid, interactive communication between two devices, but is limited to small amounts of data and will fail if either of the devices becomes unreachable during communication.

If you send this from watchOS it will also wake up your iOS app in the background if it needs to so long as the current Reachability is .immediatelyReachable.

On the receiving device you listen for new messages:

ImmediateMessage.observe { message in
  if message.identifier == "JourneyComplete" {
    // Handle message
  }
}

NOTE: The value of Communicator.currentReachability must be .immediatelyReachable otherwise an error will occur which you can catch by assigning an error handler when sending the message.

InteractiveImmediateMessage

An InteractiveImmediateMessage is similar to a regular ImmediateMessage but it additionally takes a reply handler that you must execute yourself on the receiving device. Once you execute the handler on the receiving device, it is called by the system on the sending device.

This provides a means for extremely fast communication between devices, but like an ImmediateMessage, the reachability must be .immediatelyReachable during both the send and the reply.

On the sending device, send the message:

let message = InteractiveImmediateMessage(identifier: "message", content: ["hello": "world"])
Communicator.shared.send(message) { error in

}

And on the receiving device, listen for the message and

View on GitHub
GitHub Stars159
CategoryDevelopment
Updated4mo ago
Forks21

Languages

Swift

Security Score

97/100

Audited on Nov 10, 2025

No findings