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

The upcoming OSX 10.10 ("Yosemite") offers a new type of view, NSVisualEffectView, which supports through-the-window or within-the-window translucency. I'm mostly interested in through-the-window translucency, so I'm going to focus on that in this question, but it applies to within-the-window translucency as well.
Using through-the-window translucency in 10.10 is trivial. You just place an NSVisualEffectView somewhere in your view hierarchy and set it's blendingMode to NSVisualEffectBlendingModeBehindWindow. That's all it takes.
Under 10.10 you can define NSVisualEffectViews in IB, set their blending mode property, and you're off and running.
However, if you want to be backwards-compatible with earlier OSX versions, you can't do that. If you try to include an NSVisualEffectView in your XIB, you'll crash as soon as you try to load the XIB.
I want a "set it and forget it" solution that will offer translucency when run under 10.10 and simply degrade to an opaque view when run on earlier OS versions.
What I've done so far is to make the view in question a normal NSView in the XIB, and then add code (called by awakeFromNib) that checks for [NSVisualEffectView class] != nil, and when it's the class is defined, I create an instance of the NSVisualEffectView, move all my current view's subviews to the new view, and install it in place. This works, but it's custom code that I have to write every time I want a translucent view.
I'm thinking this might be possible using an NSProxy object. Here's what I'm thinking:
Define a custom subclass of NSView (let's call it MyTranslucentView). In all the init methods (initWithFrame and initWithCoder) I would throw away the newly created object and instead create a subclass of NSProxy that has a private instance variable (myActualView). At init time it would decide to create the myActualView object as an NSVisualEffectView if OS>=10.10, and a normal NSView under OS<10.10.
The proxy would forward ALL messages to it's myActualView.
This would be a fair amount of fussy, low-level code, but I think it should work.
Has anybody done something like this? If so, can you point me in the right direction or give me any pointers?
Apple is MUCH more open with the Beta agreement with Yosemite a than with previous Betas. I don't think I'm violating my Beta NDA by talking about this in general terms, but actual code using NSVisualEffectView would probably need to be shared under NDA...

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?
Before you access any properties of NSVisualEffectView in code (eg material), make sure that the class responds to the selector ([view respondsToSelector:#selector(setMaterial:)])
[[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

Related

How to use NSViewController in an NSDocument-based Cocoa app

I've got plenty of experience with iOS, but Cocoa has me a bit confused. I read through several Apple docs on Cocoa but there are still details that I could not find anywhere. It seems the documentation was written before the NSDocument-based Xcode template was updated to use NSViewController, so I am not clear on how exactly I should organize my application. The template creates a storyboard with an NSWindow, NSViewController.
My understanding is that I should probably subclass NSWindowController or NSWindow to have a reference to my model object, and set that in makeWindowControllers(). But if I'd like to make use of the NSViewController instead of just putting everything in the window, I would also need to access my model there somehow too. I notice there is something called a representedObject in my view controller which seems like it's meant to hold some model object (to then be cast), but it's always nil. How does this get set?
I'm finding it hard to properly formulate this question, but I guess what I'm asking is:how do I properly use NSViewController in my document-based application?
PS: I understand that NSWindowController is generally meant to managing multiple windows that act on one document, so presumably if I only need one window then I don't need an NSWindowController. However, requirements might change and having using NSWindowController may be better in the long run, right?
I haven't dived into storyboards but here is how it works:
If your app has to support 10.9 and lower create custom of subclass NSWindowController
Put code like this into NSDocument subclass
- (void)makeWindowControllers
{
CustomWindowController *controller = [[CustomWindowController alloc] init];
[self addWindowController:controller];
}
If your app has multiple windows than add them here or somewhere else (loaded on demand) but do not forget to add it to array of document windowscontroller (addWindowController:)
If you create them but you don't want to show all the windows then override
- (void)showWindows
{
[controller showWindow:nil]
}
You can anytime access you model in your window controller
- (CustomDocument *)document
{
return [self document];
}
Use bindings in your window controller (windowcontroller subclass + document in the keypath which is a property of window controller)
[self.textView bind:#"editable"
toObject:self withKeyPath:#"document.readOnly"
options:#{NSValueTransformerNameBindingOption : NSNegateBooleanTransformerName}];
In contrast to iOS most of the views are on screen so you have to rely on patterns: Delegation, Notification, Events (responder chain) and of course MVC.
10.10 Yosemite Changes:
NSViewController starting from 10.10 is automatically added to responder chain (generally target of the action is unknown | NSApp sendAction:to:from:)
and all the delegates such as viewDidLoad... familiar from iOS are finally implemented. This means that I don't see big benefit of subclassing NSWindowCotroller anymore.
NSDocument subclass is mandatory and NSViewController is sufficient.
You can anytime access you data in your view controller
- (CustomDocument *)document
{
return (CustomDocument *)[[NSDocumentController sharedDocumentController] documentForWindow:[[self view] window]];
//doesn't work if you do template approach
//NSWindowController *controller = [[[self view] window] windowController];
//CustomDocument *document = [controller document];
}
If you do like this (conforming to KVC/KVO) you can do binding as written above.
Tips:
Correctly implement UNDO for your model objects in Document e.g. or shamefully call updateChangeCount:
[[self.undoManager prepareWithInvocationTarget:self] deleteRowsAtIndexes:insertedIndexes];
Do not put code related to views/windows into your Document
Split your app into multiple NSViewControllers e.g.
- (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:AAPLListWindowControllerShowAddItemViewControllerSegueIdentifier]) {
AAPLListViewController *listViewController = (AAPLListViewController *)self.window.contentViewController;
AAPLAddItemViewController *addItemViewController = segue.destinationController;
addItemViewController.delegate = listViewController;
}
}
Previous code is called on windowcontroller with viewcontroller as delegate (again possible only after 10.10)
I always prefer to use multiple XIBs rather than one giant storyboard/XIB. Use following subclass of NSViewController and always inherit from it:
#import <Cocoa/Cocoa.h>
#interface MyViewController : NSViewController
#property(strong) IBOutlet NSView *viewToSubstitute;
#end
#import "MyViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (void)awakeFromNib
{
NSView *view = [self viewToSubstitute];
if (view) {
[self setViewToSubstitute:nil];
[[self view] setFrame:[view frame]];
[[self view] setAutoresizingMask:[view autoresizingMask]];
[[view superview] replaceSubview:view with:[self view]];
}
}
#end
Add a subclass of MyViewController to the project with XIB. Rename the XIB
Add NSViewController Object to the XIB and change its subclass name
Change the loading XIB name to name from step 1
Link view to substitute to the view you want to replace
Check example project Example Multi XIB project
Inspire yourself by shapeart or lister or TextEdit
And a real guide is to use Hopper and see how other apps are done.
PS: You can add your views/viewcontroller into responder chain manually.
PS2: If you are beginner don't over-architect. Be happy with the fact that your app works.
I'm relatively new to this myself but hopefully I can add a little insight.
You can use the view controllers much as you would in ios. You can set outlets and targets and such. For NSDocument-based apps you can use a view controller or the window controller but I think for most applications you'll end up using both with most of the logic being in the view controller. Put the logic wherever it makes the most sense. For example, if your nsdocument can have multiple window types then use the view controller for logic specific to each type and the window controller for logic that applies to all the types.
The representedObject property is primarily associated with Cocoa bindings. While I am beginning to become familiar with bindings I don't have enough background to go into detail here. But a search through the bindings programming guide might be helpful. In general bindings can take the place of a lot of data source code you would need to write on ios. When it works it's magical. When it doesn't work it's like debugging magic. It can be a challenge to see where things went wrong.
Let me add a simple copy-pastable sample for the short answer category;
In your NSDocument subclass, send self to the represented object of your view controller when you are called to makeWindowControllers:
- (void) makeWindowControllers
{
NSStoryboard* storyboard = [NSStoryboard storyboardWithName: #"My Story Board" bundle: nil];
NSWindowController* windowController = [storyboard instantiateControllerWithIdentifier: #"My Document Window Controller"];
MyViewController* myController = (id) windowController.contentViewController;
[self addWindowController: windowController];
myController.representedObject = self;
}
In you MyViewController subclass of NSViewController, overwrite setRepresentedObject to trap it's value, send it to super and then make a call to refresh your view:
- (void) setRepresentedObject: (id) representedObject
{
super.representedObject = representedObject;
[self myUpdateWindowUIFromContent];
}
Merci, bonsoir, you're done.

ScreenSaverView with CAEmitterLayer choppy on second screen

Any ideas why a screen saver using just a plain ScreenSaverView subclass with a CAEmitterLayer sublayer would render fine on the primary screen and choppy (as if every 2. frame renders there..) on the secondary screen..?
This is my initialization code:
- (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
CAEmitterLayer* emitterLayer = [MyEmitterFactory emitterLayer:self];
[self setWantsLayer:YES];
[self.layer addSublayer:emitterLayer];
[self setAnimationTimeInterval:1/2.0];
}
return self;
}
Everything else in this subclass is default (as provided by the Xcode template).
Funny enough, backingStoreType does sound like a good candidate to tweak in a ScreenSaverView subclass using CoreAnimation, alas all other modes except the default one are not to be used as per the docs..
(As the animation is powered by Core Animation it doesn't really matter what I put in setAnimationTimeInterval - or remove the call completely, as experiments have shown)
According to the documentation of NSView setWantsLayer:
To create a layer-hosting view, you must call setLayer: and supply your layer object before you call the setWantsLayer: method; the order of these method calls is crucial.
Furthermore: Which OS version is it? Does the choppiness also come up when the two displays are mirrored (or vice versa)?

Converting self-releasing objects to ARC

OK, so Apple brought ARC to us, which is great. After refactoring my Application to ARC almost everything works fine and it is a lot easier now to develop and maintain.
There is just one problem I still can't figure out.
My job management program shows different detail information of proposals, orders and so on in their own windows. So I have a special class where WindowControllers gets allocated and initiated with initWithWindowNibName and then the window is shown with showWindow:
DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:#"ThePorposalWindow"];
[proposalWindowController showWindow:nil];
Before ARC the Instance of the WindowController did the release like shown in the documentation:
- (void)windowWillClose:(NSNotification *)notification
{
[self autorelease];
}
But now with ARC this is not possible anymore and what makes it even worse, in my special class where the WindowController is allocated and initiated, the same windowController is released by ARC because there is no pointer to the windowController.
My idea was to copy the windowController into an mutuable array:
[proposalWindowArray addObject:proposalWindowController];
[[proposalWindowArray lastObject] showWindow:nil];
And in the windowControllers delegate method windowWillClose I post a notification to my special class:
- (void)windowWillClose:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ProposalWindowWillClose" object:[[self window] windowController] userInfo:nil];
}
In my special class I listen to the notification and remove the object from the array:
- (void) proposalWindowWasClosed: (NSNotification *) notification
{
[proposalWindowArray removeObjectIdenticalTo:[notification object]];
}
It works, but I still do not believe that this is the correct way.
Does anybody has the same problem or a tip to make it better?
I'd probably use a delegate approach rather than notifications. Generally it is better to have an external object that keeps track of the open windows. Self-retaining objects, like your old system, break the basic points of object ownership and make it hard to find things (such as "give me a list of open windows"). Non-Singletons that are just "floating" out there often come back to bite you in your architecture (I've had to fix this often enough).
That said, sometimes self-ownership is at least convenient, and at worst not-the-end-of-the-world. So self-own. The only difference is that you need to do it explicitly rather than matching a leak and an over-release (which is what your old code was doing).
Create a private strong property. Assign self to it. That will create a retain loop that will keep you around until you set the property to nil.
I think your alternative approach should be correct, but I don't think you need the second notification. You should be able to do:
- (void)windowWillClose:(NSNotification *)notification
{
[proposalWindowArray removeObjectIdenticalTo:self];
}
Assuming the "proposalWindowArray" is a static NSMutableArray.
Without hacks, there is no elegant way to keep an object retained other than having a strong reference to it in some other object. For example, you could keep a static NSMutableArray/NSMutableSet, add your controller there, and remove it in windowsWillClose:. This will be shorter than posting a notification. To make this reusable, create a WindowControllerRegistry singleton with an array, where you add controllers like this one, and which will automatically listen to NSWindowWillCloseNotification and remove them from its array thus releasing ownership.
As a quick workaround, you could perform retain/autorelease calls from non-ARC file:
my_retain(self);
my_autorelease(self);
// ArcDisabled.mm
void my_retain(id obj) { [obj retain]; }
void my_autorelease(id obj) { [obj autorelease]; }
I had this same issue when I switched to ARC. Your solution works, but you're making it too complicated. You can essentially do what you were doing before by having the window release itself when it closes, but in an ARC compatible manner.
The solution is to simply create a property of your class within the class itself. For your example, in DetailWindowController, you would add the following property:
#property (strong) DetailWindowController *theWindowController;
Then when you create the window with your code above, add one line like so:
DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:#"ThePorposalWindow"];
[preferenceController setTheWindowController:proposalWindowController];
[proposalWindowController showWindow:nil];
Then finally, to have ARC release the window when it is closed like you did with the autorelease pre-ARC, in the DetailWindowController class, simply do:
- (void)windowWillClose:(NSNotification *)notification
{
// Let ARC tear this down and clean it up
[self setTheWindowController:nil];
}

Manual binding in Cocoa

I have an ImageView which shows a lock, informing if an opened file is locked or not. I have 2 images for locked and unlocked cases. I want synchronize the displayed image with boolean value of my object representing an opened file.
To do this I want my ViewController to change the image in my ImageView depending on lock state of object. So both object and ViewController have a property "isLocked".
How can I synchronize them? It is easy in IB but I don't know how to do it programmatically. I tried in initialize method of my ViewController to use:
[ViewController bind:#"value" toObject:[ArrayController selection] withKeyPath:#"isLocked" options:nil];
But it doesn't work. In documentation it is said that I have to expose my binding before using it.
I try to put the following code in initializer method of my object:
[self exposeBinding:#"isLocked"];
But Xcode doesn't recognize this method.
Does somebody have experience with this kind of bindings establishing?
As #nick says, you want Key-Value-Observing.
[arrayController addObserver:self
forKeyPath:#"selection.isLocked"
options:NSKeyValueObservingOptionNew
context:#"this_context"]
Then when isLocked changes the -observeValueForKeyPath:ofObject:change:context: method that you have added to your viewController will be called (as long as you only manipulate isLocked in a KVC compliant way).
The options parameter lets you optionally tweak exactly what conditions will trigger the notification and what data is sent along with the notification. The context parameter is there to help you distinguish between notifications that you registered to receive and notifications your superclass registered to receive. It is optional.
Bindings seem like they might be useful to keep two values in sync. However, this is not what they do at all.
Yes, lots of things seem to give the impression that this is what they do, and there isn't much saying that this isn't what they do, also lots of people believe that this is what they do - but no, you cannot use them for this.
Only a handful of classes support bindings (they are listed here) and then, and this is the important bit, those classes only support binding their named bindings, and these bindings are not instance variables. eg NSTextField has a 'fontFamilyName' binding yet NSTextField does not have a 'fontFamilyName' property or instance variable, even a derived one. NSTextField does have a 'isBordered' property but not a binding - so you cannot bind 'isBordered'.
It does not mean anything to 'bind' an arbitrary property of an arbitrary Class.
Yes, you can bind two arbitrary values, the following code works just fine:
#import <Cocoa/Cocoa.h>
#interface SomeObject : NSObject
#property (retain,nonatomic) id someValue;
#end
#implementation SomeObject
#end
int main()
{
SomeObject *source=[SomeObject new];
SomeObject *target=[SomeObject new];
[target bind:#"someValue" toObject:source withKeyPath:#"someValue" options:0];
[source bind:#"someValue" toObject:target withKeyPath:#"someValue" options:0];
[source setSomeValue:#(42)];
NSLog(#"target: %#",[target someValue]);
[target setSomeValue:#(22)];
NSLog(#"source: %#",[source someValue]);
return 0;
}
As far as I can tell, the problem is the bit [ArrayController selection]. The first problem is that ArrayController is (or should be) a class, and getting the class's selection is probably pointless. The other problem is that even if this were an instance, you would be binding to the selection at the time of the call, which is almost certainly not what you want. You want to track the current selection as it changes.
So what you want is probably something like the following:
[myViewController bind:#"value" toObject:myArrayController withKeyPath:#"selection.isLocked" options:nil];

Cocoa NSTabView coding style question

I have a coding style question which probably should be asked of a senior mac programmer at work - but since I'm the only mac programmer, well, SO it is. I have a pop-up GUI for my software (3D models, data visualization) and the pop-up is Mainly a Tabbed control with a ton of stuff in each tab (sliders, radio buttons, checkboxes, etc.) With something like 20 controls per tab, and maybe half a dozen tabs... using a single controller for all the views is going to get unwieldly very quickly.
Is having a MainViewController which loads a bunch of Tabs good style?
NSView *tabA = [[NSView alloc] initWithNibName:#"tabA.nib" bundle:[NSBundle bundleWithPath:#"/Applications/BOB.app"]];
NSView *tabB = [[NSView alloc] initWithNibName:#"tabB.nib" bundle:[NSBundle bundleWithPath:#"/Applications/BOB.app"]];
It's kindof how I do it on iOS, but I'm not sure for Mac OS X. I prefer a style that offers maintainability and flexibility, as the code is going through prototyping and I may need to change it frequently.
If it's not good style, what is?
Thanks!
I think yours is a reasonable style. You create an NSViewController subclass for each tab, and assign it to the NSTabView using NSTabViewItem.
By the way, I think it's better to have
NSViewController *tabAcontroller = [[TabAController alloc] init];
with #interface TabAController:NSViewController ... #end with init defined as
-init{
self=[super initWithNibName:#"tabA" bundle:nil];
if(self){
...
}
return self;
}
Note that you don't need the extension .nib when you call initWithNibName:bundle:. And you should not specify the hard-coded path of the app. In iOS, the app's position is a given by the OS (with cryptic folder names,) but on OS X a user can freely move the app bundle to anywhere he wants. So, never refer to the main bundle as [NSBundle bundleWithPath:#"hard coded path"]. Use just [NSBundle mainBundle], or just nil in most cases. It's written in the documentation when you can just use nil.

Resources