NSOutlineView expand / collapse animation from code - cocoa

i'm wondering how does one animate the expansion/collapse of an NSOutlineView's tree node from code ?
// this works ok but doesn't animate
NSTreeNode *node = [self.outlineView itemAtRow:self.outlineView.clickedRow];
if([self.outlineView isItemExpanded:node])
{
[self.outlineView.animator collapseItem:node];
}else{
[self.outlineView.animator expandItem:node];
}
an outline view naturally animates if you expand a node via the default-drawn arrow
so there IS a way...

My original code was OK, this just wasn't available under 10.7
Original text from Application Kit Release Notes for OS X v10.8 :
NSOutlineView
The following methods now support being animated via the -animator proxy: -expandItem:, -expandItem:expandChildren:, -collapseItem:, and -collapseItem:collapseChildren:. As an example, to animate the expansion of a particular item: [[outlineView animator] expandItem:item];

The problem is likely the node you are passing to collapseItem:. You need to pass the object your tree controller uses to represent the node rather than the actual node from your data model. If you are using NSTreeController, then you need to traverse the structure returned from -[NSTreeController arrangedObjects] to locate the node that represents your data model object.

Related

Implement a Finder-like treeview and representing its nodes with lazy loading using NSOutlineview

When implement a big file system tree in NSOutlineview, it lacks an event notification when user click the left side down arrow triangle icon of a node. This is very important when lazy-loading a large amount of file nodes into a directory node and represent it into NSOutlineview. Otherwise, developer have to load entire directory into it, in this way, the loading will force end user to wait, this is not acceptable. In short, current version of NSOutlineview can't implement lazy-loading caused by this issue. Does anyone meet this issue or have an alternative solution to implement a Finder-like treeview with Cocoa NSOutlineview, any help will be great appreciate.
In Addition:
NSOutlineview does not emit an outlineViewItemWillExpand event when click the icon, and there still lacks enough info to get which node will expand even that event emitted, [NSOutlineview selectedRow] can't work because the node which will be expanded have not been selected yet.
I load my child nodes in the following call in NSOutlineViewDataSource:
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
guard let node = item as? Node else { return false }
return node.children.count > 0
}
The children property of my Node class is lazy, and hence forced to load here.
This method is only called on Nodes that are visible in the outliner. This way you are only loading one layer ahead of the visible tree. The disclosure triangle (expand arrow) will then be visible if children exist.
If you have an efficient way to know the number of children a node has without actually loading them, then it may be better just to return the child count in outlineView(_: isItemExpandable:) and then do the actual load in outlineViewItemWillExpand(_:) as Willeke has suggested in the comments.

How to reorder the way VoiceOver reads NSTextField's on a NSTableCellView?

I have a single-column NSTableView, populated with custom NSTableCellView's, very similar to the standard Mail app of macOS. Each custom NSTableCellView has four non-selectable read-only NSTextField's. VoiceOver for Accessibility reads things left to right, top to bottom. This results in a non-optimal order for VoiceOver users.
How can I change the order in which VoiceOver reads the NSTextField's within each NSTableCellView?
All examples and documentation I saw about re-ordering VoiceOver elements is related to iOS, but unfortunately the API in macOS is still different.
The order in which an element's accessibility children get visited can be controlled via accessibilityChildrenInNavigationOrder(). There are three wrinkles:
The children need to match what the view itself is reporting as its accessibilityChildren. So for controls, this will be the control's cell.
The view for which the children are being reordered must be an accessibilityElement.
The method expects all of the children which you want to visit to be passed to it. If you leave any out, they will not be visited, and will effectively be hidden from VoiceOver.
So, if you have 4 fields which VO is reading in the order labelC, labelB, labelD, labelA but you want in a different order, you can use it like this in Swift 5:
let orderedFields = [labelA, labelB, labelC, labelD]
let orderedChildren = orderedFields.compactMap { $0?.cell }
view.setAccessibilityChildrenInNavigationOrder(orderedChildren)
view.setAccessibilityElement(true)
This quickly becomes unwieldy for views with lots of children, but in your case it should work. If you want to do this dynamically, and all of your children are NSControls with cells, you can use NSCell's tag property to sort the cells:
let children = view.accessibilityChildren() as! [NSCell]
let orderedChildren = children.sorted { $0.tag < $1.tag }
view.setAccessibilityChildrenInNavigationOrder(orderedChildren as [NSAccessibilityElementProtocol])
view.setAccessibilityElement(true)
Then just set the cell tags in Interface Builder.

Change origin of node in SceneKit Interface Builder

