Forwarding property accessors to an ivar's method - cocoa

I have a panel that elaborates on the selected table row. Obviously, the panel's display often needs updating, and I've pushed that task off to an NSWindowController subclass.
I would like the custom NSWindowController to have #propertys for things like the string values in the text fields and the image values in the NSImageViews. Because I need no novel code in the accessors, I want to #synthesize them. Unfortunately (for me), there is no option to use key-paths in #synthesize property=ivar.
The obvious solution is to write my own accessors of the form
- (void)setTitle:(NSString *)title
{
[titleTextField setStringValue:title];
}
- (NSString *)title
{
return [titleTextField stringValue];
}
but I'd rather not have to do that by hand for each of several properties.
Is there a simpler way? Perhaps a generic way to set up a property to forward to a specific property (except objectValue etc. aren't actually proper properties) of another object?

If you implement valueForUndefinedKey: in your class, you should get a last chance to resolve any key-value path lookups and forward them all to the other object. This is certainly not performant, though, and will only give you significant gains if most of the property names that you're passing through match those of the target object.

I don't think doing any kind of forwarding is going to be useful since the property names on the textfield are going to be different to the ones on your class.
Abstracting away from the textfield by exposing a string property is of course a good idea, but if you want to avoid writing this boiler plate you may find it acceptable to simply expose the text field itself as a property. You can see this in some of Apples APIs, particularly on the iPhone where in SDK 3.0 instead of having properties like text on a table cell they now have textLabel. It's simpler and allows more customization if a callee wants to customize a label or text field in some way.

You have a contradiction, you say you need no novel code in the accesors so you'd like to synthesise them, but the code you need is completely novel. Properties are syntactic sugar for a few common cases, of which this isn't one.
There are several ways to do what you want, eg. as #Justin suggests overriding setvalue:forundefinedkey: - you just need a lookup of forward targets, but the sugar of properties isn't a good fit for this.

Related

What is the difference between UITextInputDelegate.selectionDidChange and UITextViewDelegate.textViewDidChangeSelection

Two delegate methods are very similar.
I want to know what's their differences.
When the first will be called, when will another.
UITextInputDelegate.selectionDidChange
and
UITextViewDelegate.textViewDidChangeSelection
The difference is that UITextInputDelegate.selectionDidChange tells the view which conforms UITextInput that the text selection has changed.
While UITextViewDelegate.textViewDidChangeSelection tells UITextView that the text selection has changed.
In reality UITextView conforms the UITextInput, so it should not make any difference which method you will be using. But if you implement UITextInputDelegate to your textview, it will give you more flexibility to alter selection process e.g selectionWillChange, selectionDidChange. If you do not this flexibility, stick to UITextViewDelegate.textViewDidChangeSelection :)

how to detect double Click in NSTextField

