How do I get the contextInfo in beginSheetModalForWindow:completionHandler:? - objective-c

I want to transition all my old usages of -beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: to the recommended -beginSheetModalForWindow:completionHandler:. How do I define contentInfo: and get it in the completionhandler?
Here is an example of how the old code looks like:
[alert beginSheetModalForWindow:window
modalDelegate:self
didEndSelector:#selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:(void *)CFBridgingRetain(fc)];
The endSelector method looks like this:
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
if (returnCode == NSAlertDefaultReturn)
{
FileController *fc = (__bridge FileController *)(contextInfo);
[...]
}
}
}
I guess the new method should look somewhat like this:
[alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse alertReturnCode)
{
if (alertReturnCode == NSAlertFirstButtonReturn)
{
// evaluate contextInfo here ...
}
}];
But I have no clue how to get the contextInfo into the completionhandler.
Any help is appreciated.

There is no context info because the completion handler block can simply look right at the surrounding environment.
NSString* s = #"heyho";
[alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse alertReturnCode) {
if (alertReturnCode == NSAlertFirstButtonReturn)
{
// s is visible here
}
}];
In other words, we don't need to pass a context because we are in a context. If you have a FileController to pass down into the block, just let it pass down into the block.

Related

ReactiveCocoa: block is never executed

