I have a BLEPeripheral that holds about thirty CBCHaracteristics. I'm building an app that interfaces with a custom BLE device. When the view loads I need to write, read, and subscribe to Characteristics on that device. The problem I'm having is I'm not sure when all my Characteristics have been assigned to the data model and if I try to load my view before thats finished my app will crash. I've confirmed this by adding a count integer and incrementing the count after it discovers each characteristic, then when my count int == 30, I load the UI. This feel extremely clunky and I feel like theres a better way to do this.
I'm trigger the loading of the data on the UI view with an observer notification. To give a little more information, I'm using a splitview, user selects the ble object that want to connect to. Once it's connected it hides the splitview and shows the detailview.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
for (CBCharacteristic *characteristic in service.characteristics) {
if(_itemCount == 30) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"STOPINDICATOR" object:self];
}
// Serial Number
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:SERIAL_NUMBER]]) {
_serialNumber = characteristic;
_itemCount++;
}
// MFG Name
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:MFG_NAME]]) {
_mfgName = characteristic;
_itemCount++;
}
// Device Type
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:DEVICE_TYPE]]) {
_deviceType = characteristic;
_itemCount++;
}
// Model Number
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:MODEL_NUMBER]]) {
_modelNumber = characteristic;
_itemCount++;
}
}
}
Related
I'm working on a simple instagram project now.
for several occasions I've encountered the same problem.
To work with instagram I use InstagramKit Engine. It has some preset (void)s to make life easier. However I'm always stuck at the same problem.
Let's say we've got this:
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
NSLog(#"%#",userDetail);
} failure:^(NSError *error) {
}];
}
Here userDetail is used inside of the "Success". And it works nice. What I need is to somehow save it after the block is done.
I've tried several things from creating a property to store the userDetails up to making my own method to return the userDetails. The same trouble with saving ints, NSStrings etc..
I think I'm missing some easy way out.
Show it to me please.
You need to capture an object in the block that you can send the response object.
This can be self.
-(void)processUserDetails:(InstagramUser *) userDetail
{
//....
}
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
[self processUserDetails: userDetail];
} failure:^(NSError *error) {
}];
}
You could create a property to hold the Instagram user that you find and assign it to it in the success block:
#property (nonatomic) InstagramUser *myUser;
- (void)getSelfUserDetails
{
[[InstagramEngine sharedEngine] getSelfUserDetailsWithSuccess:^(InstagramUser *userDetail) {
NSLog(#"%#",userDetail);
self.myUser = userDetail;
} failure:^(NSError *error) {
}];
}
This is a good use for an NSNotification (since the call above happens asynchronously).
You could call it like:
[[NSNotificationCenter defaultCenter]
postNotificationName:#"TestNotification"
object:self
userInfo:#{ #"userDetail" : userDetail }
];
And then get that userInfo data back with whatever method is listening for that event.
Every time we try to reconnect after a chatDidFail event, we:
login in chat
enter room
automatically get kicked out of the the room (chatRoomDidLeave event)
when we try to re-join the room, we get kicked out again (chatRoomDidLeave)
and it loops
why, oh why?
Why does Quickblox kicks us out of any room after we try to reconnect following a chatDidFail event?
All we do is:
-(void)chatDidFailWithError:(int)code
{
[[QBChat instance] loginWithUser:currentUser];
}
- (void)chatRoomDidLeave:(NSString *)roomName
{
[[QBChat instance] createOrJoinRoomWithName:#"roomName" membersOnly:NO persistent:YES];
}
We're running out of ideas on this one...
I managed to resolve this. It seems that Quickblox chat implementation isn't able to recover on it's own, when the application goes background and you don't leave the chat room.
This can be solved by having the ChatManager (your own singleton class used to manage chat connections) to store the currently shown room (self.currentRoom) and automatically leave the room, and log out when application resigns active. And when the application will enter foreground, you log in and rejoin the room.
Add these to the singleton class (id)init:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleApplicationWillResignActiveNotification:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleApplicationEnterForegroundNotification:) name:UIApplicationWillEnterForegroundNotification object:nil];
And the property implementation for the current room:
#property (nonatomic, strong) QBChatRoom *currentRoom;
And then implement the corresponding handlers (you can also invalidate the presence timer here):
- (void)handleApplicationWillResignActiveNotification:(NSNotification *)notification
{
lg(#"Application resigning active");
if (self.currentRoom) {
if (self.presenceTimer) {
[self.presenceTimer invalidate];
self.presenceTimer = nil;
}
[self leaveRoom:self.currentRoom clearRoom:NO];
[[QBChat instance] logout];
}
}
- (void)handleApplicationEnterForegroundNotification:(NSNotification *)notification
{
lg(#"Application entering foreground.");
if (![[QBChat instance] loginWithUser:self.currentUser]) {
lg(#"Logging in failed for some reason.");
}
}
Then in chatDidLogin you do the following:
- (void)chatDidLogin
{
...
if (self.currentRoom) {
lg(#"Auto-joining current room.");
[self joinRoom:self.currentRoom completionBlock:nil];
}
...
}
Store the room reference when you enter a room:
- (void)chatRoomDidEnter:(QBChatRoom *)room
{
...
self.currentRoom = room;
...
}
Make sure that you don't clear self.currentRoom when you leave the room on UIApplicationWillResignActiveNotification, so that there's a reference for the current room when the application comes foreground.
I'm looking for some help integrating UIAccelerometer into my cocos2d based application. My current design is roughly the following:
1 -- My HelloWorldLayer layer has a private variable -- a class called Manager -- that I will refer to as the data manager. It is the primary data management class that provides information to the UI.
2 -- The HelloWorldLayer's update method calls to the data manager's update method.
3 -- The data manger allocates a new object -- a class called accelerationMeter -- that provides accelerometer data and updates a custom data object that I'm using to track the devices current acceleration.
4 -- The accelerationMeter class sets up the accelerometer like so:
-(id) init
{
if (self == [super init])
{
// the below call is only used if this object extends CCLayer. Right now it extends NSObject
// self.isAccelerometerEnabled = YES;
[[UIAccelerometer sharedAccelerometer] setUpdateInterval: 1/60];
[[UIAccelerometer sharedAccelerometer] setDelegate: self];
// my data type to store acceleration information
self.currVector = [[vector alloc] init];
}
return self;
}
// method called when the UIAccelerometer updates
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
[self currVector].x = acceleration.x;
[self currVector].y = acceleration.y;
NSLog([NSString stringWithFormat:#"accelerometer: x = %f, y = %f",self.currVector.x,self.currVector.y]);
}
5 -- After (seemingly exactly) 1 second of successful data collection from the UIAccelerometer, the NSLog line in the "accelerometer" method stoops logging. Therefore, the delegated method stops being called.
6 -- As per request, here is the code I use to integrate the accelerometer:
// source: HelloWorldLayer.mm
// purpose: update scene
-(void) update: (ccTime) dt
{
<snip>
world->Step(dt, velocityIterations, positionIterations);
[self updateData];
<snip>
}
// source: HelloWorldLayer.mm
// purpose: update data and use that for the accelerometer display
-(void) updateData
{
if(dataManager)
{
[dataManager update]
<snip>
float accelX = [dataManager currentAcceleration].x;
float accelY = [dataManager currentAcceleration].y;
<snip>
}
}
// source: Manager.m (the data manager class)
// called by updater method copied above this
-(void) update
{
// replace outdated current accleration with the updated current accel
// right now, the accelMonitor class just returns the value of the current acceleration so I will omit the monitor class
currentAcceleration = [accelMonitor getCurrent];
NSLog(#"Manger: Update called");
}
// source accelerationMeter.m
// called by (through an intermediate class) Manager
// I have verified that this method continues to be called after the accelerometer stops working
-(vector*) getCurrentAcceleration
{
NSLog(#"accelerationMeter: getCurrentAcceleration called");
return self.currVector;
}
Note I am not using the IOS simulator -- I am reading the logs from the console of my device.
My main questions are the following:
A -- Why could the accelerometer's delegated method stop being executed?
B -- How can I resolve this so that I can make use of the accelerometer data for > 1 second?
Solved!
I ended up adding the accelerationMeter as a node to the HelloWorldLayer layer. I think that this had something to do with the runloop going out of scope or switching modes, which would therefore stop the accelerometer from collecting data, though I'm unsure.
I'm new to Obj-c. I've got a class which sets a var boolean to YES if it's successful (Game Center login = successful), what it would be great to do, is somehow have a listener to that var that listens to when it is YES and then executes some code. Do I use a block for that? I'm also using the Sparrow framework.
Here's my code in my GameCenter.m file
-(void) setup
{
gameCenterAuthenticationComplete = NO;
if (!isGameCenterAPIAvailable()) {
// Game Center is not available.
NSLog(#"Game Center is not available.");
} else {
NSLog(#"Game Center is available.");
__weak typeof(self) weakSelf = self; // removes retain cycle error
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; // localPlayer is the public GKLocalPlayer
__weak GKLocalPlayer *weakPlayer = localPlayer; // removes retain cycle error
weakPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error)
{
if (viewController != nil)
{
[weakSelf showAuthenticationDialogWhenReasonable:viewController];
}
else if (weakPlayer.isAuthenticated)
{
[weakSelf authenticatedPlayer:weakPlayer];
}
else
{
[weakSelf disableGameCenter];
}
};
}
}
-(void)showAuthenticationDialogWhenReasonable:(UIViewController *)controller
{
[[[[[UIApplication sharedApplication] delegate] window] rootViewController] presentViewController:controller animated:YES completion:nil];
}
-(void)authenticatedPlayer:(GKLocalPlayer *)player
{
NSLog(#"%#,%#,%#",player.playerID,player.displayName, player.alias);
gameCenterAuthenticationComplete = YES;
}
-(void)disableGameCenter
{
}
But I need to know from a different object if that gameCenterAuthenticationComplete equals YES.
You can use a delegate pattern. It's far easier to use than KVO or local notifications and it's used a lot in Obj-C.
Notifications should be used only in specific situations (e.g. when you don't know who wants to listen or when there are more than 1 listeners).
A block would work here but the delegate does exactly the same.
You could use KVO (Key-Value Observing) to watch a property of your object, but I'd rather post a NSNotification in your case.
You'll need to have the objects interested in knowing when Game Center login happened register themselves to NSNotificationCenter, then post the NSNotification in your Game Center handler. Read the Notification Programming Topics for more details !
If there is a single method to execute on a single delegate object, you can simply call it in the setter. Let me give a name to this property:
#property(nonatomic,assign, getter=isLogged) BOOL logged;
It's enough that you implement the setter:
- (void) setLogged: (BOOL) logged
{
_logged=logged;
if(logged)
[_delegate someMethod];
}
Another (suggested) way is to use NSNotificationCenter. With NSNotificationCenter you can notify multiple objects. All objects that want to execute a method when the property is changes to YES have to register:
NSNotificationCenter* center=[NSNotificationCenter defaultCenter];
[center addObserver: self selector: #selector(handleEvent:) name: #"Logged" object: nil];
The handleEvent: selector will be executed every time that logged changes to YES. So post a notification whenever the property changes:
- (void) setLogged: (BOOL) logged
{
_logged=logged;
if(logged)
{
NSNotificationCenter* center=[NSNotificationCenter defaultCenter];
[center postNotificationName: #"Logged" object: self];
}
}
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
}