#objective-c #xcode #macos
Вопрос:
Я пытаюсь назначить тип файла своему приложению.
В Info.plist я добавляю:
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>type</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>icon</string>
<key>CFBundleTypeName</key>
<string>My Project</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<false/>
</dict>
</array>
В Main.mm:
....
-(BOOL) application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(@"Opened by file");
return YES;
}
@end
int main(int argc, char* argv[]) {
[NSApplication sharedApplication];
[[[[Window alloc] init] autorelease] makeMainWindow];
[NSApp run];
return 0;
}
Но когда я пытаюсь дважды щелкнуть по типу файла «Мой», приложение открывается только с предупреждением: не удалось открыть, MyApp не может открыть файл в формате. Также сообщение из NSLog вообще не вызывается.
Комментарии:
1.Можем ли мы увидеть
@implementation
линию, которую вы отрезали от Main.mm? Например, как называется класс, реализующий метод-application:openFile:
<NSApplicationDelegate>
делегата? Вам нужно будет назначить экземпляр этого класса делегатом приложения после завершения вызова[NSApplication sharedApplication]
.2.
@interface Window : NSWindow {
Приложение работает без делегированного метода. Я попробовал-(BOOL) application: (NSApplication*)sharedApplication openFile:(NSString*) filename
, но то же самое, сообщение из NSLog вообще не вызывается.3. По-видимому, приложение не работает без делегированного метода. Рекомендация (еще раз): Следуйте потоку какао.
4. Нет, приложение работает без делегированного метода. Метод делегирования-это просто другой способ, который должен изменить больше других в проекте. Поэтому я предпочитаю им не пользоваться.
5. Попробуйте добавить CFBundleIdentifier и установите для него значение com.yourName. Мое приложение. Если ваши файлы имеют уникальное расширение, я бы также добавил расширения CFBundleTypeExtensions. Также может потребоваться CFBundleName, которого у вас в настоящее время нет.
Ответ №1:
В коде, который вы опубликовали, есть несколько проблем, но я смог добиться желаемого поведения с помощью нескольких изменений.
Я предполагаю, что это ваш оконный интерфейс и реализация:
@interface Window : NSWindow <NSApplicationDelegate>
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;
@end
@implementation Window
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(@"Opened by file");
return YES;
}
@end
Это чрезвычайно странно-использовать объект window в качестве делегата приложения. Обычно у вас есть объект контроллера, который владеет окном и управляет им, а также выступает в качестве делегата приложения.
В любом случае, его еще можно достать… ну, функциональное поведение, изменив main()
функцию на следующую:
int main(int argc, const char * argv[]) {
[NSApplication sharedApplication];
Window *window = [[[Window alloc] init] autorelease];
NSApp.delegate = window;
[window makeKeyAndOrderFront:nil];
[NSApp run];
return 0;
}
Есть два заметных изменения. Во-первых, ваша проблема заключалась в том, что вы не установили экземпляр окна в качестве делегата приложения. Во-вторых, IIRC, вы никогда не должны вызывать -makeMainWindow
напрямую; скорее, этот метод существует, чтобы вы могли переопределить его, если хотите. Если вы хотите отобразить окно на экране, вы звоните -makeKeyAndOrderFront:
.
При открытии файла должна отображаться строка входа в консоль (если вы используете Xcode 12.5.1, измените размер окна журнала, если это необходимо, чтобы устранить ошибку отображения).
При ручном подсчете ссылок я полагаю, что это приведет к утечке памяти, так как пул автозапуска не создается, но я не видел никаких обычных предупреждений в консоли. В любом случае, хотя этот код работает, он приводит к довольно нежелательному сценарию. В приложении нет главного меню, поэтому, чтобы выйти из него, вам нужно воспользоваться док-станцией. Созданное окно также крошечное и не имеет возможностей изменения размера и т. Д.
ИЗМЕНИТЬ: Пример проекта находится на https://github.com/NSGod/OpenFile.
Комментарии:
1. Можно создавать полнофункциональные приложения без NSDelegate; Я обычно использую NSApplicationDelegate только потому, что это соглашение Apple, но оно не является обязательным. Однако я не уверен, что вы ответили на первоначальный вопрос, который, как я понимаю, заключается в том, что у пользователя возникают проблемы с открытием файлов, созданных им с помощью своего приложения. На мой взгляд, это потому, что он неправильно настроил info.plist.
2. Пожалуйста, вы можете привести мне рабочий пример OpenFile без NSDelegate? Я протестировал метод NSDelegate, и он работает, так что с моей информацией.plist все в порядке. Проблема в том, что я должен изменить весь проект.
3. NSApplicationDelegate-это набор методов, которые могут реализовать делегаты объектов NSApplication. Добавление <NSApplicationDelegate> за подклассом NSWindow и очень мало накладных расходов. Я не понимаю, почему вы говорите, что это приведет к тому, что вам придется изменить весь проект?? Я боюсь, что без использования этого делегата вы не сможете использовать приложение method -(BOOL): OpenFile, которое позволяет открывать файлы в вашем приложении двойным щелчком по ним. Это очень маленькая цена, которую нужно заплатить, и это не весь класс.
4. @apodidae: Мне кажется, что Info.plist подходит, если он/она хочет открыть файл, используя такие
<NSApplicationDelegate>
методы, как . Другой способ-указать пользовательскийNSDocument
подкласс в Info.plist для типа файла и позволить архитектуре NSDocument обрабатывать открытие файлов за вас. Это вызовет методы вNSDocumentController
их подклассе или пользовательском подклассе. Использование архитектуры документа, по-видимому, выходит за рамки первоначального вопроса.5. @NSGod Правильно; список plist действительно необходимо изменить для проектов на основе документов, которыми этот не является. Вот почему я пошел по пути плиста, чтобы начать с того, к чему я привык. Однако в обычном проекте, похоже, мы можем обойтись без изменения списка, что было для меня новостью. Это первый раз, когда я использовал приложение -(BOOL): OpenFile и был удивлен, насколько хорошо оно работало.
Ответ №2:
Следующий подкласс NSWindow должен позволить вам сохранить файл с уникальным расширением». jaf», а затем повторно открыть файл в приложении, дважды щелкнув по нему. Список info.plist не так важен, как я изначально думал; Я не изменял список, созданный Xcode. Наиболее важным для этого приложения, не основанного на документах, по-видимому, является вызов метода NSApplicationDelegate -(BOOL) приложения: OpenFile. NSApplicationDelegate был добавлен в подкласс NSWindow вместо отдельного AppDelegate, как это обычно бывает. При правильной работе вы должны услышать звуковой сигнал, когда этот метод вызывается после двойного щелчка по файлу .jaf; Я не смог найти вывод NSLog. Чтобы запустить демонстрационную версию в Xcode, сначала создайте проект objc, удалите все в файле main.m и скопируйте/вставьте в него следующий исходный код. Удалите предварительно предоставленный класс AppDelegate, чтобы избежать дублирования символов. В разделе права установите для песочницы приложения значение НЕТ и установите значение только для чтения равным нулю. После создания приложения JAF используйте его для сохранения файла на рабочем столе с расширением.jaf. Затем сделайте копию приложения (показано в Finder) и скопируйте/вставьте его в папку Приложений. Следующий шаг имеет решающее значение; щелкните правой кнопкой мыши на только что созданном файле и используйте либо «Получить информацию», либо «Открыть с помощью», чтобы файл всегда открывался в вашем новом приложении. На этом этапе вы сможете дважды щелкнуть файл xxxx.jaf и открыть его в своем приложении с помощью звукового сигнала.
#import <Cocoa/Cocoa.h>
@interface Window : NSWindow <NSApplicationDelegate> {
NSTextView *txtView;
}
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag;
-(void) buildMenu;
-(void) openAction;
-(void) saveAction;
@end
@implementation Window
#define _wndW 700
#define _wndH 550
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
NSLog(@"This comes from JAF : filename = %@.",filename);
NSBeep(); // Listen for this.
NSError *error;
NSURL *url = [NSURL fileURLWithPath:filename];
NSString *fileStr = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:amp;error];
if (!fileStr) {
NSLog(@"Unable to open file %@", error);
} else {
[txtView setString:fileStr];
}
return YES;
}
-(void) buildMenu {
// **** Menu Bar **** //
NSMenu *menubar = [NSMenu new];
[NSApp setMainMenu:menubar];
// **** App Menu **** //
NSMenuItem *appMenuItem = [NSMenuItem new];
NSMenu *appMenu = [NSMenu new];
[appMenu addItemWithTitle: @"Quit" action:@selector(terminate:) keyEquivalent:@"q"];
[appMenuItem setSubmenu:appMenu];
[menubar addItem:appMenuItem];
}
-(void) openAction {
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setAllowedFileTypes:[NSArray arrayWithObjects: @"jaf", @"txt", nil]];
[op beginSheetModalForWindow: self completionHandler: ^(NSInteger returnCode) {
if (returnCode == NSModalResponseOK) {
NSURL *url = [op URL];
NSError *error;
NSString *fileStr = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:amp;error];
if (!fileStr) {
NSLog(@"Unable to open file %@", error);
} else {
[self->txtView setString:fileStr];
}
}
}];
}
-(void) saveAction {
NSSavePanel *sp = [NSSavePanel savePanel];
[sp setTitle:@"Save contents to file"];
[sp setAllowedFileTypes:[NSArray arrayWithObjects: @"jaf", nil]];
[sp setNameFieldStringValue: @".jaf"];
[sp beginSheetModalForWindow: self completionHandler: ^(NSInteger returnCode) {
if (returnCode == NSModalResponseOK) {
NSURL *url = [sp URL];
NSString *viewStr = [[self->txtView textStorage] string];
NSError *err;
BOOL fileSaved = [viewStr writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:amp;err];
if (!fileSaved) { NSLog(@"Unable to save file due to error: %@", err);}
}
}];
}
- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag {
self = [super initWithContentRect:NSMakeRect(0, 0, _wndW, _wndH) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO];
[self setTitle: @"Test window"];
[self center];
[self makeKeyAndOrderFront: nil];
// ****** NSTextView with Scroll ****** //
NSScrollView *scrlView = [[NSScrollView alloc] initWithFrame:NSMakeRect( 10, 10, _wndW - 20, _wndH - 80 )];
[[self contentView] addSubview:scrlView];
[scrlView setHasVerticalScroller: YES];
[scrlView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable ];
txtView = [[NSTextView alloc] initWithFrame:NSMakeRect( 0, 0, _wndW - 20, _wndH - 80 )];
[scrlView setDocumentView: txtView];
// **** Open Button **** //
NSButton *openBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 30, _wndH - 50, 95, 30 )];
[openBtn setBezelStyle:NSBezelStyleRounded ];
[openBtn setTitle: @"Open"];
[openBtn setAutoresizingMask: NSViewMinYMargin];
[openBtn setAction: @selector (openAction)];
[[self contentView] addSubview: openBtn];
// **** Save Button **** //
NSButton *saveBtn =[[NSButton alloc]initWithFrame:NSMakeRect( 130, _wndH - 50, 95, 30 )];
[saveBtn setBezelStyle:NSBezelStyleRounded ];
[saveBtn setTitle: @"Save"];
[saveBtn setAutoresizingMask: NSViewMinYMargin];
[saveBtn setAction: @selector (saveAction)];
[[self contentView] addSubview: saveBtn];
return self;
}
- (BOOL)windowShouldClose:(id)sender {
[NSApp terminate:sender];
return YES;
}
@end
int main() {
NSApplication *application = [NSApplication sharedApplication];
Window *window = [[Window alloc]init];
[window buildMenu];
[application setDelegate:window];
[application activateIgnoringOtherApps:YES];
[NSApp run];
return 0;
}