Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

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

License

NotificationsYou must be signed in to change notification settings

KaneCheshire/Communicator

Repository files navigation

Introduction

Sending messages and data between watchOS and iOS apps is possible thanks to Apple's work onWatchConnectivity, however there are alot 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 sharedCommunicator 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 withCommunicator:

letmessage=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:

switchCommunicator.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{ messageinguard 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 inCommunicator, includingReachability andWatchState, is observable using the same syntax, just callingobserve on the type you want to observe:

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

Additionally, you can unobserve at any time:

letobservation=Reachability.observe{ _in}/// ...Reachability.unobserve(observation)

Communicator can also transferGuaranteedMessages, dataBlobs and also syncContexts.

GuaranteedMessages are similar toImmediateMessages andInteractiveImmediateMessages, 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 aContext 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 aContext overwrites any previously sentContext, which you can query any time withCommunicator.shared.mostRecentlySentContext andCommunicator.shared.mostRecentlyReceivedContext

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

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

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

Usage

Communicator

Each app has its own sharedCommunicator 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{ reachabilityin  // Handle reachability change}ImmediateMessage.observe{ messagein  // Handle immediate message}GuaranteedMessage.observe{ messagein  // Handle guaranteed message}

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

Querying the current reachability

Before sending any messages or data you should check the current reachability of the counterpartapp. 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:

letreachability=Communicator.shared.currentReachability

You can also observe and react to reachability changes:

Reachability.observe{ reachabilityin  // Handle reachability change}

Different types of communication require a different minimum level of reachability.I.e.ImmediateMessage andInteractiveImmediateMessage require.immediatelyReachable,butGuaranteedMessage,Blob,Context, andComplicationInfo 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:

letstate=Communicator.shared.currentState

You can also observe state changes:

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

The state can change as the user switches watches. Generally, you won't need to use this state andinstead 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:

letwatchState=Communicator.shared.currentWatchState

You can also observe state changes:

WatchState.observe{ statein // 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 userscan install your watch app without installing the iOS app:

letphoneState=Communicator.shared.currentPhoneState

And like all other states you can observe changes:

PhoneState.observe{ statein  // Handle new state}

ImmediateMessage

AnImmediateMessage 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 toUserDefaults;String,Int,Data etc. Youcannot send large amounts of data between devices using aImmediateMessage because the system will reject it. Instead, use aBlob for sending large amounts of data.

This is how you create a simpleImmediateMessage:

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

And this is how you send it:

Communicator.shared.send(message){ errorin  // 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 currentReachability is.immediatelyReachable.

On the receiving device you listen for new messages:

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

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

InteractiveImmediateMessage

AnInteractiveImmediateMessage is similar to a regularImmediateMessage but it additionally takesa reply handler that youmust execute yourself on the receiving device. Once you execute the handleron 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 anImmediateMessage,the reachability must be.immediatelyReachable during both the send and the reply.

On the sending device, send the message:

letmessage=InteractiveImmediateMessage(identifier:"message", content:["hello":"world"])Communicator.shared.send(message){ errorin}

And on the receiving device, listen for the message and execute the reply handler:

InteractiveImmediateMessage.observe{ messageinguard message.identifier=="message"else{return}letreplyMessage=ImmediateMessage("identifier", content:["reply":"message"])  message.reply(replyMessage)}

Like anImmediateMessage, if you send this from your watch app the system will wake your iOS appup in the background if needed, so long as the current reachability is.immediatelyReachable.

GuaranteedMessage

You can also choose to send a message using the "guaranteed" method.GuaranteedMessages don't have a reply handler because messages can be queued while the receiving device is not currently receiving messages, meaning they're queued until the session is next created:

letcontent:Content=["CaloriesBurnt":400.00]letmessage=GuaranteedMessage(identifier:"WorkoutComplete", content: content)Communicator.shared.send(message){ resultin  // Handle success or failure}

Because the messages are queued, they could be received in a stream on the receiving device when it's able to process them. You should make sure your observers are set up as soon as possible to avoid missing any messages, i.e. in yourAppDelegate orExtensionDelegate:

GuaranteedMessage.observe{ messageinif message.identifier=="CaloriesBurnt"{letcontent= message.content    // Handle message}}

NOTE: On watchOS, receiving aGuaranteedMessage while in the background can cause the system to generate aWKWatchConnectivityRefreshBackgroundTask. If you assign this to theCommunicator'stask property,Communicator will automatically handle ending the task for you at the right time.

The value ofCommunicator.currentReachability must not be.notReachable otherwise an error will occur.

Blob

ABlob is very similar to aGuaranteedMessage but is better suited to sending larger bits of data. ABlob is created with anidentifier but instead of assigning a JSON dictionary as the content, you assign pureData instead.

This is how you create aBlob:

letlargeData:Data=getJourneyHistoryData()letblob=Blob(identifier:"JourneyHistory", content: largeData)

And this is how you transfer it to the other device:

Communicator.shared.transfer(blob: blob){ resultin  // Handle success or failure}

Because aBlob can be much larger than aMessage, it might take significantly longer to send. The system handles this, and continues to send it even if the sending device becomes unreachable before it has completed.

On the receiving device you listen for newBlobs. Because theseBlobs can often be queued waiting for the session to start again,Communicator will often notify observers very early on. This makes it a good idea to start observing forBlobs as soon as possible, i.e. in theAppDelegate orExtensionDelegate:

Blob.observe{ blobinif blob.identifier=="JourneyHistory"{letJourneyHistoryData:Data= blob.content    // ... do something with the data ... //}}

Additionally, you can also attach some metadata to aBlob, by passing in a dictionary of plist values when creating theBlob:

letmetadata=["DateGenerated":Date()]letblobWithMetadata=Blob(identifier:"JourneyHistory", content: data, metadata: metadata)

And then on the receiving device you can query the metadata on the receivedBlob:

Blob.observe{ blobinprint(blob.metadata)}

NOTE: On watchOS, receiving aBlob while in the background can cause the system to generate aWKWatchConnectivityRefreshBackgroundTask. If you assign this to theCommunicator'stask property,Communicator will automatically handle ending the task for you at the right time.

The value ofCommunicator.currentReachability must not be.notReachable otherwise an error will occur.

Context

AContext is a very lightweight object. AContext can be sent and received by either device, and the system stores the last sent/receivedContext that you can query at any time. This makes it ideal for syncing lightweight things like preferences between devices.

AContext has no identifier, and simply takes a JSON dictionary as content. Like anImmediateMessage, this content must be primitive types likeString,Int,Data etc, and must not be too large or the system will reject it:

letcontent:Content=["ShowTotalDistance":true]letcontext=Context(content: content)do{tryCommunicator.shared.sync(context)}catch{  // Handle error}

You can also query the last sent context from either device:

letcontext=Communicator.shared.mostRecentlySentContext

On the receiving device you listen for newContexts:

Content.observe{ contextiniflet shouldShowTotalDistance= context.content["ShowTotalDistance"]as?Bool{print("Show total distance setting changed:\(shouldShowTotalDistance)")}}

You can also query the last received context from either device:

letcontext=Communicator.shared.mostRecentlyReceivedContext

NOTE: On watchOS, receiving aContext while in the background can cause the system to generate aWKWatchConnectivityRefreshBackgroundTask. If you assign this to theCommunicator'stask property,Communicator will automatically handle ending the task for you at the right time.

The value ofCommunicator.currentReachability must not be.notReachable otherwise an error will be thrown.

WatchState

WatchState is one of the only iOS-only elements ofCommunicator. It provides some informationabout the current state of the user's paired watch or watches, like whether a complication has been enabledor whether the watch app has been installed.

You can observe any changes in theWatchState on iOS:

WatchState.observe{ statein  // Handle watch state}

You can also query the currentWatchState at any time from the iOSCommunicator:

letwatchState=Communicator.shared.currentWatchState

You can useWatchState retrieve a URL which points to a directory on the iOS device specific to the currently paired watch.

You can use this directory to store things specific to that watch, which you don't want associated with the user's other watches. This directory (and anything in it) is automatically deleted by the system if the user uninstalls your watchOS app or unpairs their watch.

PhoneState

PhoneState is similar to theWatchState but is queried from the watch's side instead.

Since watchOS 6, users can install watch apps without installing the iOS app, and you canusePhoneState to determine this.

ComplicationInfo

AComplicationInfo can only be sent from an iOS device, and can only be received on a watchOS device.Its purpose is to wake the watchOS app in the background to process the data and update its complication. At the time of writing your iOS app can do this 50 times a day, and you can query thecurrentWatchState of the sharedCommunicator object on iOS to find out how many remaining updates you have left.

Just like aContext, aComplicationInfo has no identifier and its content is a JSON dictionary:

letcontent:Content=["NumberOfStepsWalked":1000]letcomplicationInfo=ComplicationInfo(content: content)

And you send it from the iOS app like this:

Communicator.shared.transfer(complicationInfo){ resultin  // Handle success or failure}

Upon successful transfer, thesuccess case in theresult provides the remaining complicationupdates available that day.

On the watchOS side you observe newComplicationInfos being received. Just like other transfers that may happen in the background, it's a good idea to observe these early on, like in theExtensionDelegate:

ComplicationInfo.observe{ complicationInfoin  // Handle update}

The value ofCommunicator.currentReachability must not be.notReachable otherwise an error will be thrown.

NOTE: On watchOS, receiving aComplicationInfo while in the background can cause the system to generate aWKWatchConnectivityRefreshBackgroundTask. If you assign this to theCommunicator'stask property,Communicator will automatically handle ending the task for you at the right time.

Example

To run the example project, clone the repo, and runpod install from the Example directory first.

The watchOS and iOS example apps set up observers for newMessages,Blobs, reachability changes etc and prints out anychanges to the console. They set up these observers early on in the app, which is recommended for state changes andobservers of things that may have transferred while the app was terminated, likeBlobs.

Try running each target and seeing the output when you interact with the buttons.

Requirements

Communicator relies onWatchConnectivity, Apple's framework for communicating between iOS and watchOS apps, but has no external dependencies.

Communicator requires iOS 10.0 and newer and watchOS 3.0 and newer.

Installation

Swift Package Manager

Communicator supports SPM, simply add Communicator as a package dependency in Xcode 11 or newer.

Cocoapods

Add the following line to your Podfile and then runpod install in Terminal:

pod"Communicator"

Author

Kane Cheshire,@kanecheshire

License

Communicator is available under the MIT license. See the LICENSE file for more info.

About

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

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp