Cocoa app with only NSSavePanel - objective-c

I'm trying to create a Cocoa application that displays SavePanel, and after user choose file, it prints it on stdout. I'm total beginer with Objective-C and Cocao. Problem is that it doesn't take keyboard input, it is only posible to choose file with mouse.
This is the code:
#import <Cocoa/Cocoa.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
NSSavePanel *sPanel = [NSSavePanel savePanel];
int result = [sPanel runModal];
if (result == NSOKButton) {
NSString * filename = [sPanel filename];
char * fileStr = [filename UTF8String];
printf("%s\n", fileStr);
}
return 0;
}

The AppKit/Cocoa classes require an NSApplication object to be initialized in order to handle user input (among other things). Adding this line to the top of your main function should do the trick:
int main(int argc, char *argv[])
{
[NSApplication sharedApplication]; // ** Add this **
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSSavePanel *sPanel = [NSSavePanel savePanel];
int result = [sPanel runModal];
if (result == NSOKButton) {
NSString * filename = [sPanel filename];
const char * fileStr = [filename UTF8String];
printf("%s\n", fileStr);
}
[pool drain];
return 0;
}
More information about this can be found in the documentation for NSApplication, particularly these points:
Every application must have exactly one instance of NSApplication (or
a subclass of NSApplication). Your program’s main() function should
create this instance by invoking the sharedApplication class method.
NSApplication performs the important task of receiving events from the
window server and distributing them to the proper NSResponder objects.
NSApp translates an event into an NSEvent object, then forwards the
NSEvent object to the affected NSWindow object.
Along the lines of bbum and danielpunkass's comments below, this isn't the way you'd really write a Cocoa application, and while it does make your immediate issue go away, it's not a complete or completely correct solution. To expand on Daniel's comment, and to get you started easily, create a new Cocoa application project. Open up the application delegate class (created for you), and put your code in the -applicationDidFinishLaunching: method. As implied by its name, that method is called after the application has finished launching, and everything is setup such that you can use the AppKit classes normally. As you gain more experience, you'll better understand the typical Cocoa application architecture and can move on to creating user interfaces, etc.

Related

Handle any application closing in objective c

