Home > Mobile >  How to enable "Find next" when Find bar within NSTextView is non-empty and has focus
How to enable "Find next" when Find bar within NSTextView is non-empty and has focus

Time:05-22

I've got a simple, document-based, text-viewer MacOS app working. Each window's view has nothing more than a MyTextView (a subclassed NSTextView) in it. MyTextView has only two methods in it, in order to see what's happening with the Find bar and Find-related menu items:

class MyTextView : NSTextView
{
  override func performFindPanelAction(_ sender: Any?)
  {
    let menuItem = sender as! NSMenuItem
    Swift.print("performFindPanelAction: tag = \(menuItem.tag)")
    
    if menuItem.tag == 2 {     // "Find next" menu command tag
        }
    super.performFindPanelAction(sender)
  }

  override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool
  {
    Swift.print("validateMenuItem: tag = \(menuItem.tag)")
    
    return super.validateMenuItem(menuItem)
  }
}

So when the user types Command-F (keyboard equivalent for the "Find ..." command, with tag = 1), the Find bar pops open at the top of the scrolling MyTextView with focus changing to its search text entry field. And the terminal shows:

validateMenuItem: tag = 0
validateMenuItem: tag = 1
performFindPanelAction: tag = 1

So far so good. The user types in a search text, and hits Return. Nothing prints on the terminal (i.e., performFindPanelAction() is not called), but the search occurs and the text view scrolls to show the first match, even though focus remains set for the search text entry field in the Find bar. So tapping Return again and again finds subsequent matches, without any interaction with the overriding performFindPanelAction() method in my subclass.

The problem is that instead of repeatedly tapping Return, the user should be able to use the entirely equivalent "Find Next" command. But when the Find bar has focus in its search text entry field, the default Find menu's "Find Next" command is inexplicably disabled. So typing Command-G (the keyboard equivalent for "Find Next") just makes a sound to indicate that it is disabled and does nothing. This is confusing to the user, because it makes entire sense for the "Find Next" command to be enabled if the search text entry field is non-empty.

Clicking the mouse on the text in the MyTextView changes focus back to it, and suddenly the Find Next (and Find Previous) (with tags 2 and 3) commands are enabled by the system, and they work. The terminal prints out:

validateMenuItem: tag = 2
performFindPanelAction: tag = 2

for each time Command-G is typed. This continues to work (as it should) even after the Find bar has been hidden by clicking in its Done button.

I want Command-G (the "Find Next" command) to initiate a search in MyTextView as well as continue the search. No one wants to search the Find bar's own search text entry field, after all.

Question: How can I override the default system behavior for a Find bar, so that the Find Next (and Find Previous) commands in the Find sub-menu are enabled and work when the Find bar's search text entry field still has focus and is non-empty?

CodePudding user response:

In Mac OS X 10.7 performFindPanelAction: is replaced by performTextFinderAction:. The documentation of performTextFinderAction: states:

Before OS X v10.7, the default action for these menu items was performFindPanelAction:. Whenever possible which you should update your implementation to use this new action.

The Find Panel uses the "new" performTextFinderAction: but apparently Apple forgot to update the Xcode templates. Replace the action of the menu items by performTextFinderAction: to make Find Next/Prev work.

CodePudding user response:

Per Willeke's answer, the substitution of newer performTextFinderAction() in place of the older performFindPanelAction() is a hint to the solution, which I finally got working, but not really for that reason. Xcode makes getting there really confusing though, so here's what I think works and the steps I took to get there.

MyTextView is a potential first responder. In it, the disfavored performFindPanelAction() is an override method. Hence, in the storyboard, Cmd-Clicking "First Responder" shows this method name amongst so many other possibilities for a FirstResponder. This method is what the Xcode's outdated template has wired the Find Next and Find Previous menu item commands' action outlet to.

But after converting override func performFindPanelAction() to override func performTextFinderAction(), the name of the new method still didn't show up in the list when Cmd-Clicking on FirstResponder. There was nothing to (re-)wire the menu item's action outlets to.

So I inserted @IBAction in front of the override func performTextFinderAction(). This caused the method name to appear in the list and I was able to (re-)wire the Find menu commands' action outlets to the newer method. This enabled Find Next and Find Previous when the search text entry field had focus and non-empty text. Problem solved.

But curiously, I then deleted the `IBAction' modifier, and everything still works.

Even more curiously, I then deleted the entire performTextFinderAction() method in MyTextView, and everything still works. So the Menu Items in the storyboard now have the proper name of the action to call, and the system's new default behavior fixes the problem, so there's no need to override anything in MyTextView.

The culprit is Xcode 13.3's outdated template, I guess.

  • Related