I am using RMStore to verify receipts. As a note, I am not using RMStore for the actual purchase portion. The process is successfully dealing with success and failure in terms of throwing errors and not delivering content if the receipt is invalid. I purposefully changed the bundle to force a failure as a test. My question though is with the failure process and the confirmation Apple sends.
The issue is that while this process does detect the failure to verify and therefore does prevent the content from being sent to the user, Apple still afterwards comes back with a dialog box about the purchase being successful. The good news is that the purchase isn't successful and the content isn't delivered, but I would prefer that this dialog box from Apple not show as it will create confusion.
Here is my implementation of the check. For now I am just testing the failure scenario before doing more within the failure block.
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(#"completeTransaction...");
RMStoreAppReceiptVerificator *verifyReceipt = [[RMStoreAppReceiptVerificator alloc]init];
[verifyReceipt verifyTransaction:transaction success:^{
[self provideContentForProductIdentifier:transaction.payment.productIdentifier];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}failure:^(NSError *error){
NSLog(#"failure to verify: %#",error.description);
}];
}
Is there a way within the failure block to halt the process at Apple that creates their success dialog or do I need to perform this check at an earlier stage?
Update:
In looking further at this, the method above is being called by the state SKPaymentTransactionStatePurchased The definition of that state per Apple is:
"The App Store successfully processed payment. Your application should provide the content the user purchased."
This tells me that it is likely too late to prevent the dialog. There is an earlier state, however, I would think the receipt verification has to come after purchase but before delivery of content (otherwise there would not be a purchase to verify). So is this just a matter of having to deal with the conflicting message or am I missing something?
Update 2: Adding some more methods per the request in comments
#interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
#implementation IAPHelper
{
SKProductsRequest * _productsRequest;
RequestProductsCompletionHandler _completionHandler;
NSSet * _productIdentifiers;
NSMutableSet * _purchasedProductIdentifiers;
NSDictionary *_mappingDict;
}
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers andMappings:(NSDictionary *)mappingDict
{
if ((self = [super init])) {
// Store product identifiers & mappings
_productIdentifiers = productIdentifiers;
_mappingDict = mappingDict;
// Add self as transaction observer
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
// 1
_completionHandler = [completionHandler copy];
// 2
_productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
_productsRequest.delegate = self;
[_productsRequest start];
}
- (BOOL)productPurchased:(NSString *)productIdentifier {
return [_purchasedProductIdentifiers containsObject:productIdentifier];
}
- (void)buyProduct:(SKProduct *)product {
NSLog(#"Buying %#...", product.productIdentifier);
SKPayment * payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(#"Loaded list of products...");
_productsRequest = nil;
NSArray * skProducts = response.products;
for (SKProduct * skProduct in skProducts) {
NSLog(#"Found product: %# %# %0.2f",
skProduct.productIdentifier,
skProduct.localizedTitle,
skProduct.price.floatValue);
}
if (_completionHandler)
{
_completionHandler(YES, skProducts);
_completionHandler = nil;
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
NSLog(#"Failed to load list of products.");
_productsRequest = nil;
if (_completionHandler)
{
_completionHandler(NO, nil);
_completionHandler = nil;
}
}
Here is the specific method that calls completeTransaction
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
};
}
We established in the comments that this question doesn't have anything to do with RMStore.
You're not testing a real fraud scenario so it doesn't matter if Apple shows an alert.
This would involve either using a fake receipt, or sending fake calls to the Store Kit transaction observer. In neither of those cases you would get the alert.
When using a valid transaction to simulate a failure scenario you can't expect Apple to consider the transaction invalid as well. There is no API to tell Apple that a transaction is fraudulent. You can only finish a transaction, or not.
Related
I am building an application where two iOS devices both transmit and scan (peripheral and central) for each other. Due to Apple's implementation, when the app is backgrounded, all identifiable information is removed from the advertising packet, meaning I need to connect to the discovered peripherals to find out who and what they are if they are transmitting in the background.
All I really need to do is identify the peripheral. (Connect and disconnect). Currently, the only way I can find to do this is to set a static characteristic attached to a common service that allows each device to uniquely identify itself, even when backgrounded. This value will not change or get updated. If I could simply look at peripheral.UUID after connecting, this would do the trick. But I can't anymore with iOS8. So, I create a characteristic to contain the unique identifier.
(Not sure if this is the best way, but its the only way I can think of.)
Everything is working great (discovering characteristic) but I am unable to retrieve anything other than nil for the characteristic, even though I have specifically set it when I started transmitting.
Here is my (Peripheral code):
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
// Opt out from any other state
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
NSLog(#"BT Transmitter Powered On");
NSString* uniqueString = #“foobar";
NSData* characteristicValue = [uniqueString dataUsingEncoding:NSUTF8StringEncoding];
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:#"08590F7E-DB05-467E-8757-72F6FAEB13D4"]
properties:CBCharacteristicPropertyRead
value:characteristicValue
permissions:CBAttributePermissionsReadable];
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:#"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"] primary:YES];
transferService.characteristics = #[self.transferCharacteristic];
[self.peripheralManager addService:transferService];
[self.peripheralManager startAdvertising:#{ CBAdvertisementDataServiceUUIDsKey: #[[CBUUID UUIDWithString:#"E20A39F4-73F5-4BC4-A12F-17D1AD07A961"]] }];
}
And here is my Central Code:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
// print out value of discovered characteristic
NSLog (#"Characteristic discovered: %#", characteristic); // this outputs all all the properties of the characteristic, including a value of "null".
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Value: %#",value); // this prints out nothing
}
}
What am I doing wrong? I would expect to see the value of the characteristic as "foobar" when transformed back into an NSString. Instead it is null.
Having discovered the characteristic you need to perform a read request to actually get its value -
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(#"Error discovering characteristics: %#", [error localizedDescription]);
return;
}
for (CBCharacteristic *characteristic in service.characteristics) {
// print out value of discovered characteristic
NSLog (#"Characteristic discovered: %#", characteristic); // this outputs all all the properties of the characteristic, including a value of "null".
if ([characteristic.UUID.UUIDString isEqualToString:#"08590F7E-DB05-467E-8757-72F6FAEB13D4"]) {
[peripheral readValueForCharacteristic:characteristic];
NSString *value = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"Value: %#",value); // this prints out nothing
}
}
You will subsequently get a call to didUpdateValueForCharacteristic: -
-(void) peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error == nil) {
NSString *valueString=[[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(#"The value=%#",valueString
}
}
Update: to find the RSSI of the peripheral once you have connected to it and read its service, use the readRSSI method.
Then, strangely enough, even though its not in the documentation, this is the only delegate callback method (with RSSI) that works for me running 8.1.1.
-(void) peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error {
NSLog(#"Got RSSI update in didReadRSSI : %4.1f", [RSSI doubleValue]);
}
Now I just have to figure out how to link this RSSI signal with the specific peripheral I connected to and identified in the previous call.
I'm trying to create a Core Data, document based app but with the limitation that only one document can be viewed at a time (it's an audio app and wouldn't make sense for a lot of docs to be making noise at once).
My plan was to subclass NSDocumentController in a way that doesn't require linking it up to any of the menu's actions. This has been going reasonably but I've run into a problem that's making me question my approach a little.
The below code works for the most part except if a user does the following:
- Tries to open a doc with an existing 'dirty' doc open
- Clicks cancel on the save/dont save/cancel alert (this works ok)
- Then tries to open a doc again. For some reason now the openDocumentWithContentsOfURL method never gets called again, even though the open dialog appears.
Can anyone help me work out why? Or perhaps point me to an example of how to do this right? It feels like something that must have been implemented by a few people but I've not been able to find a 10.7+ example.
- (BOOL)presentError:(NSError *)error
{
if([error.domain isEqualToString:DOCS_ERROR_DOMAIN] && error.code == MULTIPLE_DOCS_ERROR_CODE)
return NO;
else
return [super presentError:error];
}
- (id)openUntitledDocumentAndDisplay:(BOOL)displayDocument error:(NSError **)outError
{
if(self.currentDocument) {
[self closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(openUntitledDocumentAndDisplayIfClosedAll: didCloseAll: contextInfo:)
contextInfo:nil];
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:#"Suppressed multiple documents" forKey:NSLocalizedDescriptionKey];
*outError = [NSError errorWithDomain:DOCS_ERROR_DOMAIN code:MULTIPLE_DOCS_ERROR_CODE userInfo:details];
return nil;
}
return [super openUntitledDocumentAndDisplay:displayDocument error:outError];
}
- (void)openUntitledDocumentAndDisplayIfClosedAll:(NSDocumentController *)docController
didCloseAll: (BOOL)didCloseAll
contextInfo:(void *)contextInfo
{
if(self.currentDocument == nil)
[super openUntitledDocumentAndDisplay:YES error:nil];
}
- (void)openDocumentWithContentsOfURL:(NSURL *)url
display:(BOOL)displayDocument
completionHandler:(void (^)(NSDocument *document, BOOL documentWasAlreadyOpen, NSError *error))completionHandler NS_AVAILABLE_MAC(10_7)
{
NSLog(#"%s", __func__);
if(self.currentDocument) {
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:[url copy], #"url",
[completionHandler copy], #"completionHandler",
nil];
[self closeAllDocumentsWithDelegate:self
didCloseAllSelector:#selector(openDocumentWithContentsOfURLIfClosedAll:didCloseAll:contextInfo:)
contextInfo:(__bridge_retained void *)(info)];
} else {
[super openDocumentWithContentsOfURL:url display:displayDocument completionHandler:completionHandler];
}
}
- (void)openDocumentWithContentsOfURLIfClosedAll:(NSDocumentController *)docController
didCloseAll: (BOOL)didCloseAll
contextInfo:(void *)contextInfo
{
NSDictionary *info = (__bridge NSDictionary *)contextInfo;
if(self.currentDocument == nil)
[super openDocumentWithContentsOfURL:[info objectForKey:#"url"] display:YES completionHandler:[info objectForKey:#"completionHandler"]];
}
There's a very informative exchange on Apple's cocoa-dev mailing list that describes what you have to do in order to subclass NSDocumentController for your purposes. The result is that an existing document is closed when a new one is opened.
Something else you might consider is to mute or stop playing a document when its window resigns main (i.e., sends NSWindowDidResignMainNotification to the window's delegate), if only to avoid forcing what might seem to be an artificial restriction on the user.
I know it's been a while, but in case it helps others....
I had what I think is a similar problem, and the solution was to call the completion handler when my custom DocumentController did not open the document, e.g.:
- (void)openDocumentWithContentsOfURL:(NSURL *)url display:(BOOL)displayDocument completionHandler:(void (^)(NSDocument * _Nullable, BOOL, NSError * _Nullable))completionHandler {
if (doOpenDocument) {
[super openDocumentWithContentsOfURL:url display:displayDocument completionHandler:completionHandler];
} else {
completionHandler(NULL, NO, NULL);
}
}
When I added the completionHandler(NULL, NO, NULL); it started working for more than a single shot.
I am planning to develop a turn-based game and is trying to understand how to communicate with Game Center and send and receive mach data. I have read about it and tested this for days now and just cannot get it to work as planned.
The only thing i try to do with the code below is to be able to save and then read the mach data. I am using two sandbox Game Center accounts for the turns.
The turns are sending the same data by pressing "endTurn" button. Every time i run the actual user is authenticated and the app is set up correctly (i believe).
This is a test app without any other purpose than test what i stated. Below is the code i use for the match data processing.
I would really appreciate any ideas and tips on what i may do wrong. Before i started serious testing i did post a similar question but that did not solve this problem, https://stackoverflow.com/questions/14447392/start-gamecenter-turn-based-match-and-initiate-match-data-for-the-very-first-tim.
I also try to catch the participants but with no success, which may mean that it is the problem when processing the completionhandler.
-(IBAction)endTurn:(id)sender {
[_gameDictionary setObject:#"The Object" forKey:#"The Key"];
NSLog(#"_gameDictionary: %#", _gameDictionary);
NSData *data = [NSPropertyListSerialization dataFromPropertyList:_gameDictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:nil];
GKTurnBasedParticipant *nextPlayer;
if (_match.currentParticipant == [_match.participants objectAtIndex:0]) {
nextPlayer = [[_match participants] lastObject];
} else {
nextPlayer = [[_match participants]objectAtIndex:0];
}
NSLog(#"_match.currentParticipant: %#", _match.currentParticipant);
[self.match endTurnWithNextParticipant:nextPlayer matchData:data completionHandler:^(NSError *error) {
if (error) {
NSLog(#"An error occured updating turn: %#", [error localizedDescription]);
}
[self.navigationController popViewControllerAnimated:YES];
}];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
_gameDictionary = [[NSMutableDictionary alloc]init];
[self.match loadMatchDataWithCompletionHandler:^(NSData *matchData, NSError *error) {
NSDictionary *myDict = [NSPropertyListSerialization propertyListFromData:_match.matchData mutabilityOption:NSPropertyListImmutable format:nil errorDescription:nil];
[_gameDictionary addEntriesFromDictionary: myDict];
if (error) {
NSLog(#"loadMatchData - %#", [error localizedDescription]);
}
}];
NSLog(#"_gameDictionary: %#", _gameDictionary);
}
Output:
"gk-cdx" = "17.173.254.218:4398";
"gk-commnat-cohort" = "17.173.254.220:16386";
"gk-commnat-main0" = "17.173.254.219:16384";
"gk-commnat-main1" = "17.173.254.219:16385";
}
2013-02-11 22:44:11.707 GC_test1[8791:14f03] _gameDictionary: {
}
2013-02-11 22:44:13.894 GC_test1[8791:14f03] _gameDictionary: {
The Object = The Key;
}
2013-02-11 22:44:13.894 GC_test1[8791:14f03] _match.currentParticipant: (null)
The fact that _match.currentParticipant evaluates to nil is troubling. I suspect that _match was never initialized, or is nil, or that it was not obtained from a Game Center facility such as loadMatchesWithCompletionHandler:, the GKTurnBasedMatchmakerViewController, or using findMatchForRequest:withCompletionHandler:.
For a new match, if created through any of these facilities, currentParticipant would be guaranteed to represent the local player. You are not allowed to instantiate a GKTurnBasedMatch yourself.
To resolve this issue at least for testing, you could assign a new _match from within the completion handler of findMatchForRequest:withCompletionHandler:. Only then should you be allowed to press your test button.
I'm trying to add Beeblex's new In App Purchase verification to my app, however i'm struggling passing a return value from within a block.
Here's the code I have now, and as you can see I set a BOOL value, then within the verification block I set the BOOL and return it at the end. However the return at the end is called before the block finishes, so what I need is to return the BOOL from within the block?
- (BOOL)verifyTransaction:(SKPaymentTransaction *)transaction
{
if (![BBXIAPTransaction canValidateTransactions]) {
return YES; // There is no connectivity to reach the server
}
BOOL __block toReturn = YES;
BBXIAPTransaction *bbxTransaction = [[BBXIAPTransaction alloc] initWithTransaction:transaction];
bbxTransaction.useSandbox = YES;
[bbxTransaction validateWithCompletionBlock:^(NSError *error) {
if (bbxTransaction.transactionVerified) {
if (bbxTransaction.transactionIsDuplicate) {
// The transaction is valid, but duplicate - it has already been sent to Beeblex in the past.
NSLog(#"Transaction is a duplicate!");
[FlurryAnalytics logEvent:#"Transaction duplicate!"];
toReturn = NO;
} else {
// The transaction has been successfully validated and is unique
NSLog(#"Transaction valid data:%#",bbxTransaction.validatedTransactionData);
[FlurryAnalytics logEvent:#"Transaction verified"];
toReturn = YES;
}
} else {
// Check whether this is a validation error, or if something went wrong with Beeblex
if (bbxTransaction.hasConfigurationError || bbxTransaction.hasServerError || bbxTransaction.hasClientError) {
// The error was not caused by a problem with the data, but is most likely due to some transient networking issues
NSLog(#"Transaction error caused by network, not data");
[FlurryAnalytics logEvent:#"Transaction network error"];
toReturn = YES;
} else {
// The transaction supplied to the validation service was not valid according to Apple
NSLog(#"Transaction not valid according to Apple");
[FlurryAnalytics logEvent:#"Transaction invalid!!"];
toReturn = NO;
}
}
}];
NSLog(#"toReturn: %#",toReturn ? #"Yes" : #"No");
return toReturn;
}
If I simply put return = NO; inside the block, I get compiler warnings of Incompatible block pointer types, and control may reach end of non-void block.
Beeblex developer here. The code inside -validateWithCompletionBlock: execute asynchronously (in the background, it needs to talk to our servers, so there's no point blocking your app completely while we wait for the Internet to do its thing).
Therefore, you need to rethink your approach to validating your receipts. Right now you, general workflow is:
Complete purchase
Call Beeblex
Wait for response
Check boolean value
But #3 returns right away, so this will never work. You should consider doing something like this:
Complete purchase
Show a “Please wait…” view, or something similar that advises the user that you're unlocking whatever they've purchased.
Call Beeblex
Inside the block, determine whether the validation succeeded or not, and then act to unlock the content from there.
Sit idle until called by the block
Here's a quick-and-dirty example. I didn't compile it, so it probably has a few bugs, but it should give you the idea behind the intended usage pattern.
- (void) showWaitView {
// Display a wait view
}
- (void) hideWaitView {
// Hide the wait view
}
- (void) completeValidationWithValidateReceiptData:(NSDictionary *) receipt isRecoverableError(BOOL) isRecoverableError {
[self hideWaitView];
if (receipt) {
// Unlock the content, tell the user
} else {
if (isRecoverableError) {
// Probably a network error of some kind. Tell user they need to be connected,
// and ask them to do it again.
} else {
// Keep the content locked, tell the user something went wrong
}
}
}
- (void) validateReceipt:(SKPaymentTransaction *) transaction {
if (![BBXIAPTransaction canValidateTransactions]) {
[self completeValidationWithValidateReceiptData:Nil isRecoverableError:YES];
return;
}
BBXIAPTransaction *bbxTransaction = [[BBXIAPTransaction alloc] initWithTransaction:transaction];
bbxTransaction.useSandbox = YES;
[bbxTransaction validateWithCompletionBlock:^(NSError *error) {
if (bbxTransaction.transactionVerified) {
if (bbxTransaction.transactionIsDuplicate) {
// The transaction is valid, but duplicate - it has already been sent to Beeblex in the past.
[FlurryAnalytics logEvent:#"Transaction duplicate!"];
[self completeValidationWithValidateReceiptData:Nil isRecoverableError:NO];
} else {
// The transaction has been successfully validated and is unique
[FlurryAnalytics logEvent:#"Transaction verified"];
[self completeValidationWithValidateReceiptData:bbxTransaction.validatedTransactionData isRecoverableError:NO];
}
} else {
// Check whether this is a validation error, or if something went wrong with Beeblex
if (bbxTransaction.hasConfigurationError || bbxTransaction.hasServerError || bbxTransaction.hasClientError) {
// The error was not caused by a problem with the data, but is most likely due to some transient networking issues
[FlurryAnalytics logEvent:#"Transaction network error"];
[self completeValidationWithValidateReceiptData:Nil isRecoverableError:YES];
} else {
// The transaction supplied to the validation service was not valid according to Apple
[FlurryAnalytics logEvent:#"Transaction invalid!!"];
[self completeValidationWithValidateReceiptData:Nil isRecoverableError:NO];
}
}
}];
}
<3 blocks
- (void)verifyTransaction:(SKPaymentTransaction *)transaction completionHandler:(void (^)(BOOL flag))completionHandler
{
if (![BBXIAPTransaction canValidateTransactions]) {
completionHandler(YES); // There is no connectivity to reach the server
}
BBXIAPTransaction *bbxTransaction = [[BBXIAPTransaction alloc] initWithTransaction:transaction];
bbxTransaction.useSandbox = YES;
[bbxTransaction validateWithCompletionBlock:^(NSError *error) {
if (bbxTransaction.transactionVerified) {
if (bbxTransaction.transactionIsDuplicate) {
// The transaction is valid, but duplicate - it has already been sent to Beeblex in the past.
NSLog(#"Transaction is a duplicate!");
[FlurryAnalytics logEvent:#"Transaction duplicate!"];
completionHandler(NO);
} else {
// The transaction has been successfully validated and is unique
NSLog(#"Transaction valid data:%#",bbxTransaction.validatedTransactionData);
[FlurryAnalytics logEvent:#"Transaction verified"];
completionHandler(YES);
}
} else {
// Check whether this is a validation error, or if something went wrong with Beeblex
if (bbxTransaction.hasConfigurationError || bbxTransaction.hasServerError || bbxTransaction.hasClientError) {
// The error was not caused by a problem with the data, but is most likely due to some transient networking issues
NSLog(#"Transaction error caused by network, not data");
[FlurryAnalytics logEvent:#"Transaction network error"];
completionHandler(YES);
} else {
// The transaction supplied to the validation service was not valid according to Apple
NSLog(#"Transaction not valid according to Apple");
[FlurryAnalytics logEvent:#"Transaction invalid!!"];
completionHandler(NO);
}
}
}];
}
Then use
[instance verifyTransaction:transaction completionHandler:^(BOOL flag) {
if (flag) {
// YOUR CODE HERE
}
}];
instead of
if ([instance verifyTransaction:transaction]) {
// YOUR CODE HERE
}
I have been trying to develop an app for my ipad which i can use to connect to my local network devices in order to get files and so on without any progress.
What i want to do is to first detect the devices on the network and then select which i want to browse files from by connecting to it.
What i got so far is
#import "BrowseForNetworkDevices.h"
#implementation BrowseForNetworkDevices
- (id)init
{
self = [super init];
if (self)
{
services = [[NSMutableArray alloc] init];
serviceBrowser = [[NSNetServiceBrowser alloc] init];
[serviceBrowser setDelegate: delegateObject];
searching = NO;
}
return self;
}
- (void)dealloc
{
[services release];
[serviceBrowser release];
[super dealloc];
}
// Sent when browsing begins
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser
{
searching = YES;
[serviceBrowser searchForServicesOfType:#" " inDomain:#"local."];
[self updateUI];
}
// Sent when browsing stops
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser
{
searching = NO;
[self updateUI];
}
// Sent if browsing fails
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didNotSearch:(NSDictionary *)errorDict
{
searching = NO;
[self handleError:[errorDict objectForKey:NSNetServicesErrorCode]];
}
// Sent when a service appears
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didFindService:(NSNetService *)aNetService
moreComing:(BOOL)moreComing
{
//[services addObject:aNetService];
if (![services containsObject:aNetService]) {
[self willChangeValueForKey:#"services"];
[services addObject:aNetService];
[self didChangeValueForKey:#"service"];
}
if(!moreComing)
{
[self updateUI];
}
}
// Sent when a service disappears
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didRemoveService:(NSNetService *)aNetService
moreComing:(BOOL)moreComing
{
//[services removeObject:aNetService];
if ([services containsObject:aNetService]) {
[self willChangeValueForKey:#"service"];
[services removeObject:aNetService];
[self didChangeValueForKey:#"service"];
}
if(!moreComing)
{
[self updateUI];
}
}
// Error handling code
- (void)handleError:(NSNumber *)error
{
NSLog(#"An error occurred. Error code = %d", [error intValue]);
// Handle error here
}
// UI update code
- (void)updateUI
{
if(searching)
{
// Update the user interface to indicate searching
for(Class obj in services){
NSLog(#"service found %#", obj);
}
// Also update any UI that lists available services
}
else
{
// Update the user interface to indicate not searching
}
}
#end
Any good tutorials on how to perform such task?
Thanks in advance for all the help
You will need both the network devices and your iPad to be speaking the same protocol. You will need to pick an appropriate protocol. It sounds like what you want to do is file transfer - there are many protocols that can handle this that are implemented in libraries for iOS:
- WebDAV
- HTTP
- FTP
- SFTP
- AFP
- SAMBA
In order for the devices to detect one another you will need to use something like Apple's Bonjour