I want to execute my method when any application is closing. My code is:
#interface FO: NSObject
- (void)applicationKilled:(NSNotification*)notification;
- (void)appDidLaunch:(NSNotification*)notification;
#end
#implementation FO
- (void)applicationKilled:(NSNotification*)notification {
NSLog(#"success");
}
- (void)appDidLaunch:(NSNotification*)notification {
NSLog(#"app info: %#", [notification userInfo]);
}
#end
#implementation Main:NSObject
FO fo;
NSString * filePath = "...MyPath";
NSString * application = "..MyApplication";
int main(int argc, const char * argv[]) {
fo = [[FO alloc]init];
[Main MyMethod];
while(1==1) {
...some code;
}
return 0;
}
+(void) MyMethod {
center = [[NSWorkspace sharedWorkspace] notificationCenter];
[center addObserver:fo selector:#selector(appDidLaunch:) name:NSWorkspaceDidLaunchApplicationNotification object:nil];
[center addObserver:fo selector:#selector(applicationKilled:) name:NSWorkspaceDidTerminateApplicationNotification
object:nil];
[[NSWorkspace sharedWorkspace] openFile:filePath withApplication:application]; }
#end
However, appDidLaunch method is not firing, even if i'll open another application in finder. Also applicationKilled method is never firing.
When i'm executing following code
[center postNotificationName:NSWorkspaceDidLaunchApplicationNotification
object:self];
appDidLaunch method is firing OK. Where can be a problem? Should this methods be fired every time when some application is opened or closed?
CRD is on the right track. You absolutely must have a runloop to receive this notification. For example:
#implementation Main : NSObject
- (void)applicationDidFinishLaunching:(NSApplication *)app {
[Main MyMethod];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// ... The rest of your program ...
});
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
MyDelegate *delegate = [Main new];
[[NSApplication sharedApplication] setDelegate:delegate];
[NSApp run];
}
return 0;
}
I've put "the rest of your program" into a dispatch_async because you must not block the main thread. The usual way that Mac apps work is not with a big while (YES) loop. The usual way is by registering for various events and then waiting from them to happen. That's what the run loop is for. But if you have to manage your own loop (you generally shouldn't, but if you must), then you need to move it off of the main queue.
Assuming you are using ARC and also guessing as the information you give seems to be incomplete:
In your updated question you show fo declared as a local variable of MyMethod. The method addObserver:selector:name:object: does not keep a strong reference to the observer. After MyMethod returns the local fo object will be reclaimed, you now have no observer to call methods on.
However, while the above would explain why your code doesn't work it wouldn't explain why your app does not crash - and you don't report that it crashes. Running the code you give above causes the app to crash. So it appears that you've missed some information out or at least not reported the crash.
Guess Two
You have no run loop.
Many parts of the framework rely on there being a run loop which dispatches incoming events to appropriate handlers - just type "run loop" into Xcode's help. If you create a standard application using Xcode's "Cocoa Application" template the run loop is created for you by the code in main.m.
Events produced by OS X when applications start and stop are dispatched by the run loop to framework handlers which produce the corresponding notifications. Without a run loop these system events will not be handled, so no notifications.
You have:
int main(int argc, const char * argv[])
{
fo = [[FO alloc]init];
[Main MyMethod];
while(1==1)
{
...some code;
}
return 0;
}
so unless "...some code" creates a run loop the system events will not be handled.
Write your project using the standard "Cocoa Application" template and, for example, put your call to setup the notification handlers in applicationDidFinishLaunching:.
HTH

NSNotifier on Mac OSX

I'm building a mac daemon, from scratch.
Here's a simplified version of the code.
#import <stdio.h>
#import <stdlib.h>
#import <Foundation/Foundation.h>
#include "notifier.h"
int new_notification();
notifier *not;
int main () {
#autoreleasepool {
not = [[[notifier alloc] init] autorelease];
pid_t pid;
pid = fork();
if(pid > 0) {
printf("my child id is %d\n", pid);
exit(0);
}
while(1) {
int n = new_notification();
if(n > 0) {
NSUserNotification *notification = [[NSUserNotification alloc] init];
notification.title = #"Hello, World!";
notification.informativeText = #"A notification";
notification.soundName = NSUserNotificationDefaultSoundName;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
printf("new notification : count = %d !!\n", n);
}
sleep(1);
}
}
return 0;
}
int new_notification() {
return [not get_notifications];
}
I don't see the notification on my window though, I think I have to make my application a "key" application, if so, how do it do that? I can see the output on my terminal though, and on checking if
(NSClassFromString(#"NSUserNotificationCenter")==nil)
I get FALSE
Extended comments that I hope answer your issue...
If your app is active, the notification is unlikely to be displayed. But you'd be able to find it in the notification center. (Notifications draw a user's attention to an app that they're not already looking at.)
I'd suggest trying [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory] to make it clear your app isn't in the foreground but I'm not totally certain whether [NSApplication sharedApplication] aka NSApp is something you'd use with a daemon.
Part of your issue may actually be that without invoking [NSApplication sharedApplication] and telling it to run you never become an app, so you can't use the notification center... or become an accessory. But I'm not certain of that either.

Obj-C design pattern : parallel task launcher

I currently have a shell script that process many images one after the other, with the help of GraphicsMagick. It works fine, all calculations are correct, everything works. (that's not a "simple" script, it involves reading dimensions from a JSON file, converting a bunch of images with respect to many constraints).
As we're working with dual-core or quad-core computer, I'd like to parallelize it. And as I'm an iPhone developer liking to introduce myself to Mac development, I'd like to create it with XCode and Objective-C using the "command-line tool" template.
So far so good, but now I'm face with the design of the "task dispatcher" object. I'm fairly lost between running NSTasks in a run loop, in separate threads, using blocks, with or without GCD, with or without ARC.
How would one achieve this? I was thinking of using simple threads to spawn NSTasks, having them report when they're done, and notify my dispatcher's delegate so that it can upgrade its progress bar. But I'd really like to get in touch with Grand Central Dispatch. Does anyone have any thoughts, ideas, advice about what to do and what not?
Edit: I'm reading Apple's docs, and have found the NSOperationQueue class. Could it be that this is precisely what I'm needing here?
A good class to use to launch independant processes including parameters and environment variables is NSTask. See the documentation for the gory details. Here is a little commandline tool that starts 10 concurrent processes and waits for them to finish. NSOperationQueue would be redundant here because the tasks are already launched concurrently.
-- Edit: Improved Version With Limited Concurrency --
int main (int argc, const char * argv[])
{
#autoreleasepool {
// Let's not have more than 5 parallel processes
dispatch_semaphore_t limit = dispatch_semaphore_create(5);
dispatch_semaphore_t done = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
// Setup the taks as you see fit including the environment variables.
// See docs on NSTask for more on how to use this object.
NSTask *task = [[NSTask alloc] init];
task.launchPath = #"/bin/ls";
task.arguments = [NSArray arrayWithObject:#"-la"];
task.terminationHandler = ^(NSTask *task) {
dispatch_semaphore_signal(limit);
if (i==9) dispatch_semaphore_signal(done);
};
dispatch_semaphore_wait(limit, DISPATCH_TIME_FOREVER);
[task launch];
}
dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
dispatch_release(limit);
dispatch_release(done);
}
return 0;
}
-- Original Version --
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSObject *lock = [[NSObject alloc] init];
int __block counter = 10;
for (int i=0; i<10; i++) {
// Setup the taks as you see fit including the environment variables.
// See docs on NSTask for more on how to use this object.
NSTask *task = [[NSTask alloc] init];
task.launchPath = #"/bin/ls";
task.arguments = [NSArray arrayWithObject:#"-la"];
task.terminationHandler = ^(NSTask *task) {
#synchronized(lock) { counter--; }
};
[task launch];
}
while (counter)
usleep(50);
[lock release];
}
return 0;
}
In your case you might want to hold the NSTask objects in an array for easier management.
yes - NSOperation/NSOperationQueue are good for this task.
i'd start with something like this:
#protocol MONTaskRequestDelegate
- (void)taskRequestDidComplete:(MONTaskRequest *)taskRequest;
#end
#interface MONTaskRequest : NSOperation
{
#private
NSTask * task;
NSObject<MONTaskRequestDelegate>* delegate; /* strong reference. cleared on cancellation and completion, */
}
- (id)initWithTask:(NSTask *)task delegate:(NSObject<MONTaskRequestDelegate>*)delegate;
// interface to access the data from the task you are interested in, whether the task completed, etc.
#end
#implementation MONTaskRequest
// ...
- (void)performDelegateCallback
{
[self.delegate taskRequestDidComplete:self];
self.delegate = nil;
}
- (void)main
{
NSAutoreleasePool * pool = [NSAutoreleasePool new];
[self runTheTask];
// grab what is needed and handle errors
[self performDelegateCallback];
[pool release];
}
- (void)cancel
{
[super cancel];
[self stopTaskIfPossible];
[self performDelegateCallback];
}
#end
then you can use NSOperationQueue to limit the number of active tasks to a reasonable number.
I use for this purpose the [myObj performSelectorInBackground:#selector(doSomething) withObject:nil]; functionality of an NSObject.
The idea is quite simple: you write a method that does the work, call it from the main thread using the aforementioned method, then call some callback selector if you need to somehow process the results from different threads.

NSAutoreleasePool is unavailable

I am following "Programming in Objective-C" 3rd edition and I am having problems with the first example.
I keep getting this error:
Semantic Issue: 'NSAutoreleasePool' is unavailable: not available in
automatic reference counting mode
Here is my code:
//
// main.m
// prog1 //
// Created by Steve Kochan on 1/30/11.
// Copyright 2011 ClassroomM, Inc.. All rights reserved. //
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog (#"Programming is fun!");
[pool drain];
return 0;
}
Any insight will be greatly appreciated.
The compiler is being asked to compile the file with ARC (automatic reference counting) enabled. Turn that off or, better yet, modernize your example:
int main (int argc, const char * argv[]) {
#autoreleasepool {
NSLog (#"Programming is fun!");
}
return 0;
}
(No, I can't tell you how, specifically, to turn off ARC, if that was the route you were to go down due to the aforementioned NDA.)
Quick post just in case you still looking
You can disable ARC in build settings.
Click on you project, in the left hand organizer.
Select your target, in the next column over.
Select the Build Settings tab at the top.
Scroll down to "Objective-C Automatic Reference Counting" (it may be
listed as "CLANG_ENABLE_OBJC_ARC" under the User-Defined settings
group), (if you do not find ARC option under build settings, you might need
to toggle you compiler. You can find it under build settings)
and set it to NO.
In my case, I wanted ARC on, and wanted to update a sample project to work properly. Apple's NSAutoReleasePool docs are technically correct, but don't come straight out and explain this. Here's how:
Take your application main, which probably looks something like this:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([DemoAppDelegate class]));
[pool release];
return retVal;
}
And change it to look like this:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([DemoAppDelegate class]));
}
}
Here is a link to Apple's transition guide to ARC.
OK...check this out. Specific change to NSAutoreleasePool - this is how Xcode initializes itself when you create your first app. I don't know about you, but I love this idea!
No worries if you are following along w/ Kochan's book. When starting your project, just uncheck the "Use ARC" box. Everything will work.
ARC is enabled when you first create a new project. Right know the only way I know how to enable or not enable it is when you first create your program. It is one of the checkboxes you have to unselect.

