I am working on code that dynamically builds a window from scratch in macOS. I would like to add NSComboBox to the list of supported controls. I am using a generic window delegate that contains a window controller that handles all the events for all the controls. As each control is added, the various action selectors or notification handlers are added for that control.
(This is code that I inherited.)
So the issue is this. I have the combo box basically working. When I type into it, I get the expected NSControlTextDidChangeNotification notifications. And when I select from the popup I get the expected NSComboBoxSelectionDidChangeNotification notifications. The issue I am having is that when the text changes because of the NSComboBoxSelectionDidChangeNotification notification, the NSControlTextDidChangeNotification does not fire. But the NSComboBoxSelectionDidChangeNotification happens before the edit text box changes.
I would like to forward a notification to the client code each time the text changes, irrespective of how it changes. The problem I have is that I can't seem to get notified when the text changes because of a selection from the popup.
Here is a simplfied version of my setup (where view is the control just added):
if ([view isKindOfClass:[NSComboBox class]])
{
NSComboBox *comboboxcontrol = (NSComboBox *) view;
SEL mySelector = #selector(comboBoxPopupChange:);
NSInteger tag = [comboboxcontrol tag];
if (tag != 0)
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:mySelector name:NSComboBoxSelectionDidChangeNotification object:comboboxcontrol];
}
}
if ([view isKindOfClass:[NSTextField class]])
{
NSTextField *textfield = (NSTextField *) view;
NSInteger tag = [textfield tag];
if (tag != 0)
{
SEL textchangeSelector = #selector(editTextChange:);
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:textchangeSelector name:NSControlTextDidChangeNotification object:textfield];
}
}
I have verified that both blocks of code execute and both selectors fire. The problem in a nutshell is that NSComboBoxSelectionDidChangeNotification fires before the control text changes but there is no subsequent NSControlTextDidChangeNotification to tell me that it changed. (The text-changed notifications only fire when I type in the edit box.)
Related
I have a subclass of NSTextField that I made so that when a user is done editing the field, the text field will lose focus. I also have it set up so whenever the user clicks on the main view, this will act as losing focus on the textfield. And this all works great. Now I want to add some additional capabilities to the subclass.
I want the textfield to send a textDidEndEditing every time a user clicks anywhere outside of the box. This includes when a user clicks on another UI component. The behavior I'm seeing right now is that when a user clicks on another UI component (let's say a combo box) the action does not trigger. Is there a way to force this? Besides manually adding it as a part of the other components actions?
Any help would be appreciated!
Here's the code for my textDidEndEditing function
- (void)textDidEndEditing:(NSNotification *)notification
{
NSString *file = nil;
char c = ' ';
int index = 0;
[super textDidEndEditing:notification];
if ([self isEditable])
{
// is there a valid string to display?
file = [self stringValue];
if ([file length] > 0)
{
c = [file characterAtIndex:([file length] - 1)];
if (c == '\n') // check for white space at the end
{
// whitespace at the end... remove
NSMutableString *newfile = [[NSMutableString alloc] init];
c = [file characterAtIndex:index++];
do
{
[newfile appendFormat:#"%c", c];
c = [file characterAtIndex:index++];
}
while ((c != '\n') && (index < [file length]));
[self setStringValue:newfile];
file = newfile;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:#"inputFileEntered" object:self];
}
}
// since we're leaving this box, show no text in this box as selected.
// and deselect this box as the first responder
[self setSelectedText:0];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"setResponderToNil" object:self];
}
Where "setSelectedText" is a public function in the text field subclass:
- (void)setSelectedText:(int) length
{
int start = 0;
NSText *editor = [self.window fieldEditor:YES forObject:self];
NSRange range = {start, length};
[editor setSelectedRange:range];
}
And the "setResponderToNil" notification is a part of my NSView subclass:
- (void)setResponderToNil
{
AppDelegate *delegate = (AppDelegate *)[NSApp delegate];
[delegate.window makeFirstResponder:nil];
}
I think I found a way to do this. It may not be the most eloquent, but it seems to work with the type of behavior I want.
I added an mouse event listener to the app's main controller:
event_monitor_mousedown_ = [NSEvent addLocalMonitorForEventsMatchingMask:NSRightMouseDown
handler:^NSEvent *(NSEvent * event)
{
NSResponder *resp = [[[NSApplication sharedApplication] keyWindow] firstResponder];
if ([resp isKindOfClass:[NSTextView class]])
{
// set UI in proper state - remove focus from text field
// even when touching a new window for the first time
[[NSNotificationCenter defaultCenter]
postNotificationName:#"setResponderToNil" object:self];
[self setStopState];
}
return event;
}];
This event checks the current responder in the application on any mouseDown action. If it's a textView object (which is type of the object that would be the first responder when editing an NSTextField) it will send the notification to set the firstResponder to nil. This forces the textDidEndEditing notification. I want to play around with it some more to see if I'm getting the right expected behavior. I hope this helps someone out there!
I would like to be able to detect if some text is changed in a UITextField so that I can then enable a UIButton to save the changes.
Instead of observing notifications or implementing textField:shouldChangeCharacterInRange:replacementString:, it's easier to just add an event target:
[textField addTarget:self
action:#selector(myTextFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
- (void)myTextFieldDidChange:(id)sender {
// Handle change.
}
Note that the event is UIControlEventEditingChanged and not UIControlEventValueChanged!
The advantages over the other two suggested solutions are:
You don't need to remember to unregister your controller with the NSNotificationCenter.
The event handler is called after the change has been made which means textField.text contains the text the user actually entered. The textField:shouldChangeCharacterInRange:replacementString: delegate method is called before the changes have been applied, so textField.text does not yet give you the text the user just entered – you'd have to apply the change yourself first.
Take advantage of the UITextFieldTextDidChange notification or set a delegate on the text field and watch for textField:shouldChangeCharactersInRange:replacementString.
If you want to watch for changes with a notification, you'll need something like this in your code to register for the notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFieldDidChange:) name:UITextFieldTextDidChangeNotification object:theTextField];
Here theTextField is the instance of UITextField that you want to watch. The class of which self is an instance in the code above must then implement textFieldDidChange, like so:
- (void)textFieldDidChange:(NSNotification *)notification {
// Do whatever you like to respond to text changes here.
}
If the text field is going to outlive the observer, then you must deregister for notifications in the observer's dealloc method. Actually it's a good idea to do this even if the text field does not outlive the observer.
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Other dealloc work
}
For that, first you need to have your textfield have it delegate reference assigned. And the delgate, should preferably be, the vew controller which is the files owner of the view.
Which goes like
myTextField.delegate = myViewControllerReferenceVariable
And in your viewController interface, tell you will be implementing UITextFieldDelegate by
#interface MyViewController:UIViewController<UITextFieldDelegate>
And in your view controller implementation override
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
So the code will look like
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
text = [textfield.text stringByReplacingCharactersInRange:range withString:string];
if (textfield == refToTextFieldYouWantToCheck) {
if ( ! [textToCheck isEqualToString:text] ) {
[theButtonRef setEnabled:YES];
}
}
return YES; //If you don't your textfield won't get any text in it
}
You can also subscribe to notification which is sort of messy IMHO
You can find how to do it here.
Swift 3.0
Process 1
Create IBOutlet of UITextfiled and Add Target to text field.
m_lblTxt.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControlEvents.editingChanged)
func textFieldDidChange(textField:UITextField)
{
NSLog(textField.text!)
}
Process 2
m_lblTxt.delegate = self
//MARK: - TextField Delegates
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
print(textField.text!)
return true
}
This can be accomplished in Interface Builder on the Editing Changed event of UITextField. Drag from it to your code and create an IBAction.
For example:
#IBAction func textFieldChanged(_ sender: UITextField) {
print(sender.text)
}
This event is the same as described in other answers here in that the .text property contains the updated text input when it gets triggered. This can help clean up code clutter by not having to programmatically add the event to every UITextField in the view.
You could create a variable to store the original string, then register with the notification center to receive UITextFieldTextDidChangeNotification event:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateButton:) name:UITextFieldTextDidChangeNotification object:nil];
Then, create a method to receive the notification, and compare the current value of the text field with the original value
-(void) updateButton:(NSNotification *)notification {
self.myButton.enabled = ![self.myTextField.text isEqualToString:originalString];
}
Don't forget to de-register the notification when the view controller is deallocated.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
You can add a class member like this NSString *changeTemp,then
changetemp = textfield;
if( change temp != textfild ){
changetemp=textfild;
NSLog(#" text is changed"
} else {
NSLog(#" text isn't changed"):
}
I use NSUserDefaults to save a switch on/off and so far it is good. It remembers the switch position in next session.
Now to the thing which I do not understand.
I use the same switch (with the same name)in another view, let´s say a flip view which is pushed in from the first view. If I change the switch in the first view it is automatically changed in the flip view.
But the other way round, if I change it in the flip view it is not changed in the first view when I go back. Only if I restart the application the first view is also changed.
How can I solve this to be changed at the same time? Or kind of refresh the first view without need to restart.
Your first view is not refreshed, as it is not initialized again. If you using ViewControllers you could update your switch in viewWillAppear (if isViewLoaded).
You should observe NSUserDefaults changes in views that are interested in the changes.
You can use the following code to observe the changes:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
And implement the defaultsChanged method:
- (void)defaultsChanged:(NSNotification *)notification
{
NSUserDefaults *defaults = (NSUserDefaults *)[notification object];
id value = [defaults objectForKey:#"keyOfDefaultThatChanged"];
self.something = [(NSNumber *)value intValue]; // For example.
}
Don't forget the remove the observer when your view closes (perhaps in dealloc):
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self];
I have UITabBarViewController which has 2 views.
The first view has a UITableView which has 1 section and 5 rows.
The second view has a UITableView as well which has a settings options like UISwitches.
My question is how can I show and hide or remove a cell from first view by using UISwitches on the settings view? Thanks in advance.
edit
this video explain what i am trying to do (check the app view)
Press Here
you can accomplish this by using NSNotificationCenter
in your firstView you can write a code like:
-(void)viewDidLoad{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(modifyCell:) name:#"modifyCell" object:nil];
}
//make sure this is declared in your .h
-(void)modifyCell:(NSNotification*)notif
{
if (notif) {
//cellindex to modify
NSString *cellIndex = [[notif userInfo] objectForKey:#"index"];
[yourDataSource removeObjectAtIndex:[cellIndex intValue]]
[yourTableView reloadData];
}
}
in your secondView:
-(void)switchChanged
{
NSNotificationCenter *ncSubject = [NSNotificationCenter defaultCenter];
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:#"indexNum",#"index", nil];
[ncSubject postNotificationName:#"modifyCell" object:nil userInfo:dict];
[ncSubject removeObserver:self];
}
You should reload your tableview after each UISwitch change. Such as:
- you set a delegate from your UISwitch to your UITabBarViewController (or the class which controls the events)
- you should store your tableview's cells' number in a variable
- this variable will change after each UISwitch change
- after the variable change, you should reload the tableview
In the viewWillAppear method of the table view controller I would check whether the setting has been changed or not. If it has changed then I would redraw the cell by calling its the reloadData method.
Sometimes it is recommended to call reloadData through performSelectorOnMainThread:
[ self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
And your data loading methods (numberOfSectionsInTableView, numberOfRowsInSection, cellForRowAtIndexPath, etc.) will have to consider the settings value accordingly.
I'm trying to parse certain parts of the string when a user types into an UITextView or the setText: method is called, and then setting an NSAttributedString back into the text view. However in my current implementation this causes an infinite recursive loop. Since setting the new attributed text causes the text to change (and the notification to fire) whereby I then re-parse the text.
Somebody suggested I use some kind of flag, so while i'm parsing and setting the text, I don't keep doing it. Though this doesn't seem like the optimal solution. Here's a snippet of my code...
CustomTextView.h (UITextView subclass)
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textViewDidChange:) name:NSTextViewTextDidChangeNotification object:self];
CustomTextView textViewDidChange:
- (void)textViewDidChange:(NSNotification *)notification;
{
__block NSString *string = self.text;
dispatch_async(parserQueue, ^{
NSAttributedString *parsedString = [self parseAttributesForString:string];
dispatch_async(dispatch_get_main_queue(), ^{
[self setAttributedText:parsedString];
});
});
}
CustomTextView setText:
- (void)setText:(NSString *)text
{
[super setText:text];
[self textViewDidChange:nil];
}
Thanks!
Okay so first I'd add an observer to the property text instead of subclassing the class and post a notification. Next, I'd just check what class the text object is. I would this by calling [text isKindOfClass:[NSString class]]. By calling this you know whether the object needs to get parsed again.