2

I have a vertical NSSplitView with a NSScrollView(NSOutlineView, source list, no header view) on the left and a WebView on the right. All the views have initial frames of NSZeroRect. Autoresizing masks are off for the NSSplitView, NSScrollView, and WebView, and the NSSplitView is made to fill the window with Auto Layout. I cannot seem to force the NSSplitView divider to be at a given position;

[sv setPosition:250 ofDividerAtIndex:0];

simply refuses to take effect. Though my use case is for an initial position, I will eventually need a solution that works at any time, since I'm not using a nib/xib file for this.

With or without the setPosition: call above, the WebView is given the entire width of the NSSplitView. If I set the initial frame of the NSScrollView to NSMakeRect(0, 0, 250, 0) it works. Of course, that's only for the initial frame, which isn't good enough (and is resistant to UI layout changes)... Everything I've found predates Auto Layout, and says you should just manipulate frames directly; since this is Auto Layout, I doubt that's a good idea...

But it gets weirder: if I set the divider style to NSSplitViewDividerStyleThin, even the NSMakeRect() is ignored and the NSScrollView is given the full width of the NSSplitView, no matter what.

So why can't I set the position of the NSSplitView?

Is it because the position is determined by Auto Layout? In that case, how do I set up a constraint to force the width of the left view to be a given width, let the layout manager run to apply that constraint, and then immediately remove the constraint? I tried overriding NSSplitView's layout and updateConstraints to do it, but Auto Layout did not really like that I did, having no effect AND crashing when I try to move the dividers again!

Or am I really supposed to manipulate the frame rects of NSSplitView subviews directly, despite what Auto Layout best practices are?

And yes, I have read every similar question here multiple times, as well as on a few other pages; all its suggestions either don't work or say setPosition: should magically work.

This is OS X 10.11, but I need a solution that goes back to 10.7.

The following Swift 2 program quickly demonstrates what I mean. You can play with the various lines of code in appLaunched() to try out the various effects. For whatever reason, the weird thing I noted above (the view on the left taking all the space if the divider style is Thin) doesn't happen here, though. That's something else I'm confused about...

Thanks.

// 3 january 2016
// scratch program 17 august 2015
import Cocoa
import WebKit

var keepAliveMainwin: NSWindow? = nil

func appLaunched() {
    let mainwin = NSWindow(
        contentRect: NSMakeRect(0, 0, 320, 240),
        styleMask: (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask),
        backing: NSBackingStoreType.Buffered,
        `defer`: true)
    let contentView = mainwin.contentView!

    let splitView = NSSplitView(frame: NSZeroRect)
    splitView.dividerStyle = NSSplitViewDividerStyle.Thin
    splitView.vertical = true
    splitView.translatesAutoresizingMaskIntoConstraints = false
    contentView.addSubview(splitView)

    let box1 = NSScrollView(frame: NSZeroRect)
    let ov = NSOutlineView(frame: NSZeroRect)
    ov.headerView = nil
    ov.selectionHighlightStyle = NSTableViewSelectionHighlightStyle.SourceList
    box1.documentView = ov
    box1.translatesAutoresizingMaskIntoConstraints = false
    splitView.addSubview(box1)

    let box2 = WebView(frame: NSZeroRect)
    box2.translatesAutoresizingMaskIntoConstraints = false
    splitView.addSubview(box2)

    let views: [String: NSView] = [
        "splitView":    splitView,
    ]
    addConstraint(contentView, "H:|-[splitView]-|", views)
    addConstraint(contentView, "V:|-[splitView]-|", views)

    splitView.setPosition(50, ofDividerAtIndex: 0)

    mainwin.cascadeTopLeftFromPoint(NSMakePoint(20, 20))
    mainwin.makeKeyAndOrderFront(mainwin)
    keepAliveMainwin = mainwin
}

func addConstraint(view: NSView, _ constraint: String, _ views: [String: NSView]) {
    let constraints = NSLayoutConstraint.constraintsWithVisualFormat(
        constraint,
        options: [],
        metrics: nil,
        views: views)
    view.addConstraints(constraints)
}

class appDelegate : NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(note: NSNotification) {
        appLaunched()
    }

    func applicationShouldTerminateAfterLastWindowClosed(app: NSApplication) -> Bool {
        return true
    }
}

