I have a custom NSTextField sub-class with a custom drawRect: implementation.
The text field is part of a view inside a table cell. I programmatically bind the fontBold property of the text field.
The problem is that I have to recalculate a few things when the fontBold binding changes, but I can't figure out how to get notified when that happens.
I tried adding an observer, but that is not called. There does not seem to be a setFontBold:(BOOL) method that I could overwrite in NSTextField.
I think I figured it out:
There really is no fontBold property. I think what happens under the hood is that the binding is transformed by a value transformer into an NSFont object and what actually changes is the font attribute of NSTextField (I confirmed that setFont: is called when the fontBold binding changes). Phew, 4 hours of my life gone.
Related
I have a custom NSTextView implementation that automatically adjusts the font size so that the text fills the entire view.
I overwrote didChangeText to call my font size adjustment method. Works great when the user is editing text, but didChangeText (and the delegate method textDidChange:) are not called when the text view contents are set via bindings.
The font adjustment code needs to run whenever the text is set/changes, not only when it's changed by the user.
How can I detect all changes to the text in an NSTextView, even via bindings?
Note: If there's a better way to have the text fill the entire text view other than increasing the font size, let me know.
It would be better to set the font attributes into the NSAttributedString that is bound to the text view's "attributedString". In the textDidChange: delegate method, you can just recreate the NSAttributedString with the correct font attributes.
The NSTextView method didChangeText is not called when a binding updates the text (as opposed to the text view updating the model).
didChangeText is the source of the binding update. If you override it and don't call super, the binding is broken. didChangeText calls the delegate method textDidChange.
Unfortunately, didChangeText is also called rather late in the NSTextView update process - after the layout and storage delegate calls.
I found that the NSTextStorageDelegate method "didProcessEditing" was the best way to catch changes to the bound string. Although you have to be careful what changes you can make back to the textview at this point - some calls crashed.
I answered my own similar question more fully here:
NSTextView textDidChange not called through binding
I've got a simple master-detail project with an array of class instances, an array controller providing a selection of a specific class instance, and an NSTableView presenting the array and the selection. My class has an NSString property, and when I bind it to to an NSTextField, everything works great.
However, when I bind the same property to the Value Path field of an NSTextView, it behaves very erratically:
Selecting among entries in the NSTableView causes no change in the NSTextView, even though the selected objects have different values for the bound property.
Nothing typed into the NSTextView is copied into the property of the selection.
The only apparent effect of the binding is that clearing the selection in the NSTableView (e.g., clicking below all of the entries) causes the contents of the NSTextView to vanish.
Any ideas?
The valuePath binding is for file paths. The value binding is for strings; there're also data (RTF/RTFD) and attributedString bindings.
I've just created my first view-based NSTableView in Interface Builder and I've correctly set up the data source and the bindings to update the views in the tableview. Each view has two labels and a NSProgressIndicator. Updating the progress indicator through the bindings and the data source works perfectly, but I'd like to change its state from determinate to indeterminate at some time. As my NSTableCellView subclass has access to the progress indicator, how can I get access to the cell view at a given row index? I've tried calling viewAtColumn:row:makeIfNecessary: on the tableview with both NO and YES for the makeIfNecessary argument, but neither seems to work.
Solution 1: In your NSTableCellView subclass add a property (IBOutlet) for your NSProgressIndicator control. Wire it in IB to set the property when the view is loaded. You can then access the progress control in your cell view subclass by using the property.
Solution 2: In IB give your NSProgressIndicator a unique integer tag. In your cell view subclass use [self viewWithTag:] to get the object.
I am not sure about the answer to your main question but you can bind the indeterminate state as well. In IB Is Indeterminate is listed in the Parameters section.
I have a custom NSCell (actually subclassing NSTextFieldCell), which is used both in a standalone editor, and in an NSTableColumn (bound to Core Data through NSArrayController). When the user changes the value, I call -[NSCell setObjectValue:] to update the value (it's an NSNumber). This works in the standalone editor, since when it's done I manually update the binding on it.
[self setObjectValue:[NSNumber numberWithInt:newValue]];
That step (updating the bound field) is missing when the cell is in an NSTableView - the updated value shows up while the user's editing (with mouse tracking), but as soon as that's over, the value snaps back to the persisted value.
The NSTableColumn is bound to a key of -[NSArrayController arrangedObjects]. Is there some sort of call to "commitEditing" or "updateBinding" that I'm missing? I couldn't find any useful functions in the docs for NSCell or NSTableView.
To solve this, I implemented -tableView:setObjectValue:forTableColumn:row: in my NSArrayController subclass. I get the instance of my NSManagedObject subclass from the Row argument, and then manually assign the new model from the ObjectValue argument. I still don't know why this is necessary, when the text cells do this automatically, but it works.
What's the minimum implementation needed to make a custom NSView with an editable text area? I assume NSTextFieldCell can be used for this. I've succeeded in drawing the cell in the view (which is straightforward), but making it editable seems to require a more complicated coordination between the view and the cell. Is there sample code available somewhere?
Update. I should have made clear that my longer-term goal is to have many more editable text areas on the same view. AFAIU it is better to use cells in that case as they are more light-weight than full-blown views. My updated question is: What's the minimum implementation needed to make a custom NSView with an editable text area using an appropriate NSCell?
What's the minimum implementation needed to make a custom NSView with an editable text area?
Make an NSView.
Put an NSTextField in it.
Remember, NSViews (custom or otherwise) can contain other NSViews, and an NSTextField is a kind of NSView.
If you don't want code outside the custom view class to know about the text field, and it probably shouldn't, the custom view can create the text field and add it to itself as a private implementation detail. To do this, simply don't expose the text field in the custom view class's #interface (aside from the instance variable declaration, which is unavoidable).
The custom view should, of course, not draw wherever it put its text field. It could draw there, but the text field would cover it.
I assume NSTextFieldCell can be used for this.
Yes, if you don't mind reimplementing NSTextField. Adding an NSTextField as a subview of your view is much easier.
If you want to make a grid of text fields (with a dynamic number of them, perhaps), use an NSMatrix of NSTextFieldCells. You can, of course, add the NSMatrix as a subview of your custom view.
If you want to edit a text cell just call editWithFrame:inView:editor:delegate:event: on the cell object. This method requires the NSEvent that started the editing, so you can only call this from an event handler. There also is selectWithFrame:inView:editor:delegate:start:length: which sets up the field editor with an selection. You can use this if you need to start the editing from outside of an event handler.
After the user is done editing you need to call endEditing: on your cell.