13

即将推出的 OSX 10.10(“Yosemite”)提供了一种新型视图 NSVisualEffectView,它支持通过窗口或窗口内的半透明。我最感兴趣的是通过窗口半透明,所以我将在这个问题中关注它,但它也适用于窗口内半透明。

在 10.10 中使用透窗半透明是微不足道的。您只需NSVisualEffectView在视图层次结构中放置一个位置并将其设置blendingModeNSVisualEffectBlendingModeBehindWindow. 这就是它所需要的。

在 10.10 下,您可以NSVisualEffectView在 IB 中定义 s,设置它们的混合模式属性,然后您就可以开始运行了。

但是,如果您想向后兼容早期的 OSX 版本,则不能这样做。如果您尝试NSVisualEffectView在您的 XIB 中包含一个,您将在尝试加载 XIB 时立即崩溃。

我想要一个“设置它并忘记它”的解决方案,它在 10.10 下运行时会提供半透明效果,而在早期操作系统版本上运行时会简单地降级为不透明视图。

到目前为止,我所做的是使有问题的视图成为 XIB 中的普通 NSView,然后添加检查 的代码(由 awakeFromNib 调用),[NSVisualEffectView class] != nil当它定义了类时,我创建了一个 NSVisualEffectView 的实例,将我当前视图的所有子视图移动到新视图,并将其安装到位。这行得通,但是每次我想要半透明视图时,我都必须编写自定义代码。

我认为这可能使用 NSProxy 对象。这就是我的想法:

定义一个自定义的 NSView 子类(我们称之为 MyTranslucentView)。在所有初始化方法(initWithFrame 和 initWithCoder)中,我会丢弃新创建的对象,而是创建一个具有私有实例变量(myActualView)的 NSProxy 子类。在初始化时,如果 OS>=10.10,它将决定将 myActualView 对象创建为 NSVisualEffectView,并在 OS<10.10 下创建正常的 NSView。

代理会将所有消息转发到它的 myActualView。

这将是相当繁琐的低级代码,但我认为它应该可以工作。

有没有人做过这样的事情?如果是这样,你能指出我正确的方向或给我任何指示吗?

与之前的 Beta 相比, Apple与 Yosemite 的 Beta 协议更加开放我不认为我通过笼统地谈论这个违反了我的 Beta NDA,但实际使用的代码NSVisualEffectView可能需要在 NDA 下共享......

4

3 回答 3

11

有一个非常简单但有点 hacky 的解决方案:只需NSVisualEffectView在您的应用启动时动态创建一个名为的类。然后,您可以加载包含该类的 nib,并在 OS X 10.9 及更早版本上优雅地回退。

这是我的应用程序委托的摘录来说明这个想法:

AppDelegate.m

#import "AppDelegate.h"
#import <objc/runtime.h>

@implementation PGEApplicationDelegate
-(void)applicationWillFinishLaunching:(NSNotification *)notification {
    if (![NSVisualEffectView class]) {
        Class NSVisualEffectViewClass = objc_allocateClassPair([NSView class], "NSVisualEffectView", 0);
        objc_registerClassPair(NSVisualEffectViewClass);
    }
}
@end

您必须针对 OS X 10.10 SDK 编译它。

它是如何工作的?

当您的应用在 10.9 及更早版本上运行时,[NSVisualEffectView class]将为 NULL。在这种情况下,以下两行创建了一个NSView没有方法和 ivars 的子类,其名称为NSVisualEffectView

因此,当 AppKit 现在NSVisualEffectView从 nib 文件中解压缩 a 时,它将使用您新创建的类。该子类的行为与 NSView 相同。

但为什么不是一切都火上浇油?

当视图从 nib 文件中取消归档时,它使用NSKeyedArchiver. 它的好处是它只是忽略了与 .properties / ivars 对应的其他键NSVisualEffectView

还有什么我需要注意的吗?

  1. 在您访问NSVisualEffectView代码中的任何属性(例如material)之前,请确保该类响应选择器([view respondsToSelector:@selector(setMaterial:)]
  2. [[NSVisualEffectView alloc] initWithFrame:]仍然无法工作,因为类名是在编译时解析的。要么使用[[NSClassFromString(@"NSVisualEffectView") alloc] initWithFrame:],要么只分配一个NSViewif[NSVisualEffectView class]为 NULL。
于 2014-10-16T14:26:11.570 回答
3

我只是在我的顶级视图中使用这个类别。

如果 NSVisualEffects 视图可用,那么它会在后面插入一个活力视图,一切正常。

唯一需要注意的是您有一个额外的子视图,因此如果您稍后要更改视图,则必须考虑到这一点。

@implementation NSView (HS)

-(instancetype)insertVibrancyViewBlendingMode:(NSVisualEffectBlendingMode)mode
{
    Class vibrantClass=NSClassFromString(@"NSVisualEffectView");
    if (vibrantClass)
    {
        NSVisualEffectView *vibrant=[[vibrantClass alloc] initWithFrame:self.bounds];
        [vibrant setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
        [vibrant setBlendingMode:mode];
        [self addSubview:vibrant positioned:NSWindowBelow relativeTo:nil];

        return vibrant;
    }

    return nil;
}

@end
于 2014-10-03T16:42:40.490 回答
0

我最终得到了@Confused Vorlon 的变体,但是将子视图移动到视觉效果视图,如下所示:

@implementation NSView (Vibrancy)


- (instancetype) insertVibrancyView
{
    Class vibrantClass = NSClassFromString( @"NSVisualEffectView" );
    if( vibrantClass ) {
        NSVisualEffectView* vibrant = [[vibrantClass alloc] initWithFrame:self.bounds];
        [vibrant setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];

        NSArray* mySubviews = [self.subviews copy];
        for( NSView* aView in mySubviews ) {
            [aView removeFromSuperview];
            [vibrant addSubview:aView];
        }

        [self addSubview:vibrant];

        return vibrant;
    }

    return nil;
}

@end
于 2015-08-11T04:33:17.743 回答