I am using core data binding in cocoa app. My app has three NSTableViews bound to there NSArrayControllers in relationship such that if first table is selected, relevant data using Core data relationship is shown in the other two tables.
I have hooked NSSearchField to first NSArrayController. The problem is when search begins the other two tables do not remain in sync with the first one.They show nothing at all.
The other two tables only shows content when first one if clicked by user.
How can I make all three NSTables update simultaneously when search occurs.
So, I have this solution which works, I would like to know if this can be improved further.
Since when search starts the first NSTableView's selection is lost and hence the connected tables too, although the table is updated correctly showing filtered value. I am setting self as observer for change is NSArrayController selectedObject.
-(void)awakeFromNib{
[super awakeFromNib];
[_firstArrayController addObserver:self forKeyPath:#"selectedObjects" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
}
Now I am observing for changes and if selected object do change and if NSSearchField is the one changing it, programatically select NSTableView first row
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)
object change:(NSDictionary *)change context:(void *)context
{
NSResponder *firstResponder = [[NSApp keyWindow] firstResponder];
if ([firstResponder isKindOfClass:[NSText class]] && [(id)firstResponder delegate] == _searchField) {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:0];
[_authorTable selectRowIndexes:indexSet byExtendingSelection:NO];
}
}
Since when filtering using NSSearchField, the first object is what the most relevant, hence I am setting selection to row 0.
Related
I have a NSCollectionView in a cocoa application.
I can get information about the currently selected object in the collection view through the following roundabout way:
NSIndexSet* index = [self.currentCollectionView selectionIndexes];
CardModel* card = [[self.currentCollectionView itemAtIndex:index.firstIndex] representedObject];
Does the NSCollectionView class have a method that returns the selected object? Or is this the preferred way to go about it?
Unlike NSTableView you do not have delegates/notifications which gives notifies you about the selection. So selectionIndexes is the way to go.
I am not sure if you have set up observers for array controllers or not. But the code which you have shown is only to retrieve the selected objects. To get notified about the selection of objects you need to add observer for key path selectionIndexes (or what ever is set in IB) on array controllers.
[myArrayController addObserver:self
forKeyPath:#"selectionIndexes"
options:NSKeyValueObservingOptionNew
context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if([keyPath isEqualTo:#"selectionIndexes"])
{
// This will be invoked whenever objects are selected in Collection View.
// Now collectionView selectionIndexes can be used to get the selected objects.
}
}
If you're asking if selectionIndexes is the only way to access an NSCollectionView's selection the answer is yes.
One approach is to use bindings in the xib. Set up an NSArrayController for the items to be represented by the views in the collection. In the xib, in the Bindings Inspector of the Collection View, bind the Content of the CollectionView to the collectionViewArrayController.arrangedObjects. Also bind the Selection Indexes to collectionViewArrayController.selectionIndexes. Now you can make an outlet to the array controller, say in the App Delegate, and access the selected objects there.
For example, declare a selectedCard property, also a collectionViewAC outlet property connected to thecollectionViewArrayController. Now you can get the card item(s) you wish by way of selectedObjects.
- (id)selectedCard
{
id selectedCards = [collectionViewAC selectedObjects];
if ([selectedCards count]) {
return [selectedCards objectAtIndex:0];
}
return nil;
}
Use of bindings keeps everything observed and updated.
I have a UITableView that has cells containing text fields. As the user edits each text field, I need to keep track of the cell's text field value (even when the cell is no longer displayed, so simply tracking the text field does not work). So I used Key-Value Observing and set an observer for each text field in my UITableViewController subclass:
[cell addObserver:self forKeyPath:#"textField.text" options:NSKeyValueObservingOptionNew context:nil];
And save new changes to the text field in the class's observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context method.
This works great in iOS 6, but in iOS 7 KVO isn't called when the user changes the text field value. Apparently the setter method called by pressing "done" on the keyboard doesn't call KVO anymore.
Is there a workaround to this? Or a better way of listening for this change? I need to know what cell the text field belongs to, so implementing the text field's method editingDidEnd doesn't work for me.
Thanks in advance.
I'm not sure about whether there is a workaround for this or not but there surely is a better way of listening for the UITextField text value. If you are creating instances of UITextField programmatically, all you need to do is add an event handler for them to notify your class about the text changing events. For example:
[myTextField addTarget:self action:#selector(editingChanged:) forControlEvents:UIControlEventEditingChanged];
- (void)editingChanged:(UITextField *)sender {
NSString *targetText = sender.text;
}
In my project I have only one window, in that window I have only one Custom view, nothing other than that. In that custom view I am loading one Default view, and in that default view I have one Button over there. When that button IBAction is performed, present custom view have to go and new one have to load in the same custom view of that window.
For these I tried like this,
my app delegate is my window controller, in that I declared one global variable and written KVO for that observing when ever it's value get changed. In that observation method I am trying to load different xib's(custom view) according to my requirement based on value in that global variable.
Along with these I am having different ViewControllers to control different views. In that view controller classes I am loading new values into that global variable by using object of AppDelegate class.
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(#"entered into key value observing");
if ([viewName isEqualToString:#"LoginView"]) {
NSLog(#"Dont change the current view");
} else {
NSLog(#"Load new view in customView");
[self loadNewView];
}
}
-(void)loadNewView
{
NSLog(#"entered into login in method");
[[_viewController view] removeFromSuperview];
_viewController=[[NSViewController alloc] initWithNibName:#"NewView" bundle:nil];
[self.window setContentSize:_viewController.view.frame.size];
[_customView addSubview:[_viewController view]];
NSLog(#"at final step");
}
Control is moving from ViewController class to AppDelegate and in that its entering into that KVO method also, it is executing every line as I want it to execute. But its not affecting the output result.
As per as I know, it is executing every thing in that ViewController class itself by using object of AppDelegate. So it is not affecting of loading of view in CustomView in in that window.
Will any one please suggest me some solution for solving these........
Problem is not in the code what is shown in the above question.
In the above code instance of NSViewController is creating again and again for the same purpose. Along with that try to create object of appDeligate in ViewController class by using Shared instance.
I have a NSCollectionView wich content is handled by a NSArrayController.
The NSCollectionView is selectable and I need to retrieve a list of selected elements.
I'm trying to observe key property of NSArrayController "selectionIndexes" but it just return me ALWAYS the value of the first element in CollectionView and not the selected items.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualTo:#"selectionIndexes"])
{
//True if in the array controller of the collection view really exists at least a selected object
if([[arrayController selectedObjects] count] > 0)
{
NSLog(#"Selected objects: %#", [arrayController selectedObjects]);
}
else
{
NSLog(#"Observer called but no objects where selected.");
}
}
}
UPDATE
I never get this method called, if I manually invoke NSLog(#"Selected objects: %#", [arrayController selectedObjects]) I get this
The result is always something like this
END UPDATE
2011-07-05 20:44:45.711 collectionView2[2153:903] Selected objects 1: (
"<Hormiga: 0x10013e330>"
)
I think I have done something wrong binding NSArrayController with NSCollectionView. What could be my fault?
Tell me if you want more info, I can even post the entire program in a zip if you need it.
UPDATE 2
This is the code I use in my controller to observe the arrayController "selectionIndexes" key.
[arrayController addObserver:self forKeyPath:#"selectionIndexes" options:NSKeyValueObservingOptionNew context:nil];
UPDATE 3
One of the problem was fixed, I forgot to set the binding between NSArrayController and NSCollectionView relative to the key "selectionIndexes". Now I can retrieve manually the list of selectedObject and its correct!
My final problem is that I don't receive the notification when selectionIndexes changes.
So observeValueForKeyPath:ofObject:change:context: never get called!
UPDATE 4
I was trying to set the observer in the init method of my controller, but in this way the arrayController is still null. Moving the addObserver in the awakeForNib resolved all my problems!
If you want to keep the array controller’s selection indexes in sync with the collection view’s, you need to bind them as well. In summary:
Bind the collection view Content to the array controller, key arrangedObjects
Bind the collection view Selection Indexes to the array controller, key selectionIndexes.
Also, make sure arrayController has been set before you add an observer. Outlets are guaranteed to be set in -awakeFromNib and other methods that are invoked after it: If you’re using a window controller, you can use -windowDidLoad; if you’re using a view controller, you can use -loadView; otherwise, -applicationDidFinishLaunching: in your application delegate.
Context:
I have an NSArrayController tied to Core Data that supplies rows for an NSTableView. When a user selects rows, the arrayController's "selectedObjects" property changes.
Now, each of those "selectedObjects" is a Core Data entity called "LPFile" that has an attribute called "style", which is an integer from 0 to 3. The "style" attribute should correspond to the selectedIndex of an NSPopUpButton.
My Question:
If a user selects multiple rows AND the LPFiles associated with these rows have the same value for "style", I would like the NSPopUpButton to set its "selectedIndex" property to that value. If the rows' objects have DIFFERENT values for "style", then the NSPopUpButton should display a blank row. (When the user then chooses a style, that blank row should disappear from the NSPopUpButton.)
I know how to achieve this by writing code manually and if selection was limited to a single row I could set up those bindings, but how do I set up the bindings to handle multiple selected objects that may or may not have different values for "style"? I've Googled quite a bit, but can't find specific info and I'm tired of experimenting! (Note: I provide the content items for the NSPopUpButton in IB, so I don't bind anything to the content bindings of the button.)
You'll probably have to write a little bit of code, but you can still use bindings to control the UI elements, in this case the popup button.
Here is one way to do it that has worked for me:
In the controller that provides the content for the array controller, define a property which contains the selection index set corresponding to the selection in the table view. Bind it to the array controller's selection index set, so it is always updated and sync'ed with the table view. For simplicity, I have called it fileSelectionIndexSet in the following.
Then, define a property that provides the index for the popup button. Below, I have called it styleIndex.
You can bind the popup buttons selection index to this property. You may have to provide its content from the controller, too. That would be a readonly property returning a static array of strings, for instance.
// Header file, just synthezise in implementation
#property (retain) NSInteger styleIndex;
Register the controller as an observer of its own fileSelectionIndexSet property:
// It doesn't have to be awakeFromNib, any method will do if called before
// you need the functionality
-(void)awakeFromNib
{
[self addObserver:self
forKeyPath: #"fileSelectionIndexSet"
options:NSKeyValueObservingOptionNew
context:NULL];
}
- (void) observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ( [keyPath isEqualToString: #"fileSelectionIndexSet"] )
{
NSInteger index;
index = ... // Compute value based on current LPFile selection
self.styleIndex = index;
}
}
Implementing self as an observer of its own property makes the styleIndex property a one-way dependant of the fileSelectionIndexSet.
This means that whenever the user changes the selection in the table view, the popup button is updated. However, when the user changes the selection in the popup button, nothing is changed in the table view.