Getting the right dictionary from delegate methods parameters - ios

I am wondering if this approach ensures that I am receiving the appropriate dictionary from the WCSessionDelegate method didReceiveMessage.
I am slightly confused with the uses of if let _, and have only come across the need to use this approach during this occurrence. What use cases is this approach for?
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
var dictionaryKey = "SomeKey"
if let _ = message[dictionaryKey] as? Bool { // receive sent over dictionary with a boolean value
// use dictionary information
}
}

Related

A proper way to receive applicationContext and avoid re-transfering? - WatchConnectivity

I have been working on sending an int notificationCount in form of ApplicationsContext from my iOS app to my WatchOS.
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {}
The problem is, the code snippet above only reacts, whenever the notificationCount is changed. Which means when I open my InterfaceController where I need the notificationCount, I don't have any numbers before the value gets updated from the iOS counterpart.
I do suspect that didReceiveApplicationContext only when the sending value is not the same. But is there a proper way to check the value of notificationCount for having the same value as the recent transfer to avoid some re-transfer?
Just save the notificationCount in your Extension Delegate's property (or any other instance you might use for storing data) and use it in your controller's awake method (or/and willActivate if appropriate). In addition, you might want to save it in your UserDefaults or a file so it would survive re-launching your app.
Something like this:
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {
var notificationCount: Int?
// ...
func applicationDidFinishLaunching() {
// do your normal init stuff
// read notificationCount from UserDefaults
}
// ...
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {
if let count = applicationContext["NotificationCount"] {
notificationCount = count
// save to UserDefaults here
}
}
}

Swift WatchConnectivity app context as Dictionary

