Target Action data source update for view based NSTableView - objective-c

I have situation where I use view based Table Views and don't want to use bindings between data source and table view. This is mainly due to the fact that my NSTableCellView can have multiple subviews, complex validation and triggered calls to methods in other objects.
We have very clear path of updating NSTableView with data source with:
tableView:viewForTableColumn:row:
However, for backwards, that is updating datasource with updates in NSTableView we have nothing of the sort we have for cell based Table views:
tableView:setObjectValue:forTableColumn:row:
Target Action pattern is suggested instead. So, I have basically 2 questions:
If I set target and action for one specific view, or its subview, how do I get proper row and column info to know what to update in data source?
Should clickedRow and clickedColumn from NSTableView do the trick, although I have edited or changed one subview object?
How can I inform the target (as other object, not NSTableView instance) about row and column if action will pass for example NSTextField as parameter?
I can basically come to clickedColumn and clickedRow (if those 2 properties are proper answer to first question) through subview tree, but I find this as pretty much non-elegant solution and have hunch there is a better way....
THanks in advance....

NSTableCellView has an objectValue. Presumably you're already setting it, so the action can use [(NSTableCellView *)[sender superview] objectValue] to find out which object it needs to manipulate.
I suggest that you also subclass NSTableCellView and implement the action there. If you need access to other parts of the model, you can add an outlet for your view controller.
If you really need the row number, you can call indexOfObject on your content array.

The two NSTableView Methods rowForView and columnForView should do the trick.
You can call them with the sender of an Target/Action Method, like one triggered by an NSButton in your TableView (its ok, to have it somewhere in a subwiew)
Or you can call these Methods from within a delegate method implementation like the textDidChange from NSTextDelegate. So you can easily update your corresponding Array.
If you don't want continous updates textDidEndEditing would also do the job.
- (void)textDidChange:(NSNotification *)notification
{
NSTextView *tv = [notification object];
int r = [tableView rowForView:tv];
int c = [tableView columnForView:tv];
NSLog(#"Row: %d Column: %d", r, c);
// updating code here
}

Related

OSX NSTableView insertRowAtIndexes

i already checked Using NSTableView insertRowsAtIndexes solution but It does not solve my problem.
i want insert row in nstableview at particular index(add dynamically)
index Set is valid, still it causes Program crash
NSIndexSet *indexSet=[NSIndexSet indexSetWithIndex:i];
[myTableView insertRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectFade];
1)is any thing wrong in my code?
2)Is there any another way to add row at particular index(add dynamically)?
The code is correct but first you have to insert the model object in the data source array to keep model and view in sync.
I got My mistake..... problem In other Code so This code is fine
But I want to add some points About insertRowsAtIndexes: method
Hope it will helps to other people
1)Dont called reloadData() because you are adding particular number of rows so calling reloadData() will reload all data and it will causes crash
2) Calling this method multiple times within the same beginUpdates and endUpdates block is allowed, and changes are processed incrementally
3)Most Important thing is indexSet must be within range
if you are Enter valid indexSet then The numberOfRows in the table view is automatically increased by the count of indexes.
4)you can select animation according to your need
Sample Code :
[yourTableView beginUpdates];
NSIndexSet* theIndexSet = [NSIndexSet indexSetWithIndex:[self.yourTableContents count]-1];
[yourTableView insertRowsAtIndexes:theIndexSet withAnimation:NSTableViewAnimationEffectFade];
[yourTableView endUpdates];

organize data model objective c

I have this visual part in my app. I've attached file below:
There are next user interface controls:
First UIScrollView (_blueScrollView) is located on main view of UIViewController that can contain many other views each one is called _magentaView...
Each _magentaView contains UIView (_labelView...) for storing UILabel objects. So there is some struct like UITableView in UITableView.
In my case all objects have UIViewController class.
I need to get access to all my labels that are storing in labelsViews. I need to get value of property text of those objects.
So if I make in standard case this will looks like this
for (UIViewController *magentaVC in scrollViewObjects) {
for (UIViewController *labelVC in magentaObjects) {
and get my UILabel objects here.
}
}
but as I think this is incorrect. Because I need to use data model for this instead to use UIViewController objects. What do you think about this and which way do I need to choose?
This is exactly the job for a UITableView. The table itself has a scroll view embedded in it; magentaView 1 and 2 would be sections of the table, and the labelViews would be table view cells.