I have a custom NSTextField and I'd like to detect double clicks by the user in the text field. My goal: I want to be able to double click on a parenthesis in an expression, such as "(2+2) = 4" and have it select everything inside the matching parentheses. Thought I could do this with...
- (void)textView:(NSTextView *)textView doubleClickedOnCell:(id <NSTextAttachmentCell>)cell inRect:(NSRect)cellFrame atIndex:(NSUInteger)charIndex;
but it never gets called in my custom NSTextField.
Then I thought I could override -mouseDown, but that isn't getting called either. I'm stumped. Any suggestions for what should be an easy function to implement.
Thanks!
Philip
A text field does not handling editing, as such. When a text field has focus, a text view is added to the window, overlapping the area of the text field. This is called the "field editor" and it is responsible for handling editing.
It seems the most likely place for you to change the behavior of a double-click is in the text storage object used by that text view. NSTextStorage inherits from NSMutableAttributedString which inherits from NSAttributedString which has a -doubleClickAtIndex: method. That method returns the range of the text that should be selected by a double-click at a particular index.
So, you'll want to implement a subclass of NSTextStorage that overrides that method and returns a different result in some circumstances. NSTextStorage is a semi-abstract base class of a class cluster. Subclassing it requires a bit more than usual. You have to implement the primitive methods of NSAttributedString and NSMutableAttributedString. See the docs about it.
There are a few places to customize the field editor by replacing its text storage object with an instance of your class:
You could implement a custom subclass of NSTextFieldCell. Set your text field to use this as its cell. In your subclass, override -fieldEditorForView:. In your override, instantiate an NSTextView. Obtain its layoutManager and call -replaceTextStorage: on that, passing it an instance of your custom text storage class. (This is easier than putting together the hierarchy of objects that is involved with text editing, although you could do that yourself.) Set the fieldEditor property of the text view to true and return it.
In your window delegate, implement -windowWillReturnFieldEditor:toObject:. Create, configure, and return an NSTextView using your custom text storage, as above.
it is simple just use this class to detect double tap
final class doubleClickableTextField : NSTextField {
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
if (event.clickCount == 2){
// do the work here
self.isEditable = true
}
}
}
The answer from Ken Thomases here is correct in its analysis of the issue regarding the field editor and how to replace it, but the solution it then recommends – replacing the NSTextStorage of the field editor – is not the correct solution, according to Apple. In their doc they specifically recommend that for delimiter-balancing the selectionRangeForProposedRange:granularity: method should be used. Once you have a custom field editor going, as per Ken's answer, you should therefore use the solution for NSTextView here, applied to a custom NSTextView subclass that you use for your field editor.
In case it is of interest, using NSTextStorage's doubleClickAtIndex: method for delimiter-balancing is probably the wrong solution for several trivial reasons: (1) because Apple says so, (2) because subclassing NSTextStorage is complicated and error-prone, and (3) because NSTextView provides a method specifically intended for the purpose of doing things like delimiter-balancing. But it is also wrong for a non-trivial reason: (4) that doubleClickAtIndex: is documented as "Returns the range of characters that form a word (or other linguistic unit) surrounding the given index, taking language characteristics into account". So doubleClickAtIndex: is really about how the linguistic units of the text (i.e. words) are defined, and redefining those in some way to make delimiter-balancing work would probably break other aspects of word-level text processing. For example, I would guess that it would be pretty tricky to make double-click-drag (dragging out a selection word by word) work properly if you have overridden doubleClickAtIndex: to do delimiter balancing. Cocoa may use doubleClickAtIndex: for other aspects of word-finding too, and may add more uses of it in the future. Since a delimiter-balanced section of text is not a "word", who knows what weirdness might result.

Binding a NSArrayController to a NSPopupButton & NSTextField

