Cocoa auto-update dictionary values from NSTextField - objective-c

Right now I have a dictionary containing a string value and an NSTextField in my interface. However, in order to update this value I have to click a button that then runs the update code. How can I make it dynamically update anytime the text field's value changes?

Look into using Cocoa Bindings.
They're designed to keep your view (NSTextField) in sync with your model (dictionary) without writing all the glue code in between. They're a bit tricky to learn, but once you understand them, they're super useful.
In your case, you'd bind the "value" binding of the NSTextField to a property in your code.
An alternative is to set up an NSTextFieldDelegate and implement:
- (void)controlTextDidChange:(NSNotification *)aNotification
to modify the value in the dictionary. For example,
- (void)controlTextDidChange:(NSNotification *)aNotification {
[myDictionary setValue:[myTextField stringValue] forKey:#"MYDictionaryKey"];
}
Now whenever the user modifies the text in the NSTextField, the text field will fire this callback to its delegate. This way, you can make sure the dictionary always has the same value as what's displayed on screen.
If you only want the changes to take effect when the user is done editing, you'd implement:
- (void)controlTextDidEndEditing:(NSNotification *)aNotification

Related

Capture the space key in a window containing several NSTextFields

I have a large window with a number of NSTextField fields on it, and I would like to intercept the space bar even while any of the text fields have focus, so that pressing space at any time will cause a separate action.
Subclassing NSTextField and overriding -keyUp: allows me to detect the space key, but a blank is still inserted in the text field. -keyDown: does not fire at all.
For other keys like Return and the arrow keys, I could use the control:textView:doCommandBySelector: delegate method, but it does not fire with the space bar.
There's a lot of advice out there for NSTextView, but I have found none for NSTextField.
Have you experimented with adding an event monitor? You create them via the NSEvent class method addLocalMonitorForEventsMatchingMask(_:handler:)
and they give you first dibs on the events you specify. In your case you'd add a monitor for keyDown events, then in the associated block you decide what to do with them. At the end of the block you can return the event just as it arrived, in which case it will behave normally, you can swallow the event (return nil), or you can modify the event by creating a new event object and returning that instead. Here's an example:
// You can add monitors anywhere - I've chosen the AppDelegate for
// this simple example.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^NSEvent * (NSEvent * theEvent) {
if (theEvent.keyCode == 49) {
NSLog(#"swallowing spacebar");
theEvent = nil;
}
return theEvent;
}];
}
Since the behavior you're trying to implement is at the level of the window (or perhaps a view that's an ancestor of all of the text fields), you should consider implementing -performKeyEquivalent: at that level instead of trying to capture the space key in each individual text field.
In your implementation, check the event's modifierFlags to make sure none of Control, Option, or Command are pressed (assuming you only want to handle unmodified space key presses) and if its charactersIgnoringModifiers equals #" ". If so, do what you want and then return YES to indicate you've handled the key event. Otherwise, return NO to allow it to be handled normally.
If you know how to do it for NSTextView, then you know how to do it for NSTextField, because NSTextField uses an NSTextView called the "field editor" for editing. You can get the window's default field editor like so:
NSTextView* fieldEd = [myWindow fieldEditor: YES forObject: nil];
You can also use a window delegate to provide a custom field editor.
So you might want to look into ReactiveCocoa
Go to the video on this page and look at 5:14 to see something similar to what you might like.
ReactiveCocoa can be hooked into all your text fields and then any time a space is hit a signal handler could pick it up. (Probably, however, more than you want to get into).

Where to define NSArray and where to define Button action?

