如何使用NSVisualEffectView向后兼容OSX< 10.10?

时间:2014-09-18 14:05:34

标签: macos osx-mavericks nsvisualeffectview

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

在10.10中使用通过窗口的半透明是微不足道的。您只需在视图层次结构中的某处放置NSVisualEffectView,并将其设置为blendingModeNSVisualEffectBlendingModeBehindWindow。这就是全部。

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

但是,如果您希望向后兼容早期的OSX版本,则无法执行此操作。如果您尝试在XIB中包含NSVisualEffectView,则在尝试加载XIB时会立即崩溃。

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

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

我认为使用NSProxy对象可能会出现这种情况。这就是我的想法:

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

代理会将所有消息转发给myActualView。

这将是相当多的繁琐,低级别的代码,但我认为它应该有效。

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

与以前的Betas相比,与Yosemite a的Beta协议相比,Apple更加开放很多。我不认为我是通过一般性的讨论来违反我的Beta NDA,但使用NSVisualEffectView的实际代码可能需要在NDA下共享......

3 个答案:

答案 0 :(得分:11)

有一个非常简单但有些笨拙的解决方案:只需在应用启动时动态创建一个名为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现在从nib文件中取消归档NSVisualEffectView时,它将使用您新创建的类。该子类的行为与NSView相同。

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

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

我还需要注意什么?

  1. 在您访问代码中的NSVisualEffectView的任何属性(例如material)之前,请确保该类响应选择器([view respondsToSelector:@selector(setMaterial:)]
  2. [[NSVisualEffectView alloc] initWithFrame:]仍然无法正常工作,因为类名在编译时已解析。如果[[NSClassFromString(@"NSVisualEffectView") alloc] initWithFrame:]为NULL,请使用NSView或仅分配[NSVisualEffectView class]

答案 1 :(得分: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

答案 2 :(得分:0)

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

{{1}}