`NSStatusItem`, созданный из `main()`, не отображается в строке состояния системы

Я пытаюсь создать простое приложение MacOS Cocoa, используя NSStatusItem для создания интерактивного значка в строке состояния системы. Однако, когда я запускаю свое приложение, я получаю это предупреждение, и значок не отображается:

2020-03-03 14:43:11.564 Mocha_bug_example[936:39572] CGSGetActiveMenuBarDrawingStyle((CGSConnectionID)[NSApp contextID], &sCachedMenuBarDrawingStyle) returned error 268435459 on line 46 in NSStatusBarMenuBarDrawingStyle _NSStatusBarGetCachedMenuBarDrawingStyle(void)

Вот минимальный воспроизводимый пример для моего приложения:

#import <AppKit/AppKit.h>

NSStatusItem* statusItem;

int main (int argc, char* argv[]) {
        statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1];
        statusItem.button.title = @"foobar";
        statusItem.visible = YES;

        [NSApplication.sharedApplication run];
        return 0;
}

Я скомпилировал и запустил пример следующим образом:

MacBook-Air-5:Mocha ericreed$ clang -o Mocha_bug_example -framework AppKit -fobjc-arc Mocha_bug_example.m
MacBook-Air-5:Mocha ericreed$ ./Mocha_bug_example
2020-03-03 14:43:11.564 Mocha_bug_example[936:39572] CGSGetActiveMenuBarDrawingStyle((CGSConnectionID)[NSApp contextID], &sCachedMenuBarDrawingStyle) returned error 268435459 on line 46 in NSStatusBarMenuBarDrawingStyle _NSStatusBarGetCachedMenuBarDrawingStyle(void)
[Application hung until I pressed Ctrl+C]
^C
MacBook-Air-5:Mocha ericreed$ 

Примечание: отключение автоматического подсчета ссылок и добавление [statusItem release]; после вызова run в соответствии с предложенным подобным вопросом не дало видимой разницы.


person Eric Reed    schedule 03.03.2020    source источник


Ответы (3)


Это не то, что вы можете сделать в main().

За исключением чрезвычайно необычных ситуаций, вы никогда не должны изменять main(), поставляемый с шаблоном приложения, и он должен вызывать NSApplicationMain():

int main(int argc, char *argv[])
{
    // start the application
    return NSApplicationMain(argc, (const char **) argv);
}

Фреймворк Cocoa не инициализируется до тех пор, пока вы не вызовете NSApplicationMain(), и до тех пор обычно непригоден для использования.

Такую настройку следует выполнять в applicationWillFinishLaunching или applicationDidFinishLaunching.

Обновить

Оригинальный постер не использует Xcode и готов в одиночку бросить вызов пустыне. ;)

Это также означает, что их пакет приложений не будет иметь основного файла NIB, который обычно создает и подключает объект делегата приложения, главное меню и т. д.

Есть бесстрашные люди, которые бросили вызов этой территории, и вы можете прочитать об этом в Создание Приложение Cocoa без файлов NIB.

person James Bucanek    schedule 04.03.2020
comment
Имеет смысл. Итак, я должен реструктурировать свое приложение, чтобы использовать объект, следующий за NSApplicationDelegate, с этими функциями и назначить его NSApplication.sharedApplication.delegate? - person Eric Reed; 04.03.2020
comment
(Я приму ваш ответ, как только подтвержу, что это была проблема) - person Eric Reed; 04.03.2020
comment
Да, это именно то, что вы должны сделать. И если вы использовали один из стандартных шаблонов приложений Cocoa, этот код (объект делегата приложения, связанный со свойством NSApplication.delegate) уже должен был быть создан. Все, что вам нужно сделать, это добавить методы в класс делегата. - person James Bucanek; 05.03.2020
comment
Извините, я был занят. Я сделал некоторую реструктуризацию, чтобы мое приложение запускалось с использованием NSApplicationMain — мне пришлось добавить Info.plist с NSPrincipalClass. Я также создал делегата и использовал NSApplication.setDelegate:delegate, чтобы назначить его приложению. Значок приложения отображается, но функции делегата не вызываются. Вот мой репозиторий: github.com/HewwoCraziness/Mocha_bug_example (кстати, спасибо за помощь) - person Eric Reed; 09.03.2020
comment
Опять же, перестаньте пытаться делать что-то в main(). Забудьте main(). main() принадлежит Какао. Приложение Cocoa создает и подключает объект делегата приложения через основной файл NIB во время его инициализации. На этом этапе я предлагаю вам просто начать новый проект с использованием стандартного шаблона приложения Cocoa. Это все, что вам нужно, и он делает все правильно. Затем измените класс делегата, который он создает для вас. - person James Bucanek; 09.03.2020
comment
Я должен уточнить... Я не использую Xcode из-за ограничений дискового пространства и проблем с компиляцией в прошлом. Мне просто интересно, есть ли способ сделать что-то подобное без использования Xcode/Interface Builder вообще. (Может быть, сработает установка NSPrincipalClass в подкласс NSApplication и переопределение init() подкласса?) - person Eric Reed; 09.03.2020
comment
Я просто не знаю, как назначить делегата, не делая этого в main() или используя NIB из Interface Builder. - person Eric Reed; 09.03.2020
comment
Обычно вы используете основной NIB. Трудный путь создания приложения Cocoa — это… ну… сложный. Есть много деталей, но создание подкласса NSApplication, который создает и соединяет объект делегата, было бы одним решением. Ознакомьтесь с Создание приложения Cocoa без файлов NIB. Не рекомендуется, но удачи! - person James Bucanek; 09.03.2020
comment
Потрясающе, спасибо. (Не уверен, что вы хотели бы, чтобы это было в вашем ответе в качестве альтернативного варианта.) Извините за путаницу. - person Eric Reed; 09.03.2020

#import <Cocoa/Cocoa.h>

int main(){
 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
 NSApplication *application = [NSApplication sharedApplication];
 NSStatusItem* statusItem;
 statusItem = [NSStatusBar.systemStatusBar statusItemWithLength: -1];
 statusItem.button.title = @"foobar";
 statusItem.visible = YES;
 [application run];
 [pool drain];
 return 0;
}

Сохраните файл с именем «statusBar_SO.m»

Компиляция из терминала: clang statusBar_SO.m -framework Cocoa -o statusBar && ./statusBar

person apodidae    schedule 15.03.2020
comment
это именно то, что мне было нужно! см. мой отдельный ответ для быстрой версии. - person Andrew Barker; 28.04.2020
comment
Интересно... Поскольку я использую автоматический подсчет ссылок, NSAutoreleasePool в моем случае не нужен. Моя проблема могла быть связана с использованием AppKit вместо Cocoa, не уверен. Я тоже попробую. - person Eric Reed; 28.04.2020

Вот как добавить элемент строки состояния в приложение командной строки Mac OSX Coco

Адаптация ответа apodidae на Swift. Просто поместите это в файл main.swift:

let app = NSApplication()
let statusItem = NSStatusBar.system.statusItem(withLength: -1)
statusItem.button!.title = "PENIS!"
app.run()

Я не понимаю более тонких деталей NSReleasePool, включая apodidae, но у меня он работает и без этого.

person Andrew Barker    schedule 28.04.2020
comment
› Я не понимаю тонкостей NSReleasePool. В Swift это не проблема, так как NSReleasePool там не используется (но используется в objc). - person apodidae; 28.04.2020
comment
Я думаю, что вы, возможно, неправильно написали КАРАНДАШ. - person Henrique Gouveia; 14.02.2021