Как мне использовать подкласс NSDocumentController в XCode 4? - PullRequest
11 голосов
/ 10 сентября 2011

В настоящее время я пытаюсь научиться самостоятельно разрабатывать какао. С этой целью я купил отличную книгу Cocoa Recipes для Mac OS X: Vermont Recipes , в которой описывается создание примера приложения. Это довольно хорошо, но написано для XCode 3.2, а не XCode 4. До сих пор я был в состоянии преодолеть это сам, но столкнулся с проблемой, когда не могу понять, как следовать инструкции.

По сути, книга проходит через пример подкласса NSDocumentController, так что приложение может обрабатывать два (возможно, произвольных числа) различных типа документов, и поэтому оно открывает соответствующее окно для каждого типа. Итак, я создал собственный подкласс NSDocumentController (который в книге называется VRDocumentController), и теперь мне нужно сделать так, чтобы экземпляр этого контроллера загружался относительно рано в процессе запуска приложения. По сути, этот класс является одноэлементным, и поэтому мне нужно создать экземпляр моего экземпляра до того, как приложение создаст экземпляр стандартного класса, и это необходимо сделать в самом начале процесса. Достаточно справедливо.

В книге цитируется документация Apple по подклассам NSDocumentController , в которой говорится, что существует два способа решения проблемы: создать экземпляр класса в вашем файле MainMenu.xib или создать экземпляр в вашем -applicationWillFinishLaunching: метод делегата. Документация Apple не дает четких инструкций о том, как это сделать (подробнее об этом чуть позже), и книга охватывает только первую версию, которая, я думаю, вероятно, является моим предпочтительным методом.

Моя проблема: я не могу на всю жизнь справиться с этим в XCode 4. Инструкции, которые приводятся в книге для XCode 3.2, более не точны, потому что Interface Builder был теперь втянут в сам XCode и новую версию " вкладка "классы" больше не показывает классы моего проекта. Я нашел этот вопрос в Переполнении стека , задающий аналогичный вопрос, поэтому я попытался следовать принятому ответу там. Однако, когда я открываю Identity Inspector и пытаюсь набрать VRDocumentController, он просто издает звуковой сигнал и не принимает его. Ни один из других классов контроллеров, которые я написал, тоже не подходит для ввода.

Я также был бы счастлив пойти другим путем; создание копии в методе -applicationWillFinishLaunching. Но я не имею ни малейшего представления, к какому классу этот метод на самом деле относится или каков его тип возвращаемого значения. Я тоже провел нетривиальный поиск этого, но не повезло.

Ответы [ 7 ]

13 голосов
/ 10 сентября 2011

В вашем делегате приложения:

// LukeAppDelegate.h
#import "LukeAppDelegate.h"
#import "VRDocumentController"

- (void)applicationWillFinishLaunching:(NSNotification *)notification {
    VRDocumentController *dc = [[VRDocumentController alloc] init];
}

Это обеспечит создание и регистрацию экземпляра VRDocumentController в качестве контроллера общего документа, что не позволит Какао использовать значение по умолчанию NSDocumentController.

Что касается того, почему вы не смогли использовать пользовательский объект в файле пера, убедитесь, что вы выбрали Object (синий куб) вместо Object Controller (синий куб внутри зеленой сферы) при перетаскивании новогообъект в файл пера.


Редактировать: Если вы ориентируетесь на версию OS X, которая поддерживает восстановление, -applicationWillFinishLaunching: может быть слишком поздно для регистрации пользовательского контроллера документов.Если делегат приложения находится внутри MainMenu.xib, то перед его восстановлением необходимо создать экземпляр процесса загрузки пера, поэтому вы можете переместить инициализацию подкласса NSDocumentController в метод init делегата приложения:

// LukeAppDelegate.h
#import "LukeAppDelegate.h"
#import "VRDocumentController"

