NSTokenField and deleting tokens - cocoa

My app allows users to attach tags to certain model objects (subclasses of NSManagedObject). The Tag class is also a subclass of NSManagedObject. I decided to use NSTokenField to display the tags, where each token holds an instance of Tag as the represented object. It all works pretty good but I'm stuck in situations where the user deletes a token as I want to check whether the associated Tag has become obsolete and should be deleted.
I was expecting a method in NSTokenFieldDelegate or NSTokenFieldCellDelegate which would allow me to intercept and check a delete action on a token.
After some googling I found this post addressing the topic. I implemented the suggested method controlTextDidChange: in my controller (the delegate of the token field). Upon inspecting the control that is passed as an argument, it revealed to be an instance of NSTokenTextView for which I cannot find any documentation (probably a private class).
Has anybody run into this and found a solution to gracefully delete tokens while maintaining the underlying model of represented objects?
EDIT
I found this as well, which seems to suggest that for some reason it just is not designed to work like the rest of us would expect.

You should be able to simulate a delete delegate by creating a token wrapper class that has a pointer back to the owner as well as the wrapped object:
#protocol TokenWrapperDelegate
-(void)tokenWasDeleted:(id)token;
#end
#interface TokenWrapper : NSObject {
id<TokenWrapperDelegate> owner;
id token;
}
-(id)initWithWrappedToken:(id)token owner:(id<TokenWrapperDelegate>)owner;
#property (nonatomic, weak) id<TokenWrapperDelegate> owner;
#property (nonatomic, strong) id token;
#end
Then have the TokenWrapper dealloc notify the owner that the token was deleted:
#implementation TokenWrapper
...
-(void)dealloc {
[owner tokenWasDeleted:self.token];
self.token = nil;
[super dealloc];
}
#end
Then in your representedObjectForEditingString callback, return an autoreleased wrapper pointing at your owner and your real token. You'll also have to make sure to change the other NSTokenField delegate callbacks to delve into the wrapper object.
Make sure the owner sets a bit to ignore these callbacks when you're manually changing the contents of the NSTokenField (like by calling setObjectValue).

I gave up (after stumbling around for more than 6 hours) on the approach of in place editing my tags using NSTokenField. I eventually ended up with a number of fragile hacks which would ripple through my application as this feature is needed in various places.
Unless somebody has some strong points to counter my current opinion, NSTokenField is a bit of an ugly monster bringing a half baked implementation to the party. Which is a shame as the presentation side of it really appeals to me...
EDIT: After some further experimenting, I settled on a reasonably acceptable compromise. I use NSTokenField in a readonly mode. It takes the relevant tags from my Core Data store and displays them as tokens. I added a menu to each token which allows the user to edit, delete or review a tag. A standard push button next to the token field allows to add a new tag. Editing and reviewing is implemented using NSPopovers. See this example:
There are still some minor issues:
The tokens tend to disappear at arbitrary times when hovering the mouse over the token field. This appears to be a bug.
As the token field only accepts NSArray for its binding, I introduced a "virtual property" named tagsAsArray that takes the associated tags and converts them from NSSet to NSArray. I think this impacts the KVO as edits of tags only show up after pressing enter or clicking outside the token field.

Related

textShouldEndEditing does not get called in NSTableView

