Validating fonts and colors NSToolbarItem items - objective-c

Using Cocoa with latest SDK on OSX 10.6.6
I have an NSToolbar with custom toolbar items and also the built in fonts and colors NSToolbarItem items (NSToolbarShowFontsItem and NSToolbarShowColorsItem identifiers).
I need to be able to enable/disable those in various situations. Problem is validateToolbarItem: is never called for these items (it is being called for my other toolbar items).
The documentation is not very clear about this:
The toolbar automatically takes care
of darkening an image item when it is
clicked and fading it when it is
disabled. All your code has to do is
validate the item. If an image item
has a valid target/action pair, then
the toolbar will call
NSToolbarItemValidation’s
validateToolbarItem: on target if the
target implements it; otherwise the
item is enabled by default.
I don't explicitly set target/action for these two toolbar items, I want to use their default behavior. Does it mean I can't validate these items? Or is there any other way I can do this?
Thanks.

After some trial and error, I think I was able to figure this out and find a reasonable workaround. I will post a quick answer here for future reference for others facing the same problem.
This is just one more of Cocoa's design flaws. NSToolbar has a hardcoded behavior to set the target/action for NSToolbarShowFontsItem and NSToolbarShowColorsItem to NSApplication so as the documentation hints it will never invoke validateToolbarItem: for these NSToolbarItem items.
If you need those toolbar items validated, the trivial thing to do is not to use the default fonts/colors toolbar items but to roll your own, calling the same NSApplication actions (see below).
If using the default ones, it is possible to redirect the target/action of them to your object and then invoke the original actions
- (void) toolbarWillAddItem:(NSNotification *)notification {
NSToolbarItem *addedItem = [[notification userInfo] objectForKey: #"item"];
if([[addedItem itemIdentifier] isEqual: NSToolbarShowFontsItemIdentifier]) {
[addedItem setTarget:self];
[addedItem setAction:#selector(toolbarOpenFontPanel:)];
} else if ([[addedItem itemIdentifier] isEqual: NSToolbarShowColorsItemIdentifier]) {
[addedItem setTarget:self];
[addedItem setAction:#selector(toolbarOpenColorPanel:)];
}
}
Now validateToolbarItem: will be called:
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem {
//validate item here
}
And here are the actions that will be invoked:
-(IBAction)toolbarOpenFontPanel:(id)sender {
[NSApp orderFrontFontPanel:sender];
}
-(IBAction)toolbarOpenColorPanel:(id)sender {
[NSApp orderFrontColorPanel:sender];
}
I guess the engineers who designed this never thought one would want to validate fonts/colors toolbar items. Go figure.

Related

iOS7 Keyboard Behavior with SearchBar/UITableView: Offset on secondary view appearances

Problem
I am having a rather big issue with the iOS7 keyboard appearance. I have a Searchbar on a UIViewController with TableView Delegation/Data Source setup (I am using the self.searchDisplayController delegates as well). I segue from this scene to a prototype tableview to show the results.
Here is the issue:
On first load I can see the keyboard being displayed when I tap into the text field of the UISearchBar. I can type and perform a search with the results being shown in the next scene.
I've added NSNotifications to view the keyboard properties in local methods keyboardWillShow and keyboardWasShown. I can see on the first scene appearance (after the view is completely loaded):
I segue to the result tableview at this point and when I navigate back and touch the text field, my keyboard shows up either fully or partially off-screen:
When I look at the keyboardWillShow notification at this point I can see that my keyboard values are incorrect:
I've researched and tried many possibilities including:
Added the following to my main view controller:
-(BOOL)canResignFirstResponder
{
return YES;
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
Configured the following in my view did load
self.searchDisplayController.searchBar.spellCheckingType = UITextSpellCheckingTypeNo;
self.searchDisplayController.searchBar.autocapitalizationType= UITextAutocapitalizationTypeNone;
self.searchDisplayController.searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
self.searchDisplayController.searchBar.keyboardType = UIKeyboardTypeDefault;
Put in standard stubs for:
-(void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView
-(void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
I've noticed that if I choose a Partial Curl as my segue mode, the keyboard remains accessible when I roll back to the main view controller (but then it was never fully off screen in that case). However if I move from the results tableview to a detail scene and then navigate back to the main view controller, the keyboard appears off-screen again.
Question
Is there a method I can use to intercept the misplaced keyboard so that it displays in the default location?
NB: Along these lines, I have created a NSDictionary property to hold the initial userInfo values with the correct keyboard placement. I am not sure how to reassign these values to get the keyboard to return to it's original placement.
BTW - This seems a bit of a hack to get the keyboard fixed due to a bug in IB, is there some other way that I can try to remedy the situation?
Thanks in advance for any feedback!
Solution
This was such an obscure issue that I'm sharing the solution to save the next person some effort. Like most programming issues, it turns out this one was self-inflicted. In my original iteration of this project I had turned off rotational support as I am learning auto-layout and I wanted to ease into the transition from Springs and Struts. Somehow between the start of the project and the code release I ended up with this bit of code in the Main Scenes' View Controller.
//BAD
- (NSUInteger) supportedInterfaceOrientations
{
return !UIInterfaceOrientationPortraitUpsideDown;
}
instead of returning a valid enumeration like...
//OK
- (NSUInteger) supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}

Disable menu highlight when pressing shortcut key

I would like to disable the Application "menu highlight" that happens when you press a shortcut key assigned to an NSMenuItem that belongs to the specific menu in question.
The issue is that in the application you use the keyboard quite a bit and having the menus becoming highlighted all the time becomes a bit annoying but I still want to have the menus (including the shortcuts) there as it shows the user which actions that can be used.
Declare a custom NSMenuItem subclass and start using that custom class instead of NSMenuItem.
In this class you should override this method:
- (BOOL)isHighlighted
{
return NO;
}
This way you will not have the menu item highlighted.
EDIT
Try this:
[item setOnStateImage: item.offStateImage];
FFR: Look up the following methods in the docs:
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
Will work for both selecting the menu item and the associated command key.
Within your NSDocument provide a body for validateMenuItem
such as,
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
SEL theAction = [menuItem action];
if (theAction == #selector(openPreferencesPanel:)) {
return !_isCurrentlyModal; //A BOOL in MyDocument
}
return [super validateMenuItem:menuItem]; // Keep this for proper cut, paste, etc validation
}
In your case, the above selector might be highlight:. Check the nib/xib and inspect it. It might be attached to the First Responder. Copy the method name.
Also have a gander at for more general items (buttons, etc) and also includes menu items.
- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)anItem

Can I use a customized checkmark with UITableView's allowsMultipleSelectionDuringEditing set to YES?

A picture's worth a thousand words...
For a bit more background, I have a UITableView leveraging iOS 5's allowsMultipleSelectionDuringEditing set to YES. This results in the empty and filled edit controls being shown on the left of the cell any time the cell is in edit mode. This behavior is exactly what I want. I just want to change the appearance of these check marks.
I know it would be possible to write custom selection logic and basically roll my own version (like this and this), but that's what I want to avoid. The system is already in place, and I want to re-use as much of it as possible.
This is the closest I've come. It's simple and it works, while reusing almost all of the pre-baked system. It's also a giant hack however, and relies on exploiting the undocumented view hierarchy of UITableViewCell after a little runtime introspection.
In a nutshell, this simply hides the view normally responsible for showing the checkmark, allowing me to add my own view that can be shown in its place. I can then manipulate this stand-in view when the cell's selection or editing state changes...
To prevent the standard checkmark from appearing, all that's needed is a custom -layoutSubviews implementation. It's called, per the documentation, after both -willTransitionToState: and -setEditing:animated:, ensuring the state is always valid when either isSelected or isEditing changes.
- (void)layoutSubviews
{
[super layoutSubviews];
// Find the offending view, and quietly bury it...
for (UIView* subview in [self subviews])
{
// As determined by NSLogging every subview's class, and guessing which was the one I wanted
if ([NSStringFromClass([subview class]) isEqualToString:#"UITableViewCellEditControl"])
{
[subview setHidden:YES];
}
}
if ([self isEditing])
{
// Show the custom view however you want.
// The value of [self isSelected] will be useful...
}
else
{
// Hide the custom view.
}
}
I would still welcome a solution that's a bit more... kosher.

What ordes does the IBActions fire in when multiple IBActions supplied for same event

As per my header, I am building a view with some UIButtons in it.
I was thinking about hooking up 2 actions to one UIButton event, since that button needs to do two things, but I need to do them in a certain order.
No matter how I try, I can't seem to change the order in which the Actions fire. Is there a way for me to decide this, or is it random?
When I right-click the UIButton in Interface Builder, I see both Actions added to the same event, but no matter in which order they appear does the firing order change.
Hoping some of you out there can help me.
UIButtons are subclasses of UIControl, which use the target-action pattern to communicate events. The programatic interface for adding targets is exposed via the addTarget:action:forControlEvents: method. Hooking up IBActions in interface builder is just a visual way of using this same interface; the bundle loader will call that method on the unarchived button when loading the .xib file that contains it.
The key point is that the target argument in that method is added to an NSSet (or mutable subclass) internally by the UIControl. NSSets are, by definition, unordered. This means that when the button needs to enumerate its set of targets to dispatch events, the order of enumeration is not well defined.
You will not get answer to this question in any book or even googling.
I have tried that personally and it work in alphabetical order according to IBActions Names. Strange but its a fact!!
Tested Example Below :
helloworld.h
-(IBAction)a:(id)sender;
-(IBAction)b:(id)sender;
-(IBAction)c:(id)sender;
-(IBAction)d:(id)sender;
-(IBAction)e:(id)sender;
-(IBAction)f:(id)sender;
Action)g:(id)sender;
**HelloWorld.m file**
-(IBAction)a:(id)sender
{
NSLog(#"I clicked a");
}
-(IBAction)f:(id)sender
{
NSLog(#"I clicked f");
}
-(IBAction)b:(id)sender
{
NSLog(#"I clicked b");
}
-(IBAction)c:(id)sender
{
NSLog(#"I clicked c");
}
-(IBAction)d:(id)sender
{
NSLog(#"I clicked d");
}
-(IBAction)e:(id)sender
{
NSLog(#"I clicked e");
}
Now link these actions to your Button in [Touch Up Inside] event in any order. You will find the same output as shown below :
Output
2012-03-19 15:35:34.548 I clicked a
2012-03-19 15:35:34.554 I clicked b
2012-03-19 15:35:34.564 I clicked c
2012-03-19 15:35:34.566 I clicked d
2012-03-19 15:35:34.568 I clicked e
2012-03-19 15:35:34.572 I clicked f
Cheers!!
Happy Coding!!
Since there's no clear way to indicate the order in which the actions should be sent, you should not rely on the order. Instead, create a separate action that calls the two actions that you care about in the desired order:
- (IBAction)doFooAndBar:(id)sender
{
[self foo:sender];
[self bar:sender];
}

Toggle-style button : how to toggle title in this case?

I have a CoreData app presenting data with a TableView, textfields, buttons... It deals with people situations and one of the button is a toggle-style button with title "Close". When we consider the user's case closed, we press and it changes the state of a boolean-type attribute in the entity, representing the closed/open state of the case, using a simple binding to the attribute value. The button title also becomes "Reopen" as the case may be reopened in the future.
Then additional things had to be done with the data on pressing the button, so I had to create an IBAction method instead of simply use the former binding. Problem: when button is pressed, the action is done, but the button title is not toggled. It makes sense since nothing tells it to toggle anymore.
I decided to remove the action on the boolean from the IBAction and use again the value binding, so the boolean change is performed by the binding and the other operations are performed by the IBAction. Problem: it modifies the data unexpectedly, sometimes working fine, sometimes not doing all things in a coherent way as expected.
So I'm back with all changes handled by the IBAction and this time, I'm using the Title/Alternate title bindings instead of the value binding. Now the button title toggles, but instead of displaying the word "Close" and "Reopen", it displays the boolean values "0" and "1".
I should perhaps handle the button title change in the IBAction as well, using "setTitle", but then I see a new problem coming. On app start-up, how will it pick the appropriate entity record for reference? And what if the table is in a "No Selection" situation? Looks like a quite extensive piece of code to handle such a small issue...
Any advice on a short, more direct way of handling this is welcome. Thanks.
Sounds like you probably have a couple of different options.
This first option is a bit more involved than the others, but is still good to know for the record. This option basically would not use bindings for the close/re-open button, but instead, set the title, etc. programmatically. The basic game plan would be as follows:
The close/re-open button's initial title when the document opens is set in the nib file (e.g. Close).
Optionally, when the document opens, you could disable the button in -awakeFromNib, since the table view will have no initial selection.
The only actions that would necessitate the button's title or enabled state to be changed is 1) when the tableview's selection is changed, and 2) when you've clicked the close/re-open button to toggle the case's state.
To achieve the desired result, you'd create an IBOutlet to the close/re-open button if you don't already have one. You'd then connect up the tableview's delegate outlet to your controller class, which will let your controller class know when the tableview's selection has been changed (via the - (void)tableViewSelectionDidChange:(NSNotification *)notification <NSTableViewDelegate> protocol method). You would also need to update the close/reopen IBAction method to make sure it switches the title on the button after being clicked (since the tableview selection wouldn't change during that operation). The controller class's code might look something like this:
// add this declaration to avoid compiler warnings:
#interface LHController (LHPrivate)
- (void)updateCloseReopenButtonTitle;
#end
- (void)awakeFromNib {
[self updateCloseReopenButtonTitle];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification {
[self updateCloseReopenButtonTitle];
}
- (IBAction)toggleCloseReopenButton:(id)sender {
// do your existing code here and then add:
[self updateCloseReopenButtonTitle];
}
- (void)updateCloseReopenButtonTitle {
// assuming 'casesController' is an IBOutlet to your NSArrayController
NSArray *selectedObjects = [casesController selectedObjects];
// loop through the selected cases to determine whether
// they're all closed, all open, or mixed to set
// the title of the button appropriately
BOOL allClosed = YES;
for (LHCase *case in selectedCases) {
if ([case isOpen]) {
allClosed = NO;
break;
}
}
[closeReopenButton setTitle:(allClosed ? #"Reopen" : #"Close")];
[closeReopenButton setEnabled:[selectedObjects count] > 0];
}
Before bindings came along, this is how we used to have to do things.
Another option might be reconsidering your user interface: maybe rather than a push/toggle button whose title you need to toggle, you could instead just have a checkbox titled Closed, which would signify whether the selected cases were closed or not. You could use bindings for the checkbox's state, like shown in the image below:
You could then have an IBAction method that handles the extra stuff that needs processing. You can ask the casesController for the -selectedObjects and then loop through them. As long as you make sure the checkbox Allows Mixed states, it has the added advantage of better representing mixed-case scenarios, in case of a selection of cases with mixed open/closed states (the checkbox a dash instead of a full check).
Another option if you want to stick with a toggle button is to create and specify a custom NSValueTransformer for the title and alternate title bindings. This value transformer would take in the boolean closed/open state of the case and turn it into a string more fitting than just 0 or 1 (which is what's being displayed now). It might look something like this:
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return NO;
}
- (id)transformedValue:(id)value {
BOOL isClosed;
if (value == nil) return nil;
// Attempt to get a reasonable value from the
// value object.
if ([value respondsToSelector: #selector(boolValue)]) {
isClosed = [value boolValue];
} else {
[NSException raise: NSInternalInconsistencyException
format: #"Value (%#) does not respond to -boolValue.",
[value class]];
}
return (isClosed ? #"Reopen" : #"Close");
}