- (id)init {
    self = [super init];
    VRDocumentController *dc = [[VRDocumentController alloc] init];
    return self;
}
5 голосов
/ 14 февраля 2017

Вы можете легко создать подкласс в Swift 3:

class AppDelegate: NSObject {
    let docController = DocController()
}

class DocController: NSDocumentController {
}
5 голосов
/ 01 февраля 2016

Более простой способ получения пользовательского контроллера документов, созданного вовремя, заключается в реализации метода + (void) load:

Это буквально все, что вам нужно для реализации пользовательского контроллера документов.

@implementation AppDocumentController

+ (void) load
{
    [AppDocumentController new];
}

// ... your overriding goes here

@end

Это круто, потому что нет разделения кода.Не уверен на 100%, если контроллер создан слишком рано, но у меня хорошо работает.

5 голосов
/ 30 августа 2013

Ответ, помеченный как правильный (@ Bavarious'answer), не работает в приложениях на основе документов, поскольку документы часто загружаются до вызова какого-либо applicationWillFinishLaunching:.Прочитав полезные подсказки в ответе @Graham Perks, я попробовал несколько разных подходов, и это, кажется, работает надежно:

@implementation AppDelegate

- (id)init
{
    self = [super init];
    if (self) {
        MyDocumentController *dc = [[MyDocumentController alloc] init];
        if (dc) {};
    }
    return self;
}

Примечание Очевидно (если он еще не создан), вы будетесначала нужно создать приложение самостоятельно и связать его с MainMenu.XIB.

2 голосов
/ 14 января 2015

Вот решение:

// In MyDocumentController.h
@interface MyDocumentController : NSDocumentController
@end

// In MyDocumentController.m
@implementation MyDocumentController
  // ... your custom code here
@end

// In MyAppDelegate.h
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (nonatomic, strong) IBOutlet MyDocumentController *myController;
@end

Теперь перейдите в MainMenu.xib и добавьте пользовательский объект в ваше перо.Обязательно используйте инспектор для этого объекта, а на третьей панели инспектора установите для настраиваемого класса значение MyDocumentController.

Теперь подключите этот объект к розетке, нажав Ctrl, щелкнув по новому объекту слева.список вещей в перо и перетащите (пока еще удерживая нажатой клавишу Ctrl) в App Delegate.Отпустите, и он должен мигнуть и показать myController.Нажмите на это, и все готово.

Теперь вы можете проверить, что вы получаете свой пользовательский контроллер со следующим кодом:

// In MyAppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSLog(@"sharedController is %@", [NSDocumentController sharedController]);
}

Это должно напечатать что-то вроде

<MyDocumentController: 0x600000044710>

Готово!

Ключ в том, чтобы подключить ваш пользовательский MyDocumentController в MainMenu.xib.Если вы попытаетесь просто инициализировать его в applicationDidFinishLaunching, это часто слишком поздно, и AppKit уже создал и установил sharedDocumentController (из которых может быть только один).

Кроме того, выход должен быть сильным, а не слабым, потому что вашПользовательский контроллер - это объект верхнего уровня в наконечнике, на который не ссылается ничего другого в наконечнике.

Это работает для меня в нескольких версиях OS X и Xcode.

Пожалуйста, отметьте это какправильный ответ, если он работает на вас!: -)

1 голос
/ 21 ноября 2012

Если - как это иногда может случиться - ваш подкласс NSDocumentController, кажется, случайно НЕ возвращается как sharedDocumentController, добавьте точку останова в - [NSDocumentController init] и посмотрите в стеке, какой бит вашего кода (вероятно, случайно) создает регулярный NSDocumentController. Первый созданный контроллер всегда возвращается из sharedDocumentController.

Это может произойти, когда что-то в вашем XIB-файле получает init'd и (даже косвенно) вызывает sharedDocumentController. Порядок создания объектов во время загрузки файла XIB не является детерминированным, поэтому иногда ваш подкласс NSDocumentController будет создан первым, в других случаях другой вызывающий объект получает сначала init'd, а затем ваш подкласс игнорируется.