Sometimes I want to stop my Cocoa app from launching, how do I stop it in init?

I just want to quit as fast as possible, before the nibs are loaded. I tried [NSApp stop:self] but that didn't seem to work. Is there a better way than getting my process and killing it?
(I know it's a weird thing to do. It's for a good reason.)
Without knowing more, I'd say put the checking code into main() before invoking NSApplicationMain
int main(int argc, char **argv)
{
if(shouldExit() == YES)
{
exit(exitCode);
}
return NSApplicationMain(argc, (const char **) argv);
}
[[NSRunningApplication currentApplication] terminate];
Apple docs here. You can also use forceTerminate. You could also use nall's suggestion, but it will only work if you can do the work to check if the app needs to be terminated in main(). Otherwise, you'll need to do something more along the lines of what I suggested.
If you can detect that you want to quit easily, modifying the main() function of your app is the "fastest" place:
int main(int argc, char **argv)
{
id pool = [[NSAutoreleasePool alloc] init]; //needed if shouldExit() uses Cocoa frameworks
#try {
if(shouldExit()) {
exit(0); //appropriate exit code, depending on whether this "fast" exit is normal or exceptional
}
}
#finally {
[pool drain];
}
return NSApplicationMain(argc, (const char **) argv);;
}
This should work:
[[NSApplication sharedApplication] terminate: nil];
Reference