Reputation: 10378
With auto layout enabled, auto saving divider positions by setting an autosave name for the NSSplitView in interface builder results in each divider being completely collapsed on an app restart. Disabling auto layout allows auto save works perfectly.
I have tried this in a new Xcode project as well, same result. Is this a bug, or a known incompatibility?
How could I work around this (or is there a fix to this, if it is a bug)?
Upvotes: 20
Views: 4401
Reputation: 1658
I am using an NSSplitViewController and I was only seeing the collapsed status of items not being restored (dimensions were correct). I could see that the information was being saved correctly.
Based on a variety of other answers here I created the following extension:
extension NSSplitViewController
{
public func ensureRestoreCollapsed()
{
guard let autosaveName = splitView.autosaveName else { return }
let framesKey = "NSSplitView Subview Frames \(autosaveName)"
guard let subViewFrames = UserDefaults.standard.array(forKey: framesKey) else { return }
for (i, frame) in subViewFrames.enumerated() {
guard let hidden = (frame as? String)?.components(separatedBy: ", ")[safe: 4] else {
return }
splitViewItems[safe: i]?.isCollapsed = hidden.boolValue
}
}
}
Upvotes: -1
Reputation: 1455
I found that setting Identifier
and Autosave
within a Storyboard with autolayout enabled doesn't work. However it did work for me once I set the autosaveName
programatically.
class MySplitViewController: NSSplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
splitView.autosaveName = "Please Save Me!"
}
}
Upvotes: 36
Reputation:
Depending on your case, the view might not be in a view hierarchy when you first instanciate it. If that is the case, the autosaveName
will only work if it is set AFTER the view has been added to the windows view hierarchy, so you might consider setting your autosave name in
func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
splitView.autosaveName = "mySplitViewState"
}
Upvotes: 11
Reputation: 1102
Found myself looking at the same old NSSplitView autosave issues from years back now with Mac OS 10.12 . Happily Joris' solution is still a great workaround. Here it is a tested Swift 3 extension that works fine in our current project.
Note: Since Auto Layout apparently overrides the autosave defaults after awakeFromNib in the NSSplitView restoreAutoSavePositions() needs to be called in viewDidLoad or thereabouts of the view controller to get this working.
extension NSSplitView {
/*
** unfortunately this needs to be called in the controller's viewDidAppear function as
** auto layout kicks in to override any default values after the split view's awakeFromNib
*/
func restoreAutoSavePositions() {
let key = String(format: "NSSplitView Subview Frames %@", self.autosaveName!)
let subViewFrames = UserDefaults.standard.array(forKey: key)
guard subViewFrames != nil else { return }
for (i, frame) in (subViewFrames?.enumerated())! {
if let frameString = frame as? String {
let components = frameString.components(separatedBy: ", ")
guard components.count >= 4 else { return }
var position: CGFloat = 0.0
// Manage the 'hidden state' per view
let hidden = NSString(string:components[4].lowercased()).boolValue
let subView = self.subviews[i]
subView.isHidden = hidden
// Set height (horizontal) or width (vertical)
if self.isVertical {
if let n = NumberFormatter().number(from: components[2]) {
position = CGFloat(n)
}
} else {
if let n = NumberFormatter().number(from: components[3]) {
position = CGFloat(n)
}
}
setPosition(position, ofDividerAt: i)
}
}
}
}
Upvotes: 1
Reputation: 61
For me, setting identifier + autosavename didn't work. I had to fall back on the solution provided by ElmerCat. However I slightly modified the code to avoid setting the divider position (didn't get it working). Instead, i'm modifying the view size. I also added hiding of collapsed views.
@interface NSSplitView (RestoreAutoSave)
- (void)restoreAutosavedPositions;
@end
@implementation NSSplitView (RestoreAutoSave)
- (void)restoreAutosavedPositions
{
NSString *key = [NSString stringWithFormat:@"NSSplitView Subview Frames %@", self.autosaveName];
NSArray *subviewFrames = [[NSUserDefaults standardUserDefaults] valueForKey:key];
// the last frame is skipped because I have one less divider than I have frames
for( NSInteger i = 0; i < subviewFrames.count; i++ ) {
if( i < self.subviews.count ) { // safety-check (in case number of views have been removed while dev)
// this is the saved frame data - it's an NSString
NSString *frameString = subviewFrames[i];
NSArray *components = [frameString componentsSeparatedByString:@", "];
// Manage the 'hidden state' per view
BOOL hidden = [components[4] boolValue];
NSView* subView =[self subviews][i];
[subView setHidden: hidden];
// Set height (horizontal) or width (vertical)
if( !self.vertical ) {
CGFloat height = [components[3] floatValue];
[subView setFrameSize: NSMakeSize( subView.frame.size.width, height ) ];
}
else {
CGFloat width = [components[2] floatValue];
[subView setFrameSize: NSMakeSize( width, subView.frame.size.height ) ];
}
}
}
}
Upvotes: 4
Reputation: 3155
NSSplitView
is notorious for being particularly fussy and troublesome; you sometimes have to go out of your way to make it behave properly. I knew my settings were being saved in User Defaults
- I could see them change correctly via the Terminal "Defaults read etc...
", but they weren't getting restored when the application reopened.
I solved it by manually reading the saved values and restoring the divider positions during awakeFromNib
.
Here's a Category on NSSplitView that politely asks it to please set its divider positions to their autosaved values:
@interface NSSplitView (PraxCategories)
- (void)restoreAutosavedPositions;
@end
@implementation NSSplitView (PraxCategories)
- (void)restoreAutosavedPositions {
// Yes, I know my Autosave Name; but I won't necessarily restore myself automatically.
NSString *key = [NSString stringWithFormat:@"NSSplitView Subview Frames %@", self.autosaveName];
NSArray *subviewFrames = [[NSUserDefaults standardUserDefaults] valueForKey:key];
// the last frame is skipped because I have one less divider than I have frames
for (NSInteger i=0; i < (subviewFrames.count - 1); i++ ) {
// this is the saved frame data - it's an NSString
NSString *frameString = subviewFrames[i];
NSArray *components = [frameString componentsSeparatedByString:@", "];
// only one component from the string is needed to set the position
CGFloat position;
// if I'm vertical the third component is the frame width
if (self.vertical) position = [components[2] floatValue];
// if I'm horizontal the fourth component is the frame height
else position = [components[3] floatValue];
[self setPosition:position ofDividerAtIndex:i];
}
}
@end
Then just call the method during awakeFromNib
for each NSSplitView
you wish to restore:
for (NSSplitView *splitView in @[thisSplitView, thatSplitView, anotherSplitView]) {
[splitView restoreAutosavedPositions];
}
Upvotes: 2
Reputation: 10355
I ran into this problem as well, and I found that I need to set both the identifier
and the autosaveName
values for the NSSplitView, and that they need to be set to different values.
Upvotes: 14
Reputation: 2427
I found that using NSSplitView is terrible in auto layout mode. So I wrote autolayout-based split view: https://github.com/silvansky/TwinPanelView
It can store its handle position (not fully automized).
Upvotes: 1