I'm trying to check in an 'independent' WatchKit app if my current Apple watch is paired with an iPhone.
I've tried to do this by using the WatchConnectivity framework, but unfortunately this framework doesn’t support the isPaired method on watchOS. Also, the isReachable method is not applicable for this app, because there is no counterpart app on the iPhone.
Any solutions or workarounds are highly appreciated.
Firstly import SDK
import WatchConnectivity
Then implement session activation request
if WCSession.isSupported() { // check if the device support to handle an Apple Watch
let session = WCSession.default
session.delegate = self
session.activate() // activate the session
}
Then implement methods from WCSessionDelegate
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if activationState == .activated && session.isPaired { // Check if the iPhone is paired with the Apple Watch
// Do stuff
}
}
Related
I am trying to send a message from my iOS app to its companion Watch App. If the watch screen is ON, then everything works fine and I can see the messages. If the screen turns black, the watch is "not reachable" and my messages don't get printed.
iOS Code
// Invoking this function from viewDidLoad() of the view controller
func checkWatchConnectivityIsSupported() {
if (WCSession.isSupported()) {
print ("WC Session is supported")
let session = WCSession.default
session.delegate = self
session.activate()
}
}
// sending messages on click of a button
func sendMessageToWatch(type: String, message: String) {
print ("Sending message to watch \(type) \(message)")
// send a message to the watch if it's reachable
if (WCSession.default.isReachable) {
print ("isReachable")
// this is a meaningless message, but it's enough for our purposes
let message = [type: message]
// WCSession.default.sendMessage(message, replyHandler: nil)
WCSession.default.sendMessage(message, replyHandler: nil, errorHandler: { (err) in
print ("There was an error in sending message \(err)")
debugPrint(err)
})
} else {
// This happens when the watch screen display goes off.
print ("watch is not reachable")
}
}
WatchOS Code - InterfaceController.swift
// invoking this function from willActivate()
func checkIfWatchIsConnected() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
// implementation of delegate methods
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
print ("Message received in watch \(message)")
WKInterfaceDevice().play(.click)
let isStatusType = message["Status"] != nil
if (isStatusType) {
let text = message["Status"] as! String
statusLabel.setText(text)
return
}
}
This is the expected behaviour.
WatchKit extension. The iOS device is within range, so communication can occur and the WatchKit extension is running in the foreground, or is running with a high priority in the background (for example, during a workout session or when a complication is loading its initial timeline data).
You use isReachable and sendMessage when live messaging is required. A watch app that provides a "remote control" for the active companion iOS app or an iOS app communicating with an active workout app on the watch are examples.
In order for live messaging to work the watch does, indeed, need to be awake.
You can use the updateApplicationContext and transferUserInfo methods to transfer data to your companion app when the watch isn't active. These transfers are queued and transferred opportunistically in order to improve battery life.
I am trying to add watch extension to my existing application. Login is compulsory for my application. How can i check from watch app if user logged-in or not... and as user gets loged-in, I want to pass that login-data from the application to the watchapplication. I don't know how to pass the login data to watch application.
Prior to WatchOS 2, you could share data between iOS companion app and watchOS app by using shared group container or iCloud to exchange data.
From WatchOS 2, since WatchKit extension now runs on Apple Watch itself, the extension must exchange data with the iOS app wirelessly. You will have to use WCSession class which is part of the WatchConnectivity framework. The framework is used to implement two-way communication between an iOS app and its paired watchOS app.
In iPhone companion app. Implement the following:
import WatchConnectivity
class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if WCSession.isSupported() {
WCSession.default().activate()
}
}
#IBAction func loginAction(_ sender: Any) {
// In this case ApiHandler is just a class which performs REST call to authenticate login credentials.
ApiHandler.login(username: txtUsername.text!, password: txtPassword.text!, { (loginSuccess) in
if loginSuccess {
let dict = ["statusLogin": "success"]
WCSession.default().sendMessage(dict, replyHandler: { (replyMessage) in
print(replyMessage)
}) { (error) in
print(error.localizedDescription)
}
}
})
}
}
In watchOS app.
import WatchConnectivity
class BaseInterfaceController: WKInterfaceController {
override func willActivate() {
super.willActivate()
if WCSession.isSupported() {
WCSession.default().delegate = self
WCSession.default().activate()
}
}
}
extension BaseInterfaceController: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Swift.Void) {
if let loginStatus = message["statusLogin"] as? String {
if loginStatus == "success" {
// login has been success in iOS app
// perform further tasks, like saving in Userdefaults etc.
}
}
}
}
You can do this using Watch Connectivity. Specifically sending a boolean value with transferUserInfo(). This will allow you to send the data from the iPhone application when the user either logs in or out, and it will be delivered to the watch extension in the background.
Here is an example:
func updateUserLoggedIn(_ loggedInValue : Bool) {
if WCSession.isSupported() {
WCSession.default.transferUserInfo(["User Logged In" : loggedInValue])
}
}
Then you simply handle the transfer in your watch extension:
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
if let userInfoFromPhone = userInfo["User Logged In"] {
// Handle Result Accordingly
}
}
I cannot figure out how to make updateApplicationContext data arrive on the watch before the watch app is foregrounded. It seems to work only when the watch app is foregrounded.
How can the watch receive files while in the background?
This is what I've been trying to accomplish:
iOS code:
func sendDataToWatch() {
if WCSession.isSupported() {
do {
try WCSession.default.updateApplicationContext(["key":value])
} catch {
print("ERROR: \(error)")
}
}
}
Watch code:
func session(_ session: WCSession, didReceiveApplicationContext
applicationContext:[String : Any]) {
//handle data when it arrives
}
I noticed that the WatchConnectivity was provided with a handler function. Is this something I should set up to be able to handle background connectivity while the Watch App is backgrounded or not even launched?
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
// Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
for task in backgroundTasks {
// Use a switch statement to check the task type
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
// Be sure to complete the background task once you’re done.
backgroundTask.setTaskCompletedWithSnapshot(false)
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
According to apple you can send data from iPhone to Apple Watch using SendMessage while session is reachable.
https://developer.apple.com/documentation/watchconnectivity/wcsession/1615687-sendmessage
Calling this method from your WatchKit extension while it is active
and running wakes up the corresponding iOS app in the background and
makes it reachable.
You can use below methods to send data from the iPhone to Apple Watch
Swift 2.2
let msg = ["IPrequest":"IsLogin"]
WCSession.defaultSession().sendMessage(msg, replyHandler: { (replyDict) in
print(replyDict)
}, errorHandler: { (error) in
print(error)
})
Received dictionary using below method
Swift 2.2
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
dispatch_async(dispatch_get_main_queue()) { () -> Void in
print("Response:\(message)")
}
}
I have implemented above solution in one of the my project.
Hope it will help you!
I updated my app to the latest swift 2.0 syntax. In doing so, My watchkit app has become broken. The issue is the watchkit app references a class that references the framework AVFoundation. WatchOS2 apparently now no longer supports some of the standard frameworks:
Support for network-based operations includes the following technologies:
WatchKit extensions can access the network directly through an
NSURLSession object. WatchKit extensions have full access to the
NSURLSession capabilities, including the ability to download files in
the background. For information on how to use this class, see URL
Loading System Programming Guide. The Watch Connectivity framework
supports bidirectional communication between your Watch app and iOS
app. Use this framework to coordinate activities between the two apps.
See Communicating with Your Companion iOS App.
Available System Technologies for WatchKit
So now I cannot compile the watch kit code as "no such module found" is an error message when trying to use the AVFoundation framework. How can I get around this and keep referencing that class and framework in my apple watch app. Should I be communicating data between the phone and the watch? Is there a way to link the framework to the extension?
What I am trying to do is the following, in my InterfaceController:
override func willActivate() {
super.willActivate()
let defaultsShared = NSUserDefaults(suiteName: "somesharedappgroup")
let defaults = NSUserDefaults.standardUserDefaults()
if let barcodeString = defaultsShared!.objectForKey("barcode") as? String {
if let barcodeContent = RSUnifiedCodeGenerator.shared.generateCode(barcodeString, machineReadableCodeObjectType: AVMetadataObjectTypeCode39Code) {
barcode.setImage(barcodeContent)
label.setText("ID: \(barcodeString)")
} else {
label.setText("Please setup extensions in the settings of SHPID.")
barcode.setImage(nil)
}
} else {
label.setText("Please setup extensions in the settings of SHPID.")
barcode.setImage(nil)
}
}
The RSUnifiedCodeGenerator being a class that utilizes AVFoundation to generate barcode images from strings. Furthermore, the type that generator takes is an AVObject: AVMetadataObjectTypeCode39Code. This solution worked well in the first WatchOS, but now remains broken in OS 2. I see that WatchConnectivity may be a solution, and have it just pass me the barcode from the phone itself, but that would require I stop supporting iOS 8. What is the best solution, if any, for using AVFoundation with WatchOS 2. If I can not do that, how else should I go about passing this image to the watch from the phone when called. Thanks.
This is an example on how you could use WatchConnectivity for your app.
Please not that this example is rough and does not handle error. The session management should also get some attention for a stable product.
iPhone AppDelegate
import UIKit
import WatchConnectivity
import AVFoundation
import RSBarcodes
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
return true
}
// On Watch sends the message.
// Will not reply as we will push a data message with image.
func session(session: WCSession, didReceiveMessage message: [String : AnyObject]) {
if "generateBarcode" == message["id"] as! String {
let code = message["code"] as! String
let barcodeImage = RSUnifiedCodeGenerator.shared.generateCode(code,
machineReadableCodeObjectType: AVMetadataObjectTypeCode39Code)!
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
session.sendMessageData(UIImagePNGRepresentation(barcodeImage)!,
replyHandler: nil, errorHandler: nil)
}
}
}
}
Watch InterfaceController
import WatchKit
import Foundation
import WatchConnectivity
class InterfaceController: WKInterfaceController, WCSessionDelegate {
#IBOutlet var barcodeImage: WKInterfaceImage!
override func willActivate() {
super.willActivate()
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
// Send a message requesting a barcode image
session.sendMessage(
["id": "generateBarcode", "code": "2166529V"],
replyHandler: nil, // Do not handle response, iPhone will push a data message
errorHandler: nil)
}
}
// On iPhone pushes a data message
func session(session: WCSession, didReceiveMessageData messageData: NSData) {
barcodeImage.setImage(UIImage(data: messageData))
}
}
I think that using WatchConnectivity is the right thing to do.
For previous version support if the only dealer breaker is AVMetadataObjectTypeCode39Code, maybe you can print its value and pass it to the function directly?
I want to know if there's an API available that shows the availability of an Apple Watch. I don't want to write an Apple Watch App yet. I want to do some analysis to see what percentage of actual users have an Apple Watch, before investing time to develop a watch version (if possible, of course).
So on WatchOS 2 that is possible !
You have to do on iPhone side :
First :
import WatchConnectivity
Then :
if WCSession.isSupported() { // check if the device support to handle an Apple Watch
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession() // activate the session
if session.paired { // Check if the iPhone is paired with the Apple Watch
// Do stuff
}
}
I hope It would help you :)
The answer is yes but you have to support watchOS 2 and iOS9 to do it.
You need to check the property pairedfrom WCSession class.
You can find all the information at Watch Connectivity Framework Reference. I also recommend to watch this video from WWDC 2015 and read this tutorial
Swift 5
import WatchConnectivity
Then:
final class WatchSessionManager: NSObject {
private override init() {}
static let shared = WatchSessionManager()
func setup() {
guard WCSession.isSupported() else {
return
}
let session = WCSession.default
session.delegate = self
session.activate()
}
}
extension WatchSessionManager: WCSessionDelegate {
func sessionDidBecomeInactive(_ session: WCSession) {}
func sessionDidDeactivate(_ session: WCSession) {}
func session(
_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?
) {
print("Apple Watch is paired: \(session.isPaired)")
}
}