What I want to accomplish seems like it should be fairly straightforward. I have placed a sample project here.
I have a NSArrayController filled with an array of NSDictionaries.
[[self controller] addObject:#{ #"name" : #"itemA", #"part" : #"partA" }];
[[self controller] addObject:#{ #"name" : #"itemB", #"part" : #"partB" }];
[[self controller] addObject:#{ #"name" : #"itemC", #"part" : #"partC" }];
I am populating a NSPopupButton with the items in this array based on the 'name' key. This is easily accomplished with the following bindings
I would then like to populate a NSTextField with the text in the 'part' key based on the current selection of the NSPopupButton. I have setup the following binding:
With these bindings alone, the text field does display 'partC'.
However, if I change the value of the NSPopupMenu, what the text field shows does not change.
I thought this would simply be a matter of setting up the 'Selected Object' binding on the NSPopupButton
but that isn't working. I end up with the proxy object in my menu for some strange reason (providing the reason why would be a bonus).
So, what do I need to do to make this work?
Don't use "Selected Object" in this case. Bind the pop-up's "Selected Index" binding to the NSArrayController's selectionIndex Controller Key. Tried it out on your sample project and it works.
EDIT:
You asked why it's appropriate to use selectionIndex over selectedObject. First some background:
When binding a popup menu, there are three virtual "Collections" you can bind: Content is the abstract "list of things that should be in the menu" -- you must always specify Content. If you specify neither Content Objects, nor Content Values, then the collection of values bound to Content will be used as the "objects" and the strings returned by their -description methods will be used as the "values". In other words, the Content Values are the strings displayed in the pop-up and the Content Objects are the things they correspond to (which are possibly not strings, and which might not have a -description method suitable for generating the text in the pop-up). What's important to realize here is that there are potentially three different 'virtual arrays' in play here: The array for Content, the array for Content Objects (which may be different) and the array for Content Values (which may also be different). They will all have the same number of values, and typically, the Content Objects and Content Values will be functions (in the mathematical sense) of the corresponding items in the Content array.
The next thing that's important to realize is that part of NSArrayController's purpose in life is to keep track of the user's selection. This is only mildly (if at all) interesting in the case of a pop-up, but starts to become far more interesting in the case of an NSTableView. Internally, NSArrayController keeps track of this by keeping an NSIndexSet containing the indexes in the Content array that are selected at any given time. From there, selection state is exposed in several different ways for your convenience:
selectionIndexes is as described - an NSIndexSet containing the indexes of the selected items in the Content array
selectionIndex is a convenient option for applications that do not support multiple selection. It can be thought of as being equivalent to arrayController.selectionIndexes.firstIndex.
selectedObject is also useful in single selection cases, and corresponds conceptually to ContentObjectsArray[arrayController.selectionIndexes.firstIndex]
selection returns a special object (opaque to the consumer) that brokers reads and writes back to the underlying object (or objects in the case of multiple selection) in the Content Array of the array controller. It exists to enable editing multiple objects at a time in multiple selection cases, and to provide support for other special cases. (You should think of this property as read-only; Since its type is opaque to the consumer, you could never make a suitable new value to write into it. It's meaningful to make calls like: -[arrayController.selection setValue: myObject forKey: #"modelKey"], but it's not meaningful to make calls like -[arrayController setValue: myObject forKey: #"selection"]
With that understanding of the selection property, let's take a step back and see why it's not the right thing to use in this case. NSPopUpButton tries to be smart: You've provided it with a list of things that should be in the menu via the Content and Content Values bindings. Then you've additionally told it that you want to bind its Selected Object to the the NSArrayController's selection property. You're probably thinking of this as a "write only" binding -- i.e. "Dear pop-up, please take the user's selection and push it into the arrayController", but the binding is really bi-directional. So when the bindings refresh, the popup first populates the menu with all the items from the Content/Content Values bindings, and then it says, "Oh, you say the value at arrayController.selection is my Selected Object. That's odd -- it's not in the list of things bound with my Content/Content Values bindings. I'd better add it to the list for you! I'll do that by calling -description on it, and plunking that string into the menu for you." But the object you get from that Selected Object binding is the opaque selection object described above (and you can see from the outcome that it is of class _NSControllerObjectProxy, a private-to-AppKit class as hinted by the leading underscore).
In sum, that's why binding your popup's Selected Object binding to the array controller's selection controller key is the wrong thing to do here. Sad to say, but as I'm sure you've discovered, the documentation for Cocoa bindings only begins to scratch the surface, so don't feel bad. I've been working with Cocoa bindings pretty much daily, in a large-scale project, for several years now, and I still feel like there are a lot of use cases I don't yet fully understand.

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.

How to get notification whenever a character is inputted into NSTextField?

I'm a little rusty on my Cocoa, so bear with me on terminology and such.
I want to write something that is essentially a reverse spell checker. As in, if a word is spell correctly, then for random words it changes it to a misspelled version. Harmless prank to play on someone.
So then, my main hitch is that I have no idea how to do this (major problem, I know). I like the way that textedit performs on-the-fly spellchecking, but I'd like to incorporate that sort of behavior into the generic behavior of the NSTextField. Is there some way for an application to be notified whenever a character is input into an NSTextField?
EDIT: My aim is to make this system-wide, as in any NSTextField in the system would get this behavior as a matter of inheritance. I'm open to some serious hacking here.
To answer your question: attach a delegate to the NSTextField control, and implement
- (void)controlTextDidChange:(NSNotification *)aNotification
Note that NSTextField uses the shared field editor to edit text, which is a NSText object. You might be able to manipulate that to do what you want.
I like the idea! This should be fairly easy to do. First you have to decide if you want to use NSTextField or NSTextView. TextEdit.app uses NSTextView which is appropriate for more extensive word processing-type tasks. NSTextField is more for smaller, minimally-formatted chunks of text. There's lots of ways to tackle this, but I'll give you a couple.
For NSTextField, set your controller object to be the delegate for the text field and override the controlTextDidChange: method. Whenever the user types a character into the text field, you'll get this message. You can then modify the field's string to introduce the misspelled word.
For NSTextView, you can activate spell checking and use the text view's delegate method textView:didCheckTextInRange:types:options:results:orthography:wordCount:. You should be able to modify the results of the spell check.

Resources