Cocoaにおける状態の保存と復元
単純な例では、カテゴリNSRestorableStateのrestorableStateKeyPathsで保存する変数のパスを返すだけ。 NSViewControllerのサブクラスにでも以下のようなコードを書けば良い。
+ (NSArray *)restorableStateKeyPaths
{
return [@"color"];// 実際には[]の前に@がつく。コードパーサーがバグってるので@を外した
}
これで、復帰時にプロパティcolorの内容が復元できる。
同じ動作をするものを、少し複雑に書くと。以下のようなコードになる。
@property (atomic, copy) NSColor* color;
.
.
.
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
NSData* theData=[NSArchiver archivedDataWithRootObject:self.color];
[coder encodeObject:theData forKey:@"color"];
}
- (void) restoreStateWithCoder:(NSCoder *)coder
{
[super restoreStateWithCoder:coder];
NSData* theData = [coder decodeObjectForKey:@"color"];
if (theData != nil)
{
self.color =(NSColor *)[NSUnarchiver unarchiveObjectWithData:theData];
}
}
@synthesize color = _color;
- (void) setColor:(NSColor*) inColor
{
@synchronized(self)
{
_color = [inColor copy];
}
[self invalidateRestorableState];
}
- (NSColor*) color
{
@synchronized(self)
{
return _color;
}
}
初期設定Windowやパレットの復帰は、https://www.bignerdranch.com/blog/cocoa-ui-preservation-yall/ のコードが参考になる。
Document-Baseアプリケーションで、1個のドキュメントに複数のウインドウが開けるPhotoShopのようなアプリケーションの場合は以下のようになる。少し違うが、http://mikeabdullah.net/lion-restore-multi-window-document.html も参考になる。
- (void)restoreDocumentWindowWithIdentifier:(NSString *)identifier
state:(NSCoder *)state
completionHandler:(void (^)(NSWindow *, NSError *))completionHandler
{
NSWindowController* theWindowController;
theWindowController = [self makeWindowController];
completionHandler([theWindowController window], nil);
}
- (NSWindowController*) makeWindowController
{
NSStoryboard* theStoryboard;
NSWindowController* theWindowController;
theStoryboard = [NSStoryboard storyboardWithName:@"Document"
bundle:nil];
theWindowController = [theStoryboard instantiateInitialController];
[self addWindowController:theWindowController];
return theWindowController;
}
ここまでは、アップルのドキュメントなどの通り。
Windowは動的に増える事を前提にしているので、restoreDocumentWindowWithIdentifier:〜なメソッドが用意されていた。 NSTabViewのタブやNSSplitViewのsplitが動的に増える場合にはどうするか?
これは、基本に戻り、encodeRestorableStateWithCoder:やrestoreStateWithCoder:のメソッドをオーバーライドする事になる。
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
NSMutableArray* theArray = [NSMutableArray array];
for(NSInteger i = 0; i <= [self countOfDivider]; i++)
{
CGFloat thePosition = [self positionOfDividerAtIndex:i];
[theArray addObject:@(thePosition)];
}
[coder encodeObject:theArray
forKey:@"splitSizes"];
}
- (void) restoreStateWithCoder:(NSCoder *)coder
{
[super restoreStateWithCoder:coder];
NSArray* theSplitSizes = [coder decodeObjectForKey:@"splitSizes"];
// 個数分分割して
NSInteger theSplitCount = theSplitSizes.count;
while(theSplitCount)
{
[self addLastSplitItem:nil];
theSplitCount--;
}
// 幅を揃える
for(NSInteger i = 0; i < [self countOfDivider]; i++)
{
CGFloat thePosition = [[theSplitSizes objectAtIndex:i] floatValue];
[self.splitView setPosition:thePosition
ofDividerAtIndex:i];
}
}
- (IBAction) addLastSplitItem:(id)sender
{
NSStoryboard* theStoryboard;
NSViewController* theViewController;
NSSplitViewItem* theItem;
theStoryboard = [NSStoryboard storyboardWithName:@"MainViewController" bundle:nil];
theViewController = [theStoryboard instantiateInitialController];
theItem = [NSSplitViewItem splitViewItemWithViewController:theViewController];
[self addSplitViewItem:theItem];
[self invalidateRestorableState];
}
- (NSInteger) countOfDivider
{
NSInteger theCount = self.splitViewItems.count;
return (theCount == 0 ) ? 0 : theCount - 1;
}
- (void)setPosition:(CGFloat)position ofDividerAtIndex:(NSInteger)dividerIndex
{
NSAssert( dividerIndex >= 0, nil);
NSAssert( dividerIndex < [self countOfDivider], nil);
[self.splitView setPosition:position
ofDividerAtIndex:dividerIndex];
}
- (CGFloat)positionOfDividerAtIndex:(NSInteger)dividerIndex
{
NSAssert( dividerIndex >= 0, nil);
NSAssert( dividerIndex <= [self countOfDivider], nil);
CGFloat thePosition = 0;
for(NSInteger i = 0; i <= dividerIndex; i++)
{
NSSplitViewItem* theItem = [self.splitViewItems objectAtIndex:i];
NSRect theFrame = theItem.viewController.view.frame;
thePosition += theFrame.size.height;
// 2個目のitem以降にはdividerThicknessを含める
if( i != 0)
{
thePosition += self.splitView.dividerThickness;
}
}
return thePosition;
}
じゃあね。