How to hide a divider of nssplitview? - cocoa

Now I want to hide or show with my condition a divider when my app run. used this delegate method:
- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
{
if (A)
return YES;
else
return NO;
}
but it didn't work , why? How to used this method? Thank you very much!

Further to #carmin’s note, above, overriding the NSSplitView dividerThickness property is the only thing that worked for me (specifically, returning NSRectZero from the splitView:effectiveRect:forDrawnRect:ofDividerAtIndex: NSSplitView delegate method — as detailed here – didn’t work and resulted in floating dividers disjointed from the views themselves).
Here’s the code in Swift:
override var dividerThickness:CGFloat
{
get { return 0.0 }
}

The split view sends that message to its delegate, to ask the delegate whether it should hide that divider. So, be the delegate, and answer the split view's question.
Be sure to check out the documentation. It's possible that that message won't accomplish what you want it to. The documentation lists everything you can do by responding to that message.

You can overload NSSplitView-dividerThickness and return 0 to hide all of the dividers. You can overload NSSplitView-drawDividerInRect: to have individual control over the dividers (choosing to allow super to draw the divider or not). These choices work even when the subviews are visible.

Here's how to do it in Obj-C that doesn't involve subclassing. Make sure that you've got the SplitView delegate in IB connected.
Then in your delegate class:
-(NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex
{
if ( [_splitView subviews][1].isHidden ==YES || [[_splitView subviews][1] frame].size.height < 50) //closed or almost closed
{
return NSZeroRect;
}
return proposedEffectiveRect;
}
- (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex
{
if ( [_splitView subviews][1].isHidden ==YES || [[_splitView subviews][1] frame].size.height < 50)
{
return YES;
}
return NO;
}
This will hide the divider when the split view is closed, but show it when it is open.
If you don't want them to be able to drag it even when its open, just cut out all the code in the first method and return only NSZeroRect. Do the same in the second method and only return YES.

For the sake of posterity, working with Swift you can call the delegate function splitView(_:effectiveRect:forDrawnRect:ofDividerAtIndex:) and just have it return an empty NSRect
override func splitView(_ splitView: NSSplitView, effectiveRect proposedEffectiveRect: NSRect, forDrawnRect drawnRect: NSRect, ofDividerAt dividerIndex: Int) -> NSRect {
if dividerIndex == 1 {
return NSRect()
}
return super.splitView(splitView, effectiveRect: proposedEffectiveRect, forDrawnRect: drawnRect, ofDividerAt: dividerIndex)
}

Use this class in Custom class of NSSplitView:
class customSplitView: NSSplitView {
override var dividerThickness: CGFloat {
return 0
}
}
For me it worked!

Related

create a subclass of NSView to enable setTag()

I am using xamarin to develop a Mac app, and somewhere in my program I want to set the tag of a NSView. However, the tag property is readonly for NSView, so I'm searching for a way to create a subclass where tag is writable. Is there any suggestion about how I should write the subclass? thanks
public class MyNSView : NSView
{
public nint _tag;
public new nint Tag
{
get
{
return _tag;
}
set
{
_tag = value;
}
}
Be aware that this is not longer the NSView Tag.
Using your custom NSView Tag
var view = new MyNSView ();
view.Tag = 100;
I found a workaround to get viewWithTag working:
#IBInspectable
override var tag: Int {
get { return super.tag }
set { super.tag = newValue }
}
In IB the NSView's Tag is still greyed out, but there is also a subclass's Tag available for setting a value.
Still, to my best effort, I can't find a logical reason for Tag to be greyed out in IB.

Drag and drop function swift OSX

This is a bit complex but I hope that someone can help me.
I am trying to build a drag and drop function for my OSX application.
This is how it is looking at the moment.
So there is just a single textfield which the user can drag and drop around the view. It is simple enough with just one textfield but if there are several textfields it is getting complicated and I don't know how to approach.
This is what I currently have:
#IBOutlet weak var test: NSTextField!
#IBAction override func mouseDragged(theEvent: NSEvent) {
NSCursor.closedHandCursor().set()
var event_location = theEvent.locationInWindow
test.frame.origin.x = event_location.x - 192
test.frame.origin.y = event_location.y
}
Test is the name of my NSTextField. I know the name of it so it is simple to move it arround. But if the user adds more textfields (see on the left pane) then I don't know how to address this textfield because I have no name of it (like "test" for the first input).
I am adding the textfields via this code:
let input = NSTextField(frame: CGRectMake(width, height, 100, 22))
self.MainView.addSubview(input)
How can I determine which textfield (if there are multiple on the view) was selected and then move the appropriate via drag and drop?
The drag and drop is working for that single static textfield
I have prepared a sample app, so consider this:
https://github.com/melifaro-/DraggableNSTextFieldSample
The idea is to introduce SelectableTextField which inherits NSTextField. SelectableTextField provides facility for subscription of interested listener on text field selection event. It has didSelectCallback block variable, where you need to set you handling code. Something like this:
textField.didSelectCallback = { (textField) in
//this peace of code will be performed once mouse down event
//was detected on the text field
self.currentTextField = textField
}
By using mentioned callback mechanism, once text field selected, we can store it in currentTextField variable. So that when mouseDragged function of ViewController is called we are aware of currentTextField and we can handle it appropriatelly. In case of sample app we need adjust currentTextField origin according drag event shift. Hope it became better now.
P.S. NSTextField is opened for inheriting from it, so you can freely use our SelectableTextField everywhere where you use NSTextField, including Interface Builder.
EDIT
I have checked out your sample. Unfortuantly I am not able to commit /create pull request into you repository, so find my suggestion here:
override func viewDidLoad() {
super.viewDidLoad()
didButtonSelectCallback = { (button) in
if let currentButton = self.currentButton {
currentButton.highlighted = !currentButton.highlighted
if currentButton == button {
self.currentButton = nil
} else {
self.currentButton = button
}
} else {
self.currentButton = button
}
button.highlighted = !button.highlighted
}
addButtonAtRandomePlace()
addButtonAtRandomePlace()
didButtonSelectCallback(button: addButtonAtRandomePlace())
}
override func mouseDragged(theEvent: NSEvent) {
guard let button = currentButton else {
return
}
NSCursor.closedHandCursor().set()
button.frame.origin.x += theEvent.deltaX
button.frame.origin.y -= theEvent.deltaY
}
private func addButtonAtRandomePlace() -> SelectableButton {
let viewWidth = self.view.bounds.size.width
let viewHeight = self.view.bounds.size.height
let x = CGFloat(rand() % Int32((viewWidth - ButtonWidth)))
let y = CGFloat(rand() % Int32((viewHeight - ButtonHeight)))
let button = SelectableButton(frame: CGRectMake(x, y, ButtonWidth, ButtonHeight))
button.setButtonType(NSButtonType.ToggleButton)
button.alignment = NSCenterTextAlignment
button.bezelStyle = NSBezelStyle.RoundedBezelStyle
button.didSelectCallback = didButtonSelectCallback
self.view.addSubview(button)
return button
}

Fullscreen NSWindow on Secondary Monitor - Swift

I have seen many posts on this in Obj C. Not too many in swift, and I just can't get it to work. I want to be able to make a window fullscreen on a specific NSScreen. The 'ToggleFullscreen' method isn't the best way because there aren't many options (for external displays). I tried:
// screen is my variable already set
outputWindow!.window!.setFrame(screen.frame, display: true, animate: true)
outputWindow!.window!.styleMask = NSFullScreenWindowMask
outputWindow!.window!.level = Int(CGShieldingWindowLevel())
// the above one doesn't make it fullscreen.
// it has a title bar and shows the menu on the screen.
// then i tried....
fullscreenWindow = NSWindow(contentRect: screen.frame, styleMask:NSBorderlessWindowMask, backing: NSBackingStoreType.Buffered, defer: false, screen: screen)
fullscreenWindow.level = Int(CGShieldingWindowLevel())
fullscreenWindow.makeKeyAndOrderFront(nil)
//that one works on my main display (somewhat). and does nothing on externals.
One thing I noticed with making my own fullscreen is that I get stuck into it. It's not like the OS X fullscreen where you can press esc to escape it. Are there any tricks to this? Thanks
I struggled with getting stuck on fullscreen as well. The reason behind that is using NSBorderlessWindowMask prevents the window from becoming key regardless of sending the makeKeyAndOrderFront: or makeKeyWindow messages to it. In Objective-C, the solution was to implement canBecomeKeyWindow in the subclassed NSWindow.
- (BOOL)canBecomeKeyWindow {
return YES;
}
Now it receives key events after going full screen as well.
This is a pseudo answer:
[in a inherited class of NSWondowController]
var fullscreen:Bool {
set {
if newValue == true {
if fullscreen == true {
return
}
frameOrig = self.window?.frame
self.window?.setFrame((NSScreen.main()?.frame)!, display: true)
}
else {
self.window?.setFrame(frameOrig!, display: true)
}
}
get {
return self.window?.frame == NSScreen.main()?.frame
}
}
I also trapped key events on both the calling-window and fullscreen window and point them to the above function so that toggling is possible. Like so:
[in some viewcontroller]:
override func keyUp(with event: NSEvent) {
switch event.keyCode {
case 53:
debugPrint("ESC - randomviewcontroller")
(randomView.window?.windowController as! RandomWindowController).fullscreen = !(RandomView.window?.windowController as! RandomWindowController).fullscreen;
break
default:
super.keyUp(with: event)
}
}
I think the above can be done much better & smarter... (picked up Swift a month ago...) but as the saying goes: "It worked on my computer!"
Hope it helps.

Cannot drag and drop on full row of NSOutlineView

I have an NSOutlineView in which Ive implemented Drag and Drop, its values are populate via bindings.
Everything is working for the logic that I want, which is basically the ability to drop only in-between root level items, and children are not allowed to be dragged or dropped anywhere. So Im just trying to implement a re-order if you will.
My issue comes into play with where on the view my NSOutlineView is accepting drops. It appears that its only allowing me to drop on the far left side of the row. I can't seem to drop anywhere else. My rows are View based, and have an image view.
This shows it working
This is it not working.
Heres my code for the acceptDrop.
- (NSDragOperation)outlineView:(NSOutlineView *)ov validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)childIndex {
// Check to see what we are proposed to be dropping on
NSTreeNode *targetNode = item;
// A target of "nil" means we are on the main root tree
if (targetNode == nil) {
//If were dropping on the physical item lets say no
if (childIndex == NSOutlineViewDropOnItemIndex) {
return NSDragOperationNone;
}
//otherwise we are in-between so lets return move.
return NSDragOperationMove;
} else {
return NSDragOperationNone;
}
}
Is it possible this has something to do with my view setup instead of my NSOutlineView code?
UPDATE:
It works on the very top row no matter where my cursor is, but only on the far left for all other rows.
This fixed it.
NSUInteger maxNumberOfRows = [[_hostController arrangedObjects] count];
// Check to see what we are proposed to be dropping on
NSTreeNode *targetNode = item;
// A target of "nil" means we are on the main root tree
if (targetNode == nil) {
//If were dropping on the physical item lets say no
if (childIndex == NSOutlineViewDropOnItemIndex) {
[ov setDropItem:nil dropChildIndex:maxNumberOfRows];
return NSDragOperationMove;
}
[ov setDropItem:nil dropChildIndex:childIndex];
return NSDragOperationMove;
} else {
//If its not a host we dont want to allow drop
if (![self isHost:[item representedObject]]) {
return NSDragOperationNone;
}
if (childIndex == NSOutlineViewDropOnItemIndex) {
[ov setDropItem:nil dropChildIndex: [ov rowForItem:item]];
return NSDragOperationMove;
}
return NSDragOperationNone;
}

Making an NSTableView behave like a WPF StackPanel

I'm trying to implement a vertical StackPanel equivalent in my MonoMac project and am having trouble figuring it out. I am not using interface builder but creating the controls programmatically (this is a restriction). I tried using a CollectionView but all items in that control are sizing to the same height.
Looking around the internet, it seems NSTableView is the way to go, but I'm not sure how to do it, especially with C# while using a MonoMac target. CollectionView was somewhat more straightforward with the CollectionViewItem.ItemPrototype allowing me to create the views I want to render. But with an NSTableView, it seems like I can only specify a data source that returns the NSObjects I want to display. How do I grab this data and then bind them to the view I want to stick in there?
I would prefer C# code but I'm at a stage where I'll accept any and all help!
I was finally able to get it working. Here is some code for anyone who wants to try it out. Basically, we need to write NSTableViewDelegates for the required functions. This implementation also doesn't cache the controls or anything. The Cocoa API documentation mentioned using an identifier to reuse the control, or something, but the identifier field is get-only in MonoMac.
I also ended up implementing my NSTableViewDelegate functions in my data-source itself which I am sure is not kosher at all, but I'm not sure what the best practice is.
Here's the data source class:
class MyTableViewDataSource : NSTableViewDataSource
{
private NSObject[] _data;
// I'm coming from an NSCollectionView, so my data is already in this format
public MyTableViewDataSource(NSObject[] data)
{
_data = data;
}
public override int GetRowCount(NSTableView tableView)
{
return _data.Length;
}
#region NSTableViewDelegate Methods
public NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, int row)
{
// MyViewClass extends NSView
MyViewClass result = tableView.MakeView("MyView", this) as MyViewClass;
if (result == null)
{
result = new MyViewClass(_data[row]);
result.Frame = new RectangleF(0, 0, tableView.Frame.Width, 100); // height doesn't matter since that is managed by GetRowHeight
result.NeedsDisplay = true;
// result.Identifier = "MyView"; // this line doesn't work because Identifier only has a getter
}
return result;
}
public float GetRowHeight(NSTableView tableView, int row)
{
float height = FigureOutHeightFromData(_data[row]); // run whatever algorithm you need to get the row's height
return height;
}
#endregion
}
And here's the snippet that programmatically creates the table:
var tableView = new NSTableView();
var dataSource = new MyTableViewDataSource();
tableView.DataSource = dataSource;
tableView.HeaderView = null; // get rid of header row
tableView.GetViewForItem = dataSource.GetViewForItem;
tableView.GetRowHeight = dataSource.GetRowHeight;
AddSubView(tableView);
So, it is not a perfect StackPanel because one needs to manually calculate row heights, but it's better than nothing.

Resources