How to access NSDocument object from another object held in NSMutableArray? - cocoa

This is really an extension of How to show calculated values in NSTableView?
The problem:
I have an NSDocument class that contains two properties: text (NSString) and phrases (NSMutableArray of NSObjects with NSString in them).
In the Doc NIB file I have a TextView (to display phrases) with two columns. First column is bound to an ArrayController and displays NSString. That works ok.
I want to count the number of NSString occurrences in text and display that in the second column.
What I tried:
Define a static var in my NSObject that would point to a TextView. Once the NIB is loaded it would set this static var to a TextView that contains the text string.
This works OK if I open a single window. But if I try opening multiple windows, the static var would get updated with the new instances of TextView (from other windows). Obviously this breaks everything.
Question:
How do I access text from each of the NSObject? In other words, the object diagram is NSDocument --(contains one)--> NSMutableArray --(contains multiple)--> NSObject, so how do I get to NSDocument from NSObject?

Instead of an array of strings, use an array of dictionaries:
NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
thePhrase, #"phrase",
theCount, #"count",
nil];
[theArray addObject:entry];
Bind to arrangedObjects.phrase and arrangedObjects.count.
Alternatively, you could create a Phrase class which contains the phrase, a reference to the document, and code to calculate the count.

Related

Separate NSPopUpButton content from label while using bindings

