validateMenuItem disappeared in ccode 10 Beta 6

New update: Closed.



--------


Hello,


I am using xcode 10 beta6 and migrated swift code to swift 4.2, now I have problem to validate app menu, the old implementation is:


override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { ... }


But now xcode (beta6) shows error "Method does not override any method from its superclass", and I could not find any method with similar func name to validate menu item.


Can you please help to let me know the solution?


Thanks,


Philip

Accepted Reply

By way of explanation, over the past N years Apple’s frameworks, and specifically AppKit, have been working to adopt formal protocols (as opposed to the informal protocol mechanism inherited from Objective-C’s distant past). This

-validateMenuItem:
change is just one more step along that path, albeit a significant one. In fact, it’s so significant that it was called out as the canonical example of this in WWDC 2018 Session 209 What’s New in Cocoa for macOS. The change is also covered in AppKit Release Notes for macOS 10.14 beta.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

It works in beta5. I'll test in beta 6 as soon as I've completed download.

Thanks @Claude31


I just found the solution. My implmentation is on the NSWindowController, now I just implement the protocol NSMenuItemValidation for the window controller class. ("override func validateMenuItem" seems deprecated)

I have it in AppController.


I'll tell for XCode beta 6 in an hour. I would be surprised it was deprecated between beta 5 and beta 6. There is no such info in doc, for an API that exists since OSX 10.0 !

Just tested on XCode 10 beta 6, works without problem.


I also checked that validateMenuItem is correctly executed.


May be all components were not yet installed when you tested ?


Anyway, you should probably close the thread.

By way of explanation, over the past N years Apple’s frameworks, and specifically AppKit, have been working to adopt formal protocols (as opposed to the informal protocol mechanism inherited from Objective-C’s distant past). This

-validateMenuItem:
change is just one more step along that path, albeit a significant one. In fact, it’s so significant that it was called out as the canonical example of this in WWDC 2018 Session 209 What’s New in Cocoa for macOS. The change is also covered in AppKit Release Notes for macOS 10.14 beta.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

When I use in XCode 10beta6 (OSX 10.13), in AppController

class AppController: NSObject


I do not declare conformance to the protocol.


So, I'll have to add this when moving to Mojave ?

I think you may not set the project "swift language" to 4.2

I think you may not set the project "swift language" to 4.2

Yep. If I open an Xcode 9 project in Xcode 10.0b6 it builds as is. I’m not required to adopt

NSMenuItemValidation
until I upgrade the project to Swift 4.2.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Well, yes, but how would you call super? This is my old code working in Swift 4.0, but breaking w/ 4.2:


  #if os(macOS) // hmm
    // NSMenuItemValidation
    // https://forums.developer.apple.com/thread/107328
    override open func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
      switch menuItem.action {
        case #selector(delete(_:)): return selectedAccount != nil
        default: return super.validateMenuItem(menuItem)
      }
    }
  #else
    override func canPerformAction(_ action: Selector,
                                   withSender sender: Any?) -> Bool
    {
      switch action {
        case #selector(delete(_:)): return selectedAccount != nil
        default: super.canPerformAction(action, withSender: sender)
      }
    }
  #endif


I actually *do* want to override and just tweak the handling a little. (this is a view controller subclass)

This depends on how your class hierarchy is laid out:

  • If your an indirect subclass of

    NSViewController
    — that is, your superclass is a class you control — then you can just have that superclass adopt
    NSMenuItemValidation
    .
  • If you’re in an immediate subclass of

    NSViewController
    you can’t call
    super
    because
    NSViewController
    does not adopt
    NSMenuItemValidation
    .

In the second case I’m not sure that calling

super
is the right thing to do. AFAICT neither
NSViewController
nor its superclass,
NSResponder
, implement
NSMenuItemValidation
. Rather, menu item validation automatically proceeds down the responder chain.

Still, this is very specific to AppKit and thus way outside of my area of expertise. If you want to pursue this further my recommendation is that you start a new thread over in App Frameworks > Cocoa. Alternatively you can open a DTS tech support incident and discuss this with one of DTS’s AppKit specialists.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

