I'm using OpenTok to create a video conference between 2 iOS devices. I want to send information about a user (i.e. a userID) to the other member of the conference so once I get the callback that a user is connected, I send that connection that information:
//Start Call - Session is already created with a valid session ID.
-(void) startCall{
OTError* error = nil;
self.session connectWithToken:myToken error:&error];
if (error){
NSLog(#"Session signal Error: %#", error);
}
}
//Session delegate methods
– (void) session:(OTSession*) session connectionCreated:(OTConnection*) connection{
OTError* error = nil;
[session signalWithType:#"UserInfo" string:self.user.userID connection:connection error:&error];
if (error){
NSLog(#"Session signal Error: %#", error);
}
}
-(void) session:(OTSession*) session receivedSignalType:(NSString*) type fromConnection:(OTConnection*) connection withString:(NSString*) string{
if ([type isEqualToString:#"UserInfo"]){
self.remoteUser.userID = string;
}
}
The problem is that in some cases, the OTConnection I bet back in the – session:receivedSignalType:fromConnection:withString: is nil. Not only that but when I put a breakpoint in that callback my local session's connection is also nil. I assume this means that my local session is not connected yet when I receive that signal but I wouldn't have thought it was possible to receive a signal if my local session is not connected. Additionally I don't send the signal until the connection is created, so how is it nil on the other device?
Has anyone else seen this or come up with a way to prevent it?
Thanks for any help.
Related
I'm trying to "send to" and "receive from" on same GCDAsyncSocket object, but it return's error as Attempting to accept while connected or accepting connections. Disconnect first. while trying to initialising the object.
My Code :
-(instancetype)initWithHost:(NSString *)host port:(NSInteger)port userData:(NSDictionary *)userData delegate:(id<AKSenderSocketDelegate>)delegate
{
self = [super init];
if (self)
{
self.delegate = delegate;
self.senderSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
self.senderSocket.userData = userData;
NSError *err = nil;
//---- Sender
if (![self.senderSocket connectToHost:host onPort:port error:&err])
{
NSLog(#"Failed to connect: %#", err);
}
else
{
NSLog(#"Connected to :%#:%ld",host,(long)port);
}
//---- Listener
if (![self.senderSocket acceptOnPort:0 error:&err])
{
NSLog(#"Failed to open lintening port. :%#",err.localizedDescription);
}
else
{
NSLog(#"Listening to port :%hu and host :%#",self.senderSocket.localPort,self.senderSocket.localHost);
}
}
return self;
}
Please help!!
You're overcooking the pudding (grin). Once you've connected, you're ready to start communicating. There's no need for the accept message.
connectToHost:onPort:error: is like placing a phone call: you call, the other party picks up (because that other party has already made itself available with acceptOnPort:error: or its equivalent). Once the connection is made, you can read and write data over the same socket; sockets are bi-directional.
So if you want to "place a call", use connectToHost:onPort:error:. If instead you want to wait for the other party to place the call, use acceptOnPort:error:. Regardless of which side of the conversation is the "connecter" or the "accepter", both sides of the conversation can both send (write) and listen (read).
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()
}
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.
WWDC 2014 Session 612 (45:14) highlights how to check the authorization status of Core Motion Services:
- (void)checkAuthorization:(void (^)(BOOL authorized))authorizationCheckCompletedHandler {
NSDate *now = [NSDate date];
[_pedometer queryPedometerDataFromDate:now toDate:now withHandler:^(CMPedometerData *pedometerData, NSError *error) {
// Because CMPedometer dispatches to an arbitrary queue, it's very important
// to dispatch any handler block that modifies the UI back to the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
authorizationCheckCompletedHandler(!error || error.code != CMErrorMotionActivityNotAuthorized);
});
}];
}
While this works, the first call to -queryPedometerDataFromDate:toDate:withHandler: will prompt the user for authorization via a system dialog. I would prefer to check the status without having to ask the user for permission for obvious UX reasons.
Is what I am trying to achieve possible or am I just thinking about the API the wrong way?
For iOS 11: Use the CMPedometer.authorizationStatus() method. By calling this method, you can determine if you are authorized, denied, restricted or notDetermined.
https://developer.apple.com/documentation/coremotion/cmpedometer/2913743-authorizationstatus
For devices running iOS 9-10, use CMSensorRecorder.isAuthorizedForRecording().
Here's a method that will work for all devices running iOS 9-11:
var isCoreMotionAuthorized: Bool {
if #available(iOS 11.0, *) {
return CMPedometer.authorizationStatus() == .authorized
} else {
// Fallback on earlier versions
return CMSensorRecorder.isAuthorizedForRecording()
}
}
[stepCounter queryStepCountStartingFrom:[NSDate date]
to:[NSDate date]
toQueue:[NSOperationQueue mainQueue]
withHandler:^(NSInteger numberOfSteps, NSError *error) {
if (error != nil && error.code == CMErrorMotionActivityNotAuthorized) {
// The app isn't authorized to use motion activity support.
This method will allow you to notify the user if the app isn't authorized to access Core Motion data. Simply create a CMPedometer instance called stepCounter and run the method above.
When receiving multiple packets via BLE notifications, iOS is only giving me access to the final packet sent. I am using YMSCoreBluetooth to connect to a BLE peripheral with multiple services, each of which has multiple characteristics. I connect to the peripheral, discover the services and discover the characteristics of those services without a problem. My goal is to subscribe to a certain characteristic's notifications and receive via the notifications a series of data packets. My subscription is successful and I can see through use of NSLogs within my code that I am receiving the notifications containing the data. The issue is that when I go to access the data from each notification as it comes in, every notification gives me only the data contained in the last packet sent.
My code for receiving notifications is as follows:
- (void)notifyCharacteristicHandler:(YMSCBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(#"Error: Error in handling notification.\n%#", error);
}
else if ([characteristic.name isEqualToString:#"InterestingChar"]) {
if (self.firstNotify) {
self.mutableData = [[NSMutableData alloc] init];
self.firstNotify = NO;
}
NSData *data = [[NSData alloc] init];
data = characteristic.cbCharacteristic.value;
[self.mutableData appendData:data];
self.notifyCounter++;
NSLog(#"Notify received! Count: %ld \nData =%#",(long)self.notifyCounter,self.mutableData);
}
else NSLog(#"Other notification received");
}
For instance, if I receive 5 notifications with the following data:
1 ababababab
2 bcbcbcbcbc
3 cdcdcdcdcd
4 dedededede
5 efefefefef
My NSLog would print out efefefefef for the first notify data, efefefefef efefefefef for the second, and so on appending the last data value for each subsequent notify.
I am trying to send the notifications as quickly as possible from the peripheral using BLE. The connection interval is between 20ms and 40ms (iOS demands a range of at least 20ms) and three packets are being sent per connection interval.
EDIT:
Paulw11's suggestion worked beautifully. I fixed the issue by amending the YMSCB 'didUpdateValueForCharacteristic' method to obtain the value of the characteristic and pass it along with the pointer to the characteristic itself onto the 'notifyCharacteristicHandler' method. The amended method now looks as follows:
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
__weak YMSCBPeripheral *this = self;
NSData *value = characteristic.value;
_YMS_PERFORM_ON_MAIN_THREAD(^{
YMSCBService *btService = [this findService:characteristic.service];
YMSCBCharacteristic *yc = [btService findCharacteristic:characteristic];
if (yc.cbCharacteristic.isNotifying) {
[btService notifyCharacteristicHandler:yc value:value error:error];
} else {
if ([yc.readCallbacks count] > 0) {
[yc executeReadCallback:characteristic.value error:error];
}
}
if ([this.delegate respondsToSelector:#selector(peripheral:didUpdateValueForCharacteristic:error:)]) {
[this.delegate peripheral:peripheral didUpdateValueForCharacteristic:characteristic error:error];
}
});
}
You obviously also need to amend the 'notifyCharacteristicHandler' method to accept the new argument.
Looking at the internal didUpdateValueForCharacteristic delegate method of the YMSCoreBluetooth library, it sends the data to your method using a "perform on main thread" and it doesn't capture the data - it just sends a reference to the characteristic. Also, it performs a "findCharacteristic" on the characteristic by executing a linear search through the array on the main thread even though this could have been done immediately on entering the delegate method on the current thread. Granted this isn't going to be a very big array but it seems that this library hasn't been created with performance in mind.
I suspect that you have a timing problem - by the time your method executes the data in the characteristic has been over written. If you have control over your peripheral, slow it right down for a test to see if the problem goes away.
If it is timing related then you could try a straight Core-Bluetooth implementation, or try a modification to YMSCoreBluetooth so that it captures the data earlier - perhaps if it created a copy of the peripheral at the start of didUpdateValueForCharacteristic and sent that to your method it would work.