Вам придется переместить создание другого объекта на более позднюю точку (например, applicationDidFinishLaunching :). Или создайте объект одновременно, но отложите выполнение инициализации.

(Просто наткнулся на это, когда инспекторская панель все испортила, подумал, что здесь стоит заметить!)

0 голосов
/ 07 июля 2012
// This looked like the best thread to post this.
// This is incomplete skeleton example of NSDocument/NSUndoManager usage.
// Use at your own risk
// Ultimately, would love if Apple reviewed/corrected this and added correct sample code to their docs.
// I put pseudo code in implementation specific spots:
//    "... implementation specific ..."
//    "... implement something inherently dangerous... loading your data from a file"
//    "... implement something inherently dangerous... commit changes to a file ..."
//    "... in my implementation, I prompt user with save/quit options when edit window is closed ..."
//

// Apple's documentation states that NSDocumentController should *rarely* be subclassed, 
// but Apple fails to provide sample code to accomplish what you need.
// After trying to subclass NSDocumentController, I decided to rip all that code out.
// What I really needed, in my case, was this:
//    @interface NSObject(NSApplicationDelegate)
//    - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
//
// My requirements in a nutshell:
// NSUndoManager, only allow 1 document open at a time (for now), multiple editing windows for 1 document, 
// automatically save to temporary file, ability to quit and relaunch without saving,
// Custom "open", "save", "save as", "revert to saved", etc... MyDocumentController
// subclass below provides a simple way to bypass much of the NSDocumentController
// file dialogs, but still use other features of NSDocument (like track unsaved changes).
// Override only "NSDocumentController documentForURL".

/* your NSDocument subclass MUST be defined in your Info.plist.
    If you don't, Lion will complain with a NSLog message that looks like this:
  -[NSDocumentController openDocumentWithContentsOfURL:display:completionHandler:] failed during state restoration. Here's the error:
   Error Domain=NSCocoaErrorDomain Code=256 "The document “blah.myfiletype” could not be opened.  cannot open files in the “blah Document” format." 
In this example, files of type .myfiletype are associated with my NSDocument subclass
Personally, I hate having to put a class name in my Info.plist, because its not maintainable!
Note to Apple developers: if you are going to force us to do this, then pleasssse make
sure that an XCode search "In Project" for "MyDocument" finds the entry in the plist file!
Oh wow, I just answered my own question: Apple! please make "All candidate files" the
default option for search!

MyDocument:
<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>My Funky App File</string>
        <key>NSDocumentClass</key>
        <string>MyDocument</string>
        <key>CFBundleTypeExtensions</key>
        <array>
            <string>myfiletype</string>
        </array>
        <key>CFBundleTypeIconFile</key>
        <string>My_File_Icon.icns</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
    </dict>
</array>
*/
#define kMyFileTypeExtension @"myfiletype"

@interface MyDocument : NSDocument
{
}
- (void) registerForUndoGrouping;
- (NSString *)filePath; // convenience function
- (void) setFilePath: (NSString *)filePath; // convenience function

+ (BOOL) openMyDocument: (NSString *)filename; // class method
@end

extern int gWantsToQuit; // global indicator that it's time to stop drawing/updating

// track my startup state so I can control order of initialization, and so I don't
// waste time drawing/updating before data is available.
enum // MyLaunchStatus
{
   kFinishedPreWaking       = 0x01, // step 1: applicationWillFinishLaunching called
   kFinishedOpenFile        = 0x02, // step 2: (optionally) application:openFile: called (important for detecting double-click file to launch app)
   kFinishedWaking          = 0x04, // step 3: NSApp run loop ready to run. applicationDidFinishLaunching
   kFinishedPreLaunchCheck  = 0x08, // step 4: error recovery check passed
   kFinishedLoadingData     = 0x10, // step 5: data loaded
   kFinishedAndReadyToDraw  = 0x20, // step 6: run loop ready for drawing

};