> AFAICT neither

NSViewController
nor its superclass,
NSResponder
, implement
NSMenuItemValidation


`Well, in Swift 4.0 I can and used to call `validateMenuItem` on NSViewController as shown (you can still try it in Xcode 10 by setting the language version to 4.0). So it did in fact have an implementation prior Swift 4.2.

Presumably this default implementation just did a simple `responds(to: item.action)` call (maybe it was an actual NSObject category, not just an informal protocol), so I'm manually doing that now. Might still be worth pointing out in the 4.2 migration documentation, I hit that in multiple projects. Maybe I'll file a Radar later.


As a summary, I think this is what you have to do now:

- explicitly implement `NSMenuItemValidation` (`class MyVC : NSViewController, NSMenuItemValidation`)

- do something like this:


@objc func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {  
  switch menuItem.action {  
    case #selector(delete(_:)): return selectedAccount != nil  
    default: return responds(to: menuItem.action) // instead of super
  }  
} 


(of course you can also pattern-match each and every selector the Java way ;-) )

You know, I should really stop now because this is way outside of my area of expertise, but I just can’t let it go )-:

You wrote:

So it did in fact have an implementation prior Swift 4.2. Presumably this default implementation just did a simple

responds(to: item.action)
call (maybe it was an actual
NSObject
category, not just an informal protocol),

As far as I can tell that’s not the case. To investigate this I created an Objective-C test app from the Cocoa App template. I then modified

ViewController.m
as follows:
  1. I added conformance to the

    NSMenuItemValidation
    protocol.
    @interface ViewController () <NSMenuItemValidation>
    @end

    .

  2. I added an implementation that just calls

    super
    .
    - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
        NSLog(@"will call super");
        BOOL result = [super validateMenuItem:menuItem];
        NSLog(@"did call super");
        return result;
    }

    I then set breakpoints on both of the

    NSLog
    calls.
  3. I added a test action.

    - (IBAction)testAction:(id)sender {
        #pragma unused(sender)
        NSLog(@"-testAction:");
    }

    .

  4. I added a menu item, Test, that has

    -testAction:
    wired to the first responder.

When I ran the app (on 10.14, but I don’t think the OS version matters) my breakpoint on the

will call super
line is hit. If I continue from that, the app does not hit my breakpoint on
did call super
and logs an
unrecognized selector
exception (the full log is kinda long, so I included it below rather than inline here).

So I’ve no idea what’s going on with your app — whether it’s a Swift thing or something specific to your app — but I’m convinced that

NSViewController
does not implement
-validateMenuItem:
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
2018-10-29 10:12:42.975878+0000 xxom10[55652:4743688] will call super
2018-10-29 10:12:42.976086+0000 xxom10[55652:4743688] -[ViewController validateMenuItem:]: unrecognized selector sent to instance 0x600002902530
2018-10-29 10:12:42.976314+0000 xxom10[55652:4743688] [General] An uncaught exception was raised
2018-10-29 10:12:42.976329+0000 xxom10[55652:4743688] [General] -[ViewController validateMenuItem:]: unrecognized selector sent to instance 0x600002902530
2018-10-29 10:12:42.978925+0000 xxom10[55652:4743688] [General] (
    0   CoreFoundation  0x00007fff398c843d __exceptionPreprocess + 256
    1   libobjc.A.dylib 0x00007fff657d5720 objc_exception_throw + 48
    2   CoreFoundation  0x00007fff39945255 -[NSObject(NSObject) __retain_OA] + 0
    3   CoreFoundation  0x00007fff3986780e ___forwarding___ + 780
    4   CoreFoundation  0x00007fff39867478 _CF_forwarding_prep_0 + 120
    5   xxom10          0x000000010000129a -[ViewController validateMenuItem:] + 106
    6   AppKit          0x00007fff36f6d1a7 -[NSMenu _enableItem:] + 563
    7   AppKit          0x00007fff370b6f2f -[NSCarbonMenuImpl _carbonUpdateStatusEvent:handlerCallRef:] + 497
    8   AppKit          0x00007fff3708913d NSSLMMenuEventHandler + 902
    9   HIToolbox       0x00007fff38aa58d9 _ZL23DispatchEventToHandlersP14EventTargetRecP14OpaqueEventRefP14HandlerCallRec + 1502
    10  HIToolbox       0x00007fff38aa4c16 _ZL30SendEventToEventTargetInternalP14OpaqueEventRefP20OpaqueEventTargetRefP14HandlerCallRec + 371
    11  HIToolbox       0x00007fff38ac21cd SendEventToEventTarget + 39
    12  HIToolbox       0x00007fff38b1529f _ZL18SendHICommandEventjPK9HICommandjjhPKvP20OpaqueEventTargetRefS5_PP14OpaqueEventRef + 380
    13  HIToolbox       0x00007fff38b25599 UpdateHICommandStatusWithCachedEvent + 51
    14  HIToolbox       0x00007fff38aa0cb7 _ZN13HIApplication12EventHandlerEP25OpaqueEventHandlerCallRefP14OpaqueEventRefPv + 2567
    15  HIToolbox       0x00007fff38aa58d9 _ZL23DispatchEventToHandlersP14EventTargetRecP14OpaqueEventRefP14HandlerCallRec + 1502
    16  HIToolbox       0x00007fff38aa4c16 _ZL30SendEventToEventTargetInternalP14OpaqueEventRefP20OpaqueEventTargetRefP14HandlerCallRec + 371
    17  HIToolbox       0x00007fff38ac21cd SendEventToEventTarget + 39
    18  HIToolbox       0x00007fff38b25158 _ZL15SendMenuOpeningP14MenuSelectDataP8MenuDatadjjP14__CFDictionaryhPh + 686
    19  HIToolbox       0x00007fff38c4c75c _SimulateMenuOpening + 111
    20  HIToolbox       0x00007fff38c423c5 _ZL21OpenMenuForInspectionP8MenuData + 94
    21  HIToolbox       0x00007fff38c431f5 _ZN8MenuData33HandleGetNamedAccessibleAttributeEyPK10__CFStringjP14OpaqueEventRef + 157
    22  HIToolbox       0x00007fff38c43143 _ZN8MenuData31GetNamedAccessibleAttributeSelfEyPK10__CFStringjP14OpaqueEventRef + 185
    23  HIToolbox       0x00007fff38c5c56b _ZN8HIObject26DispatchAccessibilityEventEP14OpaqueEventRefyPK21AccessibilityHandlersPv + 489
    24  HIToolbox       0x00007fff38c5c236 _ZN8HIObject29HandleClassAccessibilityEventEP25OpaqueEventHandlerCallRefP14OpaqueEventRefPv + 106
    25  HIToolbox       0x00007fff38aa5ecb _ZN8HIObject9EventHookEP25OpaqueEventHandlerCallRefP14OpaqueEventRefPv + 135
    26  HIToolbox       0x00007fff38aa58d9 _ZL23DispatchEventToHandlersP14EventTargetRecP14OpaqueEventRefP14HandlerCallRec + 1502
    27  HIToolbox       0x00007fff38aa4c16 _ZL30SendEventToEventTargetInternalP14OpaqueEventRefP20OpaqueEventTargetRefP14HandlerCallRec + 371
    28  HIToolbox       0x00007fff38b3bea3 CallNextEventHandler + 105
    29  AppKit          0x00007fff37299c6d -[NSCarbonMenuImpl _carbonGetAccessibleAttributeEvent:handlerCallRef:axElement:] + 37
    30  AppKit          0x00007fff370891c5 NSSLMMenuEventHandler + 1038
    31  HIToolbox       0x00007fff38aa58d9 _ZL23DispatchEventToHandlersP14EventTargetRecP14OpaqueEventRefP14HandlerCallRec + 1502
    32  HIToolbox       0x00007fff38aa4c16 _ZL30SendEventToEventTargetInternalP14OpaqueEventRefP20OpaqueEventTargetRefP14HandlerCallRec + 371
    33  HIToolbox       0x00007fff38aa4a9c SendEventToEventTargetWithOptions + 45
    34  HIToolbox       0x00007fff38b5301a _ZNK10Accessible9SendEventEP14OpaqueEventRefb + 88
    35  HIToolbox       0x00007fff38b52572 _ZN10Accessible21GetNamedAttributeDataEPK10__CFStringPKvPS4_Ph + 154
    36  HIToolbox       0x00007fff38b52314 HLTBCopyUIElementAttributeValue + 57
    37  HIToolbox       0x00007fff38b5491c _ZL32CarbonCopyAttributeValueCallbackPK8__CFDatajPK10__CFStringPPKvPv + 88
    38  AppKit          0x00007fff37211bed CopyCarbonUIElementAttributeValue + 62
    39  AppKit          0x00007fff3720ee35 CopyAttributeValue + 126
    40  HIServices      0x00007fff37fed2e6 _AXXMIGCopyAttributeValue + 272
    41  HIServices      0x00007fff37ff676e _XCopyAttributeValue + 433
    42  HIServices      0x00007fff37fcdabe mshMIGPerform + 212
    43  CoreFoundation  0x00007fff39826959 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
    44  CoreFoundation  0x00007fff398268b7 __CFRunLoopDoSource1 + 527
    45  CoreFoundation  0x00007fff3980e945 __CFRunLoopRun + 2574
    46  CoreFoundation  0x00007fff3980dce4 CFRunLoopRunSpecific + 463
    47  HIToolbox       0x00007fff38aa7895 RunCurrentEventLoopInMode + 293
    48  HIToolbox       0x00007fff38aa75cb ReceiveNextEventCommon + 618
    49  HIToolbox       0x00007fff38aa7348 _BlockUntilNextEventMatchingListInModeWithFilter + 64
    50  AppKit          0x00007fff36d6495b _DPSNextEvent + 997
    51  AppKit          0x00007fff36d636fa -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1362
    52  AppKit          0x00007fff36d5d75d -[NSApplication run] + 699
    53  AppKit          0x00007fff36d4ce97 NSApplicationMain + 780
    54  xxom10          0x0000000100001342 main + 34
    55  libdyld.dylib   0x00007fff668a3085 start + 1
)

> You know, I should really stop now because this is way outside of my area of expertise, but I just can’t let it go


🙂


Well, it might very well be a Swift issue - and this thread started out as one and this is how I found this thread. The issue discussed is:


override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool


Not working anymore in Swift 4.2.


Before Swift 4.2 you have been required to override `validateMenuItem`, and call super (or not). With Swift 4.2 the `override` is not possible anymore and the code needs to be ported - somehow. So clearly, at least on the Swift side, Swift 4.0 NSViewController did implement `validateMenuItem` and one had to use `override`.

Your answer wrt the formal protocol is related and important information here, but only part of the issue.


If you want to spend the time, this is really easy to reproduce. In Xcode 10 create a new Cocoa AppKit Swift project. Go to the Build Settings and search for "swift language version", set it from 4.2 to 4.0. Just add something like this to the AppDelegate.swift:


class MyVC : NSViewController { 
  override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
    return super.validateMenuItem(menuItem)
  }
}


And play with it ;-) Also do what I did and set the version back up to 4.2 and experience what the original poster, and myself, experienced. The super implementation is gone. You are saying that the "I don’t think the OS version matters". That is probably right, but the SDK selected may matter, maybe the one used by 4.0 included that method (maybe an older ObjC SDK does too).


So now about the actual functionality of `validateMenuItem`. From my understanding, which may very well be wrong :-) So this selector is to override the behaviour to enable/disable menu items.

By default the menu item would just walk the responder chain and check whether there is an object responding to the action selector attached to the menu item. If yes, all good, enable, if no, disable.

But sometimes, checking the selector is not enough. For example even if the controller implements `delete:`, it makes no sense to enable the delete-menu-item if for example no item is selected in a tableview. This is when you override `validateMenuItem`. So instead of checking for a selector, the menu item enable/disable logic first checks for `validateMenuItem:`. If it is there, it lets it do the job to determine whether the object can handle it right now.


Now the problem is that if you implement `validateMenuItem:`, but there is no super, what to you do? You may want to override the behaviour for just a single action, and just have the default behaviour for everything else. In Swift 4 you would just call super and it worked (as far as I can tell my code worked just fine [e.g. CodeCows crashes on 10.14 right now for other reasons, will upload a fix soon]).


So unless there is some special Swift hackery in the AppKit shim, I think it is most plausible (and sound) that before the move to the formal protocol, AppKit included a default implementation like this:


@implementation NSObject(ValidateMenuItem)// or NSResponder maybe?
- (BOOL)validateMenuItem:(NSMenuItem *)_item {
   return [self respondsToSelector:[_item action]];
}
@end


At some point that seems to have been removed in the 4.0...4.2 timeframe. That this is also gone in ObjC just supports my theory :-) I bet a beer that this category (or at least something for NSViewController) has been disabled if you compile against the newest SDK.


Regardless of all of that, I think my answer is what people affected by the original posters issue will have to do in Swift 4.2 (in addition to explicitly conforming to the `NSMenuItemValidation` protocol):


@objc func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {   
  switch menuItem.action {   
    case #selector(delete(_:)): return selectedAccount != nil   
    default: return responds(to: menuItem.action) // instead of super 
  }   
}


P.S.: I gave it a try w/ Xcode 9.2 on 10.12 and 10.10 as the deployment target, and ObjC. ObjC still can't call super. So it does seem to be Swift specific.

I bet a beer that this category (or at least something for

NSViewController
) has been disabled if you compile against the newest SDK.

I’d take you up on that beer but it’s an unfair bet because I have access to the AppKit source code (-:

I think it is most plausible (and sound) that before the move to the formal protocol, AppKit included a default implementation like this:

My Objective-C test proves that this is not the case because if you call

super
in Objective-C you crash. AppKit does not provide a default implementation of this method, at least on
NSViewController
.

Which brings us to the potential for Swift ‘magic’. You wrote:

So clearly, at least on the Swift side, Swift 4.0

NSViewController
did implement
validateMenuItem
and one had to use
override
.

I wanted to eliminate the possibility of Swift ‘magic’ so I re-ran my test, this time creating a Swift project. I made sure to set the Swift Language Version (

SWIFT_VERSION
) build setting to 4.0, as you suggested. This has the same behaviour as Objective-C, that is, it throws an
unrecognized selector
exception on the call to
super
.

I tested this with Xcode 10.0 on macOS 10.14, but just in case it’s an Xcode 10 issue I took that project and rebuilt it with Xcode 9.4 and it also failed in the same way. And just to be sure that the macOS version wasn’t a factor, I tried that 9.4-built binary on 10.13.6 and saw the same behaviour there. I couldn’t run that binary on earlier systems, so I tweaked the deployment target to 10.11 and rebuilt the app (still with Xcode 9.4). After that I ran it on 10.12.6 and 10.11.6, all with the same behaviour.

… this is really easy to reproduce. In Xcode 10 create a new Cocoa AppKit Swift project. Go to the Build Settings and search for "swift language version", set it from 4.2 to 4.0. Just add something like this to the AppDelegate.swift:

class MyVC : NSViewController {   
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {  
  return super.validateMenuItem(menuItem)  
}  
}

Can you be more precise about how you reproduced it? I’ve been unable to find any circumstances under which all of the following are true:

  • I’m in a direct subclass of

    NSViewController
  • I implemented

    -validateMenuItem:
  • It’s actually called

  • My call to

    super
    is successful

And I’ve tried it at six ways now (Xcode 10.0 / Objective-C / macOS 10.14, Xcode 10.0 / Swift 4.0 / macOS 10.14, Xcode 9.4 / Swift 4.0 / macOS 10.{14,13,12,11}).

If you give me an exact sequence of steps that shows a successful call to

super
in this situation, I’ll give it another go.

Alternatively, if you create a test project that shows the issue and post it somewhere on the ’net, I can download it and look at that.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"