Apple changed NSPathControl to work with NSPathControlItem's in Yosemite.
But from where I am sitting, these new classes don't work at all. I am trying to display a custom path in my data structures, but I have similar problems with a regular file path. Is it me or is it Apple?
Here is my code:
The first snippet works as in, it will show a path. But that is about all that works.
//MARK: notifications
func selectionDidChange(notification : NSNotification)
{
if let item = notification.object as? Group
{
//get "path" components
var components : [String] = [item.title ?? "a"]
var ancestor : Group? = item.parent
while (ancestor != nil)
{
components.append(ancestor?.title ?? "b")
ancestor = ancestor?.parent
}
components.append("")
//convert to url
let path = ("MyScheme:/" + "/".join(components.reverse()))
pathControl?.URL = NSURL(string: path.stringByAddingPe
}
}
Clicking any part of the path to try to get any property out of the NSPathControlItem does not work at all. Everything returns nil.
@IBAction func select(sender : AnyObject)
{
println(sender.clickedPathItem??.title)
println(sender.clickedPathItem??.URL)
}
If I try to build a path with NSPathControlItem, I can not set any properties (title, url).
pathComponent.URL = NSURL(string: path.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!)
//let url : NSURL? = NSURL(string: "/")
//let path = NSURL(fileURLWithPath: "/")
//pathComponent.URL = path as NSURL
pathComponent.attributedTitle = NSAttributedString(string: "/atttributes")
self.pathControl?.pathItems = [pathComponent]
println(pathComponent.description)
Also NSPathControlItem is not supposed to be subclassed.
What is going on here?
edit
There is a problem with NSPathControlItem as far as I can tell. A helper function to create a NSPathControlItem.
func pathItem(title: String, imageName: String) -> NSPathControlItem
{
let item = NSPathControlItem()
item.title = title
item.image = NSImage(named: imageName)
return item
}
A test function to create NSPathControlItem's and print out their title.
var pathItems : [NSPathControlItem] = []
for title in ["a","b","c"]
{
pathItems.append(self.pathItem(title, imageName: NSImageNameFolder))
}
for item in pathItems
{
println(item.title)
}
The expected output is three lines with a, b and c. I get nil, b, nil.
If you set the pathItems on a NSPathControl directly, it will work.
self.pathControl?.pathItems = [
self.pathItem("a", imageName: NSImageNameFolder),
self.pathItem("b", imageName: NSImageNameFolder),
self.pathItem("c", imageName: NSImageNameFolder)]
However, if you set the pathItems indirectly, all goes to hell.
self.pathControl?.pathItems = pathItems //array of NSPathControl (see above)
Edit 2
I had another look at this. I configure a NSPathControlItem in the pathItem function. Here I set the title. Makes no difference if I set the attributedTitle. Inspecting the item with lldb shows the correct (attributed)title value.
But when I assign the array of NSPathControlItem's to the NSPathControl, the title has a value of "" and the attributedTitle is uninitialized.
NSPathControlItem is seriously broken. As of 10.11.3, it contains the following method, as decompiled by Hopper:
int -[NSPathControlItem release](int arg0) {
[arg0->_secretCell autorelease];
objc_assign_ivar(0x0, arg0, *_OBJC_IVAR_$_NSPathControlItem._secretCell);
rax = [[arg0 super] release];
return rax;
}
This is evidently supposed to be a standard dealloc override, which releases the ivar, zeroes it out, and calls super. But instead, for some unknown reason possibly related to illegal drugs, it's a release override. This means that the moment one of these objects is released, even in the course of some harmless manipulation, it self-destructs and becomes unusable.
I created some code to use the runtime to eliminate the bad release override and substitute in a proper dealloc override. This is barely tested and use at your own risk:
extension NSPathControl {
class func fix() {
let itemClass = NSPathControlItem.self
let superclass: AnyClass = class_getSuperclass(itemClass)
let releaseSelector = Selector("release")
let releaseMethod = class_getInstanceMethod(itemClass, releaseSelector)
let superReleaseIMP = class_getMethodImplementation(superclass, releaseSelector)
if method_getImplementation(releaseMethod) != superReleaseIMP {
method_setImplementation(releaseMethod, superReleaseIMP)
let ivars = class_copyIvarList(itemClass, nil)
var offsets: [Int] = []
var cursor = ivars
while cursor.memory != nil {
let ivar = cursor.memory
if String.fromCString(ivar_getTypeEncoding(ivar))?.hasPrefix("@") == true {
offsets.append(ivar_getOffset(ivar))
}
cursor++
}
free(ivars)
let superDeallocIMP = class_getMethodImplementation(superclass, "dealloc")
let dealloc: @convention(block) UnsafeMutablePointer<Int8> -> Void = { obj in
for offset in offsets {
let ivarPtr = UnsafeMutablePointer<COpaquePointer>(obj + offset)
let unmanaged = Unmanaged<AnyObject>.fromOpaque(ivarPtr.memory)
unmanaged.release()
ivarPtr.memory = nil
}
typealias DeallocF = @convention(c) (UnsafeMutablePointer<Int8>, Selector) -> Void
let superDeallocF = unsafeBitCast(superDeallocIMP, DeallocF.self)
superDeallocF(obj, "dealloc")
}
let deallocIMP = imp_implementationWithBlock(unsafeBitCast(dealloc, AnyObject.self))
class_addMethod(itemClass, "dealloc", deallocIMP, "v@:")
}
}
}
I think it is not just full of bugs, it is also not documented. The documentation for NSPathControlItem is missing in the AppKit Framework Reference .. so odd.
I'm getting the same results as you. This alternative that uses sender.clickedPathComponentCell() works ok for me though:
class ViewController: NSViewController {
@IBOutlet weak var pathControl: NSPathControl!
override func viewDidLoad() {
super.viewDidLoad()
pathControl.URL = NSURL(fileURLWithPath: "/System/Library/Fonts")
}
@IBAction func pathItemSelected(sender: NSPathControl) {
if let cell = sender.clickedPathComponentCell() {
println(cell.URL)
}
}
}
This correctly prints the partial paths that I clicked on.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With