Delegate methods of NSTextField using NSNotification - objective-c

I have an NSTokenField in a window. I am using it to store tags related to a Core Data object. Right now I have it set up such that I can add tags to the objects, but I cannot delete them. I need a delegate method on the NSTokenField that can let me know when the user has moved the focus out of the NSTokenField. Since NSTokenField is a subclass of NSTextField I figured that I could use its delegate methods. It has two that I think could be useful:
- (void)textDidChange:(NSNotification *)aNotification
- (void)textDidEndEditing:(NSNotification *)aNotification
I set my controller class as the delegate of my NSTokenField and put both of these methods into my controller class. I put a basic NSLog into each of them and neither is triggered when I interact with the NSTokenField. I am guessing it has something to do with NSNotification. How do I activate these methods?

The NSTokenField invokes the controlTextDidChange: and the controlTextDidEndEditing: notifications; change the two methods above, implementing them as:
- (void)controlTextDidChange:(NSNotification*)aNotification
{
//Code here..
}
- (void)controlTextDidEndEditing:(NSNotification *)aNotification
{
//Code here..
}

Related

How to overwrite textDidChange: method correctly?

I subclassed NSTextField and overwrite the textDidChange: as:
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
}
But when I drag a input text box into my .xib file and control drag another class to assign the delegate I found the delegate's controlTextDidChange: method was never called.
Now tryingto solve this problem, I tried two ways al below:
I. calling super:
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
[super textDidChange:notification];
}
But I got an error in runtime: attempt to insert nil object from objects[0]
II. calling delegate's method
- (void)textDidChange:(NSNotification *)notification
{
// ... My own operation
if ([self.delegate responseToSelector:#selector(controlTextDidChange:)])
{
[self.delegate ...]; // <--- Opps, something not happerned here.
}
}
What not happerned? I expected that the auto-complete should display the controlTextDidChange: method at the position of ... above. But it did not, actually. I typed the method directly, compilation fail because method not found.
How should I make my sub-class call the delegate normally? How should I overwrite the textDidChange: method correctly?
Further question for Vytautas:
I am sure I was using NSTextField. And I set a break point inside controlTextDidChange: method. As it was called, I should have known.
I did control-drag the text field to the delegate object, and I print delegate object in the textDidChange: method, it was sure that the delegate was set correctly.
The other delegate methods, such as controlTextDidBeginEditing: were called correctly. But controlTextDidChange: not called
I tried comment out the over-written in the subclassed NSTextField class, then controlTextDidChange: was called.
Therefore I was quite sure that I am not overwritting the textDidChange: right. But I do not known how to fix it.
What made me confused mostly was that why auto-completion did not show the controlTextDidChange: method when I attempted to call it.
About the auto-completion, here is how it showed:
No - controlTextDidChange: method.
2nd further reply for Vytautas:
I tried calling '[self controlTextDidChange]' but it did not work, and error occurred (as highlighted below):
I can say that - controlTextDidChange: is called for sure.
Maybe there is something wrong with you bindings in your *.xib.
Also it can be that in *.xib you are using NSTextView, not NSTextField.
In this case - controlTextDidChange: won't be called for sure.
If that is the case then you should take a look to NSTextView, NSTextViewDelegate and NSTextDelegate. NSTextView delegate has an alternative method for this - textDidChange:

UICollectionView: How to get item size for a specific item after setting them in delegate method

I am fiddling with the new UICollectionView and the UICollectionViewLayout classes. I have created a custom layout, subclassing UICollectionViewFlowLayout.
My cell sizes are changing dynamically and I set the item sizes using the delegate method below
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
return CGSizeMake(80, 80);
}
Now, under the prepareLayout method of my custom UICollectionViewFlowLayout class, I need to access these size variables so that I can make calculations how to place them and cache them for layoutAttributesForItemAtIndexPath.
However, I can't seem to find any property under UICollectionView or UICollectionViewFlowLayout to reach the custom item sizes I set in the delegate method.
Found it myself.
Implement the custom class like without omitting UICollectionViewDelegateFlowLayout
#interface SECollectionViewCustomLayout : UICollectionViewFlowLayout
<UICollectionViewDelegateFlowLayout>
and then you can call
CGSize size = [self collectionView:self.collectionView
layout:self
sizeForItemAtIndexPath:indexPath];
Looking at the various UICollectionView... header files, and watching the WWDC 2012 Session 219 - Advanced Collection Views and Building Custom Layouts video (from about 6:50 onwards), it seems the extensible delegate pattern takes advantage of dynamic typing to ensure the layout can properly access its extended delegate methods.
In short...
If you define a custom layout with its own delegate, define that delegate protocol in the layout's header file.
Your delegate object (typically the UI(Collection)ViewController that manages the collection view) should declare itself to support this custom protocol.
In the case that your layout is just a UICollectionViewFlowLayout or subclass thereof, this just means declaring conformance to UICollectionViewDelegateFlowLayout.
Feel free to do this in your class extension in the .m file if you'd rather not #import the layout header into the delegate's interface.
To access the delegate methods from the layout, call through to the collection view's delegate.
Use the layout's collectionView property, and cast the delegate to an object conforming to the required protocol to convince the compiler.
Don't forget to check that the delegate respondsToSelector: as usual prior to calling optional delegate methods. In fact, if you like, there's no harm in doing this for all methods, as the typecasting means there is no runtime guarantee the delegate will even implement the required methods.
In code...
So if you implement a custom layout that requires a delegate for some of its information, your header might look something like this:
#protocol CollectionViewDelegateCustomLayout <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath;
#end
#interface CustomLayout : UICollectionViewLayout
// ...
#end
Your delegate declares conformance (I've done so in the implementation file here):
#import "CustomLayout.h"
#interface MyCollectionViewController () <CollectionViewDelegateCustomLayout>
#end
#implementation
// ...
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath
{
return [self canDoSomethingMindblowing];
}
// ...
#end
And in your layout's implementation, you access the method like this:
BOOL blowMind;
if ([self.collectionView.delegate respondsToSelector:#selecor(collectionView:layout:shouldDoSomethingMindblowingAtIndexPath:)]) {
blowMind = [(id<CollectionViewDelegateCustomLayout>)self.collectionView.delegate collectionView:self.collectionView
layout:self
shouldDoSomethingMindblowingAtIndexPath:indexPath];
} else {
// Perhaps the layout also has a property for this, if the delegate
// doesn't support dynamic layout properties...?
// blowMind = self.blowMind;
}
Note that it's safe to typecast here, as we're checking the delegate responds to that method beforehand anyway.
The evidence...
It's only speculation, but I suspect it is how Apple manages the UICollectionViewDelegateFlowLayout protocol.
There is no delegate property on the flow layout, so calls must go via the collection view's delegate.
UICollectionViewController does not publicly conform to extended flow layout delegate (and I doubt it does so in another private header).
UICollectionView's delegate property only declares conformance to the 'base' UICollectionViewDelegate protocol. Again, I doubt there is a private subclass/category of UICollectionView in use by the flow layout to prevent the need for typecasting. To add further weight to this point, Apple discourages subclassing UICollectionView at all in the docs (Collection View Programming Guide for iOS: Creating Custom Layouts):
Avoid subclassing UICollectionView. The collection view has little or no appearance of its own. Instead, it pulls all of its views from your data source object and all of the layout-related information from the layout object.
So there we go. Not complicated, but worth knowing how to do it paradigm-friendly way.
There is a swift version:
self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAtIndexPath: indexPath)
Check out UICollectionView-FlowLayout on GitHub. Same idea, this just makes accessing the extended delegate methods of flowLayout a little cleaner.
For the later readers, IOS 7 has UICollectionViewFlowLayout which has defined it.
In my case everything about layout, cell layout etc. is being defined inside nib for UIViewController and separate nib for UICollectionViewCell. MyCollectionViewCell contains UIImageView with autolayout to cell with padding/margins but square-shaped.
I need round icons instead squared but don't want to take care which nib I use for iPhone or for iPad (I have separate nibs for devices and for orientation as well).
I don't want to implement #selector(collectionView:layout:sizeForItemAtIndexPath:) into my view controller.
So, inside collectionView:cellForItemAtIndexPath:
I can just use
CGSize size = cell.imageView.bounds.size;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = size.height/2.0;
Because collectionView:layout:sizeForItemAtIndexPath: call before collectionView:cellForItemAtIndexPath: and layout done.
You can check round avatars on the bottom

textDidChange vs controlTextDidChange

Can someone explain me why textDidChange isn't handling my delegate but controlTextDidChange works from NSTextField.
- (void)controlTextDidChange:(NSNotification *)aNotification{
NSBeep();
}
from
- (void)textDidChange:(NSNotification *)aNotification{
NSBeep();
}
controlTextDidChange: is the correct delegate method defined on NSTextField (inherited from NSControl).
textDidChange: is a method that, when called on NSTextField, makes it behave as if its text changed (including calling the above method). It is not a delegate method for you to implement.
It's a little inconsistent of Apple as they do have a textDidChange: delegate method on UISearchBarDelegate.
textDidChange: Informs the delegate that the text object has changed its characters or formatting attributes.
I'm guessing that means its font (text attributes) changes, and not the text inputted.

keyUp event heard?: Overridden NSView method

UPDATED: I'm now overriding the NSView keyUp method from a NSView subclass set to first responder like below, but am still not seeing evidence that it is being called.
#implementation svsView
- (BOOL)acceptsFirstResponder {
return YES;
}
- (void)keyUp:(NSEvent *)event {
//--do key up stuff--
NSLog(#"key up'd!");
}
#end
--ORIGINAL POST--
I'm new to Cocoa and Obj-C and am trying to do a (void)keyUp: from within the implementation of my controller class (which itself is of type NSController). I'm not sure if this is the right place to put it, though. I have a series of like buttons each set to a unique key equivalent (IB button attribute) and each calls my (IBAction)keyInput method which then passes the identity of each key onto another object. This runs just fine, but I also want to track when each key is released.
--ORIGINAL [bad] EXAMPLE--
#implementation svsController
//init
//IBActions
- (IBAction)keyInput:(id)sender {
//--do key down stuff--
}
- (void)keyUp:(NSEvent *)event {
//--do key up stuff--
}
#end
Upon fail, I also tried the keyUp as an IBAction (instead of void), like the user-defined keyInput is, and hooked it up to the appropriate buttons in Interface Builder, but then keyUp was only called when the keys were down and not when released. (Which I kind of figured would happen.)
Pardon my noobery, but should I be putting this method in another class or doing something differently? Wherever it is, though, I need it be able to access objects owned by the controller class.
Thanks for any insight you may have.
NSResponder subclasses (such as a custom subclass of NSView) handle key events. They even handle more abstract-level events such as "move left/right", "insert newline", etc. They can then send a message to a controller to respond accordingly.
Wow! I think I've nailed it. I just needed to instantiate my subclass with a NSView/NSWindow reference to the class in IB. Wasn't actually creating an instance of it in the UI. The past several hours down the crapper! Sans that I learned a thing or two along the way. ;)

NSTextFieldCell Delegate?

I have a text field cell in a table view, from which I need to be made aware when it ends editing. I thought I would set my Controller class as the text field cell's delegate, and then use NSTextField's delegate method textDidEndEditing:, but realized that the text field cell doesn't seem to have delegate methods? Why is this, and what can I do (other than subclassing) to be informed when editing is finished?
Thanks
NSTextFieldCell inherits from NSCell (well, technically from NSActionCell which inherits from NSCell). The NSCell class is used to (from the docs):
The NSCell class provides a mechanism for displaying text or images in an NSView object without the overhead of a full NSView subclass.
Notably, The cell class is used for "displaying text or images", and not dealing with interaction with the user. Similarly, with the NSTextField class:
The NSTextField class uses the NSTextFieldCell class to implement its user interface.
The NSTextField deals with the actual user input, whilst using the text field cell to simply implement its user interface, and similarly, the delegate methods to provide notification when the editing of text has ended is provided through the NSTextField class and not through the NSTextFieldCell class.
If you want to be notified of when editing ends in an NSTableView, then you need to register yourself as an observer of the NSTextDidEndEditingNotification (you might want to read the NSNotificationCenter class reference if you are unfamiliar with notifications). To do this, place the following in your controller class; the awakeFromNib function is a good place to include it to ensure that it is called upon your application's startup:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(textDidEndEditing:)
name:NSTextDidEndEditingNotification
object:tableView];
Where tableView is the pointer to your NSTableView object. Then, simply implement the method as follows:
- (void)textDidEndEditing:(NSNotification *)aNotification
{
// Do what you want here
}
Don't forget to remove yourself as an observer upon deallocation:
- (void)dealloc
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
}
The reason that you set the object that you are observing to be the NSTableView instance (and not the cell itself) is that under the hood, when you edit a cell in the table, the cell that you are dealing with isn't being edited directly; it is the window's (or a custom) field editor. When editing ends, the field editor then passes the new value for that cell on to the table view. However the table view will post a notification to say that a cell has finished being edited.
Implement the tableView:setObjectValue:forTableColumn:row: method in the NSTableViewDataSource protocol. Put it next to the tableView:objectValueForTableColumn:row: method that you've already implemented.
- (void)tableView:(NSTableView *)aTableView
setObjectValue:(id)anObject
forTableColumn:(NSTableColumn *)aTableColumn
row:(NSInteger)rowIndex
{
[mutableArrayWithStrings replaceObjectAtIndex:rowIndex withObject:anObject];
}