I have an NSPopupButton whose content is bound to an NSArray, let’s say the array is
#[
#"Option 1",
#"Option 2"
];
Its selected object is bound to User Defaults Controller, and is written to a preference file by the user defaults system.
In my code I check whether the preference is set to #"Option 1" or not, and perform actions accordingly.
This all worked well (though I did feel a little uneasy checking for what is essentially a UI value, but whatever...) until I needed to localize.
Because the value is the label, I’m having an issue.
If my user is in France, his preferences file will say #"L’option 1", which is not equal to #"Option 1". I need to abstract the presentation from the meaning and it's proving pretty difficult.
I split up the binding into two arrays, let's call them values and labels.
Let’s say they look like this:
values = #[
#"option_1",
#"option_2"
];
labels = #[
NSLocalizedString(#"Option 1", nil),
NSLocalizedString(#"Option 2", nil)
];
I’ve bound the NSPopUpButton’s Content binding to values and its Content Values binding to labels. However, the popup list is showing option_1 and option_2, it does not seem to want to use the labels array to label the items in the popup button.
How do I get the NSPopUpButton to use values internally and store that in the preferences file, but display labels to the user?
It doesn’t have to be architected this way, if you can think of a better solution. The point is I want to store and check one value, and have that value associated with a label that gets localized appropriately.
Cocoa bindings work very well with value transformers, because you can apply them directly in the bindings window, for example
#implementation LocalizeTransformer
+ (Class)transformedValueClass
{
return [NSArray class];
}
+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)value {
if (![value isKindOfClass:[NSArray class]]) return nil;
NSMutableArray *output = [NSMutableArray arrayWithCapacity:[value count]];
for (NSString *string in value) {
[output addObject:NSLocalizedString(string, nil)];
}
return [output copy];
}
#end
you have to register the transformer in awakeFromNib or better in +initialize
NSValueTransformer *localizeTransformer = [[LocalizeTransformer alloc] init];
[NSValueTransformer setValueTransformer:localizeTransformer
forName:#"LocalizeTransformer"];
then it appears in the popup menu of value transformers
Bind Selected Tag to your User Defaults Controller instead of Selected Object.
If the NSPopupButton choices are fixed add the NSMenuItems in Interface Builder and set their Tags. Otherwise bind an array of NSMenuItem, again with proper Tags.
Selected Index would also work but only until you change the order.

How can I bind to NSTableColumn's headerTitle?

I would like to bind NSTableColumn's headerTitle property to an NSMutableArray in my model layer (via an NSArrayController).
Basically I want to have an array where I can change values and have the table column header titles update. Is that reasonable?
However, the headerTitle binding wants an single NSString and I'm not sure how to connect my model object to this binding via my NSArrayController. Google does not give many hits for this problem.
My model layer consists of two class (both of which are appropriately KVC compliant). The first is a model which represents a single column title, it has one property title,
// A model class representing the column title of single NSTableColumn
#interface ColumnTitle : NSObject
#property NSString *title;
+ (ColumnTitle*) columnTitleWithTitle:(NSString*) aString;
#end
The second a model object which represents an ordered group of ColumnTitle objects,
// Class representing an order collection of model items
#interface TableColumnTitles : NSObject
#property NSMutableArray* columnTitles; // an array of ColumnTitle objects
// These are the KVC array accessors
-(void) insertObject:(ColumnTitle*)columnTitle inColumnTitlesAtIndex:(NSUInteger)index;
- (void)removeObjectFromColumnTitlesAtIndex:(NSUInteger)index;
- (void)replaceObjectInColumnTitlesAtIndex:(NSUInteger)index withObject:(ColumnTitle*)columnTitle;
#end
Note that TableColumnTitles object implements the above array accessors which are required for the bindings. Any suggestions?
Haven't tried that before but what you're actually asking for is using KVC for array indexes. A quick google didn't turn up anything on that issue except some results that indicate it's not (yet) possible (check this)
The easiest work-around I could come up with would be to simply add dedicated properties for the array indexes.. not nice but does the job.
So for a NSMutableArray called myArray and contains objects with title properties of type NSString you'd do something like:
#property (nonatomic, readonly, getter = columnOneGetter) NSString *columnOneString;
(NSString*) columnOneGetter
{
return myArray[0].title;
}
Always assuming of course their number is known in advance and we're not talking 200 columns :-)
I think this may/may not be what you're after, but quick google search landed me here:
http://pinkstone.co.uk/how-to-add-touch-events-to-a-uitableviewfooter-or-header/
edit: i realize this is for mac (not ios) but should be pretty easy to translate if it actually helps.

UIPickerView with one object

I am using a UIPickerView and currently their is only a single object in it. How can I display that single object on label.
It has this weird property that when we use pickerView the data is not set selected by default.Once we choose another object or roll it, then only any particular object is selected. Hence if only one object is their in pickerView. It does not count as selected even though when you tap on that single object.
I tried a lot but found that if their are more than one object then only you can display the selected object on label but not if their is only one object.
You need to make a code that is triggered when the UIPickerView changes, like this:
#pragma
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
//Action that triggers following code:
{
NSString *nameString = [nameOnMutubaleArrayYouWannaGetDataFrom objectAtIndex:[picker selectedRowInComponent:0]]; //Or 1 if u have multiple rows
NSString *labelString = [[NSString alloc]initWithFormat:
#" %# ", nameString];
labelOutput.text = labelString;
Hope this helps.

Setting and maintaining the first SortDescriptor for an NSTableView

I have an NSTableView with columns bound to an NSArrayController.
The table view shows a list of email messages:
Flag if unread
Subject
Attachment size
The user can click on the Attachment Size column to sort the list, but I would like the table to always be sorted by the "unread" flag first so that the unread messages always remain at the top of the list.
I did not bind the Array Controller's sort descriptors to anything, yet table sorting works magically by clicking on the table columns (why?). Is there some way I can intercept setting the Array Controller's sort descriptors and insert the "Unread" sort descriptor first?
Example of a table sorted by attachment size:
UNREAD▼ SUBJECT ATTACHMENT SIZE▼
------ ------- ------------------
yes Hello.. 110kb
yes Test... 90kb
no Foobar 200kb
no Hey 100kb
no Test2 10kb
Well, the reason it "just works" is because the table columns call setSortDescriptors: on their bound NSArrayController.
Assuming you want the table to remain sortable, but you always want to sort by "unread", this is how I would go about it:
First, subclass NSArrayController and override arrangeObjects:
- (NSArray *)arrangeObjects:(NSArray *)objects {
NSMutableArray *oldSorted = [[super arrangeObjects:objects] mutableCopy];
NSMutableArray *newSorted = [NSMutableArray arrayWithCapacity:[oldSorted count]];
for (id anObject in oldSorted)
if ([[anObject valueForKey:#"isUnread"] boolValue])
[newSorted addObject:anObject];
[oldSorted removeObjectsInArray:newSorted];
[newSorted addObjectsFromArray:oldSorted];
[oldSorted release];
return newSorted;
}
This puts unread messages at the "top" (beginning of array). I'm not sure this is the most efficient sorting algorithm, but I believe it's the correct way to go about it.
I think that you can just set an array of sortDescriptors on your array controller during awakeFromNib. No need to force arrangeObjects:, this functionality totally built in.
- (void)awakeFromNib {
[super awakeFromNib];
NSSortDescriptor *unreadSorter = [[NSSortDescriptor sortDescriptorWithKey:#"isUnread" ascending:NO)] autorelease];
NSArray *arrayOfSortDescriptors = [NSArray arrayWithObject:unreadSorter];
[self setSortDescriptors:arrayOfSortDescriptors];
}
The columns will still remain sortable when you click the column header.

Cocoa Text - refreshing text on-the-fly

In an app I'm working on, the user inputs plain text, and the app reformats the text by transforming it to an NSAttributedString, and displays it. This all happens live.
Currently, I'm doing the following on my NSTextView's textDidChange delegate method:
- (void)textDidChange:(NSNotification *)notification {
// saving the cursor position
NSInteger insertionPoint = [[[self.mainTextView selectedRanges] objectAtIndex:0] rangeValue].location;
// this grabs the text view's contact as plain text
[self updateContentFromTextView];
// this creates an attributed strings and displays it
[self updateTextViewFromContent];
// resetting the cursor position
self.mainTextView.selectedRange = NSMakeRange(insertionPoint, 0);
}
While this mostly works, it's not ideal. The text seems to blink for a split second (you especially notice it on the red dots under spelling errors), and when the cursor was previously near one of the edges of the visible rect, it the scroll position gets reset. In my case, this is a very much undesirable side-effect.
So my question is: Is there a better way of doing what I'm trying to do?
I think you have a slight misconception of how an NSTextView works. The user never enters a "plain string", the data store of an NSTextView is always an NSTextStorage object, which is a subclass of NSMutableAttributedString.
What you need to do is add/remove attributes to the existing attributed string that the user is editing, rather than replacing the entire string.
You should also not make changes to the string in the ‑textDidChange: delegate method, as changing the string from that method can cause another change notification.
Instead, you should implement the delegate method ‑textStorageDidProcessEditing:. This is called whenever the text changes. You can then make modifications to the string like so:
- (void)textStorageDidProcessEditing:(NSNotification*)notification
{
//get the text storage object from the notification
NSTextStorage* textStorage = [notification object];
//get the range of the entire run of text
NSRange aRange = NSMakeRange(0, [textStorage length]);
//for example purposes, change all the text to yellow
//remove existing coloring
[textStorage removeAttribute:NSForegroundColorAttributeName range:aRange];
//add new coloring
[textStorage addAttribute:NSForegroundColorAttributeName
value:[NSColor yellowColor]
range:aRange];
}

Resources