Table view: disable a row or make an alert which stop a segue?

I have two table view filled in their viewDidLoad with data coming from a sqllite db containing names of shops. In the first view i choose one element (i.e. one shop), then in the second i have to choose another shop different from the first.
I can disable one of the row of the second table view after it has been filled?
I tried not to load the name (of the first shop choosen) in the second view but i fill the table view in its viewDidLoad and unfortunately the data regarding the name of the first shop choosen doesn't seem to be available until the viewDidAppear and that moment is too late to fill the table view.
I tried, also, to add an alert to the event of choosing one row in the second view in this way
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *choosenSecondShop= [secondShop objectAtIndex:indexPath.row];
NSString* nameFirstShop = [function loadFirstShopChoosen];
if ([choosenSecondShop isEqualToString:nameFirstShop]) {
NSString *message=#"Second shop cannot match the first";
alert = [[UIAlertView alloc] initWithTitle:#"Errore"
message: message
delegate: self cancelButtonTitle: #"Ok"
otherButtonTitles:nil,
nil];
[alert show];
}else {
// go on with the app's stuff!
}
}
but in this case the alert is displayed but also the segue followed. I can prevent the segue to go on?
There are a few different ways to attack this problem:
1. Conditional segue
This should work, though I'm not sure it's a great idea from a user experience perspective.
Presumably you made a segue in IB starting from the table cell? When you do that, you get the convenience of not needing any code to perform the segue (it's automatically done when you tap the table cell), but you don't get any run-time control over it either.
If you need programmatic control over whether/when/which segue to perform, you should create a segue starting from the view controller itself (not from a control within it), and give the segue a unique identifier in IB. Then, in your tableView:didSelectRowAtIndexPath: implementation, you can call [self performSegueWithIdentifier:#"myIdentifier"] once you know you want to perform the segue.
2. Disable cell selection
To prevent a specific cell from being selected, your table view controller can implement tableView:willSelectRowAtIndexPath: to return nil for any index path you don't want the user to select.
If you do that, you might want to make it clear to the user which rows can be selected -- you can alter the cell's appearance in tableView:cellForRowAtIndexPath:.
3. Don't show the cell
You say you're populating the table view in viewDidLoad, but it doesn't really work that way: Table views populate themselves by calling your data source & delegate methods when they need to. If you want to prevent an item from your data set from being shown as a cell in the table, you just need to alter the behavior of your data source & delegate methods:
tableView:numberOfRowsInSection: should return a number one less than it would otherwise
tableView:cellForRowAtIndexPath: should reflect the removal of the item; something like:
MyShop *shop;
if (indexPath.row < indexOfFirstShop)
shop = [shops objectAtIndex:indexPath.row];
else
shop = [shops objectAtIndex:(indexPath.row + 1)];
// then configure cell for the chosen shop
Now, if you don't know which row you want to hide as of the first time these methods are called, all you need to do once you get that information is tell the table view that it needs to call them again: [self.tableView reloadData] should do the trick.

How can an NSCell detect the model key path it's bound to?

Imagine the following:
You have an NSTableView with multiple columns.
Each NSTableColumn is bound to the same NSArrayController (we'll call it myArrayController).
This array controller holds many instances of a model class.
One column has an NSPopupButtonCell where selectedObject is bound to myArrayController.arrangedObject.somePropertyOfTheModel.
The table gets properly populated.
Q: How can an NSCell detect the model key path it's bound to? (somePropertyOfTheModel in this example)
I'm trying to make a cell reusable by not having it assume its represented value is always from somePropertyOfTheModel (could be from somethingElse). Upon a given action, it needs to bind the content of a 2nd controller to somePropertyOfTheModel or somethingElse.
[Edited] A bit more (maybe too much?) explanation:
I'm creating a popup-button which displays a few preset values of a property, and a "Custom Value" item which triggers a PopOver window to allows configuration of the property. I want to make it so that I can drop this cell into a table and having it manage the PopOver much like it already manages its own Menu.
What I've tried:
[self representedObject] returns the actual value. Setting it as content to the 2nd controller is all good and well... but whenever the model's property changes, the 2nd controller won't be notified since it's tied to the actual instance of the value... not a binding to the model property.
Querying the cell's binding gives me nothing:
[[self infoForBinding:#"selectedObject"] objectForKey:NSObservedObjectKey]; // nil returned
[[self infoForBinding:#"selectedObject"] objectForKey:NSObservedKeyPathKey]; // nil returned
Querying the cell's control's (the NSTableView) binding doesn't give me much:
[(NSTableView*)[self controlView] infoForBinding:#"content"] objectForKey:NSObservedObjectKey]; // returns myArrayController or a poxy to it.
[(NSTableView*)[self controlView] infoForBinding:#"content"] objectForKey:NSObservedKeyPathKey]; // returns #"arrangedObject"
// running the same but for #"selectedObject" returns nothing but nils
I'd like to query the NSTableColumn itself -- that's where the bindings are defined in IB -- but cell's aren't aware of their existence (unless I've overlooked something obvious). Even passing via NSTableView, no method returns an NSTableColumn for a given cell (and considering prototype cells, I doubt it would help).

Populating NSTableview from a mutable array

I've been attempting this for two days, and constantly running into dead ends.
I've been through Aaron Hillegass's Cocoa Programming for MAC OS X, and done all the relevant exercises dealing with NSTableview and mutable arrays, and I have been attempting to modify them to suit my needs.
However none of them seem to be using an array with objects as a data source, it seems to use the tableview as the datasource.
I'm trying to implement Jonas Jongejan's "reworking" of my code here, with a Cocoa front end to display the results.
Any pointers or suggestions I know this should be simple, but I'm lost in the wilderness here.
I can populate the table by setting the array
It's pretty simple really, once you get to understand it (of course!). You can't use an NSArray directly as a table source. You need to either create a custom object that implements NSTableViewDataSource or implement that protocol in some existing class - usually a controller. If you use Xcode to create a standard document based application, the document controller class - (it will be called MyDocument) is a good class to use.
You need to implement at least these two methods:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you have a mutable array whose values you'd like to use in a table view with one column, something like the following should do as a start:
– numberOfRowsInTableView: (NSTableView*) aTableView
{
return [myMutableArray count];
}
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
return [myMutableArray objectAtIndex: rowIndex];
}
It has just occurred to me that you could add the above two methods as a category to NSArray replacing myMutableArray with self and then you can use an array as a data source.
Anyway, with a mutable array, it is important that any time you change it, you need to let the table view know it has been changed, so you need to send the table view -reloadData.
If your table view has more than one column and you want to populate it with properties of objects in your array, there's a trick you can do to make it easier for yourself. Let's say the objects in your array are instances of a class called Person with two methods defined:
-(NSString*) givenName;
-(NSString*) familyName;
and you want your table view to have a column for each of those, you can set the identifier property of each column to the name of the property in Person that that column displays and use something like the following:
– tableView: (NSTableView*) aTableView objectValueForTableColumn: (NSTableColumn *)aTableColum row: (NSInteger)rowIndex
{
Person* item = [myMutableArray objectAtIndex: rowIndex];
return [item valueForKey: [tableColumn identifier]];
}
If you replace valueForKey: with valueForKeyPath: and your Person class also has the following methods:
-(Person*) mother;
-(Person*) father;
-(NSString*) fullName; // concatenation of given name and family name
you can add table columns with identifiers like: father.fullName or mother.familyName and the values will be automatically populated.
You could go the datasource route and do all of the heavy lifting yourself, or you could let bindings do all the heavy lifting for you. Add an NSArrayController to the nib file that has the table view in it. Make sure that the File's Owner of the nib is set to the same class that has the mutable array in it. Bind the contentArray of the array controller to File's Owner.myMutableArray. For each column bind Value to the array controller arrangedObjects and add the appropriate key path. This will allow you to get things like user sorting for free if you ever need it.
On the iPhone (I know you're talking about Mac, but maybe this could help) you have to use delegation for loading a tableView. It asks for a cell and you use your array to fill-in the data where needed.
I'm not sure if this works for the Mac, but it'd be worth looking into.
Maybe set dataSource to self and use those delegate methods to access your array based on the row and column #
Apple has a whole guide for Table View Programming so I suggest you start with the Using a Table Data Source section of the that guide.