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.
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
Remove Multiple Indices from Array
How to Use Unsafemutablerawpointer to Fill an Array
Does Kotlin Has Extension Class to Interface Like Swift
How to Resolve This Build Issue - Cannot Assign to Property: 'Date' Is a Get Only Property
How to Move a Rotated Scnnode in Scenekit
Using Nstimer in Swift Playground
Swiftui @Fetchrequest Core Data Changes to Relationships Don't Refresh
Smooth Transition Between 2 Scrollviews
How to Resume Audio After Interruption in Swift
How to Get All Days in Current Week in Swift
How to Use Image Literal in Xcode 13
Translucent Status Bar with No Navigation Bar
Firebase Sign Out Not Working in Swift
Swift: Does Closure Have References to Constants or Variables
Creating a Custom Scngeometry Polygon Plane with Scngeometryprimitivetype Polygon Crash/Error