Methods are being called yet not executing - objective-c

So my Twitter/Facebook implementation in my app has been a learning experience, but I'm almost there and I have one last, probably simple question. Using the MGTwitter engine, I'm calling a method from my viewcontroller in
- (void) setAccessToken: (OAServiceTicket *) ticket withData: (NSData *) data {
The method is firing off, (confirmed by NSLog calls). However, it's not doing what it's supposed to do, which is fade in my logout button for Twitter. I'm still getting my hands around the way Objective-C handles methods and all, I feel like I'm just not pointing my variables to the right place. Any direction is much appreciated, here is the code below:
SA_OAuthTwitterEngine.m -
//
// access token callback
// when twitter sends us an access token this callback will fire
// we store it in our ivar as well as writing it to the keychain
//
- (void) setAccessToken: (OAServiceTicket *) ticket withData: (NSData *) data {
if (!ticket.didSucceed || !data) return;
NSString *dataString = [[[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding] autorelease];
if (!dataString) return;
if (self.pin.length && [dataString rangeOfString: #"oauth_verifier"].location == NSNotFound) dataString = [dataString stringByAppendingFormat: #"&oauth_verifier=%#", self.pin];
NSString *username = [self extractUsernameFromHTTPBody:dataString];
if (username.length > 0) {
[self setUsername: username password: nil];
if ([_delegate respondsToSelector: #selector(storeCachedTwitterOAuthData:forUsername:)]) [(id) _delegate storeCachedTwitterOAuthData: dataString forUsername: username];
}
[_accessToken release];
_accessToken = [[OAToken alloc] initWithHTTPResponseBody:dataString];
//Call twit login from my view controller
MyView *fvController = [[MyView alloc] init];
[MyView twitLogin];
[MyView helper];
NSLog(#"LETS TWEET DIRECTLY AFTER SUCCESSFUL LOG IN!");
}
This is what my helper method is doing in my .m file:
-(void)helper{
NSLog(#"HELPER FUNCTION");
[self fadeIn:twitterLogout withDuration:2 andWait:2.0];
}
This is the method it's calling
//FADE IN FUNCTION ------------------------------//////////////////////
-(void)fadeIn:(UIView*)viewToFadeIn withDuration:(NSTimeInterval)duration
andWait:(NSTimeInterval)wait
{
[UIView beginAnimations: #"Fade In" context:nil];
[UIView setAnimationDelay:wait];
[UIView setAnimationDuration:duration];
viewToFadeIn.alpha = 1;
[UIView commitAnimations];
}

In Objective-C, methods are declared in one of two ways:
- (returnType)methodName;
or
+ (returnType) methodName;
The first type is an "instance" method and the second type is a "class" method.
These lines should be changed:
[MyView twitLogin];
[MyView helper];
Try this instead:
[fvController twitLogin];
[fvController helper];
Additionally, you may be calling your helper method before the delegate returns a value. You should see if the MGTwitterEngine contains a delegate. (I'd be surprised if it didn't.) You should use the available callbacks to call methods only when the login is finished. Simply calling the methods in order won't do what you want.

*Are both the log statements printed?
*Is setAccessToken:withData: being called from the main thread? Try calling the fadeIn method from the main thread i.e. something like this
- (void) helper{
NSLog(#"HELPER FUNCTION");
[self performSelectorOnMainThread:#selector(callFadeIn)];
}
-(void)callFadeIn{
[self fadeIn:twitterLogout withDuration:2 andWait:2.0];
}
See if that helps. It is possible that setAccessToken: (and thus helper and fadeIn) is being called from another thread. All UI operations should happen from the main thread.

Related

Unusual crash with cocoa webView -elementAtPoint: (OS X)

I have a subclass of webView that overrides - hitTest: The basic idea is that I want clicks on the webView to pass through to the nextResponder if the click was on the body DOM element. The method looks like this
- (NSView *)hitTest:(NSPoint)aPoint
{
NSDictionary *dict = [self elementAtPoint:aPoint];
if([[dict valueForKey:#"WebElementDOMNode"] isKindOfClass:[DOMHTMLBodyElement class]])
{
return (NSView *)[self nextResponder];
}
return [super hitTest:aPoint];
}
When run, it crashes on elementAtPoint with EXC_BAD_ACCESS code=2
Now, it gets weirder. If I breakpoint the app at that line, and do a po [self elementAtPoint:aPoint] in LLDB, LLDB just hangs until I do a ^C.
Weirder yet. If I comment out everything but the last return, break on the return statement, and run po [self elementAtPoint:aPoint] in LLDB—I get exactly what I expect, a nice dictionary telling me all about the DOM at that point.
What could be causing this behavior?
Note: This is on OS X, not iOS.
What could be causing this behavior?
Just look at [WebView _elementAtWindowPoint:] and [WebView _frameViewAtWindowPoint:] implementations in the WebKit source code. Unfortunately, they use hitTest: to determine elementAtPoint:.
In my case, this workaround seems to work:
- (NSView *)hitTest:(NSPoint)point
{
if (m_hitTestEnabled)
{
[NSTimer scheduledTimerWithTimeInterval:0.
target:self
selector:#selector(hitTestDelayed:)
userInfo:#[ #(point.x), #(point.y) ]
repeats:NO];
}
return [super hitTest:point];
}
- (void)hitTestDelayed:(NSTimer *)timer
{
NSPoint point = NSMakePoint([[timer userInfo][0] floatValue], [[timer userInfo][1] floatValue]);
m_hitTestEnabled = false;
NSDictionary *dict = [self elementAtPoint:point];
m_hitTestEnabled = true;
if ([[dict valueForKey:#"WebElementDOMNode"] isKindOfClass:[DOMHTMLDivElement class]])
{
NSLog(#"divAtPoint: %#", dict);
}
}
m_hitTestEnabled is set to YES in the initWith... method.
Why with a timer? Such operations on WebView are allowed only in main thread. So, we cannot launch another thread to get elementAtPoint: and wait for its completion in the "main" hitTest:. Maybe someone will come up with a better solution.

Obj-C return to a block from a delegate method?

I'm writing a mac app that runs its own web server, using the GCDWebServer library (https://github.com/swisspol/GCDWebServer). My app delegate handles GET requests like so:
__weak typeof(self) weakSelf = self;
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
return [weakSelf handleRequest:request];
}];
And then the handleRequest method returns the response data, something like:
return [GCDWebServerDataResponse responseWithHTML:#"<html><body><p>Hello World!</p></body></html>"];
So far so good. Except now I want the handleRequest method to use NSSpeechSynthesizer to create an audio file with some spoken text in it, and then wait for the speechSynthesizer:didFinishSpeaking method to be called before returning to the processBlock.
// NSSpeechSynthesizerDelegate method:
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
NSLog(#"did finish speaking, success: %d", success);
// return to processBlock...
}
Problem is, I have no idea how to do this. Is there a way to return from the speechSynthesizer:didFinishSpeaking method into the processBlock defined above?
You need to run the speech synthesizer on a separate thread with its own run loop, and use a lock to allow your request thread to wait for the operation to complete on the speech thread.
Assuming the web server maintains its own thread(s) and runloop, you can use your app's main thread to run the speech synthesizer, and you can use NSCondition to signal completion to the web response thread.
A basic (untested) example (without error handling):
#interface SynchroSpeaker : NSObject<NSSpeechSynthesizerDelegate>
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url;
- (void)run;
#end
#implementation SynchroSpeaker
{
NSCondition* _lock;
NSString* _text;
NSURL* _url;
NSSpeechSynthesizer* _synth;
}
- (id)initWithText:(NSString*)text outputUrl:(NSURL*)url
{
if (self = [super init])
{
_text = text;
_url = url;
_lock = [NSCondition new];
}
return self;
}
- (void)run
{
NSAssert(![NSThread isMainThread], #"This method cannot execute on the main thread.");
[_lock lock];
[self performSelectorOnMainThread:#selector(startOnMainThread) withObject:nil waitUntilDone:NO];
[_lock wait];
[_lock unlock];
}
- (void)startOnMainThread
{
NSAssert([NSThread isMainThread], #"This method must execute on the main thread.");
[_lock lock];
//
// Set up your speech synethsizer and start speaking
//
}
- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)success
{
//
// Signal waiting thread that speaking has completed
//
[_lock signal];
[_lock unlock];
}
#end
It's used like so:
- (id)handleRequest:(id)request
{
SynchroSpeaker* speaker = [[SynchroSpeaker alloc] initWithText:#"Hello World" outputUrl:[NSURL fileURLWithPath:#"/tmp/foo.dat"]];
[speaker run];
////
return response;
}
GCDWebServer does run into its own threads (I guess 2 of them) - not in the main one. My solution needed to run code in Main Thread when calling the ProcessBlock.
I found this way that suits my needs:
First declare a weak storage for my AppDelegate: __weak AppDelegate *weakSelf = self;. Doing so I can access all my properties within the block.
Declare a strong reference to AppDelegate from within the block like so: __strong AppDelegate* strongSelf = weakSelf;
Use NSOperationQueue to align the operation on mainThread:
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(#"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
In this way myMethodOnMainThread surely will run where it's supposed to.
For sake of clarity I quote my relevant code section:
webServer = [[GCDWebServer alloc] init];
webServer.delegate = self;
__weak AppDelegate *weakSelf = self;
// Add a handler to respond to GET requests
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
__strong AppDelegate* strongSelf = weakSelf;
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your code goes in here
NSLog(#"Main Thread Code");
[strongSelf myMethodOnMainThread];
}];
GCDWebServerDataResponse* response = [GCDWebServerDataResponse responseWithJSONObject:packet];
completionBlock(response);
}];
GCWebServer supports fully asynchronous responses as of version 3.0 and later [1].
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
// 1. Trigger speech synthesizer on main thread (or whatever thread it has to run on) and save "completionBlock"
// 2. Have the delegate from the speech synthesizer call "completionBlock" when done passing an appropriate response
}];
[1] https://github.com/swisspol/GCDWebServer#asynchronous-http-responses

Use Block in Objective C to find out if a BOOL has been set?

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];
}
}

How to subclass NSDocumentController to only allow one doc at a time

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.

Is there a general template for creating a UIPickerview which selects short sound files?

Is there a general template or tutorial or web page that describes the procedure for creating a UIPickerview which selects short sound files and plays them upon selection or with a player? Thanks
You'll need a delegate/data-source class for your picker view - something that implements the protocols UIPickerViewDelegate and UIPickerViewDataSource. This can be whatever view controller you've got handling everything else or a separate class - either way, set the UIPickerView's delegate and dataSource properties to your instance of that class.
The class should have three instance variables - an NSArray soundArr to contain the sounds, an NSTimer timer to provide a delay after selection before the sound plays (more on that below), and an AVAudioPlayer audioPlayer to play the selected sound (for which you'll need to import the AVFoundation framework - it's only available in 2.2, as sound playback used to be a lot more complicated).
When you first load the sounds (in your controller class's -init method or whatever), stick 'em in an array along with the title you want them to display, something like this:
NSBundle *bdl = [NSBundle mainBundle];
soundArr = [[NSArray alloc] initWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:#"Sound One",#"title",[NSURL URLWithString:[bdl pathForResource:#"sound1" ofType:#"wav"]],#"url",nil],
[NSDictionary dictionaryWithObjectsAndKeys:#"Sound Two",#"title",[NSURL URLWithString:[bdl pathForResource:#"sound2" ofType:#"wav"]],#"url",nil],
nil];
The methods you'll need to implement are:
-pickerView:numberOfRowsInComponent: - should return the size of soundArr
-pickerView:titleForRow:forComponent: - should return [[soundArr objectAtIndex:row] objectForKey:#"title"]
-numberOfComponentsInPickerView: - should return 1, since you've only got one column (component) to select from
-pickerView:didSelectRow:inComponent: - see below
You don't want the sound to start immediately when the row-selection delegate method gets called, or snippets of sounds will play continuously as the user scrolls the picker. Instead, use a timer with a short delay, something like this:
if(timer != nil)
{
[timer invalidate]; // remove any timer from an earlier selection
timer = nil;
}
timer = [NSTimer scheduledTimerWithTimeInterval:0.4 target:self selector:#selector(startSoundAtURL:) userInfo:[[soundArr objectAtIndex:row] objectForKey:#"url"] repeats:NO]; // and create the new one
Then, implement a -startSoundAtURL: method that sets up the AVAudioPlayer to play that sound:
- (void)startSoundAtURL:(NSURL *)url
{
if(audioPlayer != nil)
{
[audioPlayer stop];
[audioPlayer release];
}
NSError *err;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&err];
if(err != nil)
{
NSLog([err description]);
return;
}
[audioPlayer play];
}
That should pretty much do it.