typedef NSUInteger MyLaunchStatus;

#pragma mark -
@interface MyAppController : NSResponder <MidiProtocol, NSOpenSavePanelDelegate, NSTextFieldDelegate, NSWindowDelegate>
MyDocument *toDocument;
@end



#pragma mark -
@implementation MyDocument

- (id)init
{
   if ( !(self = [super init]) ) return self;
   return self;
}

- (void) registerForUndoGrouping
{


   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(beginUndoGroup:) 
                                                name:NSUndoManagerDidOpenUndoGroupNotification 
                                              object:nil]; 
}

- (void)canCloseDocumentWithDelegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
   if ( [[MyAppController instance] windowShouldClose: delegate] )
      if ( [delegate respondsToSelector:@selector(close)] )
         [delegate close];
   //[delegate performSelector:shouldCloseSelector];
   //if ( [delegate isKindOfClass:[NSWindow class]] )
   //   [delegate performClose:self]; // :self];
   return; // handled by [[MyAppController instance] windowShouldClose:(id)sender
}

- (void)shouldCloseWindowController:(NSWindowController *)windowController delegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo
{
   if ( [[MyAppController instance] windowShouldClose: [windowController window]] )
      if ( [[windowController window] respondsToSelector:@selector(close)] )
         [[windowController window] close];
}

- (void) beginUndoGroup: (NSNotification *)iNotification
{
   NSUndoManager *undoMgr = [self undoManager];
   if ( [undoMgr groupingLevel] == 1 )
   {
      // do your custom stuff here
   }
}

// convenience functions:
- (NSString *)filePath { return [[self fileURL] path]; }
- (void) setFilePath: (NSString *)filePath 
{ 
   if ( [filePath length] )
      [self setFileURL:[NSURL fileURLWithPath: filePath]];
   else
      [self setFileURL:nil];
}

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem
{
   if ( [self isDocumentEdited] && [anItem action] == @selector(revertDocumentToSaved:) )
      return YES;
   BOOL retVal = [super validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem];
   return retVal;
}

- (IBAction)revertDocumentToSaved:(id)sender
{
   NSInteger retVal = NSRunAlertPanel(@"Revert To Saved?", [NSString stringWithFormat: @"Revert to Saved File %@?", [self filePath]], @"Revert to Saved", @"Cancel", NULL);
   if ( retVal == NSAlertDefaultReturn )
      [[MyAppController instance] myOpenFile:[self filePath]];
}

