I'm developing an iPhone app using the XMPP framework for iOS/OSX and I'm using the XEP-0045 extension (multi-user chat). I've got the room creating and configuring successfully and I am able to invite other users and chat with them. The problem arises when I go to destroy the room I've created. I've followed the code-path that gets executed inside the framework and I've figured out why the framework isn't firing the method, but I'm not sure how it would ever fire the method given the behavior I'm seeing.
That behavior is as follows:
1) I request that the room be destroyed by calling [room destroyRoom]
2) I then see that the XMPPRoom class sets up its XMPPIDResponse tracker to watch for the "result" iq stanza that the server will send back saying it's successfully deleted the room.
3) (Here is where the problem arises) I receive a presence stanza from the room saying it's now unavailable (since I, too, am an occupant of the room) and the framework then clears the response tracker and calls -xmppRoomDidLeave:.
4) The server then sends back the "result" iq stanza saying that the room was successfully deleted, but no one is listening anymore. This results in the call to xmppRoomDidDestroy being missed.
This behavior is consistent with what I've read in the XEP-0045 definition and, given that, I'm not sure how the -xmppRoomDidDestroy: delegate would ever get called. Am I doing something wrong here?
I had the same problem and this is my solution:
In XMPPRoom method
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
replace
[responseTracker removeAllIDs];
with:
if ([x elementForName:#"destroy"]) {
NSArray *allKeys = [responseTracker allIDs];
for (NSString *key in allKeys) {
SEL aSel = [responseTracker selectorForElementID:key];
if (aSel != #selector(handleDestroyRoomResponse:withInfo:)) {
[responseTracker removeID:key];
}
}
} else {
[responseTracker removeAllIDs];
}
In XMPPIDTracker.h add declaration for new public methods
- (NSArray *)allIDs;
- (SEL)selectorForElementID:(NSString *)elementID;
Also in XMPPBasicTrackingInfo interface add
#property (nonatomic, readonly) SEL selector;
In XMPPIDTracker.m add two public methods
- (NSArray *)allIDs {
return [dict allKeys];
}
- (SEL)selectorForElementID:(NSString *)elementID {
id <XMPPTrackingInfo> info = [dict objectForKey:elementID];
if ([info isKindOfClass:[XMPPBasicTrackingInfo class]]) {
return [(XMPPBasicTrackingInfo *)info selector];
}
return nil;
}
At the end in the #implementation XMPPBasicTrackingInfo add
#synthesize selector;
Basicaly this code removes all response trackers except handleDestroyRoomResponse:withInfo: because we need this hander to respond on IQ destroy stanza.
I hope this will help.
Same issue with lastest XMPPFramework 3.6.6.
Here is the solution:
Still use destroyRoom function, xmppStream:didReceivePresence: and xmppRoomDidLeave: will be called, test by add NSLog inside both delegate, then i use Spark(Windows version) to destory a room, the Xcode console print the following text:
<presence xmlns="jabber:client" type="unavailable" from="wei#conference.10.50.200.94/admin" to="admin#10.50.200.94/iOS">
<x xmlns="http://jabber.org/protocol/muc#user">
<item affiliation="none" role="none"></item>
<destroy>
<reason>destroy room</reason>
</destroy>
</x>
</presence>
I use the <destroy> element to determine the destroyRoom action.
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence
{
/*
<presence xmlns="jabber:client" type="unavailable" from="wei#conference.10.50.200.94/admin" to="admin#10.50.200.94/iOS"><x xmlns="http://jabber.org/protocol/muc#user"><item affiliation="none" role="none"></item><destroy><reason>destroy room</reason></destroy></x></presence>
*/
// NSLog(#"%s %#", __FUNCTION__, presence);
if ([presence.fromStr containsString:#"conference"]) {
NSXMLElement *x = [presence elementForName:#"x"];
if (x) {
NSXMLElement *destroy = [x elementForName:#"destroy"];
if (destroy) {
XMPPJID *roomJID = presence.from.bareJID;
// TODO: do something with the roomJID
}
}
}
}
But XMPPFramework send a empty <destory/> element, i guess the openfire server simply ignore the <destroy/> and response <presence>..</presence> without <destroy>.
Modify the destroyRoom function in XMPPFramework or implement the following delegate to add a destroy reason.
- (XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq
{
/*
<iq type="set" to="weixin#conference.10.50.200.94" id="BCF55C6A-9C5E-4740-BE6A-63E17B5C58F6"><query xmlns="http://jabber.org/protocol/muc#owner"><destroy></destroy></query></iq>
*/
if ([iq isSetIQ]) {
NSXMLElement *query = [iq elementForName:#"query" xmlns:XMPPMUCOwnerNamespace];
if (query) {
NSXMLElement *destroy = [query elementForName:#"destroy"];
if (destroy) {
NSXMLElement *reason = [destroy elementForName:#"reason"];
if (!reason) {
reason = [NSXMLElement elementWithName:#"reason" stringValue:#"destroy reason"];
[destroy addChild:reason];
}
}
}
}
return iq;
}
Related
I am using Quickblox Api, for chat and video chat. iOS. And I am using the latest version of the API
When I try to Make a video call,
most of the times i don't get video, only audio.
i get video on both ends 1 out of 15 times.
3 out of 10 times video on one end.
very weird. I have good internet connection. connecting to chat users are receiving the call. Can seem to find out the issue.
After spending sometime to find the issue, I received and help from Quickblox Help Center.
If your face such Behavior on the API
1.Make Sure that you set Delegate Methods in viewDidLod, not view did appear or etc. For Ex:
- (void)viewDidLoad {
[super viewDidLoad];
[[QBChat instance] addDelegate:self];
[QBRTCClient.instance addDelegate:self];
[QBSettings setCarbonsEnabled:YES];
}
Use Breakpoints to find out if they are getting called, once you make or receive calls.
2.Make Sure that your Calling methods are correct. An array containing Users must not equal to currentUser.ID.
NSInteger currentUserID = [QBSession currentSession].currentUser.ID;
int count = 0;
NSNumber *currentUserIndex = nil;
for (NSNumber *opponentID in opponentsIDs) {
if ([opponentID integerValue] == currentUserID) {
currentUserIndex = #(count);
break;
}
count++;
}
if (currentUserIndex) [opponentsIDs removeObjectAtIndex:[currentUserIndex intValue]];
QBRTCSession *session = [QBRTCClient.instance createNewSessionWithOpponents:opponentsIDs
withConferenceType:QBRTCConferenceTypeVideo];
NSDictionary *userInfo = #{ #"key" : #"value" };
[session startCall:userInfo];
if (session) {
self.currentSession = session;
[self performSegueWithIdentifier:#"openDialogSeg" sender:self];
}
else {
[SVProgressHUD showErrorWithStatus:#"You should login to use chat API. Session hasn’t been created. Please try to relogin the chat."];
}
}
Check View Layout, size and width. make sure they are set correctly.
I'm creating an IOS App based on cocoaLibSpotify.
In some point, I'm creating a Spotify playlist from an array of songs. I have previously obtained all the Spotify URIs and then I create the playlist and add all the tracks one by one.
The code below is in the logic class of the app and then I have a controller to show the results.
The problem is that I call this logic from the controller but I dont know the way to get back when the adding process has finished. I have tried to implement a delegate but im not sure how to do it...
Which is the right way to add tracks to a playlist? I have been searching on the documentation and in the GitHub repository but I have only found an example with two nested track addings... :S
Thanks in advance! (and sorry for my english)
- (void) createPlaylist:(NSArray*)spotifyURIs withName:(NSString*)name {
int songsRead = 0;
[container createPlaylistWithName:name callback:^(SPPlaylist *createdPlaylist) {
[SPAsyncLoading waitUntilLoaded:createdPlaylist timeout:kSPAsyncLoadingDefaultTimeout then:^(NSArray *loadedPlaylist, NSArray *notLoadedPlaylist) {
for (int i=[spotifyURIs count]-1; i>=0; i--) {
NSString *trackURI = spotifyURIs[i];
if (trackURI != nil){
[[SPSession sharedSession] trackForURL:[NSURL URLWithString:trackURI] callback:^(SPTrack *track) {
if (track != nil) {
[createdPlaylist addItems:[NSArray arrayWithObject: track] atIndex:[[createdPlaylist items] count] callback:nil];
}
}];
}
songsRead++;
// If I have read the whole tracklist, end of the process, returning to controller...
if (songsRead == [spotifyURIs count]){
// ...
}
}
}];
}];
}
What you need is to use the KVO system on your new playlist or using BLOCKS
you can implement something like this
typedef void (^spotifycompletionWithData)(id data);
-(void)addTrack:(NSString *)trackURI withCompletionBlock:(spotifycompletionWithData)CompletionBlock;
then whenever you call the function you should return the loaded SPTRACK within the completion block i hope its helping
I am trying to implement IAPs in one app and I'm almost there but still facing some problems. I already posted another question before of this because I found an incomplete tutorial that was missing something so I was stuck and someone already helped me out with that... but in that tutorial they were using a table while I just want to use normal buttons. Between various answers there was an answer that gave me a new way to approach this matter.. so I am giving it a try but here comes the problems I found:
I am getting a warning, the guy told me in the answer to include each of the following protocols in my header file:
SKProductsRequestDelegate
SKPaymentTransactionObserver
SKRequestDelegate
and I did it with this code:
#interface BuyTest2 : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate>
but now I'm getting this warning in m file:
Method 'paymentQueue:updatedTransactions:' in protocol not implemented
why is that? Am I missing something? (I'm sure I am...)
when I click button to buy Coins everything works fine but after the purchase has been done I don't know how to deliver the coins... I have my code to do it but I don't know where to put it... how should I do that?
if I try again to make a test purchase I get the message that I already bought that item and that I have to click on 'OK' to download it... but after that nothing happens... and I really don't have to give something to download but I just need to add some coins to a variable and then save it with NSUserDefaults...
here is the code I am using:
in file .h i got
#interface BuyTest2 : UIViewController <SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate>
#property (nonatomic, retain) SKProduct *currentProduct;
#property(nonatomic, readonly) SKPaymentTransactionState transactionState;
#property (nonatomic, retain) SKProductsRequest *ualRequest;
and in file .m i got:
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if(response.products.count > 0)
{
SKProduct* product;
for(int i = 0; i<response.products.count; i++)
{
product = [response.products objectAtIndex:i];
if([product.productIdentifier isEqualToString:#"com.mycompany.myapp.1"])
{
self.currentProduct = product;
[self beginPaymentWithProduct:product];
}
}
}
}
- (void)beginPaymentWithProduct:(SKProduct*)product
{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- (BOOL)canMakePurchases
{
return [SKPaymentQueue canMakePayments];
}
- (IBAction)buyCoins:(id)sender
{
if([self canMakePurchases])
{
ualRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:[NSArray arrayWithObjects: #"com.mycompany.myapp.1", nil]]];
[ualRequest setDelegate:self];
[ualRequest start];
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction* transaction in transactions) {
if (transaction.transactionState == SKPaymentTransactionStatePurchased) {
NSLog(#"Transaction Purchased: %#", transaction);
// Make purchase available to the user, etc...
// Once that's all done...
[queue finishTransaction:transaction];
}
else if (transaction.transactionState == SKPaymentTransactionStateFailed) {
NSLog(#"Transaction Failed: %#", transaction);
// Display error to the user, using the error text in the transaction
// This example uses NSLog, typically you'd use UIAlertView here
NSLog(#"Error: %#", [transaction.error localizedDescription]);
}
}
}
Thanks for any help...
but now I'm getting this warning in m file:
Method 'paymentQueue:updatedTransactions:' in protocol not implemented
The error message is very informative here. paymentQueue:updatedTransactions: is part of the SKPaymentTransactionObserver protocol, and is a required method (meaning, you have to implement it if you are to become an observer of payment transactions. Remember, warnings are something you should take a serious look at, and typically you should consider them as errors). The documentation for this protocol can be found here.
The discussion for this particular method is fairly clear on it's purpose, and what you are required to do. It is the means to check whether a transaction has gone though, and if it has it is your responsibility to provide the functionality/content to the user.
The application should process each transaction by examining the transaction’s transactionState property. If transactionState is SKPaymentTransactionStatePurchased, payment was successfully received for the desired functionality. The application should make the functionality available to the user. If transactionState is SKPaymentTransactionStateFailed, the application can read the transaction’s error property to return a meaningful error to the user.
Once a transaction is processed, it should be removed from the payment queue by calling the payment queue’s finishTransaction: method, passing the transaction as a parameter.
The best documentation on how to use the IAP system is available from Apple, and I strongly recommend you give it a good read. It is available here.
Update:
Here is a code snippet showing the general gist of what's required in this method:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction* transaction in transactions) {
if (transaction.transationState == SKPaymentTransactionStatePurchased) {
NSLog(#"Transaction Purchased: %#", transaction);
// Make purchase available to the user, etc...
// Once that's all done...
[queue finishTransaction:transaction];
}
else if (transaction.transactionState == SKPaymentTransactionStateFailed) {
NSLog(#"Transaction Failed: %#", transaction);
// Display error to the user, using the error text in the transaction
// This example uses NSLog, typically you'd use UIAlertView here
NSLog(#"Error: %#", [transaction.error localizedDescription]);
}
}
}
And remember to register yourself for events. You can do this via:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
Add these in the appropriate places, such as init/dealloc. Do refrain from using this in viewDidLoad, as this is more of a visual thing, and not a data model concern.
I'm able to connect to the local server, and also the google talk and display the buddies and chat on the iphone chat client but when I try to get Buddy list of my local server, it return 0.
- (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
// a buddy went offline/online
NSString *presenceType = [presence type]; // online/offline
NSString *myUsername = [[sender myJID] user];
NSString *presenceFromUser = [[presence from] user];
if (![presenceFromUser isEqualToString:myUsername])
{
if ([presenceType isEqualToString:#"available"]) {
[_chatDelegate newBuddyOnline:[NSString stringWithFormat:#"%##%#", presenceFromUser, #"ip address"]];
} else if ([presenceType isEqualToString:#"unavailable"]) {
[_chatDelegate buddyWentOffline:[NSString stringWithFormat:#"%##%#", presenceFromUser, #"ip address"]];
}
}
}
Here ip address means my server name
Please help!
Please help me!!!
It only tells you when some one comes online or goes offline, according to this code, when any buddy will be online delegate method 'newBuddyOnline' will be fired and when someone will go offline delegate method 'buddyWentOffline' will be fired.
You can get list of all buddies by adding buddies to array when they come online after connecting to xmpp stream
I am using facebook SDK 3.0 in my app. The delegate method is called twice when after logging to facebook.
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
//loginThroughFb=TRUE;
NSString *userId=[[NSString alloc] initWithString:[user id]];
[self soapCallForLogin:#"" password:#"" deviceId:#"" fbid:userId];
NSLog(#"%#",userId);
[userId release];
}
I tried 'HelloFacebookSample' project and the method is called only once.
So I guess the best solution for such case is to keep a reference to the last user object and compare it to the new object you get the next call, and if they're equal you can just ignore that call.
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user {
if (![self isUser:cachedUser equalToUser:user]) {
cachedUser = user;
/// Do something
}
}
- (BOOL)isUser:(id<FBGraphUser>)firstUser equalToUser:(id<FBGraphUser>)secondUser {
return
[firstUser.objectID isEqual:secondUser.objectID] &&
[firstUser.name isEqual:secondUser.name] &&
[firstUser.first_name isEqual:secondUser.first_name] &&
[firstUser.middle_name isEqual:secondUser.middle_name] &&
[firstUser.last_name isEqual:secondUser.last_name] &&
...
}
I also had this problem. I managed to fix it with an ugly hack, but it works. I keep a counter in the FBLoginView delegate. When the fetchedUserInfo is called, I check the counter. If it is greater than zero, return. Otherwise, do two things -
1. increment the message counter
2. Fire a delayed event that zeroes the message counter again.
So your fetchedUserInfo method will look like this:
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
if ([self messageCounter] >0)
return;
else
{
self.messageCounter++;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
[self setMessageCounter:0];
});}
// Do whatever you were going to do }
Fixed in FB SDK 3.8 released on Sept 18 2013. The delegate methods are now called once per login regardless of how many times the repeated logging out and back in occur.
I was also able to reproduce this on FB SDK 3.7.1 and within their own sample program "Scrumptious"
As mentioned (at least for me) this only happens after:
Logging in once
Logging out
Logging back in (Now it happens)
What is interesting is the order of calls on re-logins:
On the first login I the calls I see are:
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView;
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user;
On the 2nd (and later) logins I see:
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user;
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView;
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user;
Which gives a handy little workaround of setting a flag in the middle method like so:
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// Set flag
self.isFirstLoginDone = YES;
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user {
// Check
if(self.isFirstLoginDone) {
// Execute code I want to run just once
NSLog(#"fetched");
}
// Don't forget to clear the flag (I guess it shouldn't matter if everything is cleaned up)
self.isFirstLoginDone = NO;
}
There could be another reason, which i jsut faced.
My situation:
ViewController A has a login (With fbloginview and its delegate set)
User chooses to register, moves to ViewController B with another fbloginview and its delegate set.
The above makes the delegate fire twice.
I have fixed this by setting delegate to nil on ViewWillDisappear in ViewController A.
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
fbLoginButton.delegate=self;
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
fbLoginButton.delegate=nil;
}
I used this simple trick :
(Define an int facebookCounter in your interface)
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
if (self.facebookCounter==0) {
self.facebookCounter++;
return;
}
//Do stuff here
}
I needed to add thread safety in this method. A simple class variable did not work. The following two options will work, depending on the use case-
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView user:(id<FBGraphUser>)user {
//self.executedOnce = NO; in the init method of this class
#synchronized(self){
if(!self.executedOnce) {
//do something once per init of this class
self.executedOnce = YES;
}
}
//OR- This will only execute once in the lifetime of the app, thus no need for the executedOnce flag
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//do something once per lifetime of the app
});
}
just in the loginViewFetchedUserInfo method set the delegate of the loginView to nil. then it can never be called. and if you need the login again, set the delegate to the correct object.