I am working on my first Apple Watch app (an extension to my iOS app). I am facing a small problem in sending data from one WKInterfaceController to another.
My First Controller (InterfaceController.swift) has didReceiveMessage where it receives data from my iOS app.
func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
let myQrValue = message["qrCode"] as! String
let myQrImage = message["qrCodeImageData"] as! Data
var myData: [AnyHashable: Any] = ["myQrValue": myQrValue, "myQrImage": myQrImage]
if myQrValue.isEmpty == false {
WKInterfaceController.reloadRootControllers(withNames: ["QrScreen"], contexts: [myData])
}
}
Then in my Second Controller (QrInterfaceController.swift), I am having below to fetch the data sent from the first controller -
override func awake(withContext context: Any?) {
super.awake(withContext: context)
print("context \(context)")
if let myData = context {
print("myData \(myData)")
// userQrNumber.setText(myData)
}
if let myQrImage = myQrImage {
userQrImage.setImageData(myQrImage)
}
if let myQrLabel = myQrLabel {
userQrNumber.setText(myQrLabel)
}
self.setTitle("")
}
I am stuck (could be simple/silly question) as how to parse my data from the context in the second controller?
Also, the didReceiveMessage works only the second time when I launch my ViewController where the sendMessage code is placed. Is it normal?
First, you might want to redeclare myData as this:
var myData: [String: Any] = ...
which makes it a bit simpler. Then, in the awake function, you’d go ahead like this:
if let myData = context as? [String: Any] {
if let myQrImage = myData["myQrValue"] as? Date {
...
Does this show you the right direction?

No items from ExtensionDelegate array

I'm calling on my ExtensionDelegate from ComplicationController to give an array of evnts.
Seems to work fine calling ExtensionDelegate from InterfaceController, both of which are in my watch app.
But for some reason, I get 0 items in the evnts array when calling on my ExtensionDelegate from my ComplicationController.
Any ideas? Thanks!
ExtensionDelegate:
class ExtensionDelegate: NSObject, WKExtensionDelegate {
static var evnts = [Evnt]()
ComplicationController:
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
// extEvnts = 0 somehow here
let extEvnts = ExtensionDelegate.evnts
This all works fine when I do it from my InterfaceController though:
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let tColorValue = userInfo["TeamColor"] as? String, let matchValue = userInfo["Matchup"] as? String {
receivedData.append(["TeamColor" : tColorValue , "Matchup" : matchValue])
ExtensionDelegate.evnts.append(Evnt(dataDictionary: ["TeamColor" : tColorValue , "Matchup" : matchValue]))
doTable()
} else {
print("matchValue are not same as dictionary value")
}
}
func doTable() {
let extEvnts = ExtensionDelegate.evnts
self.rowTable.setNumberOfRows(extEvnts.count, withRowType: "rows")
for (index, evt) in extEvnts.enumerate() {
if let row = rowTable.rowControllerAtIndex(index) as? TableRowController {
row.mLabel.setText(evt.eventMatch)
} else {
print("nope")
}
}
}
When you declare evnts, you've initialised it to an empty array ([Evnt]()).
When you access it from getCurrentTimelineEntryForComplication(complication: withHandler:), if nothing has modified the array, it will still be empty.
Inside session(session:didReceiveUserInfo:), you add items to the array, then immediately call doTable(), at which point ExtensionDelegate.evnts is not empty, as it contains the items you added just moments previously.
Given that you have no items when getCurrentTimelineEntryForComplication(complication: withHandler:) is being called, it would appear that this is happening before session(session:didReceiveUserInfo:) occurs.
If you want to make sure that you have data when getCurrentTimelineEntryForComplication(complication: withHandler:) is called, you should load some data before or at that point in the WatchKit application lifecycle.

Using `InterfaceController` logic for `ComplicationController'

I'm doing an Apple Watch App, with a Complication.
I've got the WatchKit App part working great with this Ev class...
class Ev {
var evTColor:String
var evMatch:String
init(dataDictionary:Dictionary<String,String>) {
evTColor = dataDictionary["TColor"]!
evMatch = dataDictionary["Match"]!
}
class func newEv(dataDictionary:Dictionary<String,String>) -> Ev {
return Ev(dataDictionary: dataDictionary)
}
}
... and this InterfaceController
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let tColorValue = userInfo["TColor"] as? String, let matchValue = userInfo["Match"] as? String {
receivedData.append(["TColor" : tColorValue , "Match" : matchValue])
evs.append(Ev(dataDictionary: ["TColor" : tColorValue , "Match" : matchValue]))
doTable()
} else {
print("tColorValue and matchValue are not same as dictionary value")
}
}
func doTable() {
self.rowTable.setNumberOfRows(self.evs.count, withRowType: "rows")
for (index, evt) in evs.enumerate() {
if let row = rowTable.rowControllerAtIndex(index) as? TableRowController {
row.mLabel.setText(evt.evMatch)
row.cGroup.setBackgroundColor(colorWithHexString(evt.evTColor))
} else {
print("nope")
}
}
}
I'm having a hard time getting the same sort of thing to work in my Complication, any ideas?
I'm not sure if I can just use the same Ev code for my ExtensionDelegate, and then what exactly to put in my ComplicationController.
If I use the same Ev code in my ExtensionDelegate I'm getting a fatal error: use of unimplemented initializer init().
And in my ComplicationController I'm not sure how to go about best using the data I already have from InterfaceController to fill out the getCurrentTimelineEntryForComplication &getTimelineEntriesForComplication methods in ComplicationController.
Will post any extra code as needed, thanks!
EDIT:
Per a question, my data comes from CloudKit to the iPhone App (which I then pass to the Watch App via WCSession, so my problem is accessing that data in my Watch App for my Complication)
Instead of having your InterfaceController implement and receive the WCSession messages, I would set up a singleton class that receives those messages instead. That class can parse and organize your user info data from the WCSession. That singleton class can/will be accessible in your ComplicationController and your InterfaceController
Singletons are fairly easy to setup in swift:
class DataManager : WCSessionDelegate {
// This is how you create a singleton
static let sharedInstance = DataManager()
override init() {
super.init()
if WCSession.isSupported() {
self.watchConnectivitySession?.delegate = self
self.watchConnectivitySession?.activateSession()
}
}
// This is where you would store your `Ev`s once fetched
var dataObjects = [Ev]()
// This is the method that would fetch them for you
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
//parse your userInfoDictionary
self.dataObjects = evs
}
}
Then in your InterfaceController you can reference it using DataManager.sharedInstance.dataObjects to build your InterfaceController or ComplicationsController
The idea with a singleton is that you have a one global reference. DataManager only gets instantiated once and only once.

KVO observation not working with Swift generics

If I observe a property using KVO, if the observer is a generic class then I receive the following error:
An -observeValueForKeyPath:ofObject:change:context: message was
received but not handled.
The following setup demonstrates the problem succinctly. Define some simple classes:
var context = "SomeContextString"
class Publisher : NSObject {
dynamic var observeMeString:String = "Initially this value"
}
class Subscriber<T> : NSObject {
override func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject : AnyObject],
context: UnsafeMutablePointer<Void>) {
println("Hey I saw something change")
}
}
Instantiate them and try to observe the publisher with the subscriber, like so (done here inside a UIViewController subclass of a blank project):
var pub = Publisher()
var sub = Subscriber<String>()
override func viewDidLoad() {
super.viewDidLoad()
pub.addObserver(sub, forKeyPath: "observeMeString", options: .New, context: &context)
pub.observeMeString = "Now this value"
}
If I remove the generic type T from the class definition then everything works fine, but otherwise I get the "received but not handled error". Am I missing something obvious here? Is there something else I need to do, or are generics not supposed to work with KVO?
Explanation
There are two reasons, in general, that can prevent a particular Swift class or method from being used in Objective-C.
The first is that a pure Swift class uses C++-style vtable dispatch, which is not understood by Objective-C. This can be overcome in most cases by using the dynamic keyword, as you obviously understand.
The second is that as soon as generics are introduced, Objective-C looses the ability to see any methods of the generic class until it reaches a point in the inheritance hierarchy where an ancestor is not generic. This includes new methods introduced as well as overrides.
class Watusi : NSObject {
dynamic func watusi() {
println("watusi")
}
}
class Nguni<T> : Watusi {
override func watusi() {
println("nguni")
}
}
var nguni = Nguni<Int>();
When passed to Objective-C, it regards our nguni variable effectively as an instance of Watusi, not an instance of Nguni<Int>, which it does not understand at all. Passed an nguni, Objective-C will print "watusi" (instead of "nguni") when the watusi method is called. (I say "effectively" because if you try this and print the name of the class in Obj-C, it shows _TtC7Divided5Nguni00007FB5E2419A20, where Divided is the name of my Swift module. So ObjC is certainly "aware" that this is not a Watusi.)
Workaround
A workaround is to use a thunk that hides the generic type parameter. My implementation differs from yours in that the generic parameter represents the class being observed, not the type of the key. This should be regarded as one step above pseudocode and is not well fleshed out (or well thought out) beyond what's needed to get you the gist. (However, I did test it.)
class Subscriber : NSObject {
private let _observe : (String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void
required init<T: NSObject>(obj: T, observe: ((T, String) -> Void)) {
_observe = { keyPath, obj, changes, context in
observe(obj as T, keyPath)
}
}
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
_observe(keyPath, object, change, context)
}
}
class Publisher: NSObject {
dynamic var string: String = nil
}
let publisher = Publisher()
let subscriber = Subscriber(publisher) { _, _ in println("Something changed!") }
publisher.addObserver(subscriber, forKeyPath: "string", options: .New, context: nil)
publisher.string = "Something else!"
This works because Subscriber itself is not generic, only its init method. Closures are used to "hide" the generic type parameter from Objective-C.

Resources