Resizing NSTokenField after populating with tokens - objective-c

I am using an NSTokenField as a way for users to enter tags. Everything works fine and it hooks up with CoreData managing the tags both when the user adds or deletes a tag.
I recently added logic so that the NSTokenField would resize vertically as the user adds tags and they break to the next line using Andrew Bowman's IFVerticallyExpandingTextField. Again this all works fine.
The issue is that when I have to initially populate the NSTokenField with tags, I need it to resize. I populate the field by calling:
[tagField setObjectValue: anArray];
Where anArray is a series of objects that represent a tag or a Token. This in turn calls the NSTokenField delegate method
tokenField:displayStringForRepresentedObject:
Which returns the string representation for the object passed in the previous array.
I need to resize the NSTokenField after all of the calls to displayStringForRepresentedObject. Does anyone have any ideas of a notification or a way of finding out that it's all done? Even a way of calling the resize in between each call to displayStringForRepresentedObject would probably work.
Thanks in advance.

You might try something similar to -setNeedsDisplay: and -displayIfNeeded ... i.e., -setNeedsSizeToFit: and -sizeToFitIfNeeded.
You'll just need a "needsSizeToFit" BOOL flag and the -setNeedsSizeToFit: and -sizeToFitIfNeeded methods.
After you set your tokens, call -setNeedsSizeToFit:YES. It in turn will set the instance's needsSizeToFit flag, then if the flag is YES, it will call [self performSelector:#selector(sizeToFitIfNeeded) withObject:nil afterDelay:0]. Your -sizeToFitIfNeeded method will check if your needsSizeToFit flag is YES, call [self sizeToFit], then set the needsSizeToFit flag to NO.
Update
Here's a complete class (JLNAutoSizingTokenField) that does basic autosizing as described above. The only augmentation was to call this in the afore-mentioned delegate method:
- (NSString *)tokenField:(NSTokenField *)aTokenField
displayStringForRepresentedObject:(id)representedObject
{
[(JLNAutoSizingTokenField *)aTokenField setNeedsSizeToFit:YES];
return representedObject;
}

Related