In my app, I have a download manager. After any of tasks is finished I need to get all data for tableView again and reload it. But I can't get data inside RACObserve signal. Here's my code.
NSArray *activeTasks = [[DownloadManager instance] tasksToProcess];
for (DownloadTask *task in activeTasks) {
[[[self
checkTask:task]
map:^(id value
return [self fetchDownloadedData];
}]
subscribeNext:^(NSArray *models) {
// models returns RACDynamicSignal not NSArray
NSLog(#"%#", models); // <RACDynamicSignal: 0x11611cb50> name:
NSLog(#"checktask next");
} completed:^{
// This is never being executed
NSLog(#"checktask completed");
}];
}
- (RACSignal *)checkTask: (DownloadTask *)task {
return [RACObserve(task, isFinished) map:^id(id _) {
return nil;
}];
}
- (RACSignal *)fetchDownloadedData {
return [[MyCoreDataModel fetchAll] flattenMap:^id(NSArray *models) {
// This is never being executed
return [models filter:^BOOL(MyCoreDataModel *model) {
return model.isDownloaded;
}];
}];
}
- (RACSignal *)fetchAll
{
return [[[MyCoreDataModel findAll] sortBy:#"title"] fetch];
}
Would be great if someone helps me getting where is my mistake is. Thanks in advance.
There are few mistakes:
In map function you use method which returns RACSignal - it's not correct. You should use flattenMap instead.
Complete block will never be called, because RACObserve - hot signal, it only sends next event.
I wrote small example, I hope it helps you.
NSMutableArray<RACSignal *> *signals = [NSMutableArray array];
for (DownloadTask *task in activeTasks) {
RACSignal *signal = [[RACObserve(task, isFinished) ignore:#NO] take:1];
[signals addObject:signal];
}
#weakify(self);
[[[RACSignal merge:signals] flattenMap:^RACStream *(id _) {
#strongify(self);
return [self fetchDownloadedData];
}] subscribeNext:^(NSArray *models) {
}];
Here I created array of signals. Each signal - observing the isFinished property. Also I added ignore:#NO] take:1]; - I think it's more right, because you only need YES value and after that no observe anymore (take:1). Then I merge these signals and each time when someone of them sends finished state, we fetch data.
Please, let me know if something is not understand, I try to explain more clearly.

"disable" button-->method until operation is done

I am using the following method that invoked by pressing a button thru sprite builder.
- (void)method {
//static dispatch_once_t pred; //
//dispatch_once(&pred, ^{ // run only once code below
[self performSelector:#selector(aaa) withObject:nil afterDelay:0.f];
[self performSelector:#selector(bbb) withObject:nil afterDelay:1.f];
[self performSelector:#selector(ccc) withObject:nil afterDelay:1.5f];
[self performSelector:#selector(ddd) withObject:nil afterDelay:4.f];
[self performSelector:#selector(eee) withObject:nil afterDelay:4.5f];
CCLOG(#"Received a touch");
//}); //run only once code above
}
as you can see from the comments i tried running it once. that works good, but if a user comes back to this scene, it's disabled until you restart the app.
how can i block this method from being executed a second time until the first time is done.
i know the code is rough, i'm just learning here....
thanks in advance.
Add a BOOL instance variable which serves as a flag as to whether or not this action is taking place. As soon as the method starts, check the flag. If you need to execute, set the flag.
Add another performSelector:withObject:afterDelay: which calls a method to reset the flag back.
#implementation SomeClass {
BOOL _onceAtATime;
}
- (void)method {
#synchronized(self) {
if (!_onceAtATime) {
_onceAtATime = YES;
// do all the stuff you need to do
[self performSelector:#selector(resetOnceAtATime)
withObject:nil
afterDelay:delay];
// where delay is sufficiently long enough for all the code you
// are executing to complete
}
}
}
- (void)resetOnceAtATime {
_onceAtATime = NO;
}
#end
A simpler way would be to use a serial NSOperationQueue as such (in Swift):
class ViewController: UIViewController {
let queue: NSOperationQueue
required init(coder aDecoder: NSCoder) {
queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
super.init(coder: aDecoder)
}
#IBAction func go(sender: AnyObject) {
if (queue.operationCount == 0) {
queue.addOperationWithBlock() {
// do the first slow thing here
}
queue.addOperationWithBlock() {
// and the next slow thing here
}
// ..and so on
}
else {
NSLog("busy doing those things")
}
}
}

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.

NSTextField autocomplete

Does anyone know of any class or lib that can implement autocompletion to an NSTextField?
I'am trying to get the standard autocmpletion to work but it is made as a synchronous api. I get my autocompletion words via an api call over the internet.
What have i done so far is:
- (void)controlTextDidChange:(NSNotification *)obj
{
if([obj object] == self.searchField)
{
[self.spinner startAnimation:nil];
[self.wordcompletionStore completeString:self.searchField.stringValue];
if(self.doingAutocomplete)
return;
else
{
self.doingAutocomplete = YES;
[[[obj userInfo] objectForKey:#"NSFieldEditor"] complete:nil];
}
}
}
When my store is done, i have a delegate that gets called:
- (void) completionStore:(WordcompletionStore *)store didFinishWithWords:(NSArray *)arrayOfWords
{
[self.spinner stopAnimation:nil];
self.completions = arrayOfWords;
self.doingAutocomplete = NO;
}
The code that returns the completion list to the nstextfield is:
- (NSArray *)control:(NSControl *)control textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger *)index
{
*index = -1;
return self.completions;
}
My problem is that this will always be 1 request behind and the completion list only shows on every 2nd char the user inputs.
I have tried searching google and SO like a mad man but i cant seem to find any solutions..
Any help is much appreciated.
Instead of having the boolean property doingAutocomplete, make the property your control that made the request. Let's call it autoCompleteRequestor:
#property (strong) NSControl* autoCompleteRequestor;
So where you set your current property doingAutocomplete to YES, instead store a reference to your control.
- (void)controlTextDidChange:(NSNotification *)obj
{
if([obj object] == self.searchField)
{
[self.spinner startAnimation:nil];
[self.wordcompletionStore completeString:self.searchField.stringValue];
if(self.autoCompleteRequestor)
return;
else
{
self.autoCompleteRequestor = [[obj userInfo] objectForKey:#"NSFieldEditor"];
}
}
}
Now when your web request is done, you can call complete: on your stored object.
- (void) completionStore:(WordcompletionStore *)store didFinishWithWords:(NSArray *)arrayOfWords
{
[self.spinner stopAnimation:nil];
self.completions = arrayOfWords;
if (self.autoCompleteRequestor)
{
[self.autoCompleteRequestor complete:nil];
self.autoCompleteRequestor = nil;
}
}
NSTextView has the functionality of completing words of partial words.
Take a look at the documentation for this component.
Maybe you can switch to this component in your application.

NSSpeechRecognizer example

Ok so I need to do this:
Wait for command, "Goodnight". Then run an action.
Can someone explain how do accomplish this?
Try this website:
http://www.cocoadev.com/index.pl?NSSpeechRecognizer
And modify as such:
NSSpeechRecognizer *listen;
NSArray *cmds = [NSArray arrayWithObjects:#"goodnight",nil];
listen = [[NSSpeechRecognizer alloc] init];
[listen setCommands:cmds];
[listen setDelegate:self];
[listen setListensInForegroundOnly:NO];
[listen startListening];
[listen setBlocksOtherRecognizers:YES];
- (void)speechRecognizer:(NSSpeechRecognizer *)sender didRecognizeCommand:(id)aCmd {
if ([(NSString *)aCmd isEqualToString:#"goodnight"]) {
[self performSelector:#selector(goodnightMethod:)];
}
}
Your method for handling good night would be (with accordance to what I have written):
-(void)goodnightMethod:(id)sender {
//Do stuff here...
}