NSButton subclass as colorwell & preventing NSColorPanel from touching the first responder

末鹿安然 提交于 2019-12-07 11:25:41

Yes, a better web search term (NSColorWell "first responder") and I see others have struggled with NSColorWell and this same problem for a long time. Many old mailing list threads have covered this and saw 3 solutions:

  1. http://www.cocoabuilder.com/archive/cocoa/82832-nscolorwell-changecolor-and-first-responder.html Douglas Davidson suggests subclassing the potential first responder(s) so they ignore changeColor: (probably what I'll do)

  2. http://www.cocoabuilder.com/archive/cocoa/3263-your-nscolorwell-got-in-my-nstext.html Guy English suggests making the color well the first responder temporarily (what I tried but had problems due to color well being in a panel which I don't want becoming key)

  3. http://www.cocoabuilder.com/archive/cocoa/180323-detecting-currently-active-nscolorwell.html Martin suggests cutting preventing the changeColor: call in the first place by posing as NSColorPanel and overriding a private method (closest to what I wanted, but more risk of app store rejection than I'm comfortable with)

UPDATE: I tried #1 but it turns out I can't override the first responder (WebView / WebHTMLView grrr). Going with #3, I put the following in a NSColorPanel category, made my color button set panel.avoidsChangingFirstResponder=YES, and it seems to work:

static char changeColorPatchAssociatedObjectKey; // address of this is used as a unique runtime value

- (BOOL)avoidsChangingFirstResponder
{
    NSNumber *changeColorPatchFlag = (NSNumber *)objc_getAssociatedObject(self, &changeColorPatchAssociatedObjectKey);
    return changeColorPatchFlag && changeColorPatchFlag.boolValue;
}

- (void)setAvoidsChangingFirstResponder:(BOOL)enablePatch
{
    NSNumber *changeColorPatchFlag = (NSNumber *)objc_getAssociatedObject(self, &changeColorPatchAssociatedObjectKey);
    if ((!changeColorPatchFlag && enablePatch) || (changeColorPatchFlag && changeColorPatchFlag.boolValue != enablePatch))
        objc_setAssociatedObject(self, &changeColorPatchAssociatedObjectKey, @( enablePatch ), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (void)load
{
    if (self == [NSColorPanel class])
    {
        // patch implementation of _forceSendAction:notification:firstResponder: (use swizzle technique from MAKVONotificationCenter.m)
        // for one that calls original but with the last BOOL parameter conditionally changed to NO
        SEL methodSel = NSSelectorFromString(@"_forceSendAction:notification:firstResponder:");
        Method method = class_getInstanceMethod(self, methodSel);
        IMP origImpl = method_getImplementation(method);
        IMP newImpl = imp_implementationWithBlock(^(void *obj, SEL s, BOOL isAct, BOOL isNotif, BOOL isFirstResp) {
            NSNumber *changeColorPatchFlag = (NSNumber *)objc_getAssociatedObject((__bridge id)(obj), &changeColorPatchAssociatedObjectKey);
            if (changeColorPatchFlag && changeColorPatchFlag.boolValue)
                isFirstResp = NO;
            ((void (*)(void *, SEL, BOOL, BOOL, BOOL))origImpl)(obj, s, isAct, isNotif, isFirstResp);
        });
        class_replaceMethod(self, methodSel, newImpl, method_getTypeEncoding(method));
    }
}

I hope someone else finds this useful.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!