I am trying to create a simple set of buttons which show/hide a text field.
I created a BOOL in .h;
BOOL showBool;
#property BOOL showBool;
Then have buttons linked to actions in .h:
- (IBAction) showText:(id)sender;
- (IBAction) hideText:(id)sender;
Then in .m, I have the actions, which should be setting the bool;
- (IBAction) showText:(id)sender;
{
showBool = YES;
}
- (IBAction) hideText:(id)sender;
{
showBool = NO;
}
I have a text field key value bound via App Delegate as 'hidden' to showBool. But it does not change (hide/show) during run...
Am I setting the booleans incorrectly ??
Bindings rely on key value observing. Use the accessor self.showBool = YES; to trigger the change for the binding, rather than directly giving the ivar a value. This assumes you did the #synthesize in your implementation.
Related
Ok, I have used interface builder and added tooltips to all controls.
I would like to offer the user a menu item "disable tooltips".
How do I disable all tooltips globally on a cocoa application?
Instead of setting the text for the tooltips in directly in Interface Builder, make NSString properties for them in your view controller (or other bindable object). Use a Boolean property to control whether or not the tooltips will be shown.
#interface YourViewController : NSViewController
#property (readonly) NSString *thisTooltip;
#property (readonly) NSString *thatTooltip;
#property BOOL showTooltips;
#end
#implementation YourViewController
- (NSString *)thisTooltip {
if (showTooltips) {
return #"This is a tooltip";
}
else return #"";
}
- (NSString *)thatTooltip {
if (showTooltips) {
return #"That is a tooltip";
}
else return #"";
}
#end
Use the Bindings Inspector in IB to bind the Tooltip to the Property:
As you can see, this strategy makes it possible to customize your tooltips dynamically, while your application is running.
I have a NSTextField inside of a NSTableCellView, and I want an event which informs me when my NSTextField has got the focus for disabling several buttons, I found this method:
-(void)controlTextDidBeginEditing:(NSNotification *)obj{
NSTextField *textField = (NSTextField *)[obj object];
if (textField != _nombreDelPaqueteTextField) {
[_nuevaCuentaActivoButton setEnabled:FALSE];
[_nuevaCuentaPasivoButton setEnabled:FALSE];
[_nuevaCuentaIngresosButton setEnabled:FALSE];
[_nuevaCuentaEgresosButton setEnabled:FALSE];
}
}
but it triggers just when my textfield is begin editing as this says, I want the buttons disabled when I get the focus on the textField, not when I already started to type
EDIT: Gonna put my code based on the help received by Joshua Nozzi, it still doesn't work
MyNSTextField.h
#import <Cocoa/Cocoa.h>
#class MyNSTextField;
#protocol MyNSTextFieldDelegate
#optional -(BOOL)textFieldDidResignFirstResponder:(NSTextField *)sender;
#optional -(BOOL)textFieldDidBecomeFirstResponder:(NSTextField *)sender;
#end
#interface MyNSTextField : NSTextField
#property (strong, nonatomic) id <MyNSTextFieldDelegate> cellView;
#end
MyNSTextField.m
#import "MyNSTextField.h"
#implementation MyNSTextField
- (BOOL)becomeFirstResponder
{
BOOL status = [super becomeFirstResponder];
if (status)
[self.cellView textFieldDidBecomeFirstResponder:self];
return status;
}
- (BOOL)resignFirstResponder
{
BOOL status = [super resignFirstResponder];
if (status)
[self.cellView textFieldDidResignFirstResponder:self];
return status;
}
#end
on my viewcontroller EdicionDeCuentasWC.m
#import "MyNSTextField.h"
#interface EdicionDeCuentasWC ()<NSTableViewDataSource, NSTableViewDelegate, NSControlTextEditingDelegate, NSPopoverDelegate, MyNSTextFieldDelegate>
#end
#implementation EdicionDeCuentasWC
#pragma mark MyNSTextFieldDelegate
-(BOOL)textFieldDidBecomeFirstResponder:(NSTextField *)sender{
NSLog(#"textFieldDidBecomeFirstResponder");
return TRUE;
}
-(BOOL)textFieldDidResignFirstResponder:(NSTextField *)sender{
NSLog(#"textFieldDidResignFirstResponder");
return TRUE;
}
#pragma mark --
#end
it's important to say in visual editor, already changed all my NSTextFields to MyNSTextField class and set delegate to my File's Owner (EdicionDeCuentasWC)
I think I nailed it. I was trying subclassing NSTextFiled to override becomeFirstResponder() and resignFirstResponder(), but once I click it, becomeFirstResponder() gets called and resignFirstResponder() gets called right after that. Huh? But search field looks like still under editing and focus is still on it.
I figured out that, when you clicked on search field, search field become first responder once, but NSText will be prepared sometime somewhere later, and the focus will be moved to the NSText.
I found out that when NSText is prepared, it is set to self.currentEditor() . The problem is that when becomeFirstResponder()'s call, self.currentEditor() hasn't set yet. So becomeFirstResponder() is not the method to detect it's focus.
On the other hand, when focus is moved to NSText, text field's resignFirstResponder() is called, and you know what? self.currentEditor() has set. So, this is the moment to tell it's delegate that that text field got focused.
Then next, how to detect when search field lost it's focus. Again, it's about NSText. Then you need to listen to NSText delegate's methods like textDidEndEditing(), and make sure you let it's super class to handle the method and see if self.currentEditor() is nullified. If it is the case, NSText lost it's focus and tell text field's delegate about it.
I provide a code, actually NSSearchField subclass to do the same thing. And the same principle should work for NSTextField as well.
protocol ZSearchFieldDelegate: NSTextFieldDelegate {
func searchFieldDidBecomeFirstResponder(textField: ZSearchField)
func searchFieldDidResignFirstResponder(textField: ZSearchField)
}
class ZSearchField: NSSearchField, NSTextDelegate {
var expectingCurrentEditor: Bool = false
// When you clicked on serach field, it will get becomeFirstResponder(),
// and preparing NSText and focus will be taken by the NSText.
// Problem is that self.currentEditor() hasn't been ready yet here.
// So we have to wait resignFirstResponder() to get call and make sure
// self.currentEditor() is ready.
override func becomeFirstResponder() -> Bool {
let status = super.becomeFirstResponder()
if let _ = self.delegate as? ZSearchFieldDelegate where status == true {
expectingCurrentEditor = true
}
return status
}
// It is pretty strange to detect search field get focused in resignFirstResponder()
// method. But otherwise, it is hard to tell if self.currentEditor() is available.
// Once self.currentEditor() is there, that means the focus is moved from
// serach feild to NSText. So, tell it's delegate that the search field got focused.
override func resignFirstResponder() -> Bool {
let status = super.resignFirstResponder()
if let delegate = self.delegate as? ZSearchFieldDelegate where status == true {
if let _ = self.currentEditor() where expectingCurrentEditor {
delegate.searchFieldDidBecomeFirstResponder(self)
// currentEditor.delegate = self
}
}
self.expectingCurrentEditor = false
return status
}
// This method detect whether NSText lost it's focus or not. Make sure
// self.currentEditor() is nil, then that means the search field lost its focus,
// and tell it's delegate that the search field lost its focus.
override func textDidEndEditing(notification: NSNotification) {
super.textDidEndEditing(notification)
if let delegate = self.delegate as? ZSearchFieldDelegate {
if self.currentEditor() == nil {
delegate.searchFieldDidResignFirstResponder(self)
}
}
}
}
You will need to change NSSerachField to ZSearchField, and your client class must conform to ZSearchFieldDelegate not NSTextFieldDelegate. Here is a example. When user clicked on search field, it extend it's width and when you click on the other place, search field lost it's focus and shrink its width, by changing the value of NSLayoutConstraint set by Interface Builder.
class MyViewController: NSViewController, ZSearchFieldDelegate {
// [snip]
#IBOutlet weak var searchFieldWidthConstraint: NSLayoutConstraint!
func searchFieldDidBecomeFirstResponder(textField: ZSearchField) {
self.searchFieldWidthConstraint.constant = 300
self.view.layoutSubtreeIfNeeded()
}
func searchFieldDidResignFirstResponder(textField: ZSearchField) {
self.searchFieldWidthConstraint.constant = 100
self.view.layoutSubtreeIfNeeded()
}
}
It might depend on the behavior of the OS, I tried on El Capitan 10.11.4, and it worked.
The code can be copied from Gist as well.
https://gist.github.com/codelynx/aa7a41f5fd8069a3cfa2
I have a custom NSTextField subclass that overrides -becomeFirstResponder and -resignFirstResponder. Its -cellView property requires conformance to a protocol that declares -textDidBecome/ResignFirstResponder:(NSTextField *)sender but it's enough to give you the general idea. It can easily be modified to post notifications for which your controller can register as an observer. I hope this helps.
- (BOOL)becomeFirstResponder
{
BOOL status = [super becomeFirstResponder];
if (status)
[self.cellView textFieldDidBecomeFirstResponder:self];
return status;
}
- (BOOL)resignFirstResponder
{
BOOL status = [super resignFirstResponder];
if (status)
[self.cellView textFieldDidResignFirstResponder:self];
return status;
}
I found the following code on the macrumors forums.
Is the first responder a text view (the field editor is a text view).
Does the field editor exist?
Is the text field the field editor's delegate
It seems to work.
- (BOOL)isTextFieldInFocus:(NSTextField *)textField
{
BOOL inFocus = NO;
inFocus = ([[[textField window] firstResponder] isKindOfClass:[NSTextView class]]
&& [[textField window] fieldEditor:NO forObject:nil]!=nil
&& [textField isEqualTo:(id)[(NSTextView *)[[textField window] firstResponder]delegate]]);
return inFocus;
}
Just in case, as a slight variation over the idea of #sam, we can observe NSWindow.firstResponder property itself, it's KVO-compliant according to the documentation. Then compare it with textField or textField.currentEditor() to figure out whether the field is focused.
I have a cell-based NSOutlineView which displays NSTextFieldCell objects.
I'd like to respond to keydown or keyup events so as to make the text contained in the NSTextFieldCell bold when the text contains certain preset keywords. What is the most elegant way to achieve this - should I:
Subclass NSOutlineView and override the keydown method
Subclass NSTextFieldCell
Utilize a delegate of some kind
Utilize some other approach
Thanks very much in advance to all for any info!
Found it.
In awakeFromNib:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(actionToTakeOnKeyPress:) name:NSControlTextDidChangeNotification object:theNSOutlineViewThatContainsTheNSTextFieldCell];
Then add a method like this:
- (void) actionToTakeOnKeyPress: (id) sender
{
//will be called whenever contents of NSTextFieldCell change
}
To intercept key presses in a way that they can still be filtered out, various NSResponder messages may be overwritten, such as keyDown: or interpretKeyEvents:.
To be able to do that, a subclass of a NSTextView needs to be used as the field editor. For that, one subclasses NSTextFieldCell and overrides fieldEditorForView:, returning the subclass (see Custom field editor for NSTextFieldCell in an NSTableView).
Here's the relevant code excerpts:
In a subclassed NSTextFieldCell (which then has to be assigned in Interface Builder for the editable column, or returned by the NSTableViewDelegate's dataCellForTableColumn message):
- (NSTextView *)fieldEditorForView:(NSView *)aControlView
{
if (!self.myFieldEditor) {
self.myFieldEditor = [[MyTextView alloc] init];
self.myFieldEditor.fieldEditor = YES;
}
return self.myFieldEditor;
}
It also requires the declaration of a property in the #interface section:
#property (strong) MyTextView *myFieldEditor;
And then in MyTextView, which is a subclass of NSTextView:
-(void)keyDown:(NSEvent *)theEvent
{
NSLog(#"MyTextView keyDown: %#", theEvent.characters);
static bool b = true;
if (b) { // this silly example only lets every other keypress through.
[super keyDown:theEvent];
}
b = !b;
}
I created a View-Based NSTableView with a single column. This column is populated with a standard NSTableCellView from Interface Builder (I chose the version with image and textfield).
Now I want to make the textfield in the column editable.
My first attempt was to modify the NSTextField from Interface builder and set its behaviour as Editable. It works, indeed when I select a row and I push the enter key the field becomes editable and I can change its value. I thought I would be able to intercept this change thanks to some NSTableViewDataSource method like tableView:setObjectValue:forTableColumn:row: but this method never gets called in response of a textfield edit action.
Which is the right way to deal with editable field in a view-based NSTableView system? I suppose that the NSTableViewDataSource has something to do with it but I don't know how to get its methods called.
Create a subclass of NSTableCellView. (The appropriate .h and .m files) Make the class respond to the NSTextFieldDelegate protocol. Implement the control:textShouldEndEditing: method. Make this subclass the delegate of your label control.
Here is some example code.
CategoryListCell.h
#interface CategoryListCell : NSTableCellView
#end
CategoryListCell.m
#interface CategoryListCell()<NSTextFieldDelegate>
#property (weak) IBOutlet NSTextField *categoryLabel;
#property (assign) BOOL editing;
#property (copy) NSString* category;
#end
#implementation CategoryListCell
- (BOOL)control:(NSControl*)control textShouldBeginEditing:(NSText *)fieldEditor {
self.editing = YES;
return YES;
}
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor; {
if (self.editing) {
self.editing = NO;
[self mergeFromSource:self.category toDestination:self.categoryLabel.stringValue];
}
return YES;
}
- (void)mergeFromSource:(NSString*)source toDestination:(NSString*) destination {
// your work here
}
#end
Sounds like you need to subclass the NSView that's in the NSTableView cell and make the subclassed view a delegate of the textfield. Your view will then get text change notifications via the NSTextField delegate method:
- (void)textDidChange:(NSNotification *)notification;
I need to have a NSTextField working with a NSStepper as being one control so that I can edit an integer value either by changing it directly on the text field or using the stepper up/down arrows.
In IB I've added both of these controls then connected NSStepper's takeIntValueFrom to NSTextField and that makes the text value to change whenever I click the stepper arrows. Problem is that if I edit the text field then click the stepper again it will forget about the value I manually edited and use the stepper's internal value.
What's the best/easiest way to have the stepper's value be updated whenever the text field value is changed?
Skip the takeIntValueFrom: method. Instead, bind both views to the same property in your controller. You may also want to create a formatter and hook up the text field's formatter outlet to it.
I would have a model with one integer variable, which represents the value of both controls.
In my controller, I would use one IBAction, connected to both controls, and two IBOutlets, one for each control. then I would have a method for updating outlets from model value.
IBOutlet NSStepper * stepper;
IBOutlet NSTextField * textField;
- (IBAction) controlDidChange: (id) sender
{
[model setValue:[sender integerValue]];
[self updateControls];
}
- (void) updateControls
{
[stepper setIntegerValue:[model value]];
[textField setIntegerValue:[model value]];
}
This is the principle. As said by Peter Hosey, a formatter may be useful on your text field, at least to take min and max values of stepper into account.
I found easy way is to bind stepper value to input and input value to stepper
#property (strong) IBOutlet NSTextField *timeInput;
#property (strong) IBOutlet NSStepper *timeStepper;
If one is keeping track of the value of a field in one's model, such as a current page number, then there's no need to keep another copy in the stepper control. I just configure the control to have an initial value of 0, and a range from -1 to 1. In the IBAction method for the stepper control, which gets called for any click (or for auto-repeat) on the control, ask for its current value, which will be 1 if the up-arrow was clicked, or -1 for the down-arrow. Immediately reset the control's current value to 0, and then update the model and anything else (the associated text field, or a new page view, etc.) with a new value based on the direction 1 or -1. E.g.,
- (IBAction) bumpPageNum:(id)sender
{
int whichWay = [sender intValue]; // Either 1 or -1
[sender setIntValue:0]; // Same behavior next time
[model changePageBy:whichWay];
}
This way, the stepper control doesn't have to be linked to any values in the model at all.
I did as Peter Hosey suggested as it seems to me the cleanest approach. Created a property in my controller:
int editValue_;
...
#property (nonatomic, readwrite) int editValue;
...
#synthesize editValue = editValue_;
then in IB for both controls in the Bindings tab I've set the "Bind to:" check box and selected my controller, then on the "Model Key Path" field set "editValue" and voilá, it worked! With just 3 lines of code and some IB editing. And if I need to change the value on my controller I use setEditValue: and the text field gets updated.
This is for people who care about Cocoa.
The only reason to use NSStepper together with NSTextField is because there is some number in the textfield.
Steps for complete advanced Cocoa solution (which is sadly missing here):
Step 1: add number formatters to your textfields and format as you wish.
Step 2: add NSObjectController and glue your textfields/steppers to it. This is a common mistake when people do direct bindings. Meh. Add respective keyPaths as you have in your model.
Step 3: make sure your textfields react to key events. Always missing by newbies. Hook textfield delegate to our controller and add code.
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
{
if (commandSelector == #selector(moveUp:) || commandSelector == #selector(moveDown:)) {
if (control == [self minAgeTextField]) {
return [[self minAgeStepper] sendAction:commandSelector to:[self minAgeStepper]];
}
if (control == [self maxAgeTextField]) {
return [[self maxAgeStepper] sendAction:commandSelector to:[self maxAgeStepper]];
}
}
return NO;
}
Step 4: Some glue code. This is also the place where we set content of our objectController.
#property (weak) IBOutlet NSObjectController *profilesFilterObjectController;
#property (weak) IBOutlet NSTextField *minAgeTextField;
#property (weak) IBOutlet NSTextField *maxAgeTextField;
#property (weak) IBOutlet NSStepper *minAgeStepper;
#property (weak) IBOutlet NSStepper *maxAgeStepper;
#property (nonatomic) ProfilesFilter *filter;
- (void)awakeFromNib //or viewDidLoad...
{
[self setFilter:[ProfilesFilter new]];
[[self profilesFilterObjectController] setContent:[self filter]];
}
Step 5: Validate your values (KVC validation)
#implementation ProfilesFilter
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
if ([inKey isEqualToString:#"minAge"] || [inKey isEqualToString:#"maxAge"]) {
if (*ioValue == nil) {
return YES;
}
NSInteger minAge = [[self minAge] integerValue];
NSInteger maxAge = [[self maxAge] integerValue];
if ([inKey isEqualToString:#"minAge"]) {
if (maxAge != 0) {
*ioValue = #(MAX(18, MIN([*ioValue integerValue], maxAge)));
}
}
if ([inKey isEqualToString:#"maxAge"]) {
if (minAge != 0) {
*ioValue = #(MIN(99, MAX([*ioValue integerValue], minAge)));
}
}
}
return YES;
}
#end
Notes: Wrong values? NSNumberFormatter will show error. Max age lower than min age? We use KVC validation (step 5). Eureka!
BONUS: What if user holds CTRL or SHIFT or both (user wants slower or faster increment)? We can modify increment based on key pressed (subclass NSStepper and overrider increment getter and check e.g. NSEvent.modifierFlags.contains(.shift)).
- (double)increment
{
BOOL isShiftDown = ([NSEvent modifierFlags] & NSEventModifierFlagShift) ? YES : NO;
//BOOL isOptionDown = ([NSEvent modifierFlags] & NSEventModifierFlagOption) ? YES : NO;
double increment = ([self defaultIncrement] - 0.001 > 0) ? [self defaultIncrement] : 1.0;
if (isShiftDown) {
increment = increment * 5;
}
return increment;
}
Add this to - (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
//JUST AN ILLUSTRATION, holding shift + key up doesn't send moveUp but moveUpAndModifySelection (be careful of crash, just modify the command to moveUp; if not `NSStepper` doesn't know `moveUpAndModifySelection`)
if (commandSelector == #selector(moveUpAndModifySelection:)) {
commandSelector = #selector(moveUp:);
}
if (commandSelector == #selector(moveToEndOfDocument:) || commandSelector == #selector(moveDownAndModifySelection:)) {
commandSelector = #selector(moveDown:);
}
PS: The best solution is to use custom NSTextField that has and draws stepper and controls all events. You end up with a smaller controller!