12

我知道这不会是一个流行的问题,而且有些人不喜欢具有非标准外观的应用程序,但它对我的应用程序很有用。

是否可以以“标准”非私有 API 方式更改 NSWindow 标题栏文本的颜色?

我知道如果我使用私有 API(如本答案中所述)是可能的,但我认为可以在没有私有 API 的情况下做到这一点,因为 Pixelmator 已经完成了它并且没有被 MAS 拒绝。我知道也可以通过制作无边框窗口并自己绘制所有内容来做到这一点,但我不认为 Pixelmator 是这样做的,因为他们仍然可以获得标准 NSWindow 标题栏附带的所有附加位;可拖动图标、重命名窗口、文档修订的下拉菜单和全屏按钮。

基本上,我使用 setBackgroundColor: 制作了一个黑色窗口:但文本仍然显示为黑色,这在黑色背景上不起作用。

那么有没有人知道这样做的方法,或者 Pixelmator 是如何做到的?

4

4 回答 4

11

从 OS X 10.10 开始,将窗口的外观更改为 NSAppearanceNameVibrantDark 就足够了

  window.appearance = NSAppearance(named:NSAppearanceNameVibrantDark)

认为值得一提,因为您可以找到的大多数答案都已过时。

于 2015-08-27T06:22:14.803 回答
3

这是 Swift 中的一个解决方案。已经很晚了,我很累,所以这可能不是最佳选择,但它确实有效。

首先,这是一个在层次结构中查找视图的函数,可以选择跳过特定视图。(如果我们正在搜索window.contentView.superview.subviews并且我们想忽略您在 中的观点,这很有用contentView

func findViewInSubview(subviews: [NSView], #ignoreView: NSView, test: (NSView) -> Bool) -> NSView? {
    for v in subviews {
        if test(v) {
            return v
        } else if v != ignoreView {
            if let found = findViewInSubview(v.subviews as [NSView], ignoreView: ignoreView, test) {
                return found
            }
        }
    }
    return nil
}

以下是您将如何使用它,例如从NSViewController子类中。请注意,您需要在窗口变得可见时执行此操作,因此您不能在viewDidLoad.

override func viewDidAppear() {
    if let windowContentView = view.window?.contentView as? NSView {
        if let windowContentSuperView = windowContentView.superview {
            let titleView = findViewInSubview(windowContentSuperView.subviews as [NSView], ignoreView: windowContentView) { (view) -> Bool in
                // We find the title by looking for an NSTextField. You may
                // want to make this test more strict and for example also
                // check for the title string value to be sure.
                return view is NSTextField
            }
            if let titleView = titleView as? NSTextField {
                titleView.attributedStringValue = NSAttributedString(string: "Hello", attributes: [NSForegroundColorAttributeName: NSColor.redColor()])
            }
        }
    }
}

请注意,您是在玩火。出于某种原因,未指定像这样的内部结构。

于 2015-03-29T01:40:27.747 回答
1

这就是我这样做的方式:

#import <objc/runtime.h>

@interface SOWindow : NSWindow
@end

@interface SOWindowFrameOverrides : NSView
@end

@implementation SOWindow

+ (void)load
{
    SEL selector = NSSelectorFromString(@"_currentTitleColor");
    SEL originalSelector = NSSelectorFromString(@"_original_currentTitleColor");
    Class frameClass = NSClassFromString(@"NSThemeFrame");

    Method m = class_getInstanceMethod(frameClass, selector);
    Method m2 = class_getInstanceMethod([SOWindowFrameOverrides class], selector);
    class_addMethod(frameClass, originalSelector, method_getImplementation(m), method_getTypeEncoding(m));
    method_exchangeImplementations(m, m2);
}

@end

@implementation SOWindowFrameOverrides

- (NSColor *)_currentTitleColor
{
    if ([self.window isKindOfClass:[SOWindow class]]) {
        return [NSColor redColor];
    } else {
        return [self _original_currentTitleColor];
    }
}

- (NSColor *)_original_currentTitleColor
{
    // will be filled in at runtime
    return nil;
}

@end

虽然从 Mavericks 到 Yosemite 的视图层次结构发生了很大变化,_currentTitleColor但它已经在 API 中使用了很长时间,并且在不久的将来可能不会改变。即使是艰难的方法调配也是一种 hacky 方式,我发现它比遍历视图层次结构更优雅,但这只是我。

如果您想进一步自定义标题,您可以覆盖_titleTextFieldonNSThemeFrame以返回自定义文本字段(我使用它来更改我的F3X项目的背景样式)。

于 2015-09-11T02:39:34.577 回答
0

您可以使用一些私有 API 如您的问题的评论中所述解决此问题 - 但是,您将无法将该应用程序提交到 AppStore。

其他解决方案是获取[[myWindow contentView] superview]- 它为您提供上述NSThemeView实例。然后,您需要做的就是在视图(实际上称为框架视图)子视图的子视图中搜索 NSTextField 的任何实例并修改它们。请注意,这些私有视图的层次结构可能会随着 OS X 的每个版本而改变,可能会破坏您的代码。

可能最好的解决方案是子类化(仅子类化,没有其他自定义)NSWindow并实现-titleand-setTitle:方法 - 对于每个窗口,您将实际标题设置为@""(通过调用[super setTitle:@""]),然后将您自己的、以编程方式创建的NSTextField实例放入框架中视图(即[[[self contentView] superview] addSubview:myTextField]myTextField文本字段在哪里。您需要弄清楚该字段的确切位置等,但这是最简单的部分。

于 2013-06-26T09:02:55.330 回答