When a user adds a new managed object, it shows up in a table, which scrolls down to the new entry, and the name of the new object (a default value) goes into editing mode.
I need to check if the name of the new object is unique in the datastore, so I can't use a formatter for this. I think the perfect moment where I should validate this, is whenever the user tries to commit the entry's name value, using textShouldEndEditing:.
I subclassed NSTableView and overrid following methods, just to be able to check in the log if they get called.
- (BOOL)textShouldEndEditing:(NSText *)textObject {
NSLog(#"textSHOULDendEditing fired in MyTableView");
return [super textShouldEndEditing:textObject];
}
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
NSLog(#"control:textShouldEndEditing fired in MyTableView");
return YES;
}
- (void)textDidEndEditing:(NSNotification *)aNotification {
NSLog(#"textDIDEndEditing fired in MyTableView");
}
textDidEndEditing: gets called fine, but textShouldEndEditing: does not.
In the NSTableView Class Reference, under Text Delegate Methods, both methods textShouldEndEditing: and textDidEndEditing: are listed. Someone please explain why one gets called and the other doesn't.
I think the NSTableView acts as the delegate for an NSTextField that gets instantiated as a black box delegate for the NSTextFieldCell. So what is referred to as delegate methods in the NSTableView Class Reference, actually implement the text manipulating methods for the NSTextField object.
I tried to declare the NSTextFieldCell as an outlet in my NSTableView. I also tried to declare several protocols in the NSTableView.
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#interface MyTableView : NSTableView <NSTextDelegate, NSTextFieldDelegate, NSControlTextEditingDelegate, NSTableViewDelegate, NSTableViewDataSource> {
}
#end
Don't laugh, I even tried to declare my table view as its own delegate :P
After banging my head one entire day on this issue without finding any conclusive answer in Apple documentation, I decided to share the solution I've found in case somebody else struggles with the same problem.
According to the documentation, as the original poster mentioned, the methods control:textShouldBeginEditing and control:textShouldEndEditing of NSControlTextEditingDelegate should be called directly on the delegate:
This message is sent by the control directly to its delegate object.
Furthermore, a Technical Q&A was issued by Apple with the title Detecting the start and end edit sessions of a cell in NSTableView where it's clearly stated the following:
A: How do I detect start and end edit sessions of a cell in NSTableView?
In order to detect when a user is about to start and end an edit session of a cell in NSTableView, you need to be set as the delegate of that table and implement the following NSControl delegate methods:
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor;
- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor;
The table forwards the delegate message it is getting from the text view on to your delegate object using the control:textShouldEndEditing: method. This way your delegate can be informed of which control the text view field editor is acting on its behalf.
I found nothing in Apple's documentation stating anything different and if someone does, a documentation pointer would really be appreciated.
In fact, this appears to be true if a cell-based NSTableView is being used. But as soon as you change the table to a view-based table, the delegate method is not called any longer on the table delegate object.
A Solution
However, some heuristic tests I performed showed that those delegate methods get called on a view-based table delegate if (and as far as I know: and only if):
The table delegate is set.
The delegate of the editable control is set.
If you remove either delegate, the methods of the NSControlTextEditingDelegate protocol will not be called.
What's unexpected according to the (only) documentation is setting the delegate of the editable control. On the other hand setting the delegate object to receive delegate notifications sounds rather intuitive to me, and that's why I tried in the first place. But there's a catch! The curious thing, though, is that that's not sufficient. If the table delegate is removed, the NSControlTextEditingDelegate methods will not be called even if the delegate of the editable control is set (which is the weirdest thing to me).
Hope this helps somebody else not to lose time on this issue.
in your question you mention the insertion of a "managed object" and that was the problem. It seems that you are using a view based table, but the textShouldEndEditing: method only gets called for cell based tables.
I overrid -(void)awakeFromInsert; in the (subclassed) managed object, to construct a unique default value for the name-property.
Also, I ended up not overriding the -(BOOL)textShouldEndEditing: method in the table view. Instead, I check if a newly entered name-property is unique in the (subclassed) managed object's -(BOOL)validate<Key>:error:.
Together, the above two strategies result in unique name-properties in all managed objects.
Maybe I could have forced the NSTextFieldCell to go into editing mode, resulting in -(BOOL)textShouldEndEditing: to get called every time.
Some remarks though:
It seems -(BOOL)textShouldEndEditing: returns NO when the -(BOOL)validate<Key>:error: returns NO.
Both -(BOOL)textShouldEndEditing: and -(BOOL)validate<Key>:error: methods are called only when the user actually makes changes to the property.

Updating NSTokenField after changing content programmatically

I have implemented a NSTokenField which uses some custom data objects for display of dynamic data. Delegate is set up fine and displays the correct values of the token.
I've also implemented a menu on the tokens which allows selection of the format used for display of each token. My problem is however that I'm unable to make the NSTokenField respond immediately to these changes and redraw the token with the newly selected format.
If I click outside of the NSTokenField so it resigns first responder it redraws immediately. I can also do this programmatically by explicitly setting first responder to nil. The only problem with this is that the NSTokenField looses focus - and reassigning it as first responder selects everything in the field so the user may accidentally overwrite the entire content.
So my question is whether there are any way of just triggering the NSTokenField to redraw its content without changing focus and selection?
I had the same problem and found the only workable solution was to "reset" the token field every time its contents changed. Basically this boiled down to running the following method of the view controller that handled the view with the token field:
- (void) resetTokenField {
// Force the token field to redraw itself by resetting the represented object.
id anObject = [self representedObject];
[self setRepresentedObject: nil];
[self setRepresentedObject: anObject];
[[self tokenField] setNeedsDisplay: YES];
}
The represented object is the object that holds all the tokens that are being displayed. In my case that was a chunk of richt text where tokens are basically tags.
I found NSTokenField to be a royal pain in the neck but persevered as I quite like the presentation side of it.
EDIT: My token field was bound to the tagsAsArray method of the represented object of the view controller. So basically I used Cocoa bindings to solve the problem.
It turns out that you are supposed to call setNeedsDisplay on the NSTextView object used by the NSTokenField, instead of calling setNeedsDisplay on the NSTokenField itself.
Thus the following code works: (Written in PyObjC)
text_view = token_field.cell().fieldEditorForView_(token_field)
text_view.setNeedsDisplay_(YES)

Why would I use NSObjectController?

Although I have searched for many information about Cocoa Bindings, I still remain relatively unsatisfied with information I have and got. It seems that topic is somewhat troublesome for many and many are just avoiding this pattern, which I believe should not be.
Of course, it may seem that bindings are sometimes too complicated or perhaps designed with too much overhead...
However, I have one very direct and specific question: Why is NSObjectController needed if I can establish bindings directly?
For example, the code:
[controller bind:#"contentObject" toObject:self withKeyPath:#"numberOfPieSlices" options:nil];
[slicesTextField bind:#"value" toObject:controller withKeyPath:#"content" options:nil];
[stepperControl bind:#"value" toObject:controller withKeyPath:#"content" options:nil];
Does exactly the same as:
[slicesTextField bind:#"value" toObject:self withKeyPath:#"numberOfPieSlices" options:nil];
[stepperControl bind:#"value" toObject:self withKeyPath:#"numberOfPieSlices" options:nil];
In my case here, we are talking about property of the class inside which everything is happening, so I am guessing the need for NSObjectController is when:
key path for controller is object and binding of other controls is needed to its properties, not to its value as with primitives and wrappers around them is the case (numberOfPiesSlices in my case is NSInteger)
or when binding is needed from other outside objects, not only between objects within one
Can anybody confirm or reject this?
One of the benefits/points of bindings is to eliminate code. To that end, NSObjectController etc. have the benefit that they can be used directly in interface builder and set up with bindings to various UI elements.
Bindings only represent part of the functionality on offer. The *ObjectController classes can also automatically take care of a lot of the other more repetitive controller (as in Model, View, Controller) code that an application usually needs. For example they can:
connect to your core data store and perform the necessary fetches, inserts and deletes
manage the undo / redo stack
Pick up edited but not committed changes to your UI and save them (e.g. if a window is closed while focus is still on an edited text field - this was a new one to me, I found it from mmalc's answer in the thread below).
If you're doing none of this, then it probably isn't worth using NSObjectController. Its subclasses (NSArrayController etc) are more useful.
Also see here for a discussion of your exact question!
Why is NSObjectController needed if I can establish bindings directly?
I read this question a few days ago while looking for some information about NSObjectController, and today while continuing my search, I found the following passage which seemed relevant to the question:
There are benefits if the object being bound to implements
NSEditorRegistration. This is one reason why it’s a good idea to bind
to controller objects rather than binding directly to the model.
NSEditorRegistration lets the binding tell the controller that its
content is in the process of being edited. The controller keeps track
of which views are currently editing the controller’s content. If the
user closes the window, for example, every controller associated with
that window can tell all such views to immediately commit their
pending edits, and thus the user will not lose any data. Apple supply some generic controller objects (NSObjectController,
NSArrayController, NSTreeController) that can be used to wrap your
model objects, providing the editor registration functionality.
Using
a controller also has the advantage that the bindings system isn’t
directly observing your model object — so if you replace your model
object with a new one (such as in a detail view where the user has
changed the record that is being inspected), you can just replace the
model object inside the controller, KVO notices and the binding
updates.

Manual binding in Cocoa

I have an ImageView which shows a lock, informing if an opened file is locked or not. I have 2 images for locked and unlocked cases. I want synchronize the displayed image with boolean value of my object representing an opened file.
To do this I want my ViewController to change the image in my ImageView depending on lock state of object. So both object and ViewController have a property "isLocked".
How can I synchronize them? It is easy in IB but I don't know how to do it programmatically. I tried in initialize method of my ViewController to use:
[ViewController bind:#"value" toObject:[ArrayController selection] withKeyPath:#"isLocked" options:nil];
But it doesn't work. In documentation it is said that I have to expose my binding before using it.
I try to put the following code in initializer method of my object:
[self exposeBinding:#"isLocked"];
But Xcode doesn't recognize this method.
Does somebody have experience with this kind of bindings establishing?
As #nick says, you want Key-Value-Observing.
[arrayController addObserver:self
forKeyPath:#"selection.isLocked"
options:NSKeyValueObservingOptionNew
context:#"this_context"]
Then when isLocked changes the -observeValueForKeyPath:ofObject:change:context: method that you have added to your viewController will be called (as long as you only manipulate isLocked in a KVC compliant way).
The options parameter lets you optionally tweak exactly what conditions will trigger the notification and what data is sent along with the notification. The context parameter is there to help you distinguish between notifications that you registered to receive and notifications your superclass registered to receive. It is optional.
Bindings seem like they might be useful to keep two values in sync. However, this is not what they do at all.
Yes, lots of things seem to give the impression that this is what they do, and there isn't much saying that this isn't what they do, also lots of people believe that this is what they do - but no, you cannot use them for this.
Only a handful of classes support bindings (they are listed here) and then, and this is the important bit, those classes only support binding their named bindings, and these bindings are not instance variables. eg NSTextField has a 'fontFamilyName' binding yet NSTextField does not have a 'fontFamilyName' property or instance variable, even a derived one. NSTextField does have a 'isBordered' property but not a binding - so you cannot bind 'isBordered'.
It does not mean anything to 'bind' an arbitrary property of an arbitrary Class.
Yes, you can bind two arbitrary values, the following code works just fine:
#import <Cocoa/Cocoa.h>
#interface SomeObject : NSObject
#property (retain,nonatomic) id someValue;
#end
#implementation SomeObject
#end
int main()
{
SomeObject *source=[SomeObject new];
SomeObject *target=[SomeObject new];
[target bind:#"someValue" toObject:source withKeyPath:#"someValue" options:0];
[source bind:#"someValue" toObject:target withKeyPath:#"someValue" options:0];
[source setSomeValue:#(42)];
NSLog(#"target: %#",[target someValue]);
[target setSomeValue:#(22)];
NSLog(#"source: %#",[source someValue]);
return 0;
}
As far as I can tell, the problem is the bit [ArrayController selection]. The first problem is that ArrayController is (or should be) a class, and getting the class's selection is probably pointless. The other problem is that even if this were an instance, you would be binding to the selection at the time of the call, which is almost certainly not what you want. You want to track the current selection as it changes.
So what you want is probably something like the following:
[myViewController bind:#"value" toObject:myArrayController withKeyPath:#"selection.isLocked" options:nil];

iOS layout; I'm not getting it

Well, "not getting it" is too harsh; I've got it working in for what for me is a logical setup, but it does not seem to be what iOS deems logical. So I'm not getting something.
Suppose I've got an app that shows two pieces of information; a date and a table. According to the MVC approach I've got three MVC at work here, one for the date, one for the table and one that takes both these MCVs and makes it into a screen, wiring them up.
The master MVC knows how/where it wants to layout the two sub MVC's. Each detail MVC only takes care of its own childeren within the bounds that were specified by the master MVC. Something like:
- (void)loadView {
MVC* mvc1 = [[MVC1 alloc] initwithFrame:...]
[self.view addSubview:mvc1.view];
MVC* mvc2 = [[MVC2 alloc] initwithFrame:...]
[self.view addSubview:mvc2.view];
}
If the above is logical (which is it for me) then I would expect any MVC class to have a constructor "initWithFrame". But an MVC does not, only view have this.
Why?
How would one correctly layout nested MVCs? (Naturally I do not have just these two, but the detail MVCs have sub MVCs again.)
Thanks all for replying. I will study the links that were provided.
Let me try to explain my issue one more time, hopefully to making it more clear. Do note that I already figured out that my view does not match iOS's, since I do not like where my code is going.
Yes, I'm calling a UIViewController an "MVC", since it for me at the moment implements all aspects of a MVC; it has controller code and an embedded view, plus the controller usually also holds and provides the data (all TableView examples implement it like this).
MVC can be present on many levels; basically a UITextField could (should?) be a MVC; there is a view, but also controller logic involved that you do not want to mix with other code. Encapsulation. For example: Java's Swing JTextField has a MVC. So does a JTable, JList, ... Multiple MVC patterns nested in other MVC's to build a whole screen.
This what I expect when some platform says it uses the MVC pattern. So When I coded the table, I created a MVC and only send the loadData message with a date as the parameter to it. It needs to take care of the rest itself. I have a Detail MVC that can slide in; I then tell it the object it needs to show and it needs to take care of the rest itself. Encapsulation.
So I have a lot of UIViewControllers with embedded UIViews. And that is not the way to do it...
One more potential link is the great talk from WWDC 2010 on MVC.
http://developer.apple.com/videos/wwdc/2010/
It is Session 116 - Model-View-Controllr for iPhone OS
The session is chock full of practical advice on how MVC really works, what makes it tick, why it's good. But it also has a lot of intro stuff to help folks new to the concept to wrap their heads around it.
If I understand your sentence on Java's Swing classes above are you talking about the anonymous classes that respond to events? If so those are not "MVC's", they are what is termed 'Observers', when they observe an event from the view they take some action (usually send a message to a controller). Cocoa Touch uses the Target/Action paradigm (and delegation) to achieve this.
I'd also strongly suggest you take Matthew and Stephen's advice and write a bunch of code. If you don't build that base of intuition, asking the right question (which is most of what is needed to get a good answer) is very difficult.
I really think the WWDC 2010 talk will help.
Good Luck!
If I understand your question -- and I may not, see my comments on it -- I think you're applying the MVC design pattern far too granularly. Most commonly in the setup you describe you'll have a single Model, a single Controller, and multiple Views that are grouped/combined, as in a .xib file.
In Cocoa Touch terms you'd have one UIView that contains a UILabel with the date and a UITableView for your table. These are your Views.
You'll certainly have a Model for the table data, likely an array of data. Your date data might be from its own model if it's a date retrieved from something or calculated or whatever, something entirely separate from the array of data. If it's instead associated with the array data -- they're both pulling from a database, or the date is calculated from the array data, or what have you -- then you have a single Model.
If the data is all coming from a single Model then a single Controller is likely fine. Even if the data is coming from more than one source/Model you likely only need/want one controller in this setup. The UITableView will have a UITableViewController, and that same controller can take care of providing your date as well.
To sum, the Model View Controller design pattern doesn't call for having a bunch of nested sets of models, views, and controllers. They could be, and sufficiently complex projects may call for it. Broadly, though, you'll have a controller that's associated with a model and one or more views, and that set of objects works together to provide a piece of functionality.
Tbee,
I'll post a tiny code example here, since it seems you're not really getting it.
#interface MyView : UIView
#property (retain) IBOutlet UIButton *button1;
#property (retain) IBOutlet UIButton *button2;
#property (assign) bool myData;
-(IBAction) doButton1:(id)sender;
-(IBAction) doButton2:(id)sender;
#end;
#implementation MyView
#synthesize button1 = _button1;
#synthesize button2 = _button2;
#synthesize myData = _myData;
// I'm leaving out the initWithNib, viewDidLoad, etc.
- (IBAction) doButton1:(id)sender
{
// do something as a result of clicking button1
_myData = YES;
}
- (IBAction) doButton2:(id)sender
{
// do something as a result of clicking button2
_myData = NO;
}
#end
Connect those up in InterfaceBuilder, and you've got a working "MVC." You don't need a completely new UIViewController for each button. The one for the View takes care of it.
UITableView and it's associated Views are more complex, and may require an additional UIViewController to help encapsulate. I really don't suggest starting out by using them, but this is a good tutorial here. It's got a lot of images which will show you how to connect things up in IB and the like. It's old, so your XCode may not look like the images, but it helps.
Thanks for the links, I'll look into them.
So far I've rewritten most of my application to using views instead of viewcontrollers (except the toplevel one) and it starts to match up with the API calls that are available like layoutSubviews. What I find disturbing that I need to do this now:
[tableDataSource loadData:date];
[tableView reloadData];
Where in my previous setup all I did was:
[tableViewController loadData:date];
But apparently that is the way to do it. One thing is unclear to me ATM. Since I construct and layout the view in loadView in my AppViewController, how do they get relayouted if the orientation changes. The VC does not have a layoutSubviews, so I should use the didRotateFromInterfaceOrientation and reposition the subviews from there?
BTW, I'm not mixing registering anonymous inner classes as listeners (observers). I'm very experienced with writing Swing components and JavaFX controls. And that probably is the culprit, in Java(FX) every component has a view and a controller (not always a model).

Resources