Does anyone know how to change the origin of a node in SceneKit Interface Builder? The origin is incorrect, but moving it simply moves the model, I want to move the origin so it's in the middle of the object :-/
Maybe I need to re-export my model with a different origin?
Reposition the node's pivot. For example,
truckNode.pivot = SCNMatrix4MakeTranslation(13.4, 12.9, 28.9)
You can also change rotation of the pivot but it appears you are interested in just the translational aspects which the above example satisfies.
Another option is to make your node a child of an otherwise empty "container node". Reposition your node within the container and then do all subsequent translations and rotations on the container node.
Another option using Interface Builder. Select your dae file. At the bottom of the editor window, on the left, is a square button with a bar on its left. Click it to open the Scene Graph. This will show a hierarchical list of the nodes in your file. In the Utilities panel to the right in your Xcode window, select the "box" icon. You can adjust a nodes position numerically there. You can also drag its axes directly in the editor. But this will not change the pivot which I think is what you want to do. You may have to create a new node to act as a container as mentioned above. This can be done by right-clicking on a node in the Scene Graph to bring up a menu. In my opinion, setting the pivot in code is easier. Details below.
BTW, you can get the names of the nodes in your dae scene from the Scene Graph in this editor. Here's how you access dae nodes in code:
guard let theTruck = SCNScene(named: "myTruck") else {
print("Couldn't find molecule in dictionary (myTruck)")
return }
let truck = theTruck.rootNode.childNodeWithName("truckBase", recursively: true)!
truck.pivot = SCNMatrix4MakeTranslation(12.0, 0, 8.0)
In this example: "myTruck" is the name of your dae file without the dae suffix. "truckBase" is the name of your root node within the dae file (you can change the name in the editor Scene Graph if desired). "truck" is a new node to which you've assigned the root node of your file. Manipulate it just as you would, and instead of, the root node of your dae. "theTruck" is a temporary holder for the dae SCNScene, used just to access the nodes within. You can access any other nodes in your dae this same way allowing you to manipulate them in code.

how Xcode automatically remove skspritenode form its parent's node

I'm new in objectif-c and sprite-kit development (but not in programming). So I'm starting the Apple's Spri. The app works perfectly, except that the nodes representing the rocks are automatically removed form its parent when rocks fall through the bottom of the scene. This, without implementing the -(void)didSimulatedPhysics method :
- (void)didSimulatedPhysics
{
[self enumerateChildNodesWithName:#"rock" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0)
[node removeFromParent];
}];
}
I can see that with the nodes counter shown on the screen : it doesn't increase with the number of rocks.
So my question is : is Xcode manage the removeFromParent method for my program ? if yes, could you tell me the option to disable. I try to sert ARC to NO, but it doesn't make any change.
I hope my message is clear. If not, please tell me. Thanks in advance for your answer.
Raphael
I can see that with the nodes counter shown on the screen : it doesn't
increase with the number of rocks.
This is because Sprite Kit counts only "rendered" nodes (ie nodes on screen) by default. To see culled nodes, you have to enable an additional, undocumented debug flag:
[self.scene.view setValue:#(YES) forKey:#"_showsCulledNodesInNodeCount"];
Alternatively, to get the true node count of a node, such as the scene, add this where you want to log the node count:
NSLog(#"node count: %u", (unsigned int)self.children.count);
In other words: if you don't remove a node from its parent, Sprite Kit will not do this for automatically under no circumstances. It will however clear up the node graph of the old scene when presenting a new scene, provided there aren't any retain cycles (commonly found when holding a strong reference to a parent or sibling node in a custom SKNode subclass).
Before you order an SKView to present a scene, you must be calling these lines:
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES; // This is what shows the node count.
From Apple's documentation, the showsNodeCount property is described as:
A Boolean value that indicates whether the view displays the count of
the nodes visible in the scene.
Hence, Sprite Kit does not remove them from their parent. If those nodes were to come back into the view's bounds, they would in turn increase this node count.

iOS - passing information between subviews within a single view controller

In my drawing app I have a switch called "Snap to grid" -- When its state is changed I need drawing in my canvas view to honor that change. At present, the switch is a subview of a UIView so that the text label and the switch can travel together during a device orientation change.
I'm trying to decide what is the best way of communicating the state change. My thought train has been comparing this to using jQuery selector, like a $("#switch") -- in essence, when the view controller has instantiated the subviews (and subviews of those subviews), how do you get a pointer to the specific subview you're looking for?
I should add that my views are created in the Interface Builder portion of Xcode, so they are loaded from a .nib file rather than me creating them (which obviously would give me a chance to hold on to a pointer to the subviews I need).

Resources