How to Use Nsvisualeffectview to Blend Window with Background

Change color of NSVisualEffectView

You don't change the color "of" a visual effect view because it has no color. The visual effect view blends the background and foreground (subviews) according to the view's material and blending mode. The "background" is either what's under the visual effect view within the window, or what's behind the window.

In these examples there's a blue NSBox within a visual effect view which is within a red NSBox.

This visual effect view's blending mode is "behind" window. Note that you see the green from behind the window as the blended background within the visual effect view.
Behind

This visual effect view's blending mode is "within" window. Note that you see the red box which is the background of the visual effect view.
Within

In both cases the material is "light" in appearance. Different materials will have a different appearance.

There is no generic way have "blurry colored background" created by a visual effect view. It's intended to be used certain ways, based on the material/blending mode combination, and each will have different appearances.

How can I create Yosemite-style view with translucent/blurry background?

With the introduction of the Yosemite version of the OSX operating system, Apple introduced a new mode called vibrancy, which is a light diffusion blur, to Cocoa window and window components. It's sort of like looking through a shower door, and uses the NSVisualEffectView. Apple explains this effect here.

I use this category on NSView.
Simply call on the view you want to make vibrant.
It is also backwards compatible with pre-Yosemite. (If you have a pre-Yosemite, you won't see the effect)

@implementation NSView (HS)

-(instancetype)insertVibrancyViewBlendingMode:(NSVisualEffectBlendingMode)mode
{
Class vibrantClass=NSClassFromString(@"NSVisualEffectView");
if (vibrantClass)
{
NSVisualEffectView *vibrant=[[vibrantClass alloc] initWithFrame:self.bounds];
[vibrant setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
// uncomment for dark mode instead of light mode
// [vibrant setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
[vibrant setBlendingMode:mode];
[self addSubview:vibrant positioned:NSWindowBelow relativeTo:nil];

return vibrant;
}

return nil;
}

@end

Detailed instructions from @Volomike follow…

How To Use

  1. Add AppKit.framework to your Project Settings > Build Phases > Link Binary with Libraries so that it can recognize NSVisualEffectView.

  2. Make an outlet delegate of your main window's default view, not the window itself, to your AppDelegate.m or AppDelegate.mm file. (If you're new at that, read this tutorial.) For purposes here, let's assume you named this as mainview, which then is addressable in code as _mainview.

  3. Include the category in your project. If you're new at that, add the category before any @implementation line in your AppDelegate.m or AppDelegate.mm file.

  4. In your AppDelegate.m or AppDelegate.mm file, in your @implementation AppDelegate, inside your applicationDidFinishLaunching class method, add this line of code:

[_mainview insertVibrancyViewBlendingMode:NSVisualEffectBlendingModeBehindWindow];

  1. Now you need to learn how to add some code to give the other elements on your window, as well as the window itself, translucency. That translucency will allow this effect to show through into your window components as you require. This is explained here.

The net effect now is that either the entire window below the titlebar, or only parts you want (such as a sidebar), will show this vibrancy effect.

How to use NSVisualEffectView backwards-compatible with OSX 10.10?

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.

NSVisualEffectView behind NSScrollView

Took me a while to find it, but there are a couple of new methods on NSWorkspace that you can use to find out about the preferences for OS X Yosemite’s new accessibility features. -[NSWorkspace accessibilityDisplayShouldReduceTransparency] is the one you want.

By Listening for NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification you can find out when that preference changes. Note that you’ll have to register for that notification on the correct NSNotificationCenter, that is [[NSWorkspace sharedWorkspace] notificationCenter].

Graphical bug NSVisualEffectView when app in background

The problem was that I was trying to have a NSOutlineView translucent (Behind-window blending), but under a TabView.

Apple UI guidelines (here) state the following:

Use an opaque background when a window contains more than one sidebar, and when using a sidebar in a panel or preferences window. All other times, use a translucent background.

Because of the TabView, I was clearly against this rule which had technical side effects (which led to this post :-)).

--> making the NSOutlineView opaque (not as a SourceList) solved the problem.

How can I use NSVisualEffectView in window's title bar?

@sgonzalez's answer forced me to explore NSWindow.h file where i found titlebarAppearsTransparent property.

So we get:

class BluredWindow: NSWindow {

override func awakeFromNib() {

let visualEffectView = NSVisualEffectView(frame: NSMakeRect(0, 0, 300, 180))
visualEffectView.material = NSVisualEffectView.Material.dark
visualEffectView.blendingMode = NSVisualEffectView.BlendingMode.behindWindow
visualEffectView.state = NSVisualEffectView.State.active

self.styleMask = self.styleMask | NSFullSizeContentViewWindowMask
self.titlebarAppearsTransparent = true
//self.appearance = NSAppearance(named: NSAppearanceNameVibrantDark)

self.contentView.addSubview(visualEffectView)

self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[visualEffectView]-0-|",
options: NSLayoutConstraint.FormatOptions.directionLeadingToTrailing,
metrics: nil,
views: ["visualEffectView":visualEffectView]))

self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[visualEffectView]-0-|",
options: NSLayoutConstraint.FormatOptions.directionLeadingToTrailing,
metrics: nil,
views: ["visualEffectView":visualEffectView]))

Also you can setup NSVisualEffectView in IB it will be expanded on titlebar.



Related Topics



Leave a reply



Submit