Question

I have an NSOpenPanel with an accessoryView; in this view the user chooses a couple of radio button to change the allowed types. When the panel opens, the right files are enabled, the other disabled. Ok, good.

Now the user changes the radio buttons, the viewController of the accessoryView observe the changes in the radio button matrix and changes consequently the allowedTypes of the NSOpenPanel.

After that, following Apple documentation, it calls -validateVisibleColumns, but nothing visible changes in the panel. That is: the right files seems disabled: I can choose them but they are in grey! Another wrong effect: I select a file (enabled), change the file type, the (now wrong) file remains selected, with the OK button enabled: but this is the wrong file type! It seems that the change happens but the interface doesn't know!

My code is (selected is bound to the matrix of radio button):

- (void)observeValueForKeyPath..... 
{
    NSString *extension = (self.selected==0) ? @"txt" : @"xml";
    [thePanel setAllowedFileTypes:@[extension, [extension uppercaseString]]];
    [thePanel validateVisibleColumns];
}

I first tried to insert a call

[thePanel displayIfNeeded]

then I tried with

[thePanel contentView] setNeedsDisplay]

with no results. I also tried to implement the panel delegate method panel:shouldEnableURL:, that should be called by validateVisibleColumns: I just found that it was called just once, at the opening of NSOpenPanel.

Can someone have an idea why this happens? I tried all this with sandboxed and not-sandboxed applications, no difference. I'm developing on ML with 10.8 sdk.

Edit

By now the only way to avoid the problem is to implement panel:validateURL:error, but this is called after the user clicked 'open' and it's very bad.

Was it helpful?

Solution

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! ;)

OTHER TIPS

An implementation in swift of Eidola solution. Biggest difference is that I search for a NSBrowser (sub)class rather than a specific class name. Tested on 10.10 (not sandboxed).

private weak var panelBrowser : NSBrowser? //avoid strong reference cycle
func reloadBrowser()
{
    if let assumedBrowser = panelBrowser
    {
        assumedBrowser.reloadColumn(assumedBrowser.lastColumn)
    }
    else if let searchResult = self.locateBrowser(self.panel?.contentView as! NSView)
    {
        searchResult.reloadColumn(searchResult.lastColumn)
        self.panelBrowser = searchResult //hang on to result
    }
    else
    {
        assertionFailure("browser not found")
    }
}

//recursive search function
private func locateBrowser(view: NSView) -> NSBrowser?
{
    for subview in view.subviews as! [NSView]
    {
        if subview is NSBrowser
        {
            return subview as? NSBrowser
        }
        else if let result = locateBrowser(subview)
        {
            return result
        }
    }
    return nil
}

Edit:

Ok, so the code above will not work all the time. If it's not working and a file is selected (you can see the details/preview), then you have to reload the last to one column instead of the last column. Either reload the last two columns (make sure there are at least 2 columns) or reload all columns.

Second problem: if you reload the column, then you lose the selection. Yes, the selected files/directory will still be highlighted, but the panel will not return the correct URL's.

Now I am using this function:

func reloadBrowser()
{
    //obtain browser
    if self.panelBrowser == nil
    {
        self.panelBrowser = self.locateBrowser(self.panel?.contentView as! NSView)
    }
    assert(panelBrowser != nil, "browser not found")

    //reload browser
    let panelSelectionPatch = panelBrowser.selectionIndexPaths //otherwise the panel return the wrong urls
    if panelBrowser.lastColumn > 0
    {
        panelBrowser.reloadColumn(panelBrowser.lastColumn-1)
    }
    panelBrowser.reloadColumn(panelBrowser.lastColumn)
    panelBrowser.selectionIndexPaths = panelSelectionPatch
}

Just upgrade to xcode 6.3 (on Yosemite 10.10.3) and its ok. Apple fixed the bug (no more need Eidola Hack).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top