diff --git a/.gitignore b/.gitignore index 619395b..f27644a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ logs npm-debug.log* yarn-debug.log* yarn-error.log* +.DS_Store # Runtime data pids diff --git a/ios/.DS_Store b/ios/.DS_Store new file mode 100644 index 0000000..36fe090 Binary files /dev/null and b/ios/.DS_Store differ diff --git a/ios/RCTARKit.h b/ios/RCTARKit.h index de5c781..0c96997 100644 --- a/ios/RCTARKit.h +++ b/ios/RCTARKit.h @@ -12,6 +12,7 @@ #import "RCTARKitDelegate.h" #import "RCTARKitNodes.h" +#import "RCTMultiPeer.h" typedef void (^RCTBubblingEventBlock)(NSDictionary *body); typedef void (^RCTARKitResolve)(id result); @@ -22,16 +23,19 @@ typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error + (instancetype)sharedInstance; + (bool)isInitialized; +- (instancetype)initWithARViewAndBrowser:(ARSCNView *)arView multipeer:(MultipeerConnectivity *)multipeer; - (instancetype)initWithARView:(ARSCNView *)arView; @property (nonatomic, strong) NSMutableArray> *touchDelegates; @property (nonatomic, strong) NSMutableArray> *rendererDelegates; @property (nonatomic, strong) NSMutableArray> *sessionDelegates; +@property (nonatomic, strong) NSMutableArray> *multipeerDelegate; #pragma mark - Properties @property (nonatomic, strong) ARSCNView *arView; +@property (nonatomic, strong) MultipeerConnectivity *multipeer; @property (nonatomic, strong) RCTARKitNodes *nodeManager; @property (nonatomic, assign) BOOL debug; @@ -60,6 +64,14 @@ typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error @property (nonatomic, copy) RCTBubblingEventBlock onEvent; @property (nonatomic, copy) RCTBubblingEventBlock onARKitError; +@property (nonatomic, copy) RCTBubblingEventBlock onPeerConnected; +@property (nonatomic, copy) RCTBubblingEventBlock onPeerConnecting; +@property (nonatomic, copy) RCTBubblingEventBlock onPeerDisconnected; + +@property (nonatomic, copy) RCTBubblingEventBlock onMultipeerJsonDataReceived; + + + @property NSMutableDictionary *planes; // plane detected @@ -70,6 +82,7 @@ typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error - (void)resume; - (void)reset; - (void)hitTestPlane:(CGPoint)tapPoint types:(ARHitTestResultType)types resolve:(RCTARKitResolve)resolve reject:(RCTARKitReject)reject; +- (void)getCurrentWorldMap:(RCTARKitResolve)resolve reject:(RCTARKitReject)reject; - (void)hitTestSceneObjects:(CGPoint)tapPoint resolve:(RCTARKitResolve) resolve reject:(RCTARKitReject)reject; - (SCNVector3)projectPoint:(SCNVector3)point; - (float)getCameraDistanceToPoint:(SCNVector3)point; diff --git a/ios/RCTARKit.m b/ios/RCTARKit.m index 36105fe..a5ba8fd 100644 --- a/ios/RCTARKit.m +++ b/ios/RCTARKit.m @@ -8,15 +8,17 @@ #import "RCTARKit.h" #import "RCTConvert+ARKit.h" +#import "RCTMultiPeer.h" @import CoreLocation; -@interface RCTARKit () { +@interface RCTARKit () { RCTARKitResolve _resolve; } @property (nonatomic, strong) ARSession* session; @property (nonatomic, strong) ARWorldTrackingConfiguration *configuration; +@property (nonatomic, strong) ARWorldMap *worldMap; @end @@ -49,7 +51,10 @@ + (instancetype)sharedInstance { dispatch_once_on_main_thread(&onceToken, ^{ if (instance == nil) { ARSCNView *arView = [[ARSCNView alloc] init]; + MultipeerConnectivity *multipeer = [[MultipeerConnectivity alloc] init]; instance = [[self alloc] initWithARView:arView]; + multipeer.delegate = instance; + instance.multipeer = multipeer; } }); @@ -57,7 +62,6 @@ + (instancetype)sharedInstance { } - (bool)isMounted { - return self.superview != nil; } @@ -102,7 +106,55 @@ - (instancetype)initWithARView:(ARSCNView *)arView { return self; } - +- (void)receivedDataHandler:(NSData *)data PeerID:(MCPeerID *)peerID +{ + id parsedJSON; + @try { + NSError *error = nil; + parsedJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + } @catch (NSException *exception) { + // TODO: make a onMultipeerDataFailure callback + } @finally { + + } + + if (parsedJSON) { + if (self.onMultipeerJsonDataReceived) { + dispatch_async(dispatch_get_main_queue(), ^{ + self.onMultipeerJsonDataReceived(@{ + @"data": parsedJSON, + }); + }); + } + } else { + id unarchived = [NSKeyedUnarchiver unarchivedObjectOfClass:[ARWorldMap classForKeyedUnarchiver] fromData:data error:nil]; + + if ([unarchived isKindOfClass:[ARWorldMap class]]) { + NSLog(@"[unarchived class]====%@",[unarchived class]); + ARWorldMap *worldMap = unarchived; + self.configuration = [[ARWorldTrackingConfiguration alloc] init]; + self.configuration.worldAlignment = ARWorldAlignmentGravity; + self.configuration.planeDetection = ARPlaneDetectionHorizontal|ARPlaneDetectionVertical; + self.configuration.initialWorldMap = worldMap; + [self.arView.session runWithConfiguration:self.configuration options:ARSessionRunOptionResetTracking|ARSessionRunOptionRemoveExistingAnchors]; + + return; + } + + unarchived = [NSKeyedUnarchiver unarchivedObjectOfClass:[ARAnchor classForKeyedUnarchiver] fromData:data error:nil]; + + if ([unarchived isKindOfClass:[ARAnchor class]]) { + NSLog(@"[unarchived class]====%@",[unarchived class]); + ARAnchor *anchor = unarchived; + + [self.arView.session addAnchor:anchor]; + + return; + } + + NSLog(@"unknown data recieved from \(%@)",peerID.displayName); + } +} - (void)layoutSubviews { @@ -128,6 +180,19 @@ - (void)session:(ARSession *)session didFailWithError:(NSError *)error { } } + +- (void)getCurrentWorldMap:(RCTARKitResolve)resolve reject:(RCTARKitReject)reject { + [self.arView.session getCurrentWorldMapWithCompletionHandler:^(ARWorldMap * _Nullable worldMap, NSError * _Nullable error) { + NSLog(@"got the current world map!!!"); + if (error) { + NSLog(@"error====%@",error); + } + + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:worldMap requiringSecureCoding:true error:nil]; + [[ARKit sharedInstance].multipeer sendToAllPeers:data]; + }]; +} + - (void)reset { if (ARWorldTrackingConfiguration.isSupported) { [self.session runWithConfiguration:self.configuration options:ARSessionRunOptionRemoveExistingAnchors | ARSessionRunOptionResetTracking]; @@ -157,7 +222,7 @@ - (BOOL)debug { - (void)setDebug:(BOOL)debug { if (debug) { self.arView.showsStatistics = YES; - self.arView.debugOptions = ARSCNDebugOptionShowWorldOrigin | ARSCNDebugOptionShowFeaturePoints; + self.arView.debugOptions = ARSCNDebugOptionShowFeaturePoints; } else { self.arView.showsStatistics = NO; self.arView.debugOptions = SCNDebugOptionNone; diff --git a/ios/RCTARKit.podspec b/ios/RCTARKit.podspec new file mode 100644 index 0000000..db4ae68 --- /dev/null +++ b/ios/RCTARKit.podspec @@ -0,0 +1,18 @@ +require 'json' +version = JSON.parse(File.read('../package.json'))["version"] + +Pod::Spec.new do |s| + + s.name = "RCTARKit" + s.version = version + s.summary = "ARKit for react native" + s.homepage = "https://github.com/react-native-ar/react-native-arkit" + s.license = "MIT" + s.author = { "macrozone" => "https://github.com/macrozone" } + s.platforms = { :ios => "9.0", :tvos => "9.0" } + s.source = { :git => "https://github.com/react-native-ar/react-native-arkit"", :tag => #{s.version}" } + s.source_files = '**/*.{h,m}' + s.preserve_paths = "**/*.js" + s.dependency 'React' + +end \ No newline at end of file diff --git a/ios/RCTARKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/RCTARKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/RCTARKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/RCTARKitManager.m b/ios/RCTARKitManager.m index 2df43c4..b562257 100644 --- a/ios/RCTARKitManager.m +++ b/ios/RCTARKitManager.m @@ -12,7 +12,11 @@ #import #import #import "color-grabber.h" +#import "RCTMultiPeer.h" +@interface RCTARKitManager () + +@end @implementation RCTARKitManager RCT_EXPORT_MODULE() @@ -172,6 +176,13 @@ - (NSDictionary *)constantsToExport RCT_EXPORT_VIEW_PROPERTY(onAnchorUpdated, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onAnchorRemoved, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onMultipeerJsonDataReceived, RCTBubblingEventBlock) + +// TODO: Option to lock these three below down for host only +RCT_EXPORT_VIEW_PROPERTY(onPeerConnected, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onPeerConnecting, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onPeerDisconnected, RCTBubblingEventBlock) + RCT_EXPORT_VIEW_PROPERTY(onTrackingState, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onFeaturesDetected, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLightEstimation, RCTBubblingEventBlock) @@ -179,6 +190,7 @@ - (NSDictionary *)constantsToExport RCT_EXPORT_VIEW_PROPERTY(onTapOnPlaneNoExtent, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onEvent, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onARKitError, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(worldMap, NSObject); RCT_EXPORT_METHOD(pause:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { [[ARKit sharedInstance] pause]; @@ -199,6 +211,50 @@ - (NSDictionary *)constantsToExport resolve(@([ARKit isInitialized])); } +RCT_EXPORT_METHOD(openMultipeerBrowser:(NSString *)serviceType resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [[ARKit sharedInstance].multipeer openMultipeerBrowser:serviceType]; +} + +RCT_EXPORT_METHOD(startBrowsingForPeers:(NSString *)serviceType resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [[ARKit sharedInstance].multipeer startBrowsingForPeers:serviceType]; +} + +RCT_EXPORT_METHOD(getFrontOfCameraPosition:resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + resolve(@{@"frontOfCamera": [ARKit sharedInstance].nodeManager.frontOfCamera}); +} + +RCT_EXPORT_METHOD(getFrontOfCamera:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + resolve(@{ + @"x": @([ARKit sharedInstance].nodeManager.frontOfCamera.position.x), + @"y": @([ARKit sharedInstance].nodeManager.frontOfCamera.position.y), + @"z": @([ARKit sharedInstance].nodeManager.frontOfCamera.position.z) + }); +} + +RCT_EXPORT_METHOD(advertiseReadyToJoinSession:(NSString *)serviceType resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [[ARKit sharedInstance].multipeer advertiseReadyToJoinSession:serviceType]; +} + +// TODO: Should be optionally to only be available to host +RCT_EXPORT_METHOD(sendDataToAllPeers:(NSDictionary *)data resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [self sendDataToAll:data callback:resolve]; +} + +// TODO: Should be optional to lock it down so peers can only send to host +RCT_EXPORT_METHOD(sendDataToPeer:(NSDictionary *)data recipientId:(NSString *)recipientId resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [self sendData:recipientId data:data callback:resolve]; +} + +// TODO: Should be optional to only be available to host +RCT_EXPORT_METHOD(sendWorldmapData:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + [[ARKit sharedInstance] getCurrentWorldMap:resolve reject:reject]; +} + +// TODO: Should be optional to only be available to host +RCT_EXPORT_METHOD(getAllConnectedPeers:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + //TODO: get all peer ids +} + RCT_EXPORT_METHOD(isMounted:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { if( [ARKit isInitialized]) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -228,6 +284,34 @@ - (NSDictionary *)constantsToExport [[ARKit sharedInstance] hitTestSceneObjects:point resolve:resolve reject:reject]; } +- (void)sendDataToAll:(NSDictionary *)data callback:(RCTResponseSenderBlock)callback { + NSError *error = nil; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error]; + [[ARKit sharedInstance].multipeer.session sendData:jsonData toPeers:[RCTARKit sharedInstance].multipeer.session.connectedPeers withMode:MCSessionSendDataReliable error:&error]; + NSLog(@"Sending data..."); + if (error == nil) { + callback(@[[NSNull null]]); + } + else { + callback(@[[error description]]); + } +} + +- (void)sendData:(NSString *)recipient data:(NSDictionary *)data callback:(RCTResponseSenderBlock)callback { + NSError *error = nil; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"peerUUID == %@", recipient]; + NSArray *recipients = [[RCTARKit sharedInstance].multipeer.session.connectedPeers filteredArrayUsingPredicate:predicate]; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data options:0 error:&error]; + [[ARKit sharedInstance].multipeer.session sendData:jsonData toPeers:recipients withMode:MCSessionSendDataReliable error:&error]; + NSLog(@"Sending data..."); + if (error == nil) { + callback(@[[NSNull null]]); + } + else { + callback(@[[error description]]); + } +} + @@ -398,4 +482,24 @@ - (void)storeImage:(UIImage *)image options:(NSDictionary *)options reject:(RCTP resolve(@{}); } +- (void)browserViewControllerDidFinish:(nonnull MCBrowserViewController *)browserViewController { + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootViewController dismissViewControllerAnimated:YES completion:^{ + + }]; + }); +} + +- (void)browserViewControllerWasCancelled:(nonnull MCBrowserViewController *)browserViewController { + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootViewController dismissViewControllerAnimated:YES completion:^{ + + }]; + }); +} + @end diff --git a/ios/RCTARKitNodes.m b/ios/RCTARKitNodes.m index 6379fb8..b2d5640 100644 --- a/ios/RCTARKitNodes.m +++ b/ios/RCTARKitNodes.m @@ -15,7 +15,7 @@ @implementation SCNNode (ReferenceFrame) @dynamic referenceFrame; @end -CGFloat focDistance = 0.2f; +CGFloat focDistance = 0.45f; @interface RCTARKitNodes () diff --git a/ios/RCTMultiPeer.h b/ios/RCTMultiPeer.h new file mode 100644 index 0000000..33e1c84 --- /dev/null +++ b/ios/RCTMultiPeer.h @@ -0,0 +1,48 @@ + +// Created by Matt Thompson on 01/25/20. +// MIT Licence. +// lwansbrough/react-native-multipeer +// https://developer.apple.com/documentation/arkit/creating_a_multiuser_ar_experience?language=objc + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^RCTBubblingEventBlock)(NSDictionary *body); +typedef void (^RCTARKitResolve)(id result); +typedef void (^RCTARKitReject)(NSString *code, NSString *message, NSError *error); + +@protocol MultipeerConnectivityDelegate +@optional + +- (void)receivedDataHandler:(NSData *)data PeerID:(MCPeerID *)peerID; + +@end + +@interface MultipeerConnectivity : NSObject + +@property(nonatomic, strong)MCPeerID *myPeerID; + +@property(nonatomic, strong)MCSession *session; + +@property(nonatomic, strong)MCNearbyServiceAdvertiser *serviceAdvertiser; + +@property(nonatomic, strong)MCNearbyServiceBrowser *serviceBrowser; +@property(nonatomic, strong)MCBrowserViewController *mpBrowser; + +@property(nonatomic, strong)NSMutableDictionary *connectedPeersDictionary; + +@property(nonatomic, weak)id delegate; + +- (void)sendToAllPeers:(NSData *)data; + +- (void)startBrowsingForPeers:(NSString *)serviceType; +- (void)advertiseReadyToJoinSession:(NSString *)serviceType; +- (void)openMultipeerBrowser:(NSString *)serviceType; + +- (NSArray *)connectedPeers; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/RCTMultiPeer.m b/ios/RCTMultiPeer.m new file mode 100644 index 0000000..a6d1f95 --- /dev/null +++ b/ios/RCTMultiPeer.m @@ -0,0 +1,193 @@ +// Created by Matt Thompson on 01/25/20. +// MIT Licence. +// lwansbrough/react-native-multipeer +// https://developer.apple.com/documentation/arkit/creating_a_multiuser_ar_experience?language=objc + +#import "RCTMultiPeer.h" +#import "RCTARKit.h" + +@interface MultipeerConnectivity () { + RCTARKitResolve _resolve; +} + +@end + +@implementation MultipeerConnectivity + +- (instancetype)init +{ + self = [super init]; + + if (self) { + self.myPeerID = [[MCPeerID alloc] initWithDisplayName:[[NSUUID UUID] UUIDString]]; + + self.session = [[MCSession alloc] initWithPeer:self.myPeerID securityIdentity:nil encryptionPreference:MCEncryptionRequired]; + self.session.delegate = self; + self.connectedPeersDictionary = [NSMutableDictionary dictionary]; + } + + return self; +} + +- (void)startBrowsingForPeers:(NSString *)serviceType +{ + // browseForSessions + // this starts the multi peer service looking for peers that are looking for it's service type (string id) + + self.serviceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.myPeerID serviceType:serviceType]; + self.serviceBrowser.delegate = self; + [self.serviceBrowser startBrowsingForPeers]; +} + +- (void)advertiseReadyToJoinSession:(NSString *)serviceType +{ + self.serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.myPeerID discoveryInfo:nil serviceType:serviceType]; + self.serviceAdvertiser.delegate = self; + [self.serviceAdvertiser startAdvertisingPeer]; +} + +- (void)openMultipeerBrowser:(NSString *)serviceType +{ + if (!self.mpBrowser) { + self.mpBrowser = [[MCBrowserViewController alloc] initWithServiceType:serviceType session:self.session]; + self.mpBrowser.delegate = self; + } + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; + + [rootViewController presentViewController:self.mpBrowser animated: YES completion:^{ + // TODO: have a onPreviewVisible callback + }]; + }); +} + +- (void)sendToAllPeers:(NSData *)data +{ + @try { + [self.session sendData:data toPeers:self.session.connectedPeers withMode:MCSessionSendDataReliable error:nil]; + } @catch (NSException *exception) { + NSLog(@"error sending data to peers: \(error.localizedDescription)"); + } @finally { + + } +} + +- (void)sendToPeers:(NSArray *)recipients data:(NSData *)data +{ + @try { + [self.session sendData:data toPeers:recipients withMode:MCSessionSendDataReliable error:nil]; + } @catch (NSException *exception) { + NSLog(@"error sending data to peers: \(error.localizedDescription)"); + } @finally { + + } +} + +- (NSArray *)connectedPeers +{ + return self.session.connectedPeers; +} + +#pragma mark MCSessionDelegate + +- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state { + // don't react to own state changes + if ([peerID.displayName isEqualToString:self.myPeerID.displayName]) return; + + if (state == MCSessionStateConnected) { + [self.connectedPeersDictionary setValue:peerID forKey:peerID.displayName]; + if ([ARKit sharedInstance].onPeerConnected) { + dispatch_async(dispatch_get_main_queue(), ^{ + [ARKit sharedInstance].onPeerConnected(@{ + @"peer": @{ + @"id": peerID.displayName + } + }); + }); + } + } + else if (state == MCSessionStateConnecting) { + if ([ARKit sharedInstance].onPeerConnecting) { + dispatch_async(dispatch_get_main_queue(), ^{ + [ARKit sharedInstance].onPeerConnecting(@{ + @"peer": @{ + @"id": peerID.displayName + } + }); + }); + } + } + else if (state == MCSessionStateNotConnected) { + if ([ARKit sharedInstance].onPeerDisconnected) { + dispatch_async(dispatch_get_main_queue(), ^{ + [ARKit sharedInstance].onPeerDisconnected(@{ + @"peer": @{ + @"id": peerID.displayName + } + }); + }); + } + } +} + +// Received data from remote peer. +- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID +{ + if (self.delegate && [self.delegate respondsToSelector:@selector(receivedDataHandler:PeerID:)]) { + [self.delegate receivedDataHandler:data PeerID:peerID]; + } +} + +// Received a byte stream from remote peer. +- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID +{ + assert(@"This service does not send/receive streams."); +} + +// Start receiving a resource from remote peer. +- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress +{ + assert(@"This service does not send/receive resources."); +} + +// Finished receiving a resource from remote peer and saved the content +// in a temporary location - the app is responsible for moving the file +// to a permanent location within its sandbox. +- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(nullable NSURL *)localURL withError:(nullable NSError *)error +{ + assert(@"This service does not send/receive resources."); +} + +#pragma mark MCNearbyServiceBrowserDelegate +// Found a nearby advertising peer. +- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(nullable NSDictionary *)info +{ + // Invite the new peer to the session. + [browser invitePeer:peerID toSession:self.session withContext:nil timeout:10]; +} + +// A nearby peer has stopped advertising. +- (void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID +{ + // This app doesn't do anything with non-invited peers, so there's nothing to do here. +} + +#pragma mark MCNearbyServiceAdvertiserDelegate +// 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:(nullable NSData *)context invitationHandler:(void (^)(BOOL accept, MCSession * __nullable session))invitationHandler +{ + // Call handler to accept invitation and join the session. + invitationHandler(true, self.session); +} + +- (void)browserViewControllerDidFinish:(nonnull MCBrowserViewController *)browserViewController { + +} + +- (void)browserViewControllerWasCancelled:(nonnull MCBrowserViewController *)browserViewController { + +} + +@end +