I'd like a correct behaviour from the blue drag bar, and no blue rect when dragging.
Do you know where is my mistake ?
(as you can see, the blue bar is stuck in top, like in this topic :
Little circle-line bar stuck at top of NSOutlineView when rearranging using drag and drop)
import Cocoa
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate, NSPasteboardItemDataProvider {
#IBOutlet weak var outlineView: NSOutlineView!
let REORDER_PASTEBOARD_TYPE = "com.test.calques.item"
override func viewDidLoad() {
super.viewDidLoad()
//Register for the dropped object types we can accept.
outlineView.register(forDraggedTypes: [REORDER_PASTEBOARD_TYPE])
//Disable dragging items from our view to other applications.
outlineView.setDraggingSourceOperationMask(NSDragOperation(), forLocal: false)
//Enable dragging items within and into our view.
outlineView.setDraggingSourceOperationMask(NSDragOperation.every, forLocal: true)
outlineView.delegate = self;
outlineView.dataSource = self;
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
var items: [(String, NSColor)] = [
("Item 1", NSColor.black),
("Item 2", NSColor.red),
("Item 3", NSColor.red),
("Item 4", NSColor.red),
("Item 5", NSColor.red),
("Item 6", NSColor.red)];
//NSOutlineViewDataSource
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return items.count;
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
return items[index];
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let image: NSImage = NSImage(size: NSSize(width: 17, height: 17));
let calquesItem: (String, NSColor) = item as! (String, NSColor);
let path = NSBezierPath(ovalIn: CGRect(x: 2, y: 2, width: 17 - 4, height: 17 - 4));
image.lockFocus();
calquesItem.1.setFill();
path.fill();
image.unlockFocus();
let cell = outlineView.make(withIdentifier: "DataCell", owner: nil) as! NSTableCellView;
cell.textField!.stringValue = calquesItem.0;
cell.imageView!.image = image;
return cell;
}
//Drag - NSOutlineViewDataSource
var fromIndex: Int? = nil;
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
let pastBoardItem: NSPasteboardItem = NSPasteboardItem();
pastBoardItem.setDataProvider(self, forTypes: [REORDER_PASTEBOARD_TYPE]);
return pastBoardItem;
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
Swift.print("willBeginAt")
let item = draggedItems[0] as! (String, NSColor);
fromIndex = items.index(where: { (_item: (String, NSColor)) -> Bool in
return _item.0 == item.0
});
session.draggingPasteboard.setData(Data(), forType: REORDER_PASTEBOARD_TYPE)
}
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
Swift.print("acceptDrop")
if(fromIndex! != index && index != -1) {
let toIndex: Int = fromIndex! < index ? index - 1 : index;
outlineView.moveItem(at: fromIndex!, inParent: nil, to: toIndex, inParent: nil);
items.insert(items.remove(at: fromIndex!), at: toIndex);
return true;
}
return false;
}
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
if(item == nil) {
return NSDragOperation.generic;
}
return [];
}
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
Swift.print("Drag session ended")
fromIndex = nil;
}
//NSPasteboardItemDataProvider
func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String)
{
item.setString("Outline Pasteboard Item", forType: type)
}
}
From https://developer.apple.com/reference/appkit/nsoutlineview :
Each item in the outline view must be unique. In order for the
collapsed state to remain consistent between reloads the item's
pointer must remain the same and the item must maintain isEqual(_:)
sameness.
I was using Tuples (String, NSColor). And Tuples don't conform to the hashable protocol !
After switching from Tuples items to MyClass items, everything works fine !
(correct behaviour from the blue drag bar, and no blue rect when dragging)
class MyClass {
var name: String!
var color: NSColor!
init(_ _name: String, _ _color: NSColor) {
name = _name
color = _color
}
}
Related
I have an NSOutlineView, and the space between items 1 and 2 is greater than the spacing between all other items. These are group items. The outline view is very bare bones. Below is my code, and below that is an image of the issue I am referring to:
var sections: [SidebarSection] = [
SidebarSection(title: "Section 1", items: ["1", "2", "3", "4", "5"]),
SidebarSection(title: "Section 2", items: ["1", "2"]),
SidebarSection(title: "Section 3", items: ["1", "2"]),
SidebarSection(title: "Section 4", items: ["1", "2"]),
SidebarSection(title: "Section 5", items: ["1", "2"])]
private let outlineView: SidebarOutlineView = SidebarOutlineView()
extension SidebarViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is SidebarSection
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let section = item as? SidebarSection {
return section.items[index]
}
return sections[index]
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let section = item as? SidebarSection {
return section.items.count
}
return sections.count
}
}
extension SidebarViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
return 30
}
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return item is SidebarSection
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SidebarPlaylistCell"), owner: self) as! SidebarPlaylistCell
view.wantsLayer = true
view.textLabel.stringValue = (item as? SidebarSection)?.title ?? ""
view.imgView.wantsLayer = true
view.imgView.layer?.backgroundColor = NSColor.red.cgColor
return view
}
}
extension SidebarViewController {
func layout() {
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.documentView = outlineView
outlineView.headerView = nil
outlineView.delegate = self
outlineView.dataSource = self
outlineView.intercellSpacing = NSSize(width: 0, height: 0)
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
}
}
final class SidebarOutlineView: NSOutlineView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
let column: NSTableColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("col1"))
column.isEditable = false
column.minWidth = 0
addTableColumn(column)
outlineTableColumn = column
register(NSNib(nibNamed: "SidebarPlaylistCell", bundle: nil), forIdentifier: NSUserInterfaceItemIdentifier(rawValue: "SidebarPlaylistCell"))
selectionHighlightStyle = .sourceList
indentationPerLevel = 6.0
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
This behavior can be controlled by a boolean flag floatsGroupRows, which is set to true by default. So by adding:
outlineView.floatsGroupRows = false
you get the desired behavior.
Documentation can be found here:
https://developer.apple.com/documentation/appkit/nstableview/1528624-floatsgrouprows?language=swift
A Boolean value indicating whether the table view draws grouped rows as if they are floating.
Quick Test
Meaning of floatGroupRows
The effect of floatsGroupRows can be seen better in an animated graphic. During a scroll operation, the current GroupItem remains floating on top:
I want to get the selected cell from the NSOutlineView control. I found a solution here: How can I get the selected cell from a NSOutlineView?. It says
Use the delegate. willDisplayCell: is called when a cell changes its selection state.
However when I test it, I found that my willDisplayCell: is not be called.
Here's my code, it can be run normally, but the willDisplayCell: method has never been called. Where did I make a mistake? Thanks.
class TreeNode: NSObject{
var name: String = ""
private(set) var isLeaf: Bool = false
var children: [TreeNode]?
init(name: String, isLeaf: Bool){
self.name = name
self.isLeaf = isLeaf
if !isLeaf{
children = [TreeNode]()
}
}
}
class ViewController: NSViewController {
#IBOutlet weak var sourceList: NSOutlineView!
private var data = TreeNode(name: "Root", isLeaf: false)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for i in 0..<10{
let node = TreeNode(name: "name \(i)", isLeaf: i % 2 == 0)
data.children?.append(node)
}
}
}
extension ViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let item = item as? TreeNode, !item.isLeaf {
return item.children!.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return !((item as? TreeNode)?.isLeaf ?? false)
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let item = item as? TreeNode {
if item.isLeaf{
return item
}else{
return item.children![index]
}
}
return data
}
}
extension ViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, item: Any) {
print("called")
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let cell: NSTableCellView?
if let item = item as? TreeNode{
cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self) as? NSTableCellView
cell?.textField?.stringValue = item.name
}else{
cell = nil
}
return cell
}
}
I've solved this problem myself. The following code is how to get the selected cell:
func getSelectedCell() -> NSTableCellView? {
if let view = self.sourceList.rowView(atRow: self.sourceList.selectedRow, makeIfNecessary: false) {
return view.view(atColumn: self.sourceList.selectedColumn) as? NSTableCellView
}
return nil
}
Now I can access the NSTextField control by the code getSelectedCell()?.textField.
I am making a simple NSOutlineView and am having trouble with crashes and apparent non-deterministic behavior. Here's the code for my view controller.
import Cocoa
struct Hierarchy {
var name: String
var children: [String]
init(name: String, children: [String]) {
self.name = name
self.children = children
}
}
class MainViewController: NSViewController {
#IBOutlet weak var outlineView: NSOutlineView!
var data: [Hierarchy] = [Hierarchy]()
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
self.outlineView.delegate = self
self.outlineView.dataSource = self
self.data = [
Hierarchy(name: "Heading 1", children: ["Abc", "Def", "Ghi"]),
Hierarchy(name: "Heading 2", children: [String]()),
Hierarchy(name: "Heading 3", children: ["Jkl", "Mno", "Pqr"])
]
}
}
extension MainViewController: NSOutlineViewDataSource {
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let heierarchy = item as? Hierarchy {
return heierarchy.children.count
}
return self.data.count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let heierarchy = item as? Hierarchy {
return heierarchy.children[index]
}
return self.data[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let heierarchy = item as? Hierarchy {
return heierarchy.children.count > 0
}
return false
}
}
extension MainViewController: NSOutlineViewDelegate {
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
var view: NSTableCellView?
if let hierarchy = item as? Hierarchy {
view = outlineView.make(withIdentifier: "AlphaCell", owner: self) as? NSTableCellView
if let textField = view?.textField {
textField.stringValue = hierarchy.name
textField.sizeToFit()
}
} else if let hierarchyChild = item as? String {
view = outlineView.make(withIdentifier: "AlphaCell", owner: self) as? NSTableCellView
if let textField = view?.textField {
textField.stringValue = hierarchyChild
textField.sizeToFit()
}
}
return view
}
}
This will either show nothing in the Outline View, crash when trying to expand one of the headings, or open one of the headings to reveal "Heading 1", "Heading 2", "Heading 3" inside, nested.
HOWEVER. If I change from structs to classes for Hierarchy, everything works. Why is this the case?
class Hierarchy {
var name: String
var children: [String]
init(name: String, children: [String]) {
self.name = name
self.children = children
}
}
I've got a simple NSOutlineView setup in my OSX Swift project feeding from a basic array but its causing an EXC_BAD_ACCESS crash.
Having enabled zombies, it crashes with the following error:
[NSMutableIndexSet retain]: message sent to deallocated instance
Heeeelp! Here is my code:
class SidebarViewController: NSViewController, NSOutlineViewDataSource {
//MARK: Vars
#IBOutlet var sidebar : NSOutlineView?
var data : [String] = ["Assemblies", "Parts", "Customers"]
//MARK: Init
override func viewDidLoad()
{
super.viewDidLoad()
// Do view setup here.
}
//MARK: NSOutlineView Delegate / Datasource
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int
{
return data.count
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool
{
return false
}
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject
{
return data[index]
}
func outlineView(outlineView: NSOutlineView, objectValueForTableColumn tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject?
{
return item
}
}
Turns out that objectValueForTableColumn method require an obj_c object returned. So returning String in my array doesn't work. I changed this to NSString and it now works.
I'm creating a NSOutlineView. When implementing the Data Source, although I'm able to create the top hierarchy I can not implement the childHierarchy. The reason is that I can't read the item: AnyObject? which prevents me from returning the right array from the dictionary.
//MARK: NSOutlineView
var outlineTopHierarchy = ["COLLECT", "REVIEW", "PROJECTS", "AREAS"]
var outlineContents = ["COLLECT":["a","b"], "REVIEW":["c","d"],"PROJECTS":["e","f"],"AREAS":["g","h"]]
//Get the children for item
func childrenForItem (itemPassed : AnyObject?) -> Array<String>{
var childrenResult = Array<String>()
if(itemPassed == nil){ //If no item passed we return the highest level of hirarchy
childrenResult = outlineTopHierarchy
}else{
//ISSUE HERE:
//NEED TO FIND ITS TITLE to call the correct child
childrenResult = outlineContents["COLLECT"]! //FAKED, should be showing the top hierarchy item so I could return the right data
}
return childrenResult
}
//Data source
func outlineView(outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject{
return childrenForItem(item)[index]
}
func outlineView(outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool{
if(outlineView.parentForItem(item) == nil){
return true
}else{
return false
}
}
func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int{
return childrenForItem(item).count
}
func outlineView(outlineView: NSOutlineView, viewForTableColumn: NSTableColumn?, item: AnyObject) -> NSView? {
// For the groups, we just return a regular text view.
if (outlineTopHierarchy.contains(item as! String)) {
let resultTextField = outlineView.makeViewWithIdentifier("HeaderCell", owner: self) as! NSTableCellView
resultTextField.textField!.stringValue = item as! String
return resultTextField
}else{
// The cell is setup in IB. The textField and imageView outlets are properly setup.
let resultTextField = outlineView.makeViewWithIdentifier("DataCell", owner: self) as! NSTableCellView
resultTextField.textField!.stringValue = item as! String
return resultTextField
}
}
}
I used this as a reference, although it's Objective-C implemented
You need to cast the item to the correct type for your outline. Generally you'd want to use a real data model, but for your toy problem with exactly two levels in the hierarchy, this suffices:
func childrenForItem (itemPassed : AnyObject?) -> Array<String>{
if let item = itemPassed {
let item = item as! String
return outlineContents[item]!
} else {
return outlineTopHierarchy
}
}