How to properly set an NSSegmentedControl enabled - objective-c

I'd like to have my NSSegmentedControl with a segment selected when enabled and with no segment selected while disabled (the kind of behavior that the view NSSegmentedControl in iTunes has).
Here some images:
enabled and selected
disabled correctly
disabled but not correctly
(*) I recognize that I could write a function to call whenever the BOOL property changes and in this function I could set all the segments desected or select the appropriate one, BUT I'd like to know if there's a way to accomplish this through Cocoa Bindings or Interface Builder.
UPDATE: added some images of the problem

EDIT: I am not completely sure about this, but i think 'No Selection Placeholder' is your best bet. http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CocoaBindingsRef/Concepts/BindingsOptions.html%23//apple_ref/doc/uid/20002304-187525
I still think you would have to programmatically specify no selection when you conditionally disable the control though.

The programmatic solution can be something like this:
- (void)setSegmentEnabled:(BOOL)enabled{
if (enabled)
{
int vState = [[NSUserDefaults standardUserDefaults] integerForKey:#"SelectedSegmentView"];
[viewSegment setSelectedSegment:vState];
segmentEnabled = YES;
}
else
{
[viewSegment setSelected:NO forSegment:0];
[viewSegment setSelected:NO forSegment:1];
[viewSegment setSelected:NO forSegment:2];
segmentEnabled = NO;
}
}
I'm just implementing my own setter for the BOOL property segmentEnabled which is being binded with the viewSegment

Related

NSWindow with NSWindowTitleVisibilityNone saving incorrect frame to user defaults?

My app has an option that allows the user to choose between the standard "full-size" window titlebar/toolbar and the "compact" titlebar/toolbar made available in the NSWindow 10.10 API. Specifically, I'm using the -titleVisibility method to set either NSWindowTitleVisible or NSWindowTitleHidden depending on the user's preference. If the user checks the "Compact Titlebar" checkbox, NSWindowTitleHidden is applied to the window, otherwise, the window uses the default style. When the checkbox value changes, the value is stored in the app's user defaults, and the window is updated/redrawn.
Everything works great until the application is relaunched. Each time the app starts up, the window grows by exactly how much space is saved by switching from the default window style (NSWindowTitleVisible) to the new style (NSWindowTitleHidden). So restarting the app 5 – 6 times will make the window flush with the menubar and the dock, depending on how big the window was when the checkbox was initially checked.
In other words, it doesn't seem like the window's frame is being updated in NSUserDefaults when the property is set. Is there a workaround for this, or am I just overlooking something? Any advice would be muy helpful.
Thanks!
A better (and confirmed working) solution was posted at https://openradar.appspot.com/18510665 by pointum:
The problem is that window size is restored by the system using -[NSWindow setFrameUsingName:] before titleVisibility is set. Solution:
Remove "Autosave Name" value in Interface Builder.
Set it in code right after setting titleVisibility using -[NSWindow setFrameAutosaveName:].
Try setting the titleVisibility property to the number 1 in the User Defined Runtime Attributes
1 is the corresponding value for NSWindowTitleHidden
typedef NS_ENUM(NSInteger, NSWindowTitleVisibility) {
/* The default mode has a normal window title and titlebar buttons. */
NSWindowTitleVisible = 0,
/* The always hidden mode hides the title and moves the toolbar up into the area previously occupied by the title. */
NSWindowTitleHidden = 1,
} NS_ENUM_AVAILABLE_MAC(10_10);
However this would print a message to the console complaining that NSWindow is not key value coding-compliant for the key titleVisibility on OS X versions previous to 10.10
Simple fix for now is to save and restore the window's frame manually, here's how I do it:
In your app delegate, when application terminates, save the window's frame
- (void)applicationWillTerminate:(NSNotification *)notification
{
[[NSUserDefaults standardUserDefaults] setObject:NSStringFromRect(self.windowController.window.frame) forKey:#"WindowFrameKey"];
}
In your window controller's -awakeFromNib method, restore the frame
- (void)awakeFromNib
{
if([NSWindow instancesRespondToSelector:#selector(setTitleVisibility:)])
{
// Hide Titlebar
[self.window setTitleVisibility:NSWindowTitleHidden];
NSString *winFrameString = [[NSUserDefaults standardUserDefaults] stringForKey:#"WindowFrameKey"];
if(winFrameString != nil)
{
NSRect savedRect = NSRectFromString(winFrameString);
if(!NSEqualRects(self.window.frame, savedRect))
{
[self.window setFrame:savedRect display:YES animate:NO];
}
}
}

iOS 8 UINavigationController disable Back Button

in my Navigation Controller I need to temporarily disable the back button. I know that it can be hidden using the following or something similar:
[self.navigationController.navigationItem setHidesBackButton:YES animated:YES];
But that is not what I need, instead I want the back button to be greyed out and non-responsive to user touch events. Is their a way to achieve this without replacing the default back button?
Thanks in advance!
To disable the back button, these commands would make it do what you want it to do:
Enable:
self.navigationController.navigationBar.userInteractionEnabled = YES;
self.navigationController.navigationBar.tintColor = [UIColor blueColor];
Disabled:
self.navigationController.navigationBar.userInteractionEnabled = NO;
self.navigationController.navigationBar.tintColor = [UIColor lightGrayColor];
Update:
As of iOS 7, there's also a swipe that you'll want to disable on the UINavigationBar.
// You wrap it an 'if' statement so it doesn't crash
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
// disable the interactivePopGestureRecognizer
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
This hides the back button, so it becomes unreachable for the user. And it disables the swipe gesture.
[self.navigationItem setHidesBackButton:YES animated:YES];
Swift:
navigationItem.setHidesBackButton(true, animated: true)
See more info in Apple's documentation.
I know this is quite old but I had this problem too.
In my case in one scenario I had to disable the back button and in another one I had to disable all navigation buttons. my solution was disabling the navigation bar in total in both scenarios:
self.navigationController.view.userInteractionEnabled = NO;
This won't show the buttons as disabled but will prevent touches.
Hope this will help
I believe that following should help:
self.navigationController.navigationItem.backBarButtonItem.enabled = NO;
UPDATE
Sorry guys, my belief didn't come true.
It seems that property backBarButtonItem is designed only for setting custom title or image for Back Button.
From documentation:
If you want to specify a custom image or title for the back button,
you can assign a custom bar button item (with your custom title or
image) to this property instead. When configuring your bar button
item, do not assign a custom view to it; the navigation item ignores
custom views in the back bar button anyway.
The default value of this property is nil.
Unfortunately I didn't find any way of disabling back button with saving its native look and behaviour, because any time when I try to set custom UIBarButtonItem into navigationItem.backBarButtonItem property - it gets updated with appropriate native back button style and it always has enabled == YES.
I think this is done by Apple for a reason because we basically shouldn't force the user to stay on a detail screen and disable him from going back.
Also, in iOS7 and later user always can use swipe-from-left-edge gesture (if you don't disable it) to go back.
The only one ugly thing that I can recommend is to create a custom UIBarButtonItem and set it into leftBarButtonItem with 'Back' title, target and selector which will pop your viewController. By default it will substitute native back button.
Then you can disable it as usual using navigationItem.leftBarButtonItem.enabled = NO.
Unfortunately it will not look and act (in case of title updating depending on available space) as native back button :(
Just set a disabled back button on the navigation item of the previous view controller. Don't try to disable your custom back button if you already had one, won't work. Just set a new one which is disabled. You can reach the previous navigation item through the UINavigationBar.backItem property.
// set disabled back button
let backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItem.Style.plain, target: nil, action: nil)
backButton.isEnabled = false
navigationController?.navigationBar.backItem?.backBarButtonItem = backButton
// disable pop gesture
navigationController?.interactivePopGestureRecognizer?.isEnabled = false

NSStatusItem change image for dark tint

With OSX 10.10 beta 3, Apple released their dark tint option. Unfortunately, it also means that pretty much all status bar icons (with the exception of Apple's and Path Finder's that I've seen), including mine, remain dark on a dark background. How can I provide an alternate image for when dark tint is applied?
I don't see an API change on NSStatusBar or NSStatusItem that shows me a change, I'm assuming it's a notification or something reactive to easily make the change as the user alters the tint.
Current code to draw the image is encased within an NSView:
- (void)drawRect:(NSRect)dirtyRect
{
// set view background color
if (self.isActive) {
[[NSColor selectedMenuItemColor] setFill];
} else {
[[NSColor clearColor] setFill];
}
NSRectFill(dirtyRect);
// set image
NSImage *image = (self.isActive ? self.alternateImage : self.image);
_imageView.image = image;
}
TL;DR: You don't have to do anything special in Dark Theme. Give NSStatusItem (or NSStatusBarButton) a template image and it will style it correctly in any menubar context.
The reason why some apps' status items (such as PathFinder's) already work in Dark Theme is because they're not setting their own custom view on the StatusItem, but only setting a template image on the StatusItem.
Something like:
_statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
NSImage *image = [NSImage imageNamed:#"statusItemIcon"];
[image setTemplate:YES];
[_statusItem setImage:image];
This works exactly as you'd expect in Mavericks and earlier, as well as Yosemite and any future releases because it allows AppKit to do all of the styling of the image depending on the status item state.
Mavericks
In Mavericks (and earlier) there were only 2 unique styles of the items. Unpressed and Pressed. These two styles pretty much looked purely black and purely white, respectively. (Actually "purely black" isn't entirely correct -- there was a small effect that made them look slightly inset).
Because there were only two possible state, status bar apps could set their own view and easily get the same appearance by just drawing black or white depending on their highlighted state. (But again note that it wasn't purely black, so apps either had to build the effect in the image or be satisfied with a hardly-noticeable out of place icon).
Yosemite
In Yosemite there are at least 32 unique styling of items. Unpressed in Dark Theme is only one of those. There is no practical (or unpractical) way for an app to be able to do their own styling of items and have it look correct in all contexts.
Here are examples of six of those possible stylings:
Status items on an inactive menubar now have a specific styling, as opposed to a simple opacity change as in the past. Disabled appearance is one other possible variation; there are also other additional dimensions to this matrix of possibilities.
API
Arbitrary views set as NSStatusItem's view property have no way to capture all of these variations, hence it (and other related API) is deprecated in 10.10.
However, seed 3 introduces new API on NSStatusItem:
#property (readonly, strong) NSStatusBarButton *button NS_AVAILABLE_MAC(10_10);
This piece of API has a few purposes:
An app can now get the screen position (or show a popover from) a status item without setting its own custom view.
Removes the need for API like image, title, sendActionOn: on NSStatusItem.
Provides a class for new API: i.e. looksDisabled. This allows apps to get the standard disabled/off styling (like Bluetooth/Time Machine when off) without requiring a custom image.
If there's something that can't be done with the current (non- custom view) API, please file an enhancement request for it. StatusItems should provide behavior or appearances in a way that it standard across all status items.
More discussion is at https://devforums.apple.com/thread/234839, although I've summarized most everything here.
I end up did something like following to my custom drag and drop NSStatusItemView: (Using Swift)
var isDark = false
func isDarkMode() {
isDark = NSAppearance.currentAppearance().name.hasPrefix("NSAppearanceNameVibrantDark")
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
isDarkMode()
// Now use "isDark" to determine the drawing colour.
if isDark {
// ...
} else {
// ...
}
}
When the user changed the Theme in System Preferences, the NSView will be called by the system for re-drawing, you can change the icon colour accordingly.
If you wish to adjust other custom UI outside this view, you can either use KVO to observer the isDark key of the view or do it on your own.
I created a basic wrapper around NSStatusItem that you can use to provide support for 10.10 and earlier with custom views in the status bar. You can find it here: https://github.com/noahsmartin/YosemiteMenuBar The basic idea is to draw the custom view into a NSImage and use this image as a template image for the status bar item. This wrapper also forwards click events to the custom view so they can be handled the same way as pre 10.10. The project contains a basic example of how YosemiteMenuBar can be used with a custom view on the status bar.
Newest swift code set image template method is here:
// Insert code here to initialize your application
if let button = statusItem.button {
button.image = NSImage(named: "StatusIcon")
button.image?.isTemplate = true // Just add this line
button.action = #selector(togglePopover(_:))
}
Then it will change the image when dark mode.
When your application has drawn any GUI element you can get its appearance via [NSAppearance currentAppearance] which itself has a name property that holds something like
NSAppearanceNameVibrantDark->NSAppearanceNameAqua->NSAppearanceNameAquaMavericks
The first part is the appearance’s name, which is also available as a constant in NSAppearanceNameVibrantDark or NSAppearanceNameVibrantLight.
I don’t know if there’s a way to get just the first part, but I think this does the trick for now.
Example code:
-(void)awakeFromNib {
NSStatusItem* myStatusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
myStatusItem.title = #"Hello World";
if ([[[NSAppearance currentAppearance] name] containsString:NSAppearanceNameVibrantDark]) {
myStatusItem.title = #"Dark Interface";
} else {
myStatusItem.title = #"Light Interface";
}
}
But just in case you do want to monitor the status changes you can. I also know there is a better way to determine lite/dark mode than what's been said above, but I can remember it right now.
// Monitor menu/dock theme changes...
[[NSDistributedNotificationCenter defaultCenter] addObserver: self selector: #selector(themeChange:) name:#"AppleInterfaceThemeChangedNotification" object: NULL];
//
-(void) themeChange :(NSNotification *) notification
{
NSLog (#"%#", notification);
}

How can I bind to the Image property of a NSPopUpButtonCell?

I've created a NSTableView, and have set up all my bindings (via an NSArrayController). I want one of the columns to have a NSPopUpButtonCell, but, instead of showing text, I want it to show an image - so the user can change the image from the popup control.
My column is bound (using the SelectedObject binding) to the correct property, and the the NSPopUpButtonCell is bound (using the ContentValues binding) to my complete list of images (which is from another NSArrayController containing an array of image names - NSStrings).
I've created a NSValueTransformer to convert an image name (NSString) to an NSImage, and have tested this works by creating an addition column with an NSImageCell, and that displays the image correctly.
Is there a way to do this?
I'm hoping I can target the binding at the NSPopUpButtonCell's image property, rather than its title property.
Edit: I had the idea of trying to use an NSAttributedString, with an embedded image. But, the problem remains, in that I want the binding to use setAttributedTitle, rather than setTitle.
After a lot of trial and error, I've managed to solve this problem. I'm convinced there remains a more correct solution, but, for now, this'll do.
My solution:
Change the Pop Up Button Cell > Menu to a custom class
In the custom class, inherit from NSMenu:
#interface RSMenu : NSMenu
Override insertItemWithTitle, and customize to your own needs.
For example:
-(NSMenuItem *)insertItemWithTitle:(NSString *)aString
action:(SEL)aSelector
keyEquivalent:(NSString *)charCode
atIndex:(NSInteger)index {
NSImage *image = [NSImage imageNamed:aString];
NSMenuItem* menuItem = [super insertItemWithTitle:image ? #"" : aString
action:aSelector
keyEquivalent:charCode atIndex:index];
if ( nil != image ) {
[menuItem setImage:image];
}
return menuItem;
}
And here is the result:
Hope this helps someone else.

Determining which NSView instance initiated a mouseDown:

I have a gameboard with 25 tiles of myGameTile, a subclass of NSView.
In mouseDown: I want to determine which tile I clicked on and set an ivar to a representative value.
e.g. If I click on tile 12, set clickedTile to "12" or some value that uniquely represents that particular instance.
I'm open anything from the integer value 12 all the way to some sort of introspection/reflection, though built-in features and elegance are preferable to hacks, runtime wrappers, and modification. Still, I'm aware that I may have no choice but to rely on those solutions, so please to answer with those as well. I'd like to know all my options. Thanks!
You could subclass NSView and override the tag method, as written in the documentation.
You have several possibilities:
If you handle the mouseDown in the Tile view, then you need to map self to the tile ID. There are three easy ways to do this:
Pre configure Tile.tag to be the tile ID, then use self.tag.
Search for Tile in the array of Tiles to find the index [parent.tiles indexOfObject:self]
Create a dictionary mapping Tile or tile ID [[parent.tiles objectForKey:self] intValue]
Clearly, using the tag is the easiest, as long as you are not using the tag for anything else.
Alternatively, you could implement hitTest on the parent view, and return the parent view, and then handle the mouseDown in the parent view. Then mouseDown will know where the hit is and hence which tile it is.
I think hitTest: will do what you want. Something like this:
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint aPoint = [theEvent locationInWindow];
NSView* viewHit = [self hitTest:aPoint];
if( viewHit != nil && viewHit != self ) {
// viewHit is our tile.
}
}