I have made an array of text strings and want to pull these out an into a label by EITHER swiping of pressing a button. So i have two different functions/methods, the button and the swipe method.
Where and how do I define the array so that these methods can refer to it? Should it be a 'extern NSArray' ?
I have uploaded the image of full code externally http://s1.postimg.org/b2e3m4v67/Sk_rmbillede_2014_05_11_kl_15_48_28.png
Not sure though if that's a violation of some rules here(?)
You want the quote to change on swipe/button press.
In your button press/swipe methods you're setting the text property of the VC's label property to something called Quoteselected. And it looks like Quoteselected is a random element of the array Quotes - or at least maybe it is, since that random number could be 6-10, and you don't have any objects in the Quotes array at those indices - so if those numbers are ever generated by the random function, your program will crash due to an index out of bounds error.
What you probably want to do is generate a new random number on each user interaction and then at that point change the value of Quoteselected to be the object at that index of the array. And then assign that to the label's text property.
As far as defining the Array - I wouldn't have done it the way you did. What you've got there is an "ivar", an instance variable. On iOS, those are typically properties. And since it's a "private" array that outside classes won't need to know about, I'd declare it as a part of the class extension.
So,
#interface BOViewController()
#property NSArray *quotes;
#end
Also note my capitalization changes, that's better style.
So now you've got an array property declared, but there's no data in it. It depends on how you created your View Controller instance. Assuming you did it in a storyboard, it would go in awakeFromNib: or viewDidLoad: (if you instantiated the VC automatically, you might put it in the initWithNibName: method).
- (void)viewDidLoad {
[super viewDidLoad];
self.quotes = #[#"Test", #"Number 3"...];
Then when you want to reference the array in other parts of the class:
self.label.text = self.quotes[0];
Note that your existing code should work, it's just not typical Cocoa coding style.

Cocoa - capturing specific events

I'm fairly new to Cocoa programming, and have a question about control event handling.
I create an 'action' for a button, and get an updated AppDelegate.m to handle this eg.
- (IBAction)seedBtnPressed:(id)sender {
NSString* myString = #"Hi there";
[_updateLbl setStringValue:myString];
}
When running this, pressing the 'seed' button does what it should - the label updates.
My question is: why have I captured the 'button press event' by default, as I don't see any place where I've specified this. Alternately, how would I capture a mouse-over event with an action? I gather I'd create another action for the button, but am not sure how to specify this to handle 'mouse-over' events only? Sorry if I've used Windows terminology here, I understand Cocoa uses different names for things.
Thanks
Pete
You need to Subclass the NSButton class (or even better the NSButtonCell class).
- (void)mouseEntered:(NSEvent *)theEvent;
- (void)mouseExited:(NSEvent *)theEvent;
They should get called when the mouse enter and exit the area. You may also need to re create the tracking area, look here:
- (void)updateTrackingAreas
For fade in and fade out effect I played with animator and alpha value for example:
[[self animator]setAlphaValue:0.5];
To get mouse-over events for an NSView you should use the NSTrackingArea class (assuming you're targeting a relatively modern version of OS X). Apple have good documentation on this available at http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/EventOverview/TrackingAreaObjects/TrackingAreaObjects.html
For your other query about the seedBtnPressed: triggering although you don't specify it - have you set an action in Interface Builder for the button rather than programmatically?

Descendant as an parent's delegate

Lets say I want to create my own text view with maximum characters constrain. And I want to do that constrain in level below - in text view.
I think of creating CustomTextView : UITextView where customTextView.delegate would be the same object - customTextView (self.delegate = self). The definition of the class would be CustomTextView : UITextView <UITextVIewDelegate> and I would implement – textView:shouldChangeTextInRange:replacementText: to do the constrain logic in.
But somehow this do not work. Can I get explanation why or what can be wrong and how to achieve my intent?
If you are subclassing UITextView, why would you need to set itself as the delegate? The delegate is only used to notify code outside of the UITextView that something changed in the UITextView. This means that the UITextView is notified of changes to itself first and, using the delegate, you can notify external code (UIViewController, etc.) of what happened. If you are subclassing the UITextView, it should receive those change notifications from the OS.
However, looking through the documentation, I cannot see how you would track the built-in events by subclass alone. Here's an article I found with a Google search: Subclassing a UITextView

NSComboBox - Obtaining selected information and NSComboBoxDataSource

For the life of me, I am being continually stumped with NSComboBox.
I created an object that conforms the NSComboBoxDataSource protocol, and implemented:
- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox;
- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index;
I set the instance of my NSComboBox to use a Data Source, and set this object as the source. That works great, my implementation returns the number of items, and returns an NSString value for an item at specific indices.
Then I decide that I want to do something when something is selected, this is where my problems begin. There is no obvious method to override in the NSComboBoxDataSource protocol to handle the selection of items in the combo box.
So, I also have my object conform to NSComboBoxDelegate and implement:
- (void)comboBoxSelectionDidChange:(NSNotification *)notification;
Unfortunately, unlike NSTableView on selection, the notification's object is the NSComboBox not the object of the item selected. "Fine" I tell myself, I will call the NSComboBox method:
- (id)objectValueOfSelectedItem;
This should return the item that is selected and I can go from there. However, that method is to be called ONLY when usesDataSource is set to NO, which is not my case. Warnings start flying when I use this.
So, my question is, what is the proper way to handle NSComboBox selections when you are using a data source?
I think you want indexOfSelectedItem instead of objectValueOfSelectedItem. Then since you're the data source you should be able to call your own comboBox:objectValueForItemAtIndex: method.