+ (BOOL) openMyDocument: (NSString *)filename
{
   if ( ![[filename pathExtension] isEqualToString: kMyAppConsoleFileExtension] )
      return NO;

   // If the user started up the application by double-clicking a file, the delegate receives the application:openFile: message FIRST
   MyLaunchStatus launchStatus = [[MyAppController instance] isFinishedLaunching];
   BOOL userDoubleClickedToLaunchApp = !( launchStatus & kFinishedPreLaunchCheck );

   MyDocument *currDoc = [[MyAppController instance] document];
   NSString *currPath = [currDoc filePath];
   NSInteger retVal;
   NSLog( @"open file %@ currPath %@ launchStatus %d", filename, currPath, launchStatus );
   if ( userDoubleClickedFileToLaunchApp )
   {
      // user double-clicked a file to start MyApp
      currPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastSaveFile"];
      if ( [currPath isEqualToString: filename] )
      {
         sWasAlreadyOpen = YES;
         if ( [[[NSUserDefaults standardUserDefaults] objectForKey:@"isDocumentEdited"] boolValue] == YES )
         {
            retVal = NSRunAlertPanel(@"Open File", @"Revert to Saved?", @"Revert to Saved", @"Keep Changes", @"Quit", NULL);
            if ( retVal == NSAlertDefaultReturn )
            {
               [[MyAppController instance] myOpenFile:filename];
            }
            else if ( retVal == NSAlertOtherReturn )
               exit(0);
         }

         // proceed with normal startup
         if ( currDoc )
            return YES;
         else
            return NO;
      }
   }

   if ( !(launchStatus & kFinishedPreLaunchCheck ) ) // not done launching
      return YES; // startup in whatever state we were before

   if ( [currPath isEqualToString: filename] )
   {
      sWasAlreadyOpen = YES;
      NSLog( @"is edited %d currDoc %@", [currDoc isDocumentEdited], currDoc );
      if ( [currDoc isDocumentEdited] )
         [currDoc revertDocumentToSaved:self]; // will prompt
      else // document is already open, so do what Apple's standard action is... 
         [currDoc showWindows];
   }
   else 
   {
      if ( [currDoc isDocumentEdited] )
         retVal = NSRunAlertPanel(@"Open File", [NSString stringWithFormat: @"The current file has unsaved changes.  Discard unsaved changes and switch to file '%@'?", filename], @"Discard unsaved changes and switch to file", @"Keep Current", NULL);
      else
         retVal = NSRunAlertPanel(@"Switch to File", [NSString stringWithFormat: @"Switch to File '%@'?\n\nCurrent file '%@'", filename, currfilePath ? currfilePath : @"Untitled"], @"Switch", @"Keep Current", NULL);
      if ( retVal == NSAlertDefaultReturn )
         [[MyAppController instance] myOpenFile:filename];
   }

   // user cancelled
   if ( currDoc )
      return YES;
   else
      return NO;
}


// Note: readFromURL is here for completeness, but it should never be called,
// because we override NSDocumentController documentForURL below.
- (BOOL)readFromURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
   if ( outError )
      *outError = nil;
   if ( ![typeName isEqualToString: kMyFileTypeExtension ] ) // 
      return NO;
   return YES;
}

