Как назначить тип файла моему приложению на Mac

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