func main() {
    let app = NSApplication.sharedApplication()
    app.setActivationPolicy(NSApplicationActivationPolicy.Regular)
    // NSApplication.delegate is weak; if we don't use the temporary variable, the delegate will die before it's used
    let delegate = appDelegate()
    app.delegate = delegate
    app.run()
}

main()
andlabs
  • 11,290
  • 1
  • 31
  • 52
  • 1
    On OS-X 10.10 (Swift 1.2) if you make splitView.setPosition(50, ofDividerAtIndex: 0) the last line in appLaunched() everything behaves okay. – Paul Patterson Jan 03 '16 at 21:05
  • ...that actually did work, even in my real program. I'm now suspecting that the method only takes effect if the window is visible...? Or maybe if the split view is on screen... I'll have to do some more tests. Thanks. – andlabs Jan 03 '16 at 21:56
  • It keeps working if I set the position after I hide the window after it is shown once. It does not work if I have another window already opened. It does not work if I set the position before adding the split view to a window. It does not work if I add the split view to a superview, set the position, then show the window. It does not work if I remove the split view from the window, set its position, and then add it back. **It does not work if the window is already visible when I add the splitview, unless I order the window out and back in before setting the position.** – andlabs Jan 03 '16 at 23:25
  • And weirdest of all: if I create a window, add the splitview, open the window, set the position, create another window, move the splitview, set the position to something else, and *then* open the other window, the second set position works! It does not work if I set the position before adding it to the other window. (For some reason some of these situations or some variation thereof causes the NSOutlineView turns black. Bug in NSVisualEffectView? Or is it being destroyed prematurely?) – andlabs Jan 03 '16 at 23:37
  • And this second window thing also works if I add the splitview to the second window after opening it, regardless of when I remove the splitview from the first window! So no, I have no idea what NSSplitView uses to decide if a `setPosition:` should work... – andlabs Jan 03 '16 at 23:40
  • I've no idea either. In the scenarios where it doesn't work, does prompting a re-display/re-layout have any effect? Also, in some of the cases you mention where the split view isn't on screen at the time you call ``setPosition:`` perhaps the value you pass in is simply ignored by Cocoa - if the view isn't visible then what's the point of redrawing it? Finally, in the various examples you give you might gain some insight by logging the constraints managed by the split view -``setPosition:`` may ultimately form the basis of a Cocoa-created layout constraint. – Paul Patterson Jan 03 '16 at 23:54
  • Probably for the latter bit; someone else I was talking to/showed this question to also suggested forcing a relayout. **That** worked regardless of the circumstance, so that is the answer I'm personally looking for. I don't know if it's the best way, of course... – andlabs Jan 04 '16 at 00:03
  • I'm curious to know how you forced the relayout. In my case calling the `layout` method didn't work. – Nickkk Jun 24 '16 at 21:29
  • @Nickkk Did you call `setNeedsLayout()` too? Anyway looking back ([and due to another question](http://stackoverflow.com/questions/37979445/how-do-i-properly-set-up-a-scrolling-nstableview-using-auto-layout-what-ive-tr)) I wonder if what we're doing (forcing a NSSplitView to use Auto Layout) is even right... – andlabs Jun 24 '16 at 22:00
  • No, it doesn't work. For me it seems like the only workaround is my answer below ... – Nickkk Jun 24 '16 at 23:47

2 Answers2

3

I just had the same problem and I managed to solve it by calling setPosition:ofDividerAtIndex: twice with the same arguments. Maybe it works for you too.

Nickkk
  • 2,261
  • 1
  • 25
  • 34
0

What works for me, is setting the frame of the nested view, after calling the setPosition method:

let splitWidth = 600

splitViewController.splitView.setPosition(splitWidth, ofDividerAt: 0)

if let frame = splitViewController.splitViewItems.first?.viewController.view.frame {
    splitViewController.splitViewItems.first?.viewController.view.setFrameSize(NSSize(width: splitWidth, height: frame.height))
}

This code is based on using a NSSplitViewController, so perhaps not fully applicable on the use case described in the original question.

Ely
  • 8,259
  • 1
  • 54
  • 67