Searching NSMenuItem inside NSMenu recursively - cocoa

The method itemWithTitle locates a menu item within a NSMenu. However it looks only inside the first level. I cannot find a ready-to-use method that will do the same job recursively by searching inside all the nested submenus. Or, somehow equivalently, a function that swipes NSmenu's recursively.
It looks quite incredible to me that such a thing would not exist. Maybe there is some function not directly related to NSMenu that can come in handy?

Ok, since I really need to do this clumsy workaround for a number of specific reasons (beyond the scope of this post, and quite uninteresting anyway) I ended up coding it myself.
Here is the snippet:
NSMenuItem * mitem;
while (mitem) { // loop over all menu items contained in any submenu, subsubmenu, etc.
// do something with mitem ....
mitem = next_menu_item(mitem);
}
which is powered by the functions:
NSMenuItem * goto_submenu(NSMenuItem * mitem){
NSMenu * submen = [mitem submenu];
if (submen && [[submen title] isNotEqualTo:#""] && [submen numberOfItems])
return goto_submenu([submen itemAtIndex:0]);
return mitem;
};
NSMenuItem * next_menu_item(NSMenuItem * mitem){
NSMenu * menu = [mitem menu];
if ([menu indexOfItem:mitem]==([menu numberOfItems]-1)) //if is last item in submenu go to parent item
return [mitem parentItem];
return goto_submenu([menu itemAtIndex:([menu indexOfItem:mitem]+1)]);
};

#implementation NSMenu (UT)
- (NSMenuItem *)itemWithTitle:(NSString *)title recursive:(BOOL)recursive {
if (recursive) {
for (NSMenuItem *item in self.itemArray) {
if ([item.title isEqualToString:title]) {
return item;
} else if (item.submenu) {
NSMenuItem *result = [item.submenu itemWithTitle:title recursive:recursive];
if (result) {
return result;
}
}
}
return nil;
} else {
return [self itemWithTitle:title];
}
}
- (NSMenuItem *)itemWithTag:(NSInteger)tag recursive:(BOOL)recursive {
if (recursive) {
for (NSMenuItem *item in self.itemArray) {
if (item.tag == tag) {
return item;
} else if (item.submenu) {
NSMenuItem *result = [item.submenu itemWithTag:tag recursive:recursive];
if (result) {
return result;
}
}
}
return nil;
} else {
return [self itemWithTag:tag];
}
}
#end

Related

Problems implementing the NSBrowserDelegate protocol

I'm able to get my NSBrowser instance to display the correct data in the first column. When I select one of the options, however, the next column simply displays the same set of options. I have read the docs, looked at all of Apple's relevant sample code, and just about everything I could find on the internet but I simply can't figure out the correct way to implement the required methods. The data I'm supplying to the browser is an array of dictionaries. Each dictionary in turn contains a "children" key that is another array of dictionaries. And those dictionaries have their own "children" key that are also arrays of dictionaries, etc. Using JSON for descriptive purposes (objects are dictionaries, arrays are arrays), it looks like this:
data = [
{
name: 'David',
children:[
{
name: 'Sarah',
children: {...}
},
{
name: 'Kevin',
children: {...}
}
]
},
{
name: 'Mary',
children:[
{
name: 'Greg',
children: {...}
},
{
name: 'Jane',
children: {...}
}
]
}
]
So the first column should show "David" and "Mary". If "David" is selected, the next column should show "Sarah" and "Kevin", and so on.
My current implementation relies on a custom method I created that is supposed to translate the browser's index path into the corresponding NSArray level from the provided data. This method looks like:
- (NSArray *)getSelectionInBrowser:(NSBrowser *)browser
{
NSArray *selection = browserData;
NSIndexPath *path = [browser selectionIndexPath];
for (NSUInteger i = 0; i < path.length; i++) {
selection = [[selection objectAtIndex:i] objectForKey:#"children"];
}
return selection;
}
My implementation of the required NSBrowserDelegate protocol methods looks like:
- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
{
return [[self getSelectionInBrowser:sender] count];
}
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
return [[self getSelectionInBrowser:browser] count];
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
return [self getSelectionInBrowser:browser];
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
return ![item isKindOfClass:[NSMutableArray class]];
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
return nil;
}
- (void)browser:(NSBrowser *)browser willDisplayCell:(NSBrowserCell *)cell atRow:(NSInteger)row column:(NSInteger)column {
NSArray *selection = [self getSelectionInBrowser:browser];
cell.title = [[selection objectAtIndex:row] objectForKey:#"name"];
}
The first column of the NSBrowser is populated with the correct names. However, as soon as I make a selection the program crashes with the error -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 0]. After doing some debugging, the line of code it crashes on is the objectAtIndex: call in my custom getSelectionInBrowser:.
That doesn't fully surprise me because even before the crash I figured I was doing something wrong by relying on that custom method to retrieve the current selection. I imagine this work should be done within the delegate methods themselves and, when implemented correctly, the current selection should be accessible in the item variable that is provided in many of those methods. However, I couldn't get that to work. The item variable always seemed to be simply the root data object rather than reflecting the most "drilled-down" selection.
So how do I correct my implementation?
Solved it! Here is my final working code. No need for that custom getSelection... method, and a couple of the delegate methods I had were unnecessary (only used of you are NOT going with the "item-based API").
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
if (item) {
return [[item objectForKey:#"children"] count];
}
return [browserData count];
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
if (item) {
return [[item objectForKey:#"children"] objectAtIndex:index];
}
return [browserData objectAtIndex:index];
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
return [item objectForKey:#"children"] == nil;
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
return [item objectForKey:#"name"];
}
The first method is how you tell the NSBrowser the number of rows there should be. The second method is where you determine what data should be represented in a given row (index). In both cases, you must first check to see if item actually exists. If it doesn't, that's because you are at the root of the data (first column in the NSBrowser). Only when a row (or item!) in the NSBrowser gets selected will the item variable hold anything. The final method should return the string you wish to show in the given row.
Hopefully this helps people in the future.

Restrict NSTextField to only allow numbers

How do I restrict a NSTextField to allow only numbers/integers? I've found questions like this one, but they didn't help!
Try to make your own NSNumberFormatter subclass and check the input value in -isPartialStringValid:newEditingString:errorDescription: method.
#interface OnlyIntegerValueFormatter : NSNumberFormatter
#end
#implementation OnlyIntegerValueFormatter
- (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
{
if([partialString length] == 0) {
return YES;
}
NSScanner* scanner = [NSScanner scannerWithString:partialString];
if(!([scanner scanInt:0] && [scanner isAtEnd])) {
NSBeep();
return NO;
}
return YES;
}
#end
And then set this formatter to your NSTextField:
OnlyIntegerValueFormatter *formatter = [[[OnlyIntegerValueFormatter alloc] init] autorelease];
[textField setFormatter:formatter];
Swift 3 Version
import Foundation
class OnlyIntegerValueFormatter: NumberFormatter {
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
// Ability to reset your field (otherwise you can't delete the content)
// You can check if the field is empty later
if partialString.isEmpty {
return true
}
// Optional: limit input length
/*
if partialString.characters.count>3 {
return false
}
*/
// Actual check
return Int(partialString) != nil
}
}
Use:
let onlyIntFormatter = OnlyIntegerValueFormatter()
myNsTextField.formatter = onlyIntFormatter
Here's a solution with filtering. Give a delegate and an outlet to textfield and set controlTextDidChange method.
- (void)controlTextDidChange:(NSNotification *)aNotification {
NSTextField *textfield = [notification object];
NSCharacterSet *charSet = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
char *stringResult = malloc([textfield.stringValue length]);
int cpt=0;
for (int i = 0; i < [textfield.stringValue length]; i++) {
unichar c = [textfield.stringValue characterAtIndex:i];
if ([charSet characterIsMember:c]) {
stringResult[cpt]=c;
cpt++;
}
}
stringResult[cpt]='\0';
textfield.stringValue = [NSString stringWithUTF8String:stringResult];
free(stringResult);
}
Try this -
NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[textField setFormatter:formatter];
Here is a Swift version:
override func isPartialStringValid(partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>) -> Bool {
if (count(partialString.utf16)) {
return true
}
if (partialString.rangeOfCharacterFromSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet) != nil) {
NSBeep()
return false
}
return true
}
In SWIFT, I do it this way
Convert the text value to Int with Int()
Check the converted value is not less than 0
If less than 0, display error message other accept the value
if ((Int(txtField.stringValue)) < 0){
// Display error message
}
[Works with Swift 3.0.1]
As others suggested, subclass NumberFormatter and override isPartialStringValid method. The easiest way is to drop a NumberFormatter object under your NSTextField in xib/storyboard and update it's Custom Class.
Next implementation allows only integers or blank value and plays a beep if string contains illegal characters.
class IntegerFormatter: NumberFormatter {
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
// Allow blank value
if partialString.numberOfCharacters() == 0 {
return true
}
// Validate string if it's an int
if partialString.isInt() {
return true
} else {
NSBeep()
return false
}
}
}
String's numberOfCharacters() and isInt() are methods added in an extension.
extension String {
func isInt() -> Bool {
if let intValue = Int(self) {
if intValue >= 0 {
return true
}
}
return false
}
func numberOfCharacters() -> Int {
return self.characters.count
}
}
Here is the steps to create the same....
Just create the ANYCLASS(called SAMPLE) with sub classing the NSNumberFormatter ...
in .m file write the following code...
- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error {
// Make sure we clear newString and error to ensure old values aren't being used
if (newString) { *newString = nil;}
if (error) {*error = nil;}
static NSCharacterSet *nonDecimalCharacters = nil;
if (nonDecimalCharacters == nil) {
nonDecimalCharacters = [[NSCharacterSet decimalDigitCharacterSet] invertedSet] ;
}
if ([partialString length] == 0) {
return YES; // The empty string is okay (the user might just be deleting everything and starting over)
} else if ([partialString rangeOfCharacterFromSet:nonDecimalCharacters].location != NSNotFound) {
return NO; // Non-decimal characters aren't cool!
}
return YES;
}
Now.. in your Actual Class set the formatter to your NSTextField object like below...
NSTextField *mySampleTxtFld;
for this set the Formatter...
SAMPLE* formatter=[[SAMPLE alloc]init];// create SAMPLE FORMATTER OBJECT
self.mySampleTxtFld.delegate=self;
[self.mySampleTxtFld setFormatter:formatter];
Your done!!!
Swift 2.0 custom formatter with 0 instead of empty space :
class OnlyIntegerValueFormatter: NSNumberFormatter {
override func isPartialStringValid(partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>) -> Bool {
if partialString.isEmpty {
newString.memory = "0"
return false
}
if Int(partialString) < 0 {
NSBeep()
return false
} else {
return true
}
}
}
// NSTextFieldNumberFormatter+Extension.swift
import Foundation
class TextFieldIntegerValueFormatter: NumberFormatter {
var maxLength: Int
init(maxLength: Int) {
self.maxLength = maxLength
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
// Ability to reset your field (otherwise you can't delete the content)
// You can check if the field is empty later
if partialString.isEmpty {
return true
}
// Optional: limit input length
if partialString.count > maxLength {
return false
}
// Actual check
return Int(partialString) != nil
}
}
//Need to call like:
myNsTextField.formatter = TextFieldIntegerValueFormatter(maxLength: 6)

How to check that current space is Dashboard?

Sample code is
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil];
Then
- (void) activeSpaceDidChange:(NSNotification *)aNotification {
// code to check if current workspace is dashboard?
}
I want to check whether the current space is dashboard or not? Any idea?
The first think i have tried is get to the current space id according to this answer: Detecting when a space changes in Spaces in Mac OS X . The problem here is that the key kCGWindowWorkspace is deprecated in OSX 10.8. So there is no direct way to get this information.
In my solution now i check for different windows or owners which are only one the dashboard space or on all other spaces:
The user is on the dashboard if there is one window which kCGWindowName ends with .wdgt/
The user is not on the dashboard if there is one window with kCGWindowName == System Status Item Clone, kCGWindowOwnerName == SystemUIServer | Finder
So why i'm not just using the .wdgt/ check? -- Because if there is now widget on the dashboard this not working
So why i'm using more than one window check? -. Because i'm not jet sure which window is always on all spaces. At least System Status Item Clone and Finder are not always there.
Here my implementation is add this function as category to NSWorkspace
- (BOOL) userIsOnDashboardSpace {
NSArray* windowsInSpace = (__bridge NSArray *) CGWindowListCopyWindowInfo(kCGWindowListOptionAll | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger indexOfWidget = [windowsInSpace indexOfObjectPassingTest:^BOOL(NSDictionary* obj, NSUInteger idx, BOOL *stop) {
if ([obj objectForKey:(id)kCGWindowName]) {
NSString *name = (NSString *)[obj objectForKey:(id)kCGWindowName];
if ([name isEqualToString:#"System Status Item Clone"]) {
*stop = true;
return false;
}
if ([name hasSuffix:#".wdgt/"]) {
*stop = true;
return true;
}
}
if ([obj objectForKey:(id)kCGWindowOwnerName]) {
NSString *name = (NSString *)[obj objectForKey:(id)kCGWindowOwnerName];
if ([name isEqualToString:#"SystemUIServer"]) {
*stop = true;
return false;
}
if ([name isEqualToString:#"Finder"]) {
*stop = true;
return false;
}
}
return false;
}];
return indexOfWidget != NSNotFound;
}

Execute an Action when the Enter-Key is pressed in a NSTextField?

I have a small problem right now. I want to execute a method when the Enter key is pressed in a NSTextField. The user should be able to enter his data and a calculation method should be executed as soon as he hits the enter key.
You can do this by setting the text field's action. In IB, wire the text field's selector to your controller or whatever object presents the IBAction you want to use.
To set it in code, send the NSTextField a setTarget: message and a setAction: message. For example, if you're setting this on your controller object in code, and your textField outlet is called myTextField:
- (void)someAction:(id)sender
{
// do something interesting when the user hits <enter> in the text field
}
// ...
[myTextField setTarget:self];
[myTextField setAction:#selector(someAction:)];
You have to do only this
For some keys (Enter, Delete, Backspace, etc)
self.textfield.delegate = self;
and then implement this method
- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor doCommandBySelector:(SEL)commandSelector
{
NSLog(#"Selector method is (%#)", NSStringFromSelector( commandSelector ) );
if (commandSelector == #selector(insertNewline:)) {
//Do something against ENTER key
} else if (commandSelector == #selector(deleteForward:)) {
//Do something against DELETE key
} else if (commandSelector == #selector(deleteBackward:)) {
//Do something against BACKSPACE key
} else if (commandSelector == #selector(insertTab:)) {
//Do something against TAB key
} else if (commandSelector == #selector(cancelOperation:)) {
//Do something against Escape key
}
// return YES if the action was handled; otherwise NO
}
The Swift 3 / 4 / 5 version of #M.ShuaibImran's solution:
First subclass your ViewController to: NSTextFieldDelegate
class MainViewController: NSViewController, NSTextFieldDelegate {
...
}
Assign the textField delegate to the ViewController in your viewDidLoad():
self.textField.delegate = self
Include the NSTextFieldDelegate method that handles keyboard responders:
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if (commandSelector == #selector(NSResponder.insertNewline(_:))) {
// Do something against ENTER key
print("enter")
return true
} else if (commandSelector == #selector(NSResponder.deleteForward(_:))) {
// Do something against DELETE key
return true
} else if (commandSelector == #selector(NSResponder.deleteBackward(_:))) {
// Do something against BACKSPACE key
return true
} else if (commandSelector == #selector(NSResponder.insertTab(_:))) {
// Do something against TAB key
return true
} else if (commandSelector == #selector(NSResponder.cancelOperation(_:))) {
// Do something against ESCAPE key
return true
}
// return true if the action was handled; otherwise false
return false
}
In your delegate (NSTextFieldDelegate), add the following:
-(void)controlTextDidEndEditing:(NSNotification *)notification
{
// See if it was due to a return
if ( [[[notification userInfo] objectForKey:#"NSTextMovement"] intValue] == NSReturnTextMovement )
{
NSLog(#"Return was pressed!");
}
}
It's very easy and you can do it directly from UI editor
Right click the Text Feild, drag Action reference to the button as shown below in the screenshot
Now it will give you some option as shown in screen shot below, you need to select perform click
Now it should look like this
Note: The event will be raised as soon as you press Tab or Enter key. In case you want the action should only be raised when user presses the Enter key then you have to do a setting. Go to the Attribute inspector and change the Action property to Send on Enter only as shown in screen shot below
NSTextFieldDelegate's – control:textView:doCommandBySelector: is your friend.
Look for the insertNewline: command.
In Interface Builder - click on your NSTextField, go to the connections editor, drag from selector to your controller object - you're actions should come up!
Best way to do that is to bind the NSTextField value with NSString property.
For Example,define a method:
(void)setTextFieldString:(NSString *)addressString {}
add bindings:
[textField bind:#"value" toObject:self withKeyPath:#"self.textFieldString" options:nil];
Enter any text and hit the return key, setTextFieldString will be called.

How to expand and collapse parts of NSSplitView programmatically?

I want to replace RBSplitView with NSSplitView in my existing project. The application is now leopard only and I would like to replace RBSplitView with the new NSSplitView shipped with Leopard.
However, I'm missing RBSplitView's handy methods expand and collapse in NSSplitView. How can I expand and collapse parts of NSSplitView programmatically?
Simply hide the subview you want to collapse, e.g.
[aSubViewToCollapse setHidden:YES];
You might also want to implement the delegate method -(BOOL)splitView:shouldHideDividerAtIndex: to return YES to hide the divider when a collapsed.
I just got programmatic expanding and collapsing of NSSplitView to work. I've also configured my NSSplitView to expand/collapse a subview whenever the divider is double-clicked, so I wanted this to play nice with that feature (and it seems to). This is what I did:
(in this example, splitView is the NSSplitView itself, splitViewSubViewLeft is the subview I wish to expand/collapse and lastSplitViewSubViewLeftWidth is an instance variable of type CGFloat.)
// subscribe to splitView's notification of subviews resizing
// (I do this in -(void)awakeFromNib)
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(mainSplitViewWillResizeSubviewsHandler:)
name:NSSplitViewWillResizeSubviewsNotification
object:splitView
];
// this is the handler the above snippet refers to
- (void) mainSplitViewWillResizeSubviewsHandler:(id)object
{
lastSplitViewSubViewLeftWidth = [splitViewSubViewLeft frame].size.width;
}
// wire this to the UI control you wish to use to toggle the
// expanded/collapsed state of splitViewSubViewLeft
- (IBAction) toggleLeftSubView:(id)sender
{
[splitView adjustSubviews];
if ([splitView isSubviewCollapsed:splitViewSubViewLeft])
[splitView
setPosition:lastSplitViewSubViewLeftWidth
ofDividerAtIndex:0
];
else
[splitView
setPosition:[splitView minPossiblePositionOfDividerAtIndex:0]
ofDividerAtIndex:0
];
}
I tried the solution above, and found it did not work, as isSubviewCollapsed never returned YES
A combination of the suggestions yielded a result which works
if ([splitViewTop isHidden]) {
[splitViewTop setHidden:NO];
[split
setPosition:previousSplitViewHeight
ofDividerAtIndex:0];
}
else {
[splitViewTop setHidden:YES];
}
[split adjustSubviews];
In El Capitan, this did the trick for me.
splitViewItem.collapsed = YES;
After some experimenting with the suggestions this was the easiest solution I found:
-(void)toggleCollapsibleView:(ib)sender {
[collapsibleView setHidden:![splitView isSubviewCollapsed:collapsibleView]];
[splitView adjustSubviews];
}
The function is a user defined first-responder action. It is triggered by a menu-item (or keystroke).
The collapsibleView is a subview in the splitView both of which are connected in IB with their properties.
In macOS Sierra, the collapsed property is changed to isCollapsed. Is straight forward just setting the property to true or false. The following code is from my WindowController, where I have two SplitViewItems.
#IBAction func toggleMap(_ sender: Any) {
if let splitViewController = contentViewController as? NSSplitViewController {
let splitViewItem = splitViewController.splitViewItems
if splitViewItem.first!.isCollapsed {
splitViewItem.first!.isCollapsed = false
} else if splitViewItem.last!.isCollapsed {
splitViewItem.last!.isCollapsed = false
} else {
if splitViewItem.first!.isCollapsed {
splitViewItem.first!.isCollapsed = false
}
splitViewItem.last!.isCollapsed = true
}
}
}
NSSplitView actually has a private method -(void)_setSubview:(NSView *)view isCollapsed:(BOOL)collapsed that does this. Those who would like to ignore all warnings against using private methods, behold:
- (void)toggleSubview:(NSView *)view {
SEL selector = #selector(_setSubview:isCollapsed:);
NSMethodSignature *signature = [NSSplitView instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
[invocation setArgument:&view atIndex:2];
BOOL arg = ![self isSubviewCollapsed:view];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];
}
I implemented this as a category on NSSplitView. The only issue is that Xcode gives a warning about _setSubview:isCollapsed: being undeclared... I'm not really sure how to get around that.
El Capitan Update
I haven't written any code for OS X in ~2 years now so I haven't been able to verify this, but according to lemonmojo in the comments below, _setSubview:isCollapsed: was renamed in El Capitan to _setArrangedView:isCollapsed:.
In swift this works
func togglePanel() {
let splitViewItem = self.mySplitView.arrangedSubviews
if mySplitView.isSubviewCollapsed(outline.view){
splitViewItem[0].hidden = false
} else {
splitViewItem[0].hidden = true
}
call this from IBAction,
outline is an OutlineViewController with own xib and we need the view hence outline.view, keeping it simple but hope you get the idea
#IBAction func segmentAction(sender: NSSegmentedControl) {
splitVC?.togglePanel(sender.selectedSegment)
}
and
func togglePanel(segmentID: Int) {
let splitViewItem = self.mySplitView.arrangedSubviews
switch segmentID {
case segmentID:
if mySplitView.isSubviewCollapsed(splitViewItem[segmentID]) {
splitViewItem[segmentID].hidden = false
} else {
splitViewItem[segmentID].hidden = true
}
default:
break
}
}
And implement delegate
func splitView(splitView: NSSplitView, shouldHideDividerAtIndex dividerIndex: Int) -> Bool {
return true
}
And with 10.11 you might just use toggleSidebar action method.
How to toggle visibility of NSSplitView subView + hide Pane Splitter divider?
https://github.com/Dis3buted/SplitViewController
I recommend to use NSSplitViewController instead, and NSSplitViewItem.isCollapsed to control them. This just work.
let item: NSSplitViewItem = ...
item.isCollapsed = true
To make this to work properly, you have to configure split-UI components with mainly view-controllers. Otherwise, it can be broken.
You could try Brandon Walkin's BWToolKit.
The BWSplitView class has a method
- (IBAction)toggleCollapse:(id)sender;
#IBOutlet weak var horizontalSplitView: NSSplitView!
var splitViewItem : [NSView]?
var isSplitViewHidden: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// To Hide Particular Sub-View.
splitViewItem = self.horizontalSplitView.arrangedSubviews
splitViewItem?[0].isHidden = true
isSplitViewHidden = true
}
//MARK: View / Manage All Jobs Button Tapped.
#IBAction func actionManageScheduleJobsButtonTapped(_ sender: Any) {
if isSplitViewHidden == true {
isSplitViewHidden = false
splitViewItem?[0].isHidden = false
} else {
isSplitViewHidden = true
splitViewItem?[0].isHidden = true
}
}
--------- OR ----------
//MARK: View / Manage All Jobs Button Tapped.
#IBAction func actionManageScheduleJobsButtonTapped(_ sender: Any) {
if splitViewItem?[0].isHidden == true {
splitViewItem?[0].isHidden = false
} else {
splitViewItem?[0].isHidden = true
}
}

Resources