I'm using a host/client approach to using MultiPeer Connectivity.
So, when a user his the disconnect button
-(IBAction)disconnect:(id)sender {
[_appDelegate.mcManager.session disconnect];
[_arrConnectedDevices removeAllObjects];
ConnectionsViewController *game = [self.storyboard instantiateViewControllerWithIdentifier:#"ConnectionsViewController"];
[self presentViewController:game animated:YES completion:nil];
}
Now, this works fine. From the hosts point of view it receives a disconnect message in the log. and the client moves to the new view controller. And the table is updated. with this.
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
if (_makeSureImHost) {
[_arrConnectedDevices addObject:peerDisplayName];
[_tblConnectedDevices performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
else {
[self sendMessageToHostWithMessage:#"deviceInfo"];
}
}
else if (state == MCSessionStateNotConnected){
if ([_arrConnectedDevices count] > 0) {
int indexOfPeer = (int)[_arrConnectedDevices indexOfObject:peerDisplayName];
[_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
NSLog(#"%# Disconnected", peerDisplayName);
[_tblConnectedDevices performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
_tblConnectedDevices.frame = CGRectMake(_backgroundImage.frame.size.width / 2 - 150, self.backgroundImage.frame.size.height / 3, 300, 150);
}
}
}
}
END OF LOBBY VIEW CONTROLLER
START OF CONNECTION VIEW CONTROLLER
When a client presses to browse for local devices this runs
- (IBAction)browseForDevices:(id)sender {
[UIView animateWithDuration:0.5f
animations:^{
_searchButton.frame = CGRectMake(-100, self.backgroundImage.frame.size.height/2 + 60, 100, 35.0);
_hostButton.alpha = 0;
_modeLabel.alpha = 0;
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[_appDelegate mcManager] setupPeerAndSessionWithDisplayName:[UIDevice currentDevice].name];
[[_appDelegate mcManager] advertiseSelf:false];
[[_appDelegate mcManager] setupMCBrowser];
[[[_appDelegate mcManager] browser] setDelegate:self];
_appDelegate.mcManager.browser.maximumNumberOfPeers = 1;
_appDelegate.mcManager.browser.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentViewController:[[_appDelegate mcManager] browser] animated:YES completion:nil];
});
}
When a connection is established
-(void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController {
[_appDelegate.mcManager.browser dismissViewControllerAnimated:NO completion:^{
[self launchViewController];
}];
}
-(void)launchViewController {
LobbyViewController *lobby = [self.storyboard instantiateViewControllerWithIdentifier:#"LobbyViewController"];
[self presentViewController:lobby animated:NO completion:nil];
}
From this
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification {
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
[self browserViewControllerDidFinish:[[_appDelegate mcManager] browser]];
}
}
else if (state == MCSessionStateNotConnected){
if ([_arrConnectedDevices count] > 0) {
int indexOfPeer = (int)[_arrConnectedDevices indexOfObject:peerDisplayName];
[_arrConnectedDevices removeObjectAtIndex:indexOfPeer];
}
}
}
Now. When a connection is made for the first time. This all works flawlessly. It connects, loads the view, host starts the game, game works fine and data is transferred perfectly.
However, if you disconnect from the lobby. Get moved to the connection viewcontroller then browse for devices again. It will connect, however the lobby viewcontroller will NOT be in the view hierarchy and will close the browser and stay in the connection view controller.
Then, to top it off, the connection has been made. yet, when it receives a message from the host, it will send the response, twice.. or three times, or four, leading me to a dead end. The only thing I can presume is that the previous session is being remembered somehow from the "clients" point of view.
Now, steps I can take to avoid this mess. If I kill the app and relaunch it I can now connect again from the clients point of view. Which leads me to believe, the problem is on the clients end.
My problem is that I have to absolutely sort this out. So a disconnect will fully remove everything from the session. So they can reconnect again. And cannot rely on a message to tell the user to restart their application. It just cannot be.
Here's my entire MCManager.m file.
#implementation MCManager
-(id)init{
self = [super init];
if (self) {
_peerID = nil;
_session = nil;
_browser = nil;
_advertiser = nil;
}
return self;
}
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName{
_peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
_session = [[MCSession alloc] initWithPeer:_peerID];
_session.delegate = self;
}
-(void)setupMCBrowser{
_browser = [[MCBrowserViewController alloc] initWithServiceType:#"chat-files" session:_session];
}
-(void)advertiseSelf:(BOOL)shouldAdvertise{
if (shouldAdvertise) {
_advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:#"chat-files"
discoveryInfo:nil
session:_session];
[_advertiser start];
}
else{
[_advertiser stop];
_advertiser = nil;
}
}
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSDictionary *dict = #{#"peerID": peerID,
#"state" : [NSNumber numberWithInt:state]
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"MCDidChangeStateNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSDictionary *dict = #{#"data": data,
#"peerID": peerID
};
[[NSNotificationCenter defaultCenter] postNotificationName:#"MCDidReceiveDataNotification"
object:nil
userInfo:dict];
}
-(void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress{
}
-(void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error{
}
-(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID{
}
#end
#import <MultipeerConnectivity/MultipeerConnectivity.h>
#interface MCManager : NSObject <MCSessionDelegate>
#property (nonatomic, strong) MCPeerID *peerID;
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCBrowserViewController *browser;
#property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
-(void)setupPeerAndSessionWithDisplayName:(NSString *)displayName;
-(void)setupMCBrowser;
-(void)advertiseSelf:(BOOL)shouldAdvertise;
#end
If anyone knows what I'm doing wrong I'd much appreciate it. This is driving me nuts.
[[NSNotificationCenter defaultCenter] removeObserver:name:object:];
Has fixed all my problems. Hopefully helps some other people too.
With Swift, I end the multipeer session as follows (after completing checking the deinit of session tracker):
func stopSession() {
self.serviceBrowser.stopBrowsingForPeers()
self.serviceBrowser.delegate = nil
self.serviceAdvertiser.stopAdvertisingPeer()
self.serviceAdvertiser.delegate = nil
self.session.disconnect()
self.peerSessionIDs.removeAll()
self.session.delegate = nil
self.session = nil
self.multipeerConnectivityService = nil
self.serviceType = nil
}
In other words, everything that was registered and the initializer is de-initialized. I am doing this in reverse order, but I'm not sure if order is important here.
Related
My App was rejected by Apple because it can't connect to other device running iOS 10.1.1 on Wi-Fi connected to an IPv6 network.
When I tap on connect, the app continues to search for invitees and no further user action is produced.
I use Multi-peer Connectivity and I never tested my App being connected to an IPv6(It's my first release). But the App run very fine without having any connection or being connected to IPv4 network.
I don't know why the App is running and connecting fine using the IPv4 and doesn't connect to peer if the iPad is connected to an IPv6 network.
So my Question: Is it possible to use Multi-peer Connectivity with IPv6 so that Apple can approve the App or how should I handle this Issue ?
Here is my Code, maybe it is something wrong there.
#interface ConnectionManageriOS7 () <MCSessionDelegate, MCBrowserViewControllerDelegate>
{
UILocalNotification *_expireNotification;
UIBackgroundTaskIdentifier _taskId;
}
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCPeerID *localPeerID;
#property (nonatomic, strong) MCBrowserViewController *browserVC;
#property (nonatomic, strong) MCAdvertiserAssistant *advertiser;
#end
static ConnectionManageriOS7 *_manager = nil;
#implementation ConnectionManageriOS7
+ (ConnectionManageriOS7 *)connectManager {
#synchronized([ConnectionManageriOS7 class]){
if (_manager == nil) {
_manager = [[ConnectionManageriOS7 alloc] init];
}
return _manager;
}
return nil;
}
- (id)init {
self = [super init];
if (self) {
[self setupSessionAndAdvertiser];
}
return self;
}
- (void)setupSessionAndAdvertiser {
_localPeerID = [[MCPeerID alloc] initWithDisplayName:[UIDevice currentDevice].name];;
_session = [[MCSession alloc] initWithPeer:_localPeerID];
_session.delegate = self;
}
- (void)connectWithDelegate:(id)delegate {
_delegate = delegate;
if (_session.connectedPeers.count) {
if ([_delegate respondsToSelector:#selector(didConntectedWithManager:)]) {
[_delegate didConntectedWithManager:self];
}
} else {
if (_advertiser == nil) {
_advertiser = [[MCAdvertiserAssistant alloc] initWithServiceType:VISUS_Service
discoveryInfo:nil
session:_session];
_isConnected = NO;
[_advertiser start];
}
if (_browserVC == nil) {
_browserVC = [[MCBrowserViewController alloc] initWithServiceType:VISUS_Service session:_session];
_browserVC.delegate = self;
}
[(UIViewController *)delegate presentViewController:_browserVC
animated:YES completion:nil];
}
}
- (void)sendMessage:(NSString *)message {
NSData *textData = [message dataUsingEncoding:NSASCIIStringEncoding];
NSLog(#"Send Data: %#", message);
NSError *error = nil;
[_session sendData:textData
toPeers:_session.connectedPeers
withMode:MCSessionSendDataReliable
error:&error];
if (error) {
//
[self session:_session peer:nil didChangeState:MCSessionStateNotConnected];
NSLog(#"error %#", error.userInfo);
}
}
- (void)stopService {
NSLog(#"Stop Service");
[_advertiser stop];
_advertiser = nil;
_browserVC = nil;
}
#pragma marks -
#pragma marks MCBrowserViewControllerDelegate
- (void) dismissBrowserVC{
[_browserVC dismissViewControllerAnimated:YES completion:nil];
}
// Notifies the delegate, when the user taps the done button
- (void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController {
if ([_delegate respondsToSelector:#selector(didConntectedWithManager:)]) {
[_delegate didConntectedWithManager:self];
}
[self dismissBrowserVC];
}
// Notifies delegate that the user taps the cancel button.
- (void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{
if (_browserVC == nil) {
[browserViewController dismissViewControllerAnimated:YES completion:nil];
}else {
[self dismissBrowserVC];
}
}
#pragma marks -
#pragma marks MCBrowserViewControllerDelegate
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
_isConnected = true;
if ([_delegate respondsToSelector:#selector(willConntectedWithManager:)]) {
[_delegate willConntectedWithManager:self];
}
}
else {
_isConnected = false;
[self stopService];
if ([_delegate respondsToSelector:#selector(didDisconntectedWithManager:)]) {
[_delegate didDisconntectedWithManager:self];
}
}
}
}
// Received data from remote peer
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
// Decode data back to NSString
NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Receive Data: %#", message);
// append message to text box:
dispatch_async(dispatch_get_main_queue(), ^{
if ([_delegate respondsToSelector:#selector(connectionManager:receivedString:)]) {
[_delegate connectionManager:self receivedString:message];
}
});
}
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error {
_isConnected = false;
[self stopService];
NSLog(#"----- Error ----- %#", error.localizedDescription);
}
// Received a byte stream from remote peer
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID {
}
// Start receiving a resource from remote peer
- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress {
}
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
- (void) createExpireNotification
{
[self killExpireNotification];
if (_session.connectedPeers.count != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
_expireNotification = n;
_expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
_expireNotification.alertBody = #"Bluetooth Connectivity is about to disconnect. Open the app to resume Test";
_expireNotification.soundName = UILocalNotificationDefaultSoundName;
_expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:_expireNotification];
}
}
- (void) killExpireNotification
{
if (_expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:_expireNotification];
_expireNotification = nil;
}
}
- (void)bacgroundHandling {
_taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self stopService];
[[UIApplication sharedApplication] endBackgroundTask:_taskId];
_taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
// http://down.vcnc.co.kr/WWDC_2013/Video/708.pdf -- wwdc tutorial, this part is towards the end (p119)
// self.arrayInvitationHandler = [NSArray arrayWithObject:[invitationHandler copy]];
// ask the user
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:peerID.displayName
message:#"Would like to create a session with you"
delegate:self
cancelButtonTitle:#"Decline" otherButtonTitles:#"Accept", nil];
[alertView show];
}
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
// retrieve the invitationHandler and check whether the user accepted or declined the invitation...
BOOL accept = (buttonIndex != alertView.cancelButtonIndex) ? YES : NO;
// respond
if(accept) {
// void (^invitationHandler)(BOOL, MCSession *) = [self.arrayInvitationHandler objectAtIndex:0];
// invitationHandler(accept, self.mySession);
}
else
{
NSLog(#"Session disallowed");
}
}
- (void)terminate {
[self killExpireNotification];
[self stopService];
}
#end
I have solved the problem. For everybody with simular problem:
It wasn't a problem with IPv6, it is a matter of how to use Multipeer connectivity. In my Case I tryied to the the IPv6 connection with a iPad and a Simulator. And I used my Macbook for creating a nat64 network. And the reason why the simulator and iPad never saw each other the fact that they where not connected to same wifi network.
Solution:
Just take for testing two iPads and use your mac as nat64 network accesspoint.
So, I have all multipeer connectivity related code in the main thread. I have a MCSession, MCNearbyServiceAdvertiser, and a MCNearbyServiceBrowser. These are all created with peerID, and I make sure only one sends an invitation.
The session becomes connected. My problems is that, two clients take around 20-30 seconds to connect. This is unacceptable. The clients are on good Wifi, and bluetooth. I want the browsing, invitation handler, and connection to take place within 1 second. Does anyone have any idea what is slowing things down?
Code is exactly as provided here and I also implemented certificateHandler(YES)
#interface SessionController () // Class extension
#property (nonatomic, strong) MCPeerID *peerID;
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCNearbyServiceAdvertiser *serviceAdvertiser;
#property (nonatomic, strong) MCNearbyServiceBrowser *serviceBrowser;
// Connected peers are stored in the MCSession
// Manually track connecting and disconnected peers
#property (nonatomic, strong) NSMutableOrderedSet *connectingPeersOrderedSet;
#property (nonatomic, strong) NSMutableOrderedSet *disconnectedPeersOrderedSet;
#end
#implementation SessionController
static NSString * const kMCSessionServiceType = #"mcsessionp2p";
#pragma mark - Initializer
- (instancetype)init
{
self = [super init];
if (self)
{
_peerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];
_connectingPeersOrderedSet = [[NSMutableOrderedSet alloc] init];
_disconnectedPeersOrderedSet = [[NSMutableOrderedSet alloc] init];
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
// Register for notifications
[defaultCenter addObserver:self
selector:#selector(startServices)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[defaultCenter addObserver:self
selector:#selector(stopServices)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[self startServices];
_displayName = self.session.myPeerID.displayName;
}
return self;
}
#pragma mark - Memory management
- (void)dealloc
{
// Unregister for notifications on deallocation.
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Nil out delegates
_session.delegate = nil;
_serviceAdvertiser.delegate = nil;
_serviceBrowser.delegate = nil;
}
#pragma mark - Override property accessors
- (NSArray *)connectedPeers
{
return self.session.connectedPeers;
}
- (NSArray *)connectingPeers
{
return [self.connectingPeersOrderedSet array];
}
- (NSArray *)disconnectedPeers
{
return [self.disconnectedPeersOrderedSet array];
}
#pragma mark - Private methods
- (void)setupSession
{
// Create the session that peers will be invited/join into.
_session = [[MCSession alloc] initWithPeer:self.peerID];
self.session.delegate = self;
// Create the service advertiser
_serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID
discoveryInfo:nil
serviceType:kMCSessionServiceType];
self.serviceAdvertiser.delegate = self;
// Create the service browser
_serviceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID
serviceType:kMCSessionServiceType];
self.serviceBrowser.delegate = self;
}
- (void)teardownSession
{
[self.session disconnect];
[self.connectingPeersOrderedSet removeAllObjects];
[self.disconnectedPeersOrderedSet removeAllObjects];
}
- (void)startServices
{
[self setupSession];
[self.serviceAdvertiser startAdvertisingPeer];
[self.serviceBrowser startBrowsingForPeers];
}
- (void)stopServices
{
[self.serviceBrowser stopBrowsingForPeers];
[self.serviceAdvertiser stopAdvertisingPeer];
[self teardownSession];
}
- (void)updateDelegate
{
[self.delegate sessionDidChangeState];
}
- (NSString *)stringForPeerConnectionState:(MCSessionState)state
{
switch (state) {
case MCSessionStateConnected:
return #"Connected";
case MCSessionStateConnecting:
return #"Connecting";
case MCSessionStateNotConnected:
return #"Not Connected";
}
}
#pragma mark - MCSessionDelegate protocol conformance
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state
{
NSLog(#"Peer [%#] changed state to %#", peerID.displayName, [self stringForPeerConnectionState:state]);
switch (state)
{
case MCSessionStateConnecting:
{
[self.connectingPeersOrderedSet addObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
break;
}
case MCSessionStateConnected:
{
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
break;
}
case MCSessionStateNotConnected:
{
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet addObject:peerID];
break;
}
}
[self updateDelegate];
}
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID
{
// Decode the incoming data to a UTF8 encoded string
NSString *receivedMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"didReceiveData %# from %#", receivedMessage, peerID.displayName);
}
- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress
{
NSLog(#"didStartReceivingResourceWithName [%#] from %# with progress [%#]", resourceName, peerID.displayName, progress);
}
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error
{
NSLog(#"didFinishReceivingResourceWithName [%#] from %#", resourceName, peerID.displayName);
// If error is not nil something went wrong
if (error)
{
NSLog(#"Error [%#] receiving resource from %# ", [error localizedDescription], peerID.displayName);
}
else
{
// No error so this is a completed transfer. The resources is located in a temporary location and should be copied to a permenant location immediately.
// Write to documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *copyPath = [NSString stringWithFormat:#"%#/%#", [paths firstObject], resourceName];
if (![[NSFileManager defaultManager] copyItemAtPath:[localURL path] toPath:copyPath error:nil])
{
NSLog(#"Error copying resource to documents directory");
}
else
{
// Get a URL for the path we just copied the resource to
NSURL *url = [NSURL fileURLWithPath:copyPath];
NSLog(#"url = %#", url);
}
}
}
// Streaming API not utilized in this sample code
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID
{
NSLog(#"didReceiveStream %# from %#", streamName, peerID.displayName);
}
#pragma mark - MCNearbyServiceBrowserDelegate protocol conformance
// Found a nearby advertising peer
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
NSString *remotePeerName = peerID.displayName;
NSLog(#"Browser found %#", remotePeerName);
MCPeerID *myPeerID = self.session.myPeerID;
BOOL shouldInvite = ([myPeerID.displayName compare:remotePeerName] == NSOrderedDescending);
if (shouldInvite)
{
NSLog(#"Inviting %#", remotePeerName);
[browser invitePeer:peerID toSession:self.session withContext:nil timeout:30.0];
}
else
{
NSLog(#"Not inviting %#", remotePeerName);
}
[self updateDelegate];
}
- (void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID
{
NSLog(#"lostPeer %#", peerID.displayName);
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet addObject:peerID];
[self updateDelegate];
}
- (void)browser:(MCNearbyServiceBrowser *)browser didNotStartBrowsingForPeers:(NSError *)error
{
NSLog(#"didNotStartBrowsingForPeers: %#", error);
}
#pragma mark - MCNearbyServiceAdvertiserDelegate protocol conformance
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
NSLog(#"didReceiveInvitationFromPeer %#", peerID.displayName);
invitationHandler(YES, self.session);
[self.connectingPeersOrderedSet addObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
[self updateDelegate];
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didNotStartAdvertisingPeer:(NSError *)error
{
NSLog(#"didNotStartAdvertisingForPeers: %#", error);
}
#end
I've noticed this issue to and i can't seem to figure out whats happening here, it appears when debugging that there is a huge delay between the notification for state changing being fired, perhaps something to do with the view itself.
Update: Well i think i figured the issues out after a bit of reading, the response is pretty much instant now, in the notification in the view i have pushed the process back to the main thread like so:
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
// add the user
[arrConnectedDevices addObject:peerDisplayName];
}
else if (state == MCSessionStateNotConnected){
// do we have connections
if ([arrConnectedDevices count] > 0) {
int indexOfPeer = [arrConnectedDevices indexOfObject:peerDisplayName];
[arrConnectedDevices removeObjectAtIndex:indexOfPeer];
}
}
}
// push to main queue for speedy response
dispatch_async(dispatch_get_main_queue(), ^(void) {
[collView reloadData];
BOOL peersExist = ([[appDelegate.mcManager.session connectedPeers] count] == 0);
NSLog(#"PEER COUNT IS %lu",(unsigned long)[[appDelegate.mcManager.session connectedPeers] count]);
[disconnectButton setEnabled:!peersExist];
if ([disconnectButton isEnabled]) {
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(51/255.0) green:(202/255.0) blue:(168/255.0) alpha:1.0]];
}
else{
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(107/255.0) green:(107/255.0) blue:(107/255.0) alpha:1.0]];
}
});
}
Hope this helps anyone who ran into the issues.
I am trying to establish one to one peer connection using multipeer connectivity framework. When i am sending invitation to nearby peer that device gets connected(sometime takes long time to get connected), and the peer which has send the request is always taking long time to change its state to connected & once it is connected immediately it is disconnecting from the session. I tried to debug the issue but not found anything, dont know what wrong is happening, please let me know if i am missing anything or any mistake is done in code.
Below given is the code snippet of the same.
MPCHandler.m
#define DidChangeStateNotification #"E_DidChangeStateNotification"
#define DidReceiveDataNotification #"E_DidReceiveDataNotification"
#define DidInviteNotification #"E_DidInvitedNotification"
#define DidReceivedInvetationNotification #"E_DidReceivedInvetationNotification"
static NSString * const EServiceType = #"E-service";
#interface MPCHandler ()
#property AppDelegate *appDelegate;
#end
#implementation MPCHandler
- (void)setupPeerWithDisplayName:(NSString *)displayName {
self.appDelegate = [[UIApplication sharedApplication] delegate];
self.peerID = [[MCPeerID alloc] initWithDisplayName:displayName];
self.connectedPeers = [NSMutableArray array];
self.foundPeers = [NSMutableArray array];
self.invitedPeers = [NSMutableArray array];
}
- (void)setupSession {
self.session = [[MCSession alloc] initWithPeer:self.peerID securityIdentity:nil encryptionPreference:MCEncryptionNone];
self.session.delegate = self;
}
- (void)setupBrowser {
self.browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID serviceType:EServiceType];
self.browser.delegate = self;
}
- (void)advertiseSelf:(BOOL)advertise {
if (advertise) {
self.advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID discoveryInfo:nil serviceType:EServiceType];
self.advertiser.delegate = self;
[self.advertiser startAdvertisingPeer];
} else {
[self.advertiser stopAdvertisingPeer];
self.advertiser = nil;
}
}
#pragma MCSessionDelegate methods
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
NSDictionary *userInfo = #{ #"peerID": peerID,
#"state" : #(state) };
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:DidChangeStateNotification
object:nil
userInfo:userInfo];
});
}
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
NSDictionary *userInfo = #{ #"data": data,
#"peerID": peerID };
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:DidReceiveDataNotification
object:nil
userInfo:userInfo];
});
}
- (void)session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL))certificateHandler{
certificateHandler(YES);
}
#pragma MCNearbyServiceAdvertiserDelegate methods
// Incoming invitation request. Call the invitationHandler block with YES and a valid session to connect the inviting peer to the session.
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler{
NSDictionary *info =[NSDictionary dictionaryWithObject:peerID.displayName forKey:#"displayName"];
NSLog(#"%# Received Invetation from : %#",self.peerID.displayName,peerID.displayName);
invitationHandler(YES,self.session);
[self.connectedPeers addObject:peerID];
self.invited = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:DidReceivedInvetationNotification
object:nil
userInfo:info];
});
}
#pragma MCNearbyServiceBrowserDelegate methods
// Found a nearby advertising peer
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo: (NSDictionary *)info{
// NSLog(#"Peer : %# found.",peerID.displayName);
// if (![self.peerID isEqual:peerID]) {
NSLog(#"%# Found Peer : %#",self.peerID.displayName,peerID.displayName);
[self.foundPeers addObject:peerID];
if (![self.connectedPeers containsObject:peerID])
{
if ([self.invitedPeers count] == 0 && [self.session.connectedPeers count]==0)
{
NSLog(#"%# Invited Peer : %#",self.peerID.displayName,peerID.displayName);
[self.invitedPeers addObject:peerID];
[browser invitePeer:peerID
toSession:self.session
withContext:[#"Empath" dataUsingEncoding:NSUTF8StringEncoding]
timeout:0];
// [browser stopBrowsingForPeers];
self.invited = YES;
NSDictionary *userInfo = #{ #"peerID": peerID };
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:DidInviteNotification
object:nil
userInfo:userInfo];
});
}
}
// }
}
// A nearby peer has stopped advertising
- (void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID{
[self.foundPeers removeObject:peerID];
[self.invitedPeers removeAllObjects];
[browser startBrowsingForPeers];
}
- (void)browser:(MCNearbyServiceBrowser *)browser didNotStartBrowsingForPeers:(NSError *)error
{
NSLog( #"Unable to start browsing for peers. Error: %#", error );
}
ViewController.m
#define DidChangeStateNotification #"E_DidChangeStateNotification"
#define DidReceiveDataNotification #"E_DidReceiveDataNotification"
#define DidInviteNotification #"E_DidInvitedNotification"
#define DidReceivedInvetationNotification #"E_DidReceivedInvetationNotification"
#interface ViewController ()
#property (nonatomic, strong) AppDelegate *appDelegate;
#end
#implementation ViewController
#synthesize txtMessage;
#synthesize tvHistory;
#synthesize btnSend;
#synthesize lblName;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
// self.appDelegate.connectedPeers = [NSMutableArray array];
// self.appDelegate.foundPeers = [NSMutableArray array];
[self AddObservers];
[self setUserInteraction:NO];
[self.lblName setText:#""];
[self.btnSend setUserInteractionEnabled:NO];
[self performSelector:#selector(advertisePeer) withObject:nil afterDelay:0.0];
}
-(void) AddObservers{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleReceivedDataWithNotification:)
name:DidReceiveDataNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(peerChangedStateWithNotification:)
name:DidChangeStateNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedInvetationWithNotification:)
name:DidReceivedInvetationNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(peerInvitedWithNotification:)
name:DidInviteNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(sessionDidTimeout:) name:kSessionDidTimeoutNotification object:nil];
}
-(void) advertisePeer{
[self setUserInteraction:NO];
[self.lblName setText:#""];
[self.appDelegate.mpcHandler setupPeerWithDisplayName:[UIDevice currentDevice].name];
[self.appDelegate.mpcHandler setupSession];
[self.appDelegate.mpcHandler advertiseSelf:YES];
[self.btnSend setUserInteractionEnabled:YES];
}
- (IBAction)searchAndConnectPeer:(id)sender{
[self.lblName setText:#"Searching..."];
if (self.appDelegate.mpcHandler.session != nil) {
[[self.appDelegate mpcHandler] setupBrowser];
[[[self.appDelegate mpcHandler] browser] startBrowsingForPeers];
}
}
- (IBAction)sendMessage:(id)sender{
NSString *messageToSend = [txtMessage text];
NSUInteger len = 0;
if ([messageToSend length]) {
NSData *messageAsData = [messageToSend dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
[self.appDelegate.mpcHandler.session sendData:messageAsData
toPeers:self.appDelegate.mpcHandler.session.connectedPeers
withMode:MCSessionSendDataReliable
error:&error];
// If any error occurs, just log it.
// Otherwise set the following couple of flags to YES, indicating that the current player is the creator
// of the game and a game is in progress.
len = [[tvHistory text] length];
if (error != nil) {
NSLog(#"%#", [error localizedDescription]);
[self.tvHistory setText:[NSString stringWithFormat:#"%#",[error localizedDescription]]];
} else{
NSString *history = [NSString stringWithFormat:#"Me : %#\n\n",messageToSend];
[self.tvHistory setText:[self.tvHistory.text stringByAppendingString:history]];
}
}
[self.txtMessage setText:#""];
[self.tvHistory scrollRangeToVisible:NSMakeRange([self.tvHistory.text length], len)];
}
- (void)peerInvitedWithNotification:(NSNotification *)notification{
[self setUserInteraction:NO];
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
[self.lblName setText:[NSString stringWithFormat:#"Connecting to %#...",peerID.displayName]];
}
- (void)receivedInvetationWithNotification:(NSNotification *)notification{
[self setUserInteraction:YES];
NSString *name = [[notification userInfo] objectForKey:#"displayName"];
[self.lblName setText:[NSString stringWithFormat:#"Connected : %#",name]];
}
- (void)peerChangedStateWithNotification:(NSNotification *)notification {
// Get the state of the peer.
int state = [[[notification userInfo] objectForKey:#"state"] intValue];
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
// We care only for the Connected and the Not Connected states.
// The Connecting state will be simply ignored.
if (state == MCSessionStateConnected) {
// We'll just display all the connected peers (players) to the text view.
NSString *allPlayers = #"Connected : ";
allPlayers = [allPlayers stringByAppendingString:[NSString tringWithFormat:#"%#",peerID.displayName]];
[self.lblName setText:allPlayers];
[self setUserInteraction:YES];
NSLog(#"%#...",allPlayers);
// // Fire up the timer upon first event
// if(!_idleTimer) {
// [self resetIdleTimer];
// }
}else if (state == MCSessionStateConnecting){
[self setUserInteraction:NO];
[self.lblName setText:[NSString stringWithFormat:#"Connecting to %#...",peerID.displayName]];
NSLog(#"Connecting %#...",peerID.displayName);
}else if (state == MCSessionStateNotConnected){
NSLog(#"Disconnected %#...",peerID.displayName);
// [self sessionDidTimeout:nil];
}
}
-(void) setUserInteraction:(BOOL)enabled{
[self.btnSend setUserInteractionEnabled:enabled];
[self.txtMessage setUserInteractionEnabled:enabled];
}
- (void)handleReceivedDataWithNotification:(NSNotification *)notification {
// Get the user info dictionary that was received along with the notification.
NSDictionary *userInfoDict = [notification userInfo];
// Convert the received data into a NSString object.
NSData *receivedData = [userInfoDict objectForKey:#"data"];
NSString *message = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
// Keep the sender's peerID and get its display name.
MCPeerID *senderPeerID = [userInfoDict objectForKey:#"peerID"];
NSString *senderDisplayName = senderPeerID.displayName;
// Add this guess to the history text view.
NSUInteger len = [[tvHistory text] length];
NSString *history = [NSString stringWithFormat:#"%# : %#\n\n", senderDisplayName, message];
[self.tvHistory setText:[self.tvHistory.text stringByAppendingString:history]];
[self.tvHistory scrollRangeToVisible:NSMakeRange([self.tvHistory.text length], len)];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
- (void)resetIdleTimer
{
if (_idleTimer) {
[_idleTimer invalidate];
}
// Schedule a timer to fire in kApplicationTimeoutInMinutes * 60
int timeout = kSessionTimeoutInSeconds;
_idleTimer = [NSTimer scheduledTimerWithTimeInterval:timeout
target:self
selector:#selector(idleTimerExceeded)
userInfo:nil
repeats:NO];
}
- (void)idleTimerExceeded {
/* Post a notification so anyone who subscribes to it can be notified when
* the application times out */
[[NSNotificationCenter defaultCenter]
postNotificationName:kSessionDidTimeoutNotification object:nil];
}
- (void) sessionDidTimeout:(NSNotification *) notif {
// [self setUserInteraction:NO];
//
// [self.lblName setText:#"Session expired..."];
//
// NSLog(#"============================Session Expired...=====================");
//
// [[[self.appDelegate mpcHandler] session] disconnect];
//
// if ([[[self appDelegate]mpcHandler] invited]) {
// [self performSelector:#selector(advertisePeer) withObject:nil afterDelay:0.0];
// [self performSelector:#selector(searchAndConnectPeer:) withObject:nil afterDelay:1.0];
// }
}
Check for BTM disconnection to service message in the logs .It is a problem with Apple Bluetooth which they have acknowledged.Try without Bluetooth i.e peer-peer wifi or infrastructure networks
In my application, I am sending screenshot of the current view programmatically and sending to a socket server program at every 5 seconds interval.
Socket program is running in Eclipse. I checked the code earlier and able to send screenshot of the image to the socket without any issues.
After that, as per my actual requirement, I added a tab bar controller (5 tabs) and used navigation controller for the first tab selection. As per my code below, the first tab bar item is “MyHomeViewController”.
In the “MyHomeViewController” i have a button action called -(IBAction)goAhead:(id)sender. Clicking on this will take to another “HomeViewController”.
In this “HomeViewController”, I connect socket and if the socket connection is success, under “NSStreamEventHasSpaceAvailable” delegate method, i call a function called “[self coShareScreen];” to take to sending screenshot images of the current view (whatever view controller is present) and sending to that socket server program.
I use a “CobrowseSingletonSocket” class, where i have socket related variables and sending screenshot programmatically are handled in this function “-(void) takeScreenshotSend :(NSString *) endOrCancelString”.
My issue is, socket is getting connected successfully now. It should send screenshot of the image programmatically wherever the view I’m currently in.
As expected, Its start sending the screenshot of the view programmatically to socket server successfully at every 5 seconds interval.
But,
As its a navigation controller based view, If i coming back to the first view manually, which is “MyHomeViewController”. It tries to send that screen also, but it crashes after immediately write socket is done.
It is crashing immediately after writing here-> “int num = [self.outputStream write:[data bytes] maxLength:([data length])];”
This is crash is happening only if i come back in navigation controller manually. If i stay in “HomeViewController” itself after socket connection, it keeps sending screenshot programmatically a t every 5 seconds interval to socket server without any issues.
I don’t understand what could be the reason here? Please someone advise me, as i’m unable to fix this for long time. Please also let me know if i need to paste any more code here.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
UITabBarController *tabbarCtrler = [[UITabBarController alloc]init];
MyHomeViewController *myHomeCtrler = [[MyHomeViewController alloc] initWithNibName:#"MyHomeViewController" bundle:NULL];
[myHomeCtrler.tabBarItem setTitle:#"My Home"];
ProductsViewController *prodViewCtrler = [[ProductsViewController alloc] initWithNibName:#"ProductsViewController" bundle:NULL];
[prodViewCtrler.tabBarItem setTitle:#"Products"];
InboxViewController *inboxViewCtrler = [[InboxViewController alloc] initWithNibName:#"InboxViewController" bundle:NULL];
[inboxViewCtrler.tabBarItem setTitle:#"Inbox"];
ContactUSViewController *contactViewCtrler = [[ContactUSViewController alloc] initWithNibName:#"ContactUSViewController" bundle:NULL];
[contactViewCtrler.tabBarItem setTitle:#"Contact Us"];
VoiceViewController *voiceViewCtrler = [[VoiceViewController alloc] initWithNibName:#"VoiceViewController" bundle:NULL];
[voiceViewCtrler.tabBarItem setTitle:#"Voice"];
UINavigationController *navigationcontroller = [[UINavigationController alloc] initWithRootViewController:myHomeCtrler];
navigationcontroller.title = #"My News;
[navigationcontroller.navigationBar setBackgroundImage:[UIImage imageNamed:#"SettingsTitlebar.png"] forBarMetrics:UIBarMetricsDefault];
[navigationcontroller setNavigationBarHidden:YES];
//create an array of all view controllers that will represent the tab at the bottom
NSArray *arrayViewControllers = [[NSArray alloc] initWithObjects:
navigationcontroller, prodViewCtrler, inboxViewCtrler, contactViewCtrler, voiceViewCtrler, nil];
[tabbarCtrler setViewControllers:arrayViewControllers];
[self.window makeKeyAndVisible];
[self.window setRootViewController:tabbarCtrler];
return YES;
}
and
#import "MyHomeViewController.h"
#import "SettingsViewController.h"
#interface MyHomeViewController ()
#end
#implementation MyHomeViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)goAhead:(id)sender
{
HomeViewController *homeViewController = [[HomeViewController alloc] initWithNibName:#"HomeViewController" bundle:nil];
[self.navigationController pushViewController:setttingsViewController animated:YES];
}
and
// HomeViewController.m
//
//
#import "HomeViewController.h"
#import "AppDelegate.h"
#import "CobrowseSingletonSocket.h"
#import <QuartzCore/QuartzCore.h>
#import <notify.h>
#interface HomeViewController ()
#end
#implementation HomeViewController
#synthesize authTexField;
#synthesize sessionID;
#synthesize sentPing;
#synthesize bScreenOff;
#synthesize responseAlertView;
#synthesize portFld;
#synthesize ipFld;
#synthesize shareScreenTimer;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
authTexField.delegate = self;
responseAlertView.delegate = self;
ipFld.delegate = self;
portFld.delegate = self;
bScreenOff = NO;
cobrowseSingletonIns = [CobrowseSingletonSocket sharedCobrowseSocketInstance];
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)connectShare:(id)sender
{
// Connect socket freshly here
[self initSocketConnection];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
- (IBAction)backAction:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
#pragma mark Socket Connection
- (void)initSocketConnection
{
NSString * ipAddrStr = ipFld.text;
NSString * portStr = portFld.text;
NSLog(#"IPAddress: %# ; Port: %#", ipAddrStr, portStr);
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSString *ipaddress = ipAddrStr;
ipaddress = [ipaddress stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"Retrieved socket ip: %#", cobrowseSingletonIns.socketIPAddress);
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ipaddress, 8081, &readStream, &writeStream);
cobrowseSingletonIns.inputStream = (__bridge_transfer NSInputStream *)readStream;
cobrowseSingletonIns.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[cobrowseSingletonIns.inputStream setDelegate:self];
[cobrowseSingletonIns.outputStream setDelegate:self];
[cobrowseSingletonIns.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[cobrowseSingletonIns.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[cobrowseSingletonIns.inputStream open];
[cobrowseSingletonIns.outputStream open];
}
-(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
NSString *io;
if (theStream == cobrowseSingletonIns.inputStream) io = #">>";
else io = #"<<";
NSLog(#"Stream space : %d",[cobrowseSingletonIns.outputStream hasSpaceAvailable]);
NSString *event;
switch (streamEvent)
{
case NSStreamEventNone:
event = #"NSStreamEventNone";
//statusText.text = #"Can not connect to the host!";
NSLog(#"NSStreamEventNone - Can not connect to the host");
break;
case NSStreamEventOpenCompleted:
event = #"NSStreamEventOpenCompleted";
//pingButton.hidden = NO;
//statusText.text = #"Connected";
NSLog(#"Connected");
break;
case NSStreamEventHasBytesAvailable:
event = #"NSStreamEventHasBytesAvailable";
NSLog(#"NSStreamEventHasBytesAvailable called");
if (theStream == cobrowseSingletonIns.inputStream)
{
//read data
//uint8_t buffer[1024];
uint8_t buffer[2];
NSMutableData *data=[[NSMutableData alloc] init];
int len;
while ([cobrowseSingletonIns.inputStream hasBytesAvailable])
{
len = [cobrowseSingletonIns.inputStream read:buffer maxLength:sizeof(buffer)];
if(len)
{
[data appendBytes:&buffer length:len];
}
else
{
NSLog(#"no buffer!");
}
}
NSString *responseStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
responseStr = [responseStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//do something with data
NSLog(#"Server said this-> %#", responseStr);
NSString *successResponse;
responseAlertView = [[UIAlertView alloc] init];
if ( [responseStr isEqualToString:#"SUCCESS"])
{
successResponse = #"Successfully connected! Click OK to starts screen sharing!";
responseAlertView.tag = 3;
[responseAlertView addButtonWithTitle:#"OK"];
[responseAlertView addButtonWithTitle:#"CANCEL"];
}
else
{
successResponse = #"There seems to be problem in connecting..Try connecting it again with proper Random Auth ID!";
responseAlertView.tag = 4;
[responseAlertView addButtonWithTitle:#"OK"];
}
responseAlertView.delegate = self;
[responseAlertView setTitle:#"Cobrowsing"];
[responseAlertView setMessage:successResponse];
[responseAlertView show];
}
break;
case NSStreamEventHasSpaceAvailable:
{
event = #"NSStreamEventHasSpaceAvailable";
NSLog(#"space : %d", [cobrowseSingletonIns.outputStream hasSpaceAvailable]);
if ( !cobrowseSingletonIns.bConnectionEstablished )
{
NSLog(#"NSStreamEventHasSpaceAvailable - Connection established, sharing is going to be established!");
if ( theStream == cobrowseSingletonIns.outputStream && !self.sentPing )
{
if ( [sessionID length]<=0 )
sessionID = #"EMPTY";
NSLog(#"sessionID : %#", sessionID);
NSData* data = [sessionID dataUsingEncoding:NSUTF8StringEncoding];
int num = [cobrowseSingletonIns.outputStream write:[data bytes] maxLength:([data length])];
if (-1 == num) {
NSLog(#"Error writing to stream %#: %#", cobrowseSingletonIns.outputStream, [cobrowseSingletonIns.outputStream streamError]);
}else{
NSLog(#"Wrote %i bytes to stream %#.", num, cobrowseSingletonIns.outputStream);
}
sentPing = YES;
}
}
else
{
NSLog(#"NSStreamEventHasSpaceAvailable - Connection already established");
if ( [cobrowseSingletonIns.outputStream hasSpaceAvailable] )
{
[self coShareScreen];
}
}
}
break;
case NSStreamEventErrorOccurred:
{
event = #"NSStreamEventErrorOccurred";
NSLog(#"NSStreamEventErrorOccurred - Can not connect to the host");
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Co-browsing" message:#"Connection error, Cannot connect to the host!" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
alertView.tag = 5;
[alertView show];
}
break;
case NSStreamEventEndEncountered:
event = #"NSStreamEventEndEncountered";
NSLog(#"NSStreamEventEndEncountered - Connection closed by the server");
break;
default:
event = #"** Unknown";
}
NSLog(#"%# : %#", io, event);
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
int tag = alertView.tag;
NSLog(#"buttonIndex: %d ; alertView.tag: %d", buttonIndex, tag);
if ( alertView.tag==3 )
{
if ( buttonIndex==0 ) // for OK button
{
sentPing = NO;
cobrowseSingletonIns.bConnectionEstablished = YES;
[cobrowseSingletonIns shareScreen]; // call just once here, then 5 mins thread caller will be called in hasspaceavailable delegate method.
}
else if ( buttonIndex==1 ) // for Cancel button
{
NSLog(#"User selected Cancel, just stop the socket connection");
[cobrowseSingletonIns.outputStream close];
[cobrowseSingletonIns.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
}
else if (alertView.tag==4)
{
NSLog(#"Problem connecting with socket, just stop the socket connection");
sentPing = NO;
[cobrowseSingletonIns.outputStream close];
[cobrowseSingletonIns.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
else if (alertView.tag==5) // Socket connection closed Abruptly, one of the reasons, user manually locked of the phone. In this case, logout and love to login
{
NSLog(#"Socket connection closed Abruptly due to unknown reasons");
}
}
//-(void) shareScreen :(NSTimer *) timerInfo
-(void) coShareScreen
{
NSLog(#"coShareScreen called");
[cobrowseSingletonIns shareScreen];
}
#end
and
//
// CobrowseSingletonSocket.m
//
#import "CobrowseSingletonSocket.h"
#import "AppDelegate.h"
#import "MyUSAAViewController.h"
#import "USAASettingsViewController.h"
#import "ProductsViewController.h"
#import "InboxViewController.h"
#import "ContactUSViewController.h"
#import "VoiceViewController.h"
#import <QuartzCore/QuartzCore.h>
#implementation CobrowseSingletonSocket
static CobrowseSingletonSocket *sharedCobrowseSocketInstance = nil;
#synthesize loginViewController;
#synthesize outputStream;
#synthesize inputStream;
#synthesize bConnectionEstablished;
#synthesize socketIPAddress;
#synthesize servletIPAddress;
#synthesize servletPort;
+(CobrowseSingletonSocket *) sharedCobrowseSocketInstance
{
#synchronized ([CobrowseSingletonSocket class])
{
if ( !sharedCobrowseSocketInstance )
{
sharedCobrowseSocketInstance = [[super allocWithZone:NULL] init];
}
}
return sharedCobrowseSocketInstance;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedCobrowseSocketInstance];
}
-(void) takeScreenshotSend :(NSString *) endOrCancelString
{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
UIGraphicsBeginImageContext(appDelegate.window.bounds.size);
[appDelegate.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSMutableData *data = [NSMutableData data];
data = (NSMutableData *) UIImagePNGRepresentation(image);
//[data writeToFile:#"screenshot.png" atomically:YES];
NSLog(#"shareScreen [data length] %i: ", [data length]);
NSData *newData = [endOrCancelString dataUsingEncoding:NSUTF16StringEncoding];
[data appendData:newData];
NSLog(#"shareScreen [data length] %i: ", [data length]);
//sentPing = YES;
int num = [self.outputStream write:[data bytes] maxLength:([data length])];
if (-1 == num) {
NSLog(#"Error writing to stream %#: %#", self.outputStream, [self.outputStream streamError]);
}else{
NSLog(#"Wrote %i bytes to stream %#.", num, self.outputStream);
//[self.outputStream close];
}
}
-(void) shareScreenAtInterval
{
NSLog(#"Screen sharing going to happen!");
[self takeScreenshotSend:#"END"]; // appending END, to detect the same on the server side and get out of reading data loop there.
}
-(void) shareScreen
{
NSLog(#"shareScreen called!");
[self performSelector:#selector(shareScreenAtInterval) withObject:nil afterDelay:5.0];
}
-(void) disconnectSocket
{
NSLog(#"Close the socket connection by user");
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(shareScreenAtInterval) object: nil];
//[NSObject cancelPreviousPerformRequestsWithTarget:self];
// Send Cancel message to socket
[self takeScreenshotSend:#"CANCEL"]; // appending CANCEL, to detect the same on the server side and get out of reading data loop there.
[self.outputStream close];
[self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.outputStream = nil;
self.bConnectionEstablished = NO;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Co-browsing" message:#"Screen sharing disconnected!" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alertView show];
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
[appDelegate.window.rootViewController dismissModalViewControllerAnimated:YES];
}
#end
Crash error in Xcode
Your problem is here -
[cobrowseSingletonIns.inputStream setDelegate:self];
[cobrowseSingletonIns.outputStream setDelegate:self]
Your HomeViewController sets itself as the delegate for the socket, but once it has been removed from view it will be deallocated, because the delegate property on the streams will be a weak reference.
You need to make sure the socket writing is completed and cleaned up when the view controller is removed. Try setting the delegates to nil in viewWillDisappear.
I am trying to develop a realtime multiplayer game for IOS by using cocos2d by using the tutorial on http://www.raywenderlich.com/3325/how-to-make-a-simple-multiplayer-game-with-game-center-tutorial-part-22
Everything works fine including auto matching with a random player but inviting a friend doesn't work because other device cannot receive an invitation.
When I clicked on invite friends button and then selected a friend by using the standard game center interface, it says waiting (forever) and nothing happens. My friend cannot receive an invitation from game center (no notifications).
I can invite a friend by using nearby friends functionality (when this functionality is enabled on both devices) but no invitation notification when nearby friends is disabled.
I spent hours and hours for searching on Google, found similar cases but no solution.
Some early feedback about possible answers:
I use two devices (one iPhone and one iPad), no simulator
All settings on iTunes connect are fine including multiplayer settings
I validated that both devices are connected to sandbox by using different test accounts
I've already checked the notification settings for Game center on both devices
I've already checked all proxy/firewall issues and tried on both WiFi and Cellular for both devices
Game invitations are enabled for both of the devices/accounts
I've already checked the bundle IDs, app version IDs, etc...
Both of the devices are IOS 6.x and App target version os IOS 5.0
I have no other issues about game center (leaderboards, random matchmaking, etc... all fine)
I call the inviteHandler method as soon after I authenticated a user as possible as mentioned in Apple documentation.
Here is my Game center helper class Header file:
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#protocol GCHelperDelegate
- (void)matchStarted;
- (void)matchEnded;
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data
fromPlayer:(NSString *)playerID;
- (void)inviteReceived;
#end
#interface GCHelper : NSObject <GKMatchmakerViewControllerDelegate, GKMatchDelegate>{
BOOL gameCenterAvailable;
BOOL userAuthenticated;
UIViewController *presentingViewController;
GKMatch *match;
BOOL matchStarted;
id <GCHelperDelegate> delegate;
NSMutableDictionary *playersDict;
GKInvite *pendingInvite;
NSArray *pendingPlayersToInvite;
NSMutableArray *unsentScores;
}
#property (retain) GKInvite *pendingInvite;
#property (retain) NSArray *pendingPlayersToInvite;
#property (assign, readonly) BOOL gameCenterAvailable;
#property (retain) NSMutableDictionary *playersDict;
#property (retain) UIViewController *presentingViewController;
#property (retain) GKMatch *match;
#property (assign) id <GCHelperDelegate> delegate;
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers
viewController:(UIViewController *)viewController
delegate:(id<GCHelperDelegate>)theDelegate;
- (BOOL) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent;
+ (GCHelper *)sharedInstance;
- (void)authenticateLocalUser;
#end
And here is the implementation of the game center helper class
#import "GCHelper.h"
#implementation GCHelper
#synthesize gameCenterAvailable;
#synthesize presentingViewController;
#synthesize match;
#synthesize delegate;
#synthesize playersDict;
#synthesize pendingInvite;
#synthesize pendingPlayersToInvite;
#pragma mark Initialization
static GCHelper *sharedHelper = nil;
+ (GCHelper *) sharedInstance {
if (!sharedHelper) {
sharedHelper = [[GCHelper alloc] init];
}
return sharedHelper;
}
- (BOOL)isGameCenterAvailable {
// check for presence of GKLocalPlayer API
Class gcClass = (NSClassFromString(#"GKLocalPlayer"));
// check if the device is running iOS 4.1 or later
NSString *reqSysVer = #"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer
options:NSNumericSearch] != NSOrderedAscending);
return (gcClass && osVersionSupported);
}
- (id)init {
if ((self = [super init])) {
gameCenterAvailable = [self isGameCenterAvailable];
if (gameCenterAvailable) {
NSNotificationCenter *nc =
[NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(authenticationChanged)
name:GKPlayerAuthenticationDidChangeNotificationName
object:nil];
}
}
return self;
}
- (void)authenticationChanged {
if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {
NSLog(#"Authentication changed: player authenticated.");
userAuthenticated = TRUE;
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
NSLog(#"Received invite");
self.pendingInvite = acceptedInvite;
self.pendingPlayersToInvite = playersToInvite;
[delegate inviteReceived];
};
} else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {
NSLog(#"Authentication changed: player not authenticated");
userAuthenticated = FALSE;
}
}
- (void)lookupPlayers {
NSLog(#"Looking up %d players...", match.playerIDs.count);
[GKPlayer loadPlayersForIdentifiers:match.playerIDs withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
NSLog(#"Error retrieving player info: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
} else {
// Populate players dict
self.playersDict = [NSMutableDictionary dictionaryWithCapacity:players.count];
for (GKPlayer *player in players) {
NSLog(#"Found player: %#", player.alias);
[playersDict setObject:player forKey:player.playerID];
}
// Notify delegate match can begin
matchStarted = YES;
[delegate matchStarted];
}
}];
}
#pragma mark User functions
- (void)authenticateLocalUser {
if (!gameCenterAvailable) return;
NSLog(#"Authenticating local user...");
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:nil];
} else {
NSLog(#"Already authenticated!");
}
}
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers viewController:(UIViewController *)viewController delegate:(id<GCHelperDelegate>)theDelegate {
if (!gameCenterAvailable) return;
matchStarted = NO;
self.match = nil;
self.presentingViewController = viewController;
delegate = theDelegate;
if (pendingInvite != nil) {
[presentingViewController dismissModalViewControllerAnimated:NO];
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:pendingInvite] autorelease];
mmvc.matchmakerDelegate = self;
[presentingViewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
} else {
[presentingViewController dismissModalViewControllerAnimated:NO];
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
request.playersToInvite = pendingPlayersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[presentingViewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
}
}
#pragma mark GKMatchmakerViewControllerDelegate
// The user has cancelled matchmaking
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)viewController {
[presentingViewController dismissModalViewControllerAnimated:YES];
}
// Matchmaking has failed with an error
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFailWithError:(NSError *)error {
[presentingViewController dismissModalViewControllerAnimated:YES];
NSLog(#"Error finding match: %#", error.localizedDescription);
}
// A peer-to-peer match has been found, the game should start
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)theMatch {
[presentingViewController dismissModalViewControllerAnimated:YES];
self.match = theMatch;
match.delegate = self;
if (!matchStarted && match.expectedPlayerCount == 0) {
NSLog(#"Ready to start match!");
[self lookupPlayers];
}
}
#pragma mark GKMatchDelegate
// The match received data sent from the player.
- (void)match:(GKMatch *)theMatch didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
if (match != theMatch) return;
[delegate match:theMatch didReceiveData:data fromPlayer:playerID];
}
// The player state changed (eg. connected or disconnected)
- (void)match:(GKMatch *)theMatch player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (match != theMatch) return;
switch (state) {
case GKPlayerStateConnected:
// handle a new player connection.
NSLog(#"Player connected!");
if (!matchStarted && theMatch.expectedPlayerCount == 0) {
NSLog(#"Ready to start match!");
[self lookupPlayers];
}
break;
case GKPlayerStateDisconnected:
// a player just disconnected.
NSLog(#"Player disconnected!");
matchStarted = NO;
[delegate matchEnded];
break;
}
}
// The match was unable to connect with the player due to an error.
- (void)match:(GKMatch *)theMatch connectionWithPlayerFailed:(NSString *)playerID withError:(NSError *)error {
if (match != theMatch) return;
NSLog(#"Failed to connect to player with error: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
}
// The match was unable to be established with any players due to an error.
- (void)match:(GKMatch *)theMatch didFailWithError:(NSError *)error {
if (match != theMatch) return;
NSLog(#"Match failed with error: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
}
- (void)reportScore:(int64_t)score forCategory:(NSString *)category {
// Only execute if OS supports Game Center & player is logged in
if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated == YES)
{
// Create score object
GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
// Set the score value
scoreReporter.value = score;
// Try to send
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
// Handle reporting error here by adding object to a serializable array, to be sent again later
[unsentScores addObject:scoreReporter];
}
}];
}
}
- (BOOL) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent {
if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated == YES)
{
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
if (achievement)
{
achievement.percentComplete = percent;
[achievement reportAchievementWithCompletionHandler:^(NSError *error)
{
if (error != nil)
{
// Retain the achievement object and try again later (not shown).
}
}];
}
return YES;
}
return NO;
}
#end
And Finally this is how I call the game center from my game layer (I tried two different options but none of them worked)
Option 1
[[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController: [[[UIApplication sharedApplication] keyWindow] rootViewController] delegate: self];
Option 2
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
UINavigationController *viewController = [app navController];
[[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController:viewController delegate:self];
Any help will be appreciated. Thanks in advance...
I've been dealing with multiplayer for a few months now and invitations have been a real issue for me....and like you, I used Ray's tutorial to get me started. I realized today that Ray's code has a bug in it where invites will not work if both clients have the GKMatchmakerView up. You need to dismiss it when you first receive the invite with something along the lines of:
[gcdelegate.viewController dismissViewControllerAnimated:YES
completion:^{
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:pendingInvite];
mmvc.matchmakerDelegate = self;
[gcdelegate.viewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
boo_invite=true;
}];
Ok, it seems that it again works without any changes in our code or network settings. I opened ticket to Apple support, bug records, etc... and it seems that some of them worked...
Now we understand that it was a bug in Game Center sandbox. As far as I see, sanbox version of game center is not that stable and people in Apple don't give enough attention to this service. There is also no way to check the system status on the internet.
I'm still continuing to discuss with Apple support in order to understand the reason and will share the all conversation here when it's completed.
Happy coding...