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?
Related
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
}
}
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
}
}
In my viewController, I have a varible for an AVAudioPlayer
var audioPlayer = AVAudioPlayer()
I want to acess this varible in my watchKit app so that I can play and pause the AVAudioPlayer from the watchKit app. Like
audioPlayer.play()
audioPlayer.pause()
How can I acess this variable from my watchKit app? Thanks for the help! I'm using Swift 3 and Xcode 8.
Since watchOS 2, you can't use AppGroups to share data directly between your iOS app and the WatchKit app.
Your only option to communicate between the two is the WatchConnectivity framework. Using WatchConnectivity, you can signal the iOS app using instant messaging to start/stop playing. On iOS in your AppDelegate implement something like this:
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
if let content = message["play"] as? [String:Any] {
audioPlayer.play()
replyHandler(["startedPlaying":true])
} else if let content = message["pause"] as? [String:Any] {
audioPlayer.pause()
replyHandler(["pausedMusic":true])
}
}
And in your Watch app you need to send messages with the content specified in your AppDelegate's session(_:didReceiveMessage:replyHandler:). If you don't need to send a response back to the Watch app, you can just use session(_:didReceiveMessage:) and get rid of the replyHandler part.
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)")
}
}
I wonder if we can share datas between apps with the new iOS 8 feature : App groups (using NSUserDefaults) - Or if App Groups only share datas between the main app and its extension?
I actually enabled the App Groups feature on both of the apps that should share datas between them (they belong the same company). They also have the same App Groups thing (like group.com.company.myApp).
Here's the code on the first one (in Swift)
NSUserDefaults.standardUserDefaults().setBool(true, forKey:"Bool")
NSUserDefaults.standardUserDefaults().synchronize()
And here's the code on the second one (in Objective-C)
NSUserDefaults *datas = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.company.myApp"];
NSLog(#"%#", [datas boolForKey:#"Bool"]);
Sadly, the Bool always returns nil.
If anyone has a solution :)
Thanks
Check out this thread on the Apple Developer Forums:
https://devforums.apple.com/message/977151#977151
I believe that both instances need to use the group ID (initializing with initWithSuiteName:) or else they are reading/writing to different user defaults sets.
So your Swift code would change to:
var userDefaults = NSUserDefaults(suiteName: "group.com.company.myApp")
userDefaults.setBool(true, forKey: "Bool")
userDefaults.synchronize()
App Groups no longer work in WatchOS2. You must use the watch connectivity Framework.
in your iOS App:
import UIKit
import WatchConnectivity
class ViewController: UIViewController, WCSessionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
do {
let applicationDict = ["key" : "value"]
try WCSession.defaultSession().updateApplicationContext(applicationDict)
} catch {
// Handle errors here
}
}
}
In your Watch OS2 App:
import WatchKit
import WatchConnectivity
import Foundation
class InterfaceController: WKInterfaceController, WCSessionDelegate {
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
print(applicationContext)
}