NSTableView + NSTextView = Disaster :(

This is driving me a bit crazy..
Down below is a screenshot of my program so far.
On the right is an NSTableView (view-based). This is where the user can select a document they want to work on.
On the left is the NSTextView. Text will be displayed depending on what item they choose in the NSTableView.
There are also big plus and minus buttons for creating/deleting new items in the tableview.
Simple right? I wish.
Right now I have it so the tableview gets data from a mutable array. The mutable array contains objects of a class called DocumentItem. The DocumentItem just has two strings, one for the document text and one for the document title.
What works so far:
When I manually add objects to the array using code, I am able to freely switch through the documents and the textview will update accordingly.
What doesn't work:
When the user switches to a different document, I want to call the NSTableView replaceObjectAtIndex method save the changes that they have made to the object in the array.
How my code works so far:
The mutable array is stored in a data class. The data class is a shared class and is referred to in my code as theDATA.
I have a thread looping in my class that has the textview. In my tableview class I have a method called blastToScreen that will change a BOOL called shouldBLAST to YES.
Here is the code in my TableController class to set the BOOL to YES:
- (void) blastToScreen{
theDATA.blasttext = [[theDATA.globaldoclist objectAtIndex:[tablevieww selectedRow]] doccontents];
theDATA.shouldBLAST=YES;
}
Here is the shouldBLAST method in my looped thread(in a different class from the textview). Please note that the if-statement that says if(theDATA.switchedrow) is there to make sure that certain code gets runned only when a user switches their row in the tableview.
if(theDATA.shouldBLAST){
if(theDATA.switchedrow){
DocumentItem * itemr = [theDATA.globaldoclist objectAtIndex:theDATA.lastindex];
NSLog(#"(%li) prev content - >%#",(long)theDATA.lastindex,itemr.doccontents);
itemr.doccontents=textvieww.string;
NSLog(#"(%li)adding content - > %# <- to %#",theDATA.lastindex, itemr.doccontents,itemr.docname);
theDATA.switchedrow=NO;
[theDATA.globaldoclist replaceObjectAtIndex:theDATA.lastindex withObject:itemr ];
NSLog(#"changed: - > %#",[[theDATA.globaldoclist objectAtIndex:theDATA.lastindex] doccontents]);
}
textvieww.string=theDATA.blasttext;
theDATA.shouldBLAST=NO;
NSLog(#"changed: - > %#",[[theDATA.globaldoclist objectAtIndex:theDATA.lastindex] doccontents]);
theDATA.lastindex=theDATA.selectedrow;
}
Here's the weird part about all this:
According to the NSLog statements I set up, my code works for a split second and then resets.
Down below is what the console says. ignore the (0). that is just talking about the last selected index.
What it is saying is that the text before switching was nothing(fine). It is saying that it is adding the text "Potato" to that array(still fine). Then, the first time I fetched the object from the array it shows that it successfully changed to "Potato"(Still fine). Then when I tried to fetch the SAME exact data a few lines later, it returned nothing. :(
I feel like the issue resides somewhere in my TableController class. Here's a link to the code in my TableController class.
Here's what the console returned:
2015-09-14 17:17:46.024 Simplicity[4801:432580] (0) prev content - >
2015-09-14 17:17:46.025 Simplicity[4801:432580] (0)adding content - > Potato <- to Untitled
2015-09-14 17:17:46.025 Simplicity[4801:432580] changed: - > Potato
2015-09-14 17:17:46.025 Simplicity[4801:432580] changed: - >
I really hope you guys can help me. I tried pretty much everything I could to solve this issue.This is holding me back from finishing my software.
Probably, the doccontents property of your DocumentItem class is strong (or retain) when it should be copy.
From the docs for the string property of NSText (from which NSTextView inherits):
For performance reasons, this method returns the current backing store of the text object. If you want to maintain a snapshot of this as you manipulate the text storage, you should make a copy of the appropriate substring.
So, if you're just keeping a reference to that same object, when the text view's content is changed, the content of the object you've got a reference to also changes. You need to make a private copy.

Required Methods for Text Changes in Custom NSTextView

I have a custom NSTextView, and I want to be sure to send all of the proper messages and notifications when making changes to the text programmatically. I mostly care about undo/redo registration, but I'd generally like to do things the "right" way. Anyway …
The messages I'm aware of are:
-shouldChangeTextInRange:replacementString:
-didChangeText
If I understand the documentation correctly, before any changes are made to the text view's textStorage object, one needs to send the -shouldChangeTextInRange:replacementString: message to make sure a new undo group is opened (and inform any delegates of the commencement of editing). If YES is returned, the changes can be made to the text. Once all of the changes have been applied, the -didChangeText message needs to be sent to close the undo group (and again, notify any observers). Is this right?
When modifying existing characters (or attributes) those instructions make sense to me. I'm acting on an existing range of text, so it's easy to know what to send for the affectedCharRange parameter. What about when I need to insert something?
Let's say I want to insert a random word at the current insertion point index. Do I need to send the -shouldChangeTextInRange:replacementString: message? I'm not modifying existing characters, but I am adding new characters to the existing characters.
If I do need to send this message, what range do I use for the affectedCharRange argument? Whenever I try to send the new computed range of the to-be-inserted text, I get "Range Out of Bounds" errors, which makes sense, considering the text view's length has yet to change. Do I just send the range for the insertion point with an empty length (e.g. self.selectedRange when nothing's selected)?
For example:
- (void)insertRandomWord:(id)sender
{
NSAttributedString *randomAttrStr = [self randomAttributedString];
BOOL shouldChangeText = [self shouldChangeTextInRange:shouldThisBeTheSelectedRange // <-- WTF, mate?
replacementString:randomAttrStr.string];
if ( shouldChangeText ) {
[self.textStorage insertAttributedString:randomAttrStr
atIndex:self.selectedRange.location];
// This should always get called, right?
[self didChangeText];
// Is this where I would set the typing attributes?
self.typingAttributes = [randomAttrStr attributesAtIndex:0 effectiveRange:NULL];
}
}
I've gone as far as to create a method that takes a range and a block as arguments, so I don't accidentally forget to call something. Is this a good idea or not?
- (void)changeTextInRange:(NSRange)range replacementString:(NSString *)replacementString usingBlock:(void (^)(void))block
{
BOOL change = [self shouldChangeTextInRange:range replacementString:replacementString];
if ( change ) {
block();
[self didChangeText];
}
}
Last, but not least, do I need to call the NSTextStorage "editing" methods, too? The methods I'm referring to are:
-beginEditing:
-endEditing:
The documentation discusses calling those when one subclasses NSTextStorage, but I'm a little confused as to whether those messages need to be sent, too. It doesn't really say whether or not to use -fixAttributesInRange:, but I know the -endEditing message calls that to do cleanup after an editing operation.
I apologize for the discombobulated mess of questions. I'm just super tired and confused, and Apple's documentation has been … lacking. Any tips, pointers, and/or suggestions would be greatly appreciated. Thanks!

Single model update for multiple changes in NSArrayController subclass

I implemented an NSArrayController subclass to implement row reordering with bindings, using the code included in this post as a starting point. In the -tableView:acceptDrop:row:dropOperation: method, I perform the following:
[self removeObjectAtArrangedObjectIndex:removeIndex];
[self insertObject:object atArrangedObjectIndex:insertIndex];
The above code updates the model twice (one for each statement).
For my purposes, I would like to have only one update.
Is there any way to achieve this?
Thanks.
There's always the possibility of replacing the entire array if you want to incorporate many changes to an array into a single operation.
This might have unexpected effects for e.g. objects in the UI bound to your array, though.
I ended up doing the following:
[_content removeObject: [objects objectAtIndex: removeIndex]]; // < Here's the hack
[self insertObject:object atArrangedObjectIndex:insertIndex];
Where _content is in fact the content array of the controller.
The above works fine in my case (as described in my question).

When is a NSManagedObject really accessible?

I have a problem.
I have an NSObjectController called "mapController" and I want to put some defaults when the object is created. I do this inside the windowControllerDidLoadNib method of my document, as suggested by the docs. But…
if (![mapController content]){ // No map defined yet.
[mapController add: self]; // This should create the instance.
NSLog(#"%#",[mapController content]); // Gives NULL.
I tried:
BOOL ok = [mapController fetchWithRequest:nil merge:NO error:nil];
NSLog(#"%#",[mapController content]); // Gives NULL.
The content of mapController is in the Core Data "scratch pad" but I can't access it. I have to set one of its attributes like this:
[[mapController content] setValue:[matrix colorReference] forKey:#"mapData"];
This gives no error, the file is marked as changed, but it I test the value:
NSLog(#"%#",[mapController content]); // Gives NULL.
When the heck it the controller's content really HERE? Something appears on the screen but… what actually? Reading the docs doesn't help me…
OK, I found the answer in the docs:
add: Creates a new object and sets it as the receiver’s content object.
Discussion
Creates a new object of the appropriate entity (specified by
entityName) or class (specified by objectClass)—see newObject—and sets
it as the receiver’s content object using addObject:.
Special Considerations
Beginning with Mac OS X v10.4 the result of this method is deferred
until the next iteration of the runloop so that the error presentation
mechanism can provide feedback as a sheet.
That's why
[[mapController content] setValue:[matrix colorReference] forKey:#"mapData"];
worked fine when called elsewhere in the app. It was a few iterations later…
Well… maybe this post will save you a couple of hours you could use to sleep longer.
Regards,
Bernard
I don't think its your mapController, I think it is your NSLog. Try this:
NSLog(#"%#", mapController);
also try getting simple data from the content, like the float value of the CGColorRef so you can use other formatters like %f.
I would have tested this but I cannot seem to create an instance of NSObjectController because it is an undeclared identifier. What framework is it defined in? Did you have to #import anything?

How do I keep an NSPathControl updated with the path of the selected cell in an NSBrowser

I need to keep an NSPathControl updated with the currently selected path in an NSBrowser, but I'm having trouble figuring out a way of getting notifications when the path has changed from the NSBrowser. The ideal way to do this would just to be to observe the path key path in the NSBrowser, but that gives a KVO can only observe set<key> methods which return void message and no updates (setPath returns a bool success value).
I also tried observing the selectedCell key path, but I'm not getting notifications when the selection there is changed.
Is there some other really obvious way to do this that I'm missing?
Courtesy of Rob Keniger over at Cocoa Dev:
Have you looked at the SimpleBrowser
example in /Developer/Examples? It
shows how to get the current selection
when it is changed by the user,
basically by just setting up the
action of the NSBrowser.
That is indeed the way to do it. Just implement a method like - (void)browserClicked: in your controller and map it to the NSBrowser's action in interface builder with whatever you want to happen each time the selection changes inside that method, e.g.
- (void)browserClicked:(id)browser {
self.pathToSelectedCell = [browser path]; // NSPathControl is bound to pathToSelectedCell
}
I just checked in IB, and it looks like NSBrowser has a selection index paths binding (an array of NSIndexPath objects) that you could possibly monitor with KVO. It's strange but I don't see any mention of it in the docs, so you might need to do a little research to see if that's something you should or shouldn't use, even if it seems to work. If it does, in your KVO observation method you would find the browser's current path, and convert that to an NSURL the path control can use.
If that doesn't work there's also the delegate methods - (BOOL)browser:(NSBrowser *)sender selectRow:(NSInteger)row inColumn:(NSInteger)column and - (BOOL)browser:(NSBrowser *)sender selectCellWithString:(NSString *)title inColumn:(NSInteger)column.
As of 10.6, one can find out which items are selected, by using the delegate callback as follows:
- (NSIndexSet *)browser:(NSBrowser *)browser selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes inColumn:(NSInteger)column
{
NSLog(#"New first item of the new selection is at index %#", [proposedSelectionIndexes firstIndex]);
// Do something with the selected index or indicies
return proposedSelectionIndexes; // Allow the selection to occur by not changing this
}