HMAccessoryDelegate Method not called - ios

I want to get value of updatable characteristics for particular accessory.
So I am using func accessory(accessory: HMAccessory!, service: HMService!, didUpdateValueForCharacteristic characteristic: HMCharacteristic!) method.
But this method is not called. Another method from same delegate is being called. What should be the problem?

Enabled notification for the characteristic in viewWillAppear.
for (HMCharacteristic *thisCharacteristic in characteristicsArray) {
if ([thisCharacteristic.properties containsObject:HMCharacteristicPropertySupportsEventNotification]) {
[thisCharacteristic enableNotification:TRUE completionHandler:^(NSError *error) {
if (error) {
NSLog(#"Error while enabling notification");
}
else {
NSLog(#"Notification enabled");
}
}];
}
Also Disable the notification in viewWillDisappear

Related

CallDirectory Extension beginRequestWithExtensionContext not called

I'm trying to add the CallDirectory extension to enable the call identification based on the contacts available in my app.
I have enabled the permissions under Call settings.
But beginRequestWithExtensionContext is not called even when the device receives the call.
beginRequestWithExtensionContext is in the subclass of CXCallDirectoryProvider which conforms to CXCallDirectoryExtensionContextDelegate
The documentation says:
You set up both identification and blocking of incoming calls in the
implementation of the beginRequest(with:) method of the
CXCallDirectoryProvider subclass of your Call Directory extension.
This method is called when the system launches the app extension.
When does the system launches the app extension ?
I setup it as following in my delegate:
[[CXCallDirectoryManager sharedInstance] getEnabledStatusForExtensionWithIdentifier:#"com.x.CallerID" completionHandler:^(CXCallDirectoryEnabledStatus enabledStatus, NSError * _Nullable error) {
if (enabledStatus == 0) {
// NSLog(#"Code 0 tells you that there's an error. Common is that the identifierString is wrong.");
} else if (enabledStatus == 1) {
DDLogDebug(#"%#",#"CallerID - Code 1 is deactivated extension");
[self enableCallerIdAlert];
} else if (enabledStatus == 2) {
DDLogDebug(#"%#",#"CallerID - Code 2 is an activated extension");
}
}];
[[CXCallDirectoryManager sharedInstance] reloadExtensionWithIdentifier:#"com.x.CallerID" completionHandler:^(NSError *error){
if(error) {
NSLog(#"CallerID - refresh failed. error is %#",[error description]);
}
//your completetion
}];
CallDirectoryHAndler.m:
- (void)beginRequestWithExtensionContext:(CXCallDirectoryExtensionContext *)context
{
[super beginRequestWithExtensionContext:context];
context.delegate = self;
//phoneNumber , label are dummy
[context addIdentificationEntryWithNextSequentialPhoneNumber:phoneNumber label:label];
[context completeRequestWithCompletionHandler:nil];
}
Could someone please point out What I'm missing here?

watchOS Send Message not getting called

Hi I'm having an issue with my code it looks like send message isn't getting called for some reason, thanks
if ([[WCSession defaultSession] isReachable]) {
NSLog(#"Initiating WCSession to Read iPhone Data");
[[WCSession defaultSession] sendMessage:watchData replyHandler:^(NSDictionary *dataFromPhone) {
NSLog(#"Sending Empty Write Data Array to iPhone...%#", watchData);
}
errorHandler:^(NSError *error) {
// Log error
NSLog(#"Error: %#", error);
}];
} else {
//we aren't in range of the phone, they didn't bring it on their run
NSLog(#"Unable to connect to iPhone");
}
From what I see this is the code that runs on iOS, which controls whether the Apple Watch is reachable, but you have to remember (if you have not done of course) to enable the session from either device with the following code, so enable the communication system
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}

GKMatchmaker findMatchForRequest invite never received

I'm trying to invite nearby players to a match, but the invite is either never sent or never received.
GKMatchMaker startBrowsingForNearbyPlayersWithHandler works and returns nearby players that are on same wifi, but then I use findMatchForRequest and it returns a match without any players, and the players I try to invite never receive an invite notification. Here is my code.
I start by authenticating the local player:
GKLocalPlayer.localPlayer.authenticateHandler= ^(UIViewController *controller, NSError *error)
{
if (error)
{
NSLog(#"%s:: Error authenticating: %#", __PRETTY_FUNCTION__, error.localizedDescription);
return;
}
if(controller)
{
// User has not yet authenticated
[pViewController presentViewController:controller animated:YES completion:^(void)
{
[self lookForNearbyPlayers];
}];
return;
}
[self lookForNearbyPlayers];
};
-(void)lookForNearbyPlayers
{
if(!GKLocalPlayer.localPlayer.authenticated)
{
NSLog(#"%s:: User not authenticated", __PRETTY_FUNCTION__);
return;
}
I register my view controller as a delegate of GKLocalPlayerListener:
[GKLocalPlayer.localPlayer registerListener:self]; // self is a view controller.
// This works. My test local player which is a second device and appleID I setup shows up when this handler is called.
[GKMatchmaker.sharedMatchmaker startBrowsingForNearbyPlayersWithHandler:^(GKPlayer *player, BOOL reachable)
{
NSArray * paPlayers= [NSArray arrayWithObject:player];
_pMatchRequest= [[GKMatchRequest alloc] init];
_pMatchRequest.minPlayers= 2;
_pMatchRequest.maxPlayers= 4;
_pMatchRequest.recipients = paPlayers;
_pMatchRequest.inviteMessage = #"Join our match!";
_pMatchRequest.recipientResponseHandler = ^(GKPlayer *player, GKInviteeResponse response)
{
// This is never called.
NSLog((response == GKInviteeResponseAccepted) ? #"Player %# Accepted" : #"Player %# Declined", player.alias);
};
// This returns with a match without any players.
[GKMatchmaker.sharedMatchmaker findMatchForRequest:_pMatchRequest withCompletionHandler:^(GKMatch *match, NSError *error)
{
if(error)
{
NSLog(#"%s:: %#", __PRETTY_FUNCTION__, error.localizedDescription);
return;
}
else if(match != nil)
{
_pMatch= match;
match.delegate = self;
NSLog(#"players count= %lu", (unsigned long)_pMatch.players.count); // Always returns 0
}
}];
}
}
I have delegate methods for GKLocalPlayerListener setup, but they are never called:
- (void)player:(GKPlayer *)player didRequestMatchWithRecipients:(NSArray<GKPlayer *> *)recipientPlayers
{
NSLog(#"%s", __PRETTY_FUNCTION__);
}
- (void)player:(GKPlayer *)player didAcceptInvite:(GKInvite *)invite
{
NSLog(#"%s", __PRETTY_FUNCTION__);
}
Does anyone know how to get this to work without GKMatchmakerViewController and for iOS9? The only examples I can find have the deprecated -inviteHandler method.
This code is working in Swift if you know how you can convert it to Objective-C and to try it.
GKMatchmaker.sharedMatchmaker().findMatchForRequest(
request,
withCompletionHandler: {(match : GKMatch!, error: NSError!) -> Void in
NSLog("This works")
})
Based on multiple questions here on SO, Game Center seems to be getting stuck from time to time. In the best case, it returns "Game not recognized" errors. In the worst case, it just cheerfully returns nil to GC calls. Sometimes it resumes working on it's own, sometimes it doesn't. But it seems you can kickstart it again by logging into iTunesConnect and do any of the following:
Add a leaderboard
Change the default leaderboard
Add an achievement
I've added this to my debugging routine. If some aspect of GC stops working, or returns nil, I try making one of the above changes in iTunesConnect before proceeding. In my case, I get the "game not recognized" several times per week, but several others have noted the "nil return values."
I know this an older post, but I ran across it when trying to establish a connection between several app instances over the internet. I believe the part you're missing is that after registering for the listener, you need to receive the connected status with
- (void)match:(GKMatch *)match
player:(GKPlayer *)player
didChangeConnectionState:(GKPlayerConnectionState)state
{
NSLog(#">>> did change state");
if (state == GKPlayerStateConnected)
{
NSLog(#">>>> match:%# did change to Connected for player %# ",match, player.displayName);
}
else if (state == GKPlayerStateDisconnected)
{
NSLog(#">>>> match:%# disconnected for player %# ",match, player.displayName);
}
I find the match has 0 players when the completionHandler is called from findMatchForRequest:, but that I can successfully use the GKMatch and GKPlayer as returned in didChangeConnectionState:
Hope that helps someone who reads this long after the OP.

Using WCSession with more than one ViewController

I found many questions and many answers but no final example for the request:
Can anyone give a final example in Objective C what is best practice to use WCSession with an IOS app and a Watch app (WatchOS2) with more than one ViewController.
What I noticed so far are the following facts:
1.) Activate the WCSession in the parent (IOS) app at the AppDelegate:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Any other code you might have
if ([WCSession isSupported]) {
self.session = [WCSession defaultSession];
self.session.delegate = self;
[self.session activateSession];
}
}
2.) On the WatchOS2 side use <WCSessionDelegate>. But the rest is totally unclear for me! Some answers are talking from specifying keys in the passing Dictionary like:
[session updateApplicationContext:#{#"viewController1": #"item1"} error:&error];
[session updateApplicationContext:#{#"viewController2": #"item2"} error:&error];
Others are talking about retrieving the default session
WCSession* session = [WCSession defaultSession];
[session updateApplicationContext:applicationDict error:nil];
Others are talking about different queues? "It is the client's responsibility to dispatch to another queue if necessary. Dispatch back to the main."
I am totally confused. So please give an example how to use WCSession with an IOS app and a WatchOS2 App with more than one ViewController.
I need it for the following case (simplified):
In my parent app I am measuring heart rate, workout time and calories. At the Watch app 1. ViewController I will show the heart rate and the workout time at the 2. ViewController I will show the heart rate, too and the calories burned.
As far as I understand the task you just need synchronisation in a Phone -> Watch direction so in a nutshell a minimum configuration for you:
Phone:
I believe the application:didFinishLaunchingWithOptions: handler is the best place for the WCSession initialisation therefore place the following code there:
if ([WCSession isSupported]) {
// You even don't need to set a delegate because you don't need to receive messages from Watch.
// Everything that you need is just activate a session.
[[WCSession defaultSession] activateSession];
}
Then somewhere in your code that measures a heart rate for example:
NSError *updateContextError;
BOOL isContextUpdated = [[WCSession defaultSession] updateApplicationContext:#{#"heartRate": #"90"} error:&updateContextError]
if (!isContextUpdated) {
NSLog(#"Update failed with error: %#", updateContextError);
}
update:
Watch:
ExtensionDelegate.h:
#import WatchConnectivity;
#import <WatchKit/WatchKit.h>
#interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>
#end
ExtensionDelegate.m:
#import "ExtensionDelegate.h"
#implementation ExtensionDelegate
- (void)applicationDidFinishLaunching {
// Session objects are always available on Apple Watch thus there is no use in calling +WCSession.isSupported method.
[WCSession defaultSession].delegate = self;
[[WCSession defaultSession] activateSession];
}
- (void)session:(nonnull WCSession *)session didReceiveApplicationContext:(nonnull NSDictionary<NSString *,id> *)applicationContext {
NSString *heartRate = [applicationContext objectForKey:#"heartRate"];
// Compose a userInfo to pass it using postNotificationName method.
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:heartRate forKey:#"heartRate"];
// Broadcast data outside.
[[NSNotificationCenter defaultCenter] postNotificationName: #"heartRateDidUpdate" object:nil userInfo:userInfo];
}
#end
Somewhere in your Controller, let's name it XYZController1.
XYZController1:
#import "XYZController1.h"
#implementation XYZController1
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUpdatedHeartRate:) name:#"heartRateDidUpdate" object:nil];
}
-(void)handleUpdatedHeartRate:(NSNotification *)notification {
NSDictionary* userInfo = notification.userInfo;
NSString* heartRate = userInfo[#"heartRate"];
NSLog (#"Successfully received heartRate notification!");
}
#end
Code hasn't been tested I just wrote it as is so there can be some typos.
I think the main idea now is quite clear and a transfer of remaining types of data is not that tough task.
My current WatchConnectivity architecture much more complicated but nevertheless it is based on this logic.
If you still have any questions we might move a further discussion to the chat.
Well, this is simplified version of my solution as requested by Greg Robertson. Sorry it's not in Objective-C anymore; I'm just copy-pasting from existing AppStore-approved project to make sure there will be no mistakes.
Essentially, any WatchDataProviderDelegate can hook to data provider class as that provides array holder for delegates (instead of one weak var).
Incoming WCSessionData are forwarded to all delegates using the notifyDelegates() method.
// MARK: - Data Provider Class
class WatchDataProvider: WCSessionDelegate {
// This class is singleton
static let sharedInstance = WatchDataProvider()
// Sub-Delegates we'll forward to
var delegates = [AnyObject]()
init() {
if WCSession.isSupported() {
WCSession.defaultSession().delegate = self
WCSession.defaultSession().activateSession()
WatchDataProvider.activated = true;
}
}
// MARK: - WCSessionDelegate
public func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
processIncomingMessage(userInfo)
}
public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String: AnyObject]) {
processIncomingMessage(applicationContext)
}
func processIncomingMessage(dictionary: [String:AnyObject] ) {
// do something with incoming data<
notifyDelegates()
}
// MARK: - QLWatchDataProviderDelegate
public func addDelegate(delegate: AnyObject) {
if !(delegates as NSArray).containsObject(delegate) {
delegates.append(delegate)
}
}
public func removeDelegate(delegate: AnyObject) {
if (delegates as NSArray).containsObject(delegate) {
delegates.removeAtIndex((delegates as NSArray).indexOfObject(delegate))
}
}
func notifyDelegates()
{
for delegate in delegates {
if delegate.respondsToSelector("watchDataDidUpdate") {
let validDelegate = delegate as! WatchDataProviderDelegate
validDelegate.watchDataDidUpdate()
}
}
}
}
// MARK: - Watch Glance (or any view controller) listening for changes
class GlanceController: WKInterfaceController, WatchDataProviderDelegate {
// A var in Swift is strong by default
var dataProvider = WatchDataProvider.sharedInstance()
// Obj-C would be: #property (nonatomic, string) WatchDataProvider *dataProvider
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
dataProvider.addDelegate(self)
}
// WatchDataProviderDelegate
func watchDataDidUpdate() {
dispatch_async(dispatch_get_main_queue(), {
// update UI on main thread
})
}}
}
class AnyOtherClass: UIViewController, WatchDataProviderDelegate {
func viewDidLoad() {
WatchDataProvider.sharedInstance().addDelegate(self)
}
// WatchDataProviderDelegate
func watchDataDidUpdate() {
dispatch_async(dispatch_get_main_queue(), {
// update UI on main thread
})
}}
}
Doing the session management (however WCSession is singleton) in a View-Controller smells like MVC violation (and I've seen too many Watch blog posts wrong this way already).
I made an umbrella singleton class over the WCSession, that is first strongly referenced from Watch Extension Delegate to make sure it will load soon and do not get deallocated in the middle of work (e.g. when a View-Controller disappears while transferUserInfo or transferCurrentComplicationUserInfo happens in another watch thread).
Only this class then handles/holds the WCSession and decouples the session data (Model) from all the View-Controller(s) in watch app, exposing data mostly though public static class variables providing at least basic level of thread-safety.
Then this class is used both from complication controller, glance controller and other view controllers. Updates run in the background (or in backgroundFetchHandler), none of the apps (iOS/WatchOS) is required to be in foreground at all (as in case of updateApplicationContext) and the session does not necessarily have to be currently reachable.
I don't say this is ideal solution, but finally it started working once I did it this way. I'd love to hear that this is completely wrong, but since I had lots of issues before going with this approach, I'll stick to it now.
I do not give code example intentionally, as it is pretty long and I don't want anyone to blindly copy-paste it.
I found, with "try and error", a solution. It is working, but I don't know exactly why! If I send a request from the Watch to the IOS app, the delegate of that ViewController of the Watch app gets all the data from the main queue from the IOS app. I added the following code in the - (void)awakeWithContext:(id)context and the - (void)willActivate of all ViewControllers of the Watch app:
By example 0 ViewController:
[self packageAndSendMessage:#{#"request":#"Yes",#"counter":[NSString stringWithFormat:#"%i",0]}];
By example 1 ViewController1:
[self packageAndSendMessage:#{#"request":#"Yes",#"counter":[NSString stringWithFormat:#"%i",1]}];
/*
Helper function - accept Dictionary of values to send them to its phone - using sendMessage - including replay from phone
*/
-(void)packageAndSendMessage:(NSDictionary*)request
{
if(WCSession.isSupported){
WCSession* session = WCSession.defaultSession;
session.delegate = self;
[session activateSession];
if(session.reachable)
{
[session sendMessage:request
replyHandler:
^(NSDictionary<NSString *,id> * __nonnull replyMessage) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#".....replyHandler called --- %#",replyMessage);
NSDictionary* message = replyMessage;
NSString* response = message[#"response"];
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeSuccess];
if(response)
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", response);
else
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", response);
});
}
errorHandler:^(NSError * __nonnull error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", error.localizedDescription);
});
}
];
}
else
{
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", #"Session Not reachable");
}
}
else
{
NSLog(#"WK InterfaceController - (void)packageAndSendMessage = %#", #"Session Not Supported");
}
}

loadDefaultLeaderboardIdentifierWithCompletionHandler keeps calling when return from background to app

I have an app using Apple GameCenter leaderboard, when I invoke
[[GKLocalPlayer localPlayer] loadDefaultLeaderboardIdentifierWithCompletionHandler:^(NSString *leaderboardIdentifier, NSError *error) {
// This section call after become active
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
}
else {
if (success) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
success();
}
}
}];
Everything goes great.
But, when I going to background and comeback and become active, the method invoke again and I didn't call it. I guess the Framework did it.
Why that?
How can I avoid that method call?

Resources