How to use NSVisualEffectView backwards-compatible with OSX < 10.10?

生来就可爱ヽ(ⅴ<●) 提交于 2019-12-02 19:29:00

There is a really simple, but somewhat hacky solution: Just dynamically create a class named NSVisualEffectView when your app starts. Then you can load nibs containing the class, with graceful fallback on OS X 10.9 and earlier.

Here's an extract of my app delegate to illustrate the idea:

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

You have to compile this against the OS X 10.10 SDK.

How does it work?

When your app runs on 10.9 and earlier, [NSVisualEffectView class] will be NULL. In that case, the following two lines create a subclass of NSView with no methods and no ivars, with the name NSVisualEffectView.

So when AppKit now unarchives a NSVisualEffectView from a nib file, it will use your newly created class. That subclass will behave identically to an NSView.

But why doesn't everything go up in flames?

When the view is unarchived from the nib file, it uses NSKeyedArchiver. The nice thing about it is that it simply ignores additional keys that correspond to properties / ivars of NSVisualEffectView.

Anything else I need to be careful about?

  1. Before you access any properties of NSVisualEffectView in code (eg material), make sure that the class responds to the selector ([view respondsToSelector:@selector(setMaterial:)])
  2. [[NSVisualEffectView alloc] initWithFrame:] still wont work because the class name is resolved at compile time. Either use [[NSClassFromString(@"NSVisualEffectView") alloc] initWithFrame:], or just allocate an NSView if [NSVisualEffectView class] is NULL.

I just use this category on my top-level view.

If NSVisualEffects view is available, then it inserts a vibrancy view at the back and everything just works.

The only thing to watch out for is that you have an extra subview, so if you're changing views around later, you'll have to take that into account.

@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

I wound up with a variation of @Confused Vorlon's, but moving the child views to the visual effect view, like so:

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