Detailed Instruction on Use of Nsopenpanel

Detailed instruction on use of NSOpenPanel

Add a new file to your project (swift source file) and add this extension there

Xcode 9 • Swift 4

extension NSOpenPanel {
var selectUrl: URL? {
title = "Select Image"
allowsMultipleSelection = false
canChooseDirectories = false
canChooseFiles = true
canCreateDirectories = false
allowedFileTypes = ["jpg","png","pdf","pct", "bmp", "tiff"] // to allow only images, just comment out this line to allow any file type to be selected
return runModal() == .OK ? urls.first : nil
}
var selectUrls: [URL]? {
title = "Select Images"
allowsMultipleSelection = true
canChooseDirectories = false
canChooseFiles = true
canCreateDirectories = false
allowedFileTypes = ["jpg","png","pdf","pct", "bmp", "tiff"] // to allow only images, just comment out this line to allow any file type to be selected
return runModal() == .OK ? urls : nil
}
}

In your View Controller:

class ViewController: NSViewController {
@IBOutlet weak var imageView: NSImageView!
@IBAction func saveDocument(_ sender: NSMenuItem) {
print("SAVE")
}
@IBAction func newDocument(_ sender: NSMenuItem) {
print("NEW")
}
// connect your view controller to the first responder window adding the openDocument method
@IBAction func openDocument(_ sender: NSMenuItem) {
print("openDocument ViewController")
if let url = NSOpenPanel().selectUrl {
imageView.image = NSImage(contentsOf: url)
print("file selected:", url.path)
} else {
print("file selection was canceled")
}
}
}

Why is NSOpenPanel/NSSavePanel showing memory leak?

NSOpenPanel is a singleton, which means you always get the same instance of the object every time you use it. This means that the first time you call [NSOpenPanel openPanel], an instance of NSOpenPanel is created and not released.

This is not a leak, it's an optimisation. However, sometimes the Leaks instrument picks up such once-only instantiations as leaks because the instances are (by design) never released.

NSOpenPanel is such a widely-used and tested class that any leaks in its standard implementation are unlikely to exist.

How to display NSSavePanel in MacOS?

Is your application sandboxed? (Project > Capabilities > App Sandbox)

If so, ensure that you change "File Access" for "User Selected File" to Read/Write.

When I do that, your first snippet works fine for me.

Sample Image

swift + OS X sandboxing: treat 'NSVBOpenPanel' as a 'NSOpenPanel' :: because I need to get the sender in the delegate method

Instead of casting the sender to NSOpenPanel (which fails because the
sender is an instance of the private NSVBOpenPanel class),
or some performSelector magic, you can use the fact that
arbitrary methods and properties can be accessed on AnyObject
without casting, and the call behaves like an implicitly
unwrapped optional:

func panel(sender: AnyObject, shouldEnableURL url: NSURL) -> Bool {
let panelPrompt = sender.prompt ?? ""
// ...
return true
}

This gives the prompt for any sender object which has a prompt
property, and the empty string as a fallback. In my test it worked well
in a sandboxed environment.

See The strange behaviour of Swift's AnyObject for more details, examples, and references to the
documentation.

NSOpenPanel - Everything deprecated?

As far as I know, you can use the runModal method like shown below:

NSOpenPanel *openPanel = [[NSOpenPanel alloc] init];

if ([openPanel runModal] == NSOKButton)
{
NSString *selectedFileName = [openPanel filename];
}

Setting value for title of NSOpenPanel does not work

The title is something from the past when windows in macOS had title bars. To show text or an instruction on top of the NSOpenPanel object, use the message property:

nsOpenPanel.message = "Hello, Choose Files"

NSOpenPanel (doesn't) validateVisibleColumns

I have the exact same problem, under 10.9, non-sandboxed, and have spent the better part of this DAY trying to find a solution!

After A LOT of tinkering and drilling down through the various classes that make up the NSOpenPanel (well NSSavePanel really) I did find a way to force the underlying table to refresh itself:

id table = [[[[[[[[[[[[_openPanel contentView] subviews][4] subviews][0] subviews][0] subviews][0] subviews][7] subviews][0] subviews][1] subviews][0] subviews][0] subviews][0] subviews][2];
[table reloadData];

Of course, the best way to code this hack would be to walk down the subview list ensuring the right classes are found and eventually caching the end table view for the subsequent reloadData calls.

I know, I know, this is a very ugly kludge, however, I can not seem to find any other answer to fix the issue, other than "file a bug report". Which, from what I can see online people have been doing since 1.8! :(

EDIT:
Here is the code I am now using to make my NSOpenPanel behave correctly under 10.9:

- (id) openPanelFindTable: (NSArray*)subviews;
{
id table = nil;

for (id view in subviews) {
if ([[view className] isEqualToString: @"FI_TListView"]) {
table = view;
break;
} else {
table = [self openPanelFindTable: [view subviews]];
if (table != nil) break;
}
}

return table;
}


- (void) refreshOpenPanel
{
if (_openPanelTableHack == nil)
_openPanelTableHack = [self openPanelFindTable: [[_openPanel contentView] subviews]];
[_openPanelTableHack reloadData];
[_openPanel validateVisibleColumns];
}

This code requires two instance variables _openPanel and _openPanelTableHack to be declared in order to work. I declared _openPanel as NSOpenPanel* and _openPanelTableHack is declared as id.

Now, instead of calling [_openPanel validateVisibleColumns] I call [self refreshOpenPanel] to force the panel to update the filenames as expected. I tried caching the table view when the NSOpenPanel was created, however, it seems that once you "run" the panel the table view changes, so I have to cache it on the first update instead.

Again, this is a GIANT hack, however, I do not know how long it will take Apple to fix the issue with accessory views and the file panels, so for now, this works.

If anyone has any other solutions that are not huge kludges please share! ;)



Related Topics



Leave a reply



Submit