// Note: writeToURL is here for completeness, but it should never be called,
// because we override NSDocumentController documentForURL below.
- (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError
{
   if ( outError )
      *outError = nil;
   return YES;
}
@end


// kpk migrating slowly toward NSDocument framework 
// (currently most functionality is in MyAppController)
// Must bypass default NSDocumentController behavior to allow only 1 document
// and keep MyAppController responsible for read, write, dialogs, etc.
@implementation MyDocumentController

// this should be the only override needed to bypass NSDocument dialogs, readFromURL,
// and writeToURL calls.
// Note: To keep Lion happy, MainInfo.plist and Info.plist must define "MyDocument" for key "NSDocumentClass"
- (id)documentForURL:(NSURL *)absoluteURL
{
   MyDocument *currDoc = [[MyAppController instance] document];
   if ( [[currDoc filePath] isEqualToString: [absoluteURL path]] )
      return currDoc;
   else
      return nil;
}

@end

#pragma mark -
@implementation MyAppController
static MyAppController *sInstance;

+ (MyAppController *)instance
{
   return sInstance; // singleton... why is this not in all Apple's sample code?
}


// called by main.mm before MyAppController (or NSApp for that matter) is created.
// need to init some global variables here.
+ (void) beforeAwakeFromNib
{
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   ... implementation specific ...

   // disable fancy stuff that slows launch down
   [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey: @"NSAutomaticWindowAnimationsEnabled"];
   [pool release];
}


- (void) awakeFromNib
{
   NSLog(@"MyAppController awake\n");
   sInstance = self;

   [toWindow setNextResponder:self];
   [NSApp setDelegate:self];


   [self addWindowToDocument:toWindow];
}

- (MyDocument *)document 
{ 
   if ( !toDocument )
   {
      toDocument = [[MyDocument alloc] init];
   }
   return toDocument; 
}

- (NSUndoManager *)undoManager
{
   // !!! WARNING: there are multiple NSUndoManager's in this App

   // Note: when an editable text field is in focus, 
   // NSTextField will create 
   // a separate undo manager for editing text while that field is in focus.
   // This means that hitting undo/redo while editing a text field will not go beyond the scope of that field.

   // This will return the global undo manager if the keyWindow was registered
   // via [self addWindowToDocument:];
   // Windows which are NOT part of the document (such as preferences, popups, etc.), will
   // return their own undoManager, and undo will do nothing while those windows are in front.
   // You can't undo preferences window changes, so we don't want to surprise the user.
   NSUndoManager *undomgr =  [[NSApp keyWindow] undoManager];
   if ( undomgr )
   {
      static bool sFirstTime = true;
      if ( sFirstTime )
      {
         sFirstTime = false;
         [undomgr setLevelsOfUndo:1000]; // set some sane limit
         [[NSNotificationCenter defaultCenter] addObserver:self
                                                  selector:@selector(beginUndo:) 
                                                      name:NSUndoManagerWillUndoChangeNotification 
                                                    object:nil]; 
         [[NSNotificationCenter defaultCenter] addObserver:self
                                                  selector:@selector(beginUndo:) 
                                                      name:NSUndoManagerWillRedoChangeNotification 
                                                    object:nil];  

         [toDocument registerForUndoGrouping];
//         [[NSNotificationCenter defaultCenter] addObserver:self
//                                                  selector:@selector(endUndo:) 
//                                                      name:NSUndoManagerDidUndoChangeNotification 
//                                                    object:nil];       
//         [[NSNotificationCenter defaultCenter] addObserver:self
//                                                  selector:@selector(endUndo:) 
//                                                      name:NSUndoManagerDidRedoChangeNotification 
//                                                    object:nil];  
      }

   }
   return undomgr;
}

- (void) showStatusText: (id)iStatusText
{
  ... implementation specific ...
}

- (void) beginUndo:(id)sender
{
  // implementation specific stuff here
     NSUndoManager *undomgr =  [[NSApp keyWindow] undoManager];

   if ( [sender object] == undomgr )
   {
      if ( [undomgr isUndoing] )
         [self showStatusText: [NSString stringWithFormat:@"Undo %@", [undomgr undoActionName]]];
      else if ( [undomgr isRedoing] )
         [self showStatusText: [NSString stringWithFormat:@"Redo %@", [undomgr redoActionName]]];
   }
}

// Add a window (with a window controller) to our document, so that the window
// uses the document's NSUndoManager.  In the future, we may want to use other features of NSDocument.
- (void)addWindowToDocument:(NSWindow *)iWindow
{
   NSString *autosaveName = [iWindow frameAutosaveName]; // preserve for "mainWindow", others.
   NSWindowController *winController = [iWindow windowController];
   if ( !winController )
      winController = [[NSWindowController alloc] initWithWindow:iWindow];

   // create document if needed, and add window to document.
   [[self document] addWindowController: winController];
   if ( autosaveName )
      [iWindow setFrameAutosaveName:autosaveName]; // restore original for "mainWindow", others.
   [winController setNextResponder:self]; // keep last hotkey destination... see keyDown:

}

- (void) myOpenFile:(NSString*)path
{
  // this is just a skeleton of what I do to track unsaved changes between relaunches

   [toDocument setFilePath:path];

  ... implementation specific ...
      [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDocumentEdited"];
      [toDocument updateChangeCount:NSChangeCleared];
      [[NSUserDefaults standardUserDefaults] setObject:[toDocument filePath] forKey:@"LastSaveFile"];

      BOOL success = [[NSUserDefaults standardUserDefaults] synchronize]; // bootstrap
      // kpk very important... resetStandardUserDefaults forces the immutable
      // tree returned by dictionaryWithContentsOfFile to be mutable once re-read.
      // Apple: "Synchronizes any changes made to the shared user defaults object and releases it from memory.
      //         A subsequent invocation of standardUserDefaults creates a new shared user defaults object with the standard search list."
      [NSUserDefaults resetStandardUserDefaults];

      NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastSaveFile"]; 
}

- (void) mySaveData:(NSString*)path
{
  // this is just a skeleton of what I do to track unsaved changes between relaunches
     @try 
   {
  ... implement something inherently dangerous... commit changes to a file ...
         if ( !errorStr )
         {
            if ( [toDocument isDocumentEdited] )
            {
               // UInt64 theTimeNow = VMPGlue::GetMilliS();
               [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDocumentEdited"];
               [[NSUserDefaults standardUserDefaults] synchronize]; // bootstrap
               // DLog( @"synchronize success %d ms", (int)(VMPGlue::GetMilliS() - theTimeNow) );
            }
            // tbd consider MyDocument saveToURL:ofType:forSaveOperation:error:
            [toDocument updateChangeCount:NSChangeCleared];
         }
   @catch (...)
   {
      ... run critical alert ...
   }
}

- (void) finishLoadingData
{
   @try 
   {


     if ( dataexists )
     {
          ... implement something inherently dangerous... loading your data from a file


      [toDocument setFilePath: [[NSUserDefaults standardUserDefaults] objectForKey:@"LastSaveFile"]];
      NSNumber *num = [[NSUserDefaults standardUserDefaults] objectForKey:@"isDocumentEdited"];
      if ( [num boolValue] == YES )
         [toDocument updateChangeCount:NSChangeDone];
     }
     else
     {
        [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"isDocumentEdited"];
        [toDocument updateChangeCount:NSChangeCleared];
     }

     sFinishedLaunching |= kFinishedLoadingData;
   }
   @catch (...)
   {
      // !!! will not return !!!
      ... run critical alert ...
      // !!! will not return !!!
   }
}
#pragma mark NSApplication delegate

// Apple: Sent directly by theApplication to the delegate. The method should open the file filename, 
//returning YES if the file is successfully opened, and NO otherwise. 
//If the user started up the application by double-clicking a file, the delegate receives the application:openFile: message before receiving applicationDidFinishLaunching:. 
//(applicationWillFinishLaunching: is sent before application:openFile:.)
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
   BOOL didOpen = [MyDocument openMyDocument: filename];
   sFinishedLaunching |= kFinishedOpenFile;
   return didOpen;
}

// NSApplication notification
- (void) applicationDidFinishLaunching:(NSNotification*)note
{
   // kpk note: currentEvent is often nil at this point! [[NSApp currentEvent] modifierFlags]
   CGEventFlags modifierFlags = CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState);

   sFinishedLaunching |= kFinishedWaking;
   if ( modifierFlags & (kCGEventFlagMaskShift | kCGEventFlagMaskCommand) )
   {
      ... implementation specific ... alert: @"Shift or Command key held down at startup.\nWhat would you like to do?" 
                           title: @"Startup Options"
                     canContinue: @"Continue" ];
   }
   sFinishedLaunching |= kFinishedPreLaunchCheck;
   [self finishLoadingData];
   sFinishedLaunching |= kFinishedAndReadyToDraw;   
}

- (BOOL)windowShouldClose:(id)sender
{
   if ( [sender isKindOfClass: [NSWindow class]] && sender != toWindow )
      return YES; // allow non-document-edit windows to close normally

   ... in my implementation, I prompt user with save/quit options when edit window is closed ...
   return NO;
}

- (NSApplicationTerminateReply) applicationShouldTerminate:(NSApplication*)sender
{
   if ( !gWantsToQuit && [toDocument isDocumentEdited] )
   {
      if ( ![self windowShouldClose:self] )
         return NSTerminateCancel;

   }
   return NSTerminateNow;
}

- (void) applicationWillTerminate:(NSNotification *)notification
{
   if ( gWantsToQuit )
   {
      ... implementation specific ... dont save potentially wonky data if relaunch is required
   }
   else
   {  
      [self saveData: [toDocument filePath]];
   }
}
@end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...