Cocoa NSTabView coding style question - cocoa

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.

Related

Trouble matching the vibrant background of a Yosemite NSMenuItem containing a custom view

I am attempting to add a custom view to an NSMenuItem in the OS X 10.10 Yosemite menu bar.
The custom view is simply an NSView background with an NSTextField “label”.
The problem is that the background NSView is given Yosemite-style vibrancy/transparency when added to the menu. The NSTextfield label is not.
Through the use of NSRectFillUsingOperation I've gotten this to look good for some background colors in Yosemite. But others continue to not match. When it is working, after manually "highlighting" the view, the original colors change and no longer match. I can dig up some example code for this if needed.
Then, when it is looking somewhat good in Yosemite, it looks terrible in 10.9 Mavericks.
I've also tried setting the wantsLayer property to YES to turn the view into a CALayer-backed view. This creates other issues such as text not anti-aliasing correctly against a clear background.
My Question:
How do I display a label on top of a NSMenuItem custom view? The label's background must exactly match the view's background. Solution must work in Yosemite and Mavericks.
Example code below:
self.statusItem = [[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength];
[self.statusItem setTitle:#"TEST"];
[self.statusItem setHighlightMode:YES];
[self.statusItem setEnabled:YES];
[self.statusItem setTarget:self];
NSMenu *menu = [[NSMenu alloc] init];
[menu addItemWithTitle:#"Disabled menu item" action:nil keyEquivalent:#""];
[menu addItemWithTitle:#"Enabled menu item" action:#selector(enabled) keyEquivalent:#""];
NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(30, 20, 50, 20)];
label.stringValue = #"label";
label.editable = NO;
label.bordered = NO;
label.backgroundColor = [NSColor blueColor];
//label.backgroundColor = [NSColor clearColor];
PKMenuItemView *view = [[PKMenuItemView alloc] initWithFrame:NSMakeRect(0, 0, 200, 50)];
[view addSubview:label];
NSMenuItem *viewMenuItem = [[NSMenuItem alloc] init];
[viewMenuItem setView:view];
[menu addItem:viewMenuItem];
self.statusItem.menu = menu;
I've subclassed the NSView to override drawRect: and draw a colored background:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[[NSColor blueColor] setFill];
NSRectFill(dirtyRect);
//NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
}
It is surely kinda hack, but it worked for me.
Try adding an NSImageView with empty image to your custom view. Image view must be occupy the whole view.
I think I have less "hackish" solution. It's indeed caused by the new NSVisualEffectView and Vibrancy stuff in Yosemite. I learned that there are quite complex rules how views are drawn when they're subviews of NSVisualEffectView. It was discussed on WWDC 2014 in session 220 - Adopting Advanced Features of the New UI of OS X Yosemite. I recommend you to watch this session video to get comprehensive explanation.
Shortly, it seems that your problem may be caused by colors you use. There are two new system colors - [NSColor labelColor] and [NSColor secondaryLabelColor]. These two are automatically adjusted when drawn inside NSVisualEffectView. Also, your custom view should support Vibrancy effect. This is done by overriding - (BOOL)allowsVibrancy method and returning YES.
Please check the session video mentioned above or download session slides in PDF to get precise information. This stuff is discussed from slide 124 in PDF and near the middle of the video.
Unfortunately there are currently several problems in Yosemite. As Matthes already mentioned, you can use labelColor() and secondaryLabelColor(). Using those colors do not cause the label to draw the strange background you are seeing.
However, labelColor() only works fine for VibrantDark because there the label color is white when a NSMenuItem is both highlighted and when not highlighted. With VibrantLight the labelColor is black and is therefore very difficult to read on on top of the blue highlight.
For the highlight color of the custom view of your NSMenuItem one might think that you should use selectedMenuItemColor() given its name. The problem with this is that the color doesn't actually match the menu highlight color that you see in NSMenuItems without a custom view. The color is completely wrong for both VibrantLight and VibrantDark.
Tl;dr: So how can you create a custom NSMenuItem that uses the exact same text color and highlight color? You can't. You should use labelColor() and selectedMenuItemColor() but the former only works correctly for VibrantDark, and the latter doesn't match at all.
I really hope I am wrong because I am trying to achieve the same thing :(
Edit: Here is an example project if people want to have a look.
Response from a Apple Developer Technical Support ticket I opened in 2015:
Re: DTS Auto-Ack - Vibrant background and highlighting of Custom View NSMenuItems
This is a difficult problem to tackle, especially in light of the fact that menu selection drawing was not intended for menu items with custom views, and menu selection drawing (colors, etc.) may change in the future. This is why we ask you to file bug reports so that menu selection will be honored with custom views, if asked for, so that future changes to OS X won’t require developers to continually maintain their code to match future color appearances.
The “Application Menu and Pop-up List Programming Topics” says this:
Views in Menu Items -
“A menu item with a view does not draw its title, state, font, or other standard drawing attributes, and assigns drawing responsibility entirely to the view. Keyboard equivalents and type-select continue to use the key equivalent and title as normal.”
Since all drawing is up to the developer, custom views in menu items aren’t necessarily supposed to draw “selected”.
The APIs to obtain the right selection color is obviously not doing what it’s supposed to, hence the request to file a bug report. I wish we could offer more concrete solutions to the problem but a workaround offered today may not hold up tomorrow and we don’t want to set a bad precedent on workarounds that are risky. Apple apps have access to lower level private APIs that achieve their results. We cannot offer you these solutions as they are private.
If selectedMenuItemColor() does not match the menu highlight color with Vibrant light and dark, that’s a bug to be filed and to be fixed.
Lastly, Apple recommends to use NSMenuItem’s APIs as much as possible to achieve what you want in menus. The screenshots you included can likely be done without applying custom views.
I've just discovered that +[NSColor keyboardFocusIndicatorColor] is the right color (on El Capitan at least), whereas the expected selectedMenuItemColor is by far too dark.
Per AppKit engineers at WWDC, this doesn't really work with NSMenuItem. I added that answer to this question as well.
They suggested to instead use an NSPopover to create a faux-NSMenu attached to an NSStatusItem menu bar helper.
Using code similar to the below results in vibrant background selection:
override func viewDidLoad() {
super.viewDidLoad()
let visualEffectView = NSVisualEffectView()
visualEffectView.material = .selection
// .menu or .popover for the non-selected background.
visualEffectView.state = .active
visualEffectView.blendingMode = .behindWindow
visualEffectView.isEmphasized = true
let label = NSTextField(labelWithString: "Hello, world!")
label.cell?.backgroundStyle = .emphasized
visualEffectView.addSubview(label)
visualEffectView.frame = view.bounds
label.setFrameOrigin(.zero)
view.addSubview(visualEffectView)
}
At the WWDC 2019 AppKit Lab I worked through this issue with engineers from the AppKit team.
They were surprised that it did not work by default, and encouraged me to file (more) radars:
FB6143574 - Expose private API for NSMenuItem _viewHandlesEvents
They were aware of a private API _viewHandlesEvents on NSMenuItem.
// VibrantMenuBar-Bridging-Header.h
#import <AppKit/AppKit.h>
#interface NSMenuItem ()
#property (setter=_setViewHandlesEvents:) BOOL _viewHandlesEvents;
#end
Set viewHandlesEvents to false and the background of the custom view in the NSMenuItem will be selected and appear (somewhat) as expected.
There are still issues with how labels and other subviews react to the selection. Text View text is not properly changing color.
let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
menuItem.view = label
menuItem._viewHandlesEvents = false
There are some other references to _viewHandlesEvents on the internet:
How to flash a custom NSMenuItem view after selection?

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

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

How do I change a Cocoa application's title bar color?

I need to change the application's title bar color in OS X. I would preferably like a solution involving Cocoa or Carbon, though I can do other venues if necessary. Essentially, a client of mine wants the title bar to be, say, black, as opposed to gray. I know this is possible as OS X's Reminders app does this, as does Firefox with personas and Google Chrome with themes.
I know that this has the potential of ruining the user experience, and I've considered other options, but this is the way the client wants it and I need to deliver. I can't really use a title-bar less window as the window is already designed and I'm sure that would kill the user experience a whole more than simply changing the window color would.
Any thoughts would be highly appreciated.
Take a look at Apple's Core Data Stickies sample app
1. Create borderless window.
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(unsigned int)styleMask
backing:(NSBackingStoreType)backingType
defer:(BOOL)flag
{
if (self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:backingType
defer:flag]) {
[self setBackgroundColor:[NSColor yellowColor]];
[self setHasShadow:YES];
}
return self;
}
- (BOOL)canBecomeKeyWindow { return YES; }
2. Place view on that window and implement custom title bar.
OUTPUT

Storyboard in Xcode is so slow

I have 150 UIViewController in Storyboard and scrolling between these Views is so slow. I can't zoom in and zoom out easily and it takes some serious time to do sty.
I'm on MBPR and I installed Xcode 4.4
Spec: 2.3GHz / 16G / 256 which I think it's enough to handle such a thing.
Is there any options, settings, or tips/tricks to have so many views in storyboard and don't miss the performance.
NOTE: I've done all the possible solutions (deleting cache and workspace). Didn't work. It has something to do with number of UIViewController in Storyboard.
Thanks
Update 2016: Just to update this question as there is a new feature in Xcode 7 that allows you to refactor the Storyboard into multiple Storyboards.
Refactoring Storyboards
https://developer.apple.com/library/ios/recipes/xcode_help-IB_storyboard/Chapters/RefactorStoryboard.html
If you search the term "refactoring storyboards" you will find good tutorials :)
It is considered best practice to split up storyboards into lots of different modules (each one in a separate storyboard). It will take away these performance issues you are having and also has other advantages such as making it easier to manage in general (no massive SVN conflicts etc).
However I had another issue which was causing storyboard lag. I had approx 25 view controller and was receiving ALOT of lag - but only when Xcode was running on an external monitor.
I noticed that if I disabled "auto layout" for the storyboard, the lag would completely disappeared. I reverted this change, and then followed the following process:
-Delete a ViewController
-test if it still lags
-if still laggy revert changes
Eventually I found a certain ViewController which if deleted stopped all lag. I then reverted this and went through the views to see which view caused the lag. I eventually narrowed this down to a "UIButton" inside a "UIBarButtonItem". I believe I changed the "Type" property on the button, and then changed it back and the lag stopped. From SVN it seems like the frame was changed in the .storyboard file. After this point the lag never came back again.
TLDR:
Storyboard lag is not always because you have too many items in the storyboard. I managed to get rid of a lag problem by causing Xcode to re-do some layout.
Hope my experience will help someone else diagnose/solve their problems. I was working for about 0.5 years before I finally got really annoyed and tried to solve the issue.
Definitely use multiple storyboards for this. I am not aware of any limitations in using storyboards but trying to render all those UI code at one time is hard for your machine and it is also hard for developers to understand.
Try to logically divine your storyboards into categories such as: "profileSB, feedSB, mapSB, messagesSB, settingsSB"
Here are some good tutorials on the creation of these:
http://spin.atomicobject.com/2014/02/18/ios-storyboards-xcode5/
http://www.skillmasters.net/main/xcode-using-multiple-storyboards/
150 ViewControllers in one Storyboard sounds awful lot to me.
Try to minimize your flow or split into multiple storyboards if you really need so many
I had a UIStoryboard with 10 or more UIViewControllers and additional ContainerViews. After layouting the views and customizing more and more, the UIStoryboard got more and more lazy.
My approach was to setup the views inside single UIStoryboards. Loading the controllers is done inside my Menu, where I setup an NSArray with all identifiers for the UIViewController which also have to be setup inside the UIStoryboard:
When loading the menu, I loop through the NSArray and load the UIViewControllers by identifiers from the specific UIStoryboard. This is the place where I needed to implement a switch, for the different UIStoryboards:
self.arrayVCAll = [NSMutableArray new];
for ( NSArray *array in _arrayViewControllerAll ){
NSMutableArray *arrayTemp = [NSMutableArray new];
for (UIViewController *vc in array ){
NSString *strViewController = [NSString stringWithFormat:#"%#", vc];
UIStoryboard *storyboard;
if( [strViewController isEqualToString:#"CustomOneStoryboard"] ){
storyboard = [UIStoryboard storyboardWithName:#"FirstVC" bundle:nil];
} else if( [strViewController isEqualToString:#"CustomTwoStoryboard"] ){
storyboard = [UIStoryboard storyboardWithName:#"SecondVC" bundle:nil];
} else {
storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
}
UIViewController *controller = [storyboard instantiateViewControllerWithIdentifier:strViewController];
MyNavController *nav = [[MyNavController alloc] initWithRootViewController:controller];
[arrayTemp addObject:nav];
}
[self.arrayVCAll addObject:arrayTemp];
}
In my case, there was just a problem with the segues after separating the initial UINavigationController from my UIViewControllers. The segues won't push to a navigationController, if there is no initial UINavigationController. Thats why I added a UINavigationController on each UIViewController (of my NSArray) so the UIStoryboardSegue will be done correctly. The UINavigationController also doesn't need to be connected to a class, just include it inside the UIStoryboard and connect it to the first UIViewController.

How Can You Use An Image As A Background For A Text Field In Cocoa?

Is it possible to use an image as a background for a Text Field in Cocoa?
If so, how?
I don't know if this is the "correct" way to do it, but the first thing that comes to mind would be to make a custom subclass of NSTextField, which might look roughly like this:
- (void)awakeFromNib
{
[self setDrawsBackground:NO];
}
- (void)drawRect:(NSRect)rect
{
[super drawRect:rect];
[self lockFocus];
[[NSImage imageNamed:#"<#image filename#>"] drawInRect:rect
fromRect:rect
operation:NSCompositeSourceOver
fraction:1.0];
[self unlockFocus];
}
Again, that's a just rough outline of the essential parts.
Anyways, like I said, I'm not sure if this really the "correct" way to do it (or if there is even a "correct" way, for that matter), but this will give you a background image for your NSTextField.
Edit in response to Joshua's comment (I'm not going to have enough room in that tiny little comment box):
To add the image into your project, you'd drag it from wherever it is into the project window (the main list of files in the middle of the project window, although depending on how you've set up your Xcode editing environment, this might be different for you).
In order to subclass NSTextField, you would want to create a new Objective-C class file (File -> New File…), but edit the header so that the class inherits from NSTextField instead of NSObject. In other words, your header file might look like this:
#import <Cocoa/Cocoa.h>
#interface BGImageTextField : NSTextField
{
}
#end
As for the rest of the code, you would want to add that in the main body of the implementation file (BGImageTextField.m, for example), specifically in between the #implementation and #end keywords.
I'd also like to mention two things. First, I'd recommend picking up a copy of Cocoa Programming for Mac OS X, by Aaron Hillegass—it covers most of the Cocoa basics that I just went over, and is one of the best ways to learn Cocoa in general. Secondly, although my approach works, it's probably not the best approach—especially since I just recently found this post, which seems to hint at a better way of extending NSTextField.
Instead of subclassing NSTextField, just make the background color transparent [NSColor clearColor] and put the image behind it.

Resources