I'm Trying to fire a custom event in phonegap on iOS. Ok what I've done so far :
I've created a custom plugin , I'm able to call my plugin from Javascript and all work properly. Basically the plugin show a ModalViewController presenting some native functionality
such as recording and editing a video, once the user has finished I will upload the video to youtube. I would like to fire an event when the download is completed but at the moment I wasn't able to do this.
This is part of the code I use :
In my index.html I have this function triggered by a click on a button, (I'm not a Javascript developer) nativeFunction basically call my custom plugin.
function testCustomPlugin()
{
MyClass.nativeFunction(
function(result) {
alert("Success : \r\n"+result);
},
function(error) {
alert("Error : \r\n"+error);
}
);
document.addEventListener("post_sent",onPostSent,false);
}
function onPostSent()
{
alert("post sent");
}
This is my MyClass.js :
var MyClass = {
nativeFunction: function(types, success, fail) {
return Cordova.exec(success, fail, "MyClass", "showInterface", types);
}
}
Inside MyClass.m I have two methods : showInterface and sendNotification,
showInterface.m
- (void) showInterface:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options
{
self.callbackID = [arguments objectAtIndex:0];
// missing code
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(sendSuccessNotification:)
name:#"PostSentNotification" object:nil];
}
-(void)sendSuccessNotification:(NSNotification *)notification
{
if (self.callbackID) {
NSLog(#"%#",callbackID);
NSLog(#"sendSuccessNotification");
NSDictionary *dictionary = [NSDictionary dictionaryWithObject:#"testCallBack"
forKey:#"returnValue"];
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
messageAsDictionary:dictionary
[result setKeepCallbackAsBool:YES];
[super writeJavascript:[result toSuccessCallbackString:self.callbackID]];
}
}
I can see in my log that sendSuccessNotification is called but the event is not fired, I am sure that I do something wrong in the javascript but the problem is that I don't know what.
Thanks in advance for any help
According to your code, I guess you are using PhoneGap / Cordova 1.5.0, right?
I'm asking because a lot of code has been changed in version 1.6.0.
In my opinion the following line is incorrect:
self.callbackID = [arguments objectAtIndex:0];
You can check this yourself by debugging the string value.
According to my information the following statement should be used to retrieve the correct callback id for your result object:
self.callbackID = [arguments pop];
Also, please make sure that you have registered your plugin in your Cordova.plist.
Hope this solves your issue.
Best,
Martin
your js does look a little weird - I recommend following this tutorial (or just download the project and take it from there;-)
http://www.adobe.com/devnet/html5/articles/extending-phonegap-with-native-plugins-for-ios.html
Related
First, I'm bridging Appcelerator with native iOS code and I don't have access to Hyperloop. The following functions exist in the native iOS code. They are compiled as an iOS module and called directly from the Appcelerator code:
MyAppModule.m
-(void)thisA
{
NSLog(#"***** Started A");
self.MyBridge = [MyAppProxy alloc];
[self.MyBridge callThis];
NSLog(#"***** Ended A");
}
-(void)thisB
{
NSLog(#"***** Started B");
self.MyBridge = [MyAppProxy alloc];
[self.MyBridge callThisOtherThing];
NSLog(#"***** Ended B");
}
-(void)thisC
{
NSLog(#"***** Started C");
self.MyBridge = [MyAppProxy alloc];
[self.MyBridge callThisOtherThing]; // Does the same thing B does
NSLog(#"***** Ended C");
}
MyAppProxy.m
-(void)callThis
{
thisVar = 1;
NSLog(#"***** thisVar set to 1");
}
-(void)callThisOtherThing
{
thisVar = 2;
NSLog(#"***** thisVar set to 2");
}
When I initially call thisA, it works. I see all 3 NSLogs and the expected result occurs. When I initially call thisB, it works. I see all 3 NSLogs and the expected result occurs.
When I call thisA again 5 minutes later, it still works. I see all 3 NSLogs and the expected result occurs. When I call thisB again 5 minutes later, it doesn't execute at all. I don't even see the first NSLog (Started B). If I then call thisC after that, it works just fine. Ugh.
There's ostensibly no difference between thisA and thisB. Yet, one fires multiple times and the other only fires once.
What am I doing wrong? I'm at wits end. Thanks guys.
I was testing this method to remove tracks from a playlist. Basically I modified the demo project "simple track playback" provided with the SDK. I wanted to remove the track form the playlist when you hit fastForward. I changed the fastForward method this way but it's not doing anything, and error is nil.
-(IBAction)fastForward:(id)sender {
if([self.player isPlaying] && self.currentPlaylistSnapshot){
SPTAuth *auth = [SPTAuth defaultInstance];
[self.currentPlaylistSnapshot removeTracksFromPlaylist:#[self.player.currentTrackURI]
withAccessToken:auth.session.accessToken
callback:^(NSError *error) {
if (error != nil) {
NSLog(#"*** Failed to remove track : %#", self.titleLabel.text);
return;
}
}];
}
[self.player skipNext:nil];
}
self.currentPlaylistSnapshot is the one I've got from the handleNewSession method.
There's also a static method apparently offering something similar which I have't tried yet.
createRequestForRemovingTracks:fromPlaylist:withAccessToken:snapshot:error:
According to the documentation both options are implemented asynchronously and will take seconds to reflect the results in the server but I'm suspecting that there's either something wrong or I'm just missing to do an actual request to push the changes on the local snapshot maybe?
Documentation:
https://developer.spotify.com/ios-sdk-docs/Documents/Classes/SPTPlaylistSnapshot.html#//api/name/removeTracksWithPositionsFromPlaylist:withAccessToken:callback:
ios sdk:
https://github.com/spotify/ios-sdk
I solved my issue by reseting simulator + adding SPTAuthPlaylistModifyPublicScope (which I fogot to do...)
auth.requestedScopes = #[SPTAuthStreamingScope, SPTAuthPlaylistModifyPublicScope];
I'm building an app that uses the InAppBrowser quite a lot. At some point, the user is able to click an external link from within this window. I tried diffrent methods, but none seems to get a good working result.
The best solution so far is listening to the loadstart event (As described here):
app.browser.addEventListener('loadstart', function (inAppBrowser) {
if(inAppBrowser.url.match(/domain\.com/) === null) {
var url = inAppBrowser.url;
window.open(url, "_system");
}
}
This opens the link in a new window, but also in the original InAppBrowser. Is it possible to cancel this event? Or is there a other approach i can try?
I already tried the following approaches:
Cross window communication.
Inserting a history.back(-1) via the executeScript method.
Call the window.open(url, '_system'); from within the InAppBrowser.
This is for iOS specific.
EDIT:
I ended up by adding this code in platforms/ios/APPNAME/Plugins/org.apache.cordova.inappbrowser/CDVInAppBrowser.m:
NSString *domainStr = [NSString stringWithFormat:#"domain.com"];
NSString *urlStr = [NSString stringWithFormat:#"%#", request.URL];
NSRange result = [urlStr rangeOfString:domainStr];
if(result.location == NSNotFound) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
return NO;
}
above this code:
return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
You have some options:
hide the external links injecting css to inappbrowser, if they don't appear, then they can't be clicked
add a loadstop listener and then hide the links
app.browser.addEventListener('loadstop', hideLinks);
function hideLinks(){
app.browser.insertCSS({
code: "a { display: none; }"
}, function() {
console.log("Styles Altered");
});
}
modify/subclass inappbrowser, changing the shouldStartLoadWithRequest method
change the return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; to return NO if the URL isn't yours
I'm having the devils own time with Game Kit programmatic matching, and can only assume that despite reading all the tutorials I can lay my hands on... I've got the flow wrong somewhere.
Sign in to the Sandbox works fine, the app is being distributed with an App ID specific profile that is Game Center enabled. It all seems to work fine except that it never finds another "nearby" player.
The code below is my authentication handler, which is called correctly, but as I say despite it logging "Starting browser for nearby players", none are ever reported, blue tooth on same wifi network etc etc etc. Utterly perplexed as to what I am doing wrong.
-(void) authenticationHandler{
if ([_currentScene conformsToProtocol:#protocol(BOMScene)]){
MyScene<BOMScene> *theScene = (MyScene<BOMScene> *) _currentScene;
//Make sure the current scene gets the message that they are now authenticated
if ([GKLocalPlayer localPlayer].isAuthenticated){
[theScene localPlayerAuthenticated];
} else {
[theScene localPlayerDeauthenticated];
}
}
NSLog(#"Game Center Status Change: %#", _localPlayer.authenticated ? #"Available" : #"Not Available");
if (_localPlayer.authenticated){
if (!_matchMaker){
_matchMaker= [GKMatchmaker sharedMatchmaker];
NSLog(#"Starting to browser for nearby players");
[_matchMaker startBrowsingForNearbyPlayersWithReachableHandler:^(NSString *playerID, BOOL reachable) {
NSLog(#"Nearby player %# is %#",playerID, reachable ? #"available" : #"no longer available");
if (reachable){
[_nearbyPlayers addObject:playerID];
} else {
[_nearbyPlayers removeObject:playerID];
}
}];
}
} else {
_matchRequest = nil;
[_matchMaker stopBrowsingForNearbyPlayers];
[_nearbyPlayers removeAllObjects];
_matchMaker = nil;
}
You need to install an invitation handler.
Please see my answer here for a full breakdown:
Some startBrowsingForNearbyPlayersWithReachableHandler questions
This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}