I've developed an app for Mac OS X Lion using its new view-based NSTableView, but as I want to port the whole app to Snow Leopard I'm trying to figure out the best way to emulate such a tableview. So far I've created a NSCollectionView and everything is fine, except for the fact that I can't get the index of the view from which a button click event is triggered.
In Lion I have the following function:
- (IBAction)buttonClick:(id)sender
so I can get the index of the view inside the tableview using a method (I can't remember its name) like
- (NSInteger)rowForView:(NSView *)aView
with aView being the sender's superview, but I couldn't find something similar for the collection view ... The only "useful" method seems to be
- (NSCollectionViewItem *)itemAtIndex:(NSUInteger)index
(or something like this), but this can't help me as it returns a NSCollectionViewItem and I can't even access it knowing only the corresponding view!
Within buttonClick, try this code:
id collectionViewItem = [sender superview];
NSInteger index = [[collectionView subviews] indexOfObject:collectionViewItem];
return index;
Hope this helps :)
Geesh! Both of those approaches have issues. I can see how the first on may work, but note that the "collectionViewItem" is actually the view, NOT the collectionViewItem, which is a view controller.
The second way will not work, unless you subclass the button and put in a back link to the collectionViewItem. Otherwise, your view does not know what collectionViewItem controls it. You should use a selector binding to the collectionViewItem's representedObject instead, to get the action to the correct object in your array.
How about something like:
id obj = [collectonViewItem representedObject];
NSInteger index = [[collectionView contents] indexOfObject:obj];
As I suggested here: How to handle a button click from NSCollectionView
I would do it like this (because the button you want to press should be coupled with the corresponding model, therefore the represented object):
Add a method to the model of your collectionViewItem (e.g. buttonClicked)
Bind the Button Target to Collection View Item
While binding set model key path to: representedObject
While binding set selectorname to: methodname you chose earlier (e.g. buttonClicked)
Add protocol to your model, if you must tell delegate or establish observer-pattern
use NSArrayController for binding to NSCollectionView,
use collectonViewItem.representedObject to get a Custom Model defined by yourself.
save and get index in your custom model.
That's works for me.
Related
I would like to preserve my application's state in XCode 6 and iOS 8. However, all the information I can find refers to storyboards and restoration identifiers, but I am not using storyboards.
I am sure I could make sense of all these would it not be for one major thing: I can't seem to find the field for the restoration ID for View Controllers in Xcode 6. I have found the ones for views, but every tutorial makes it clear that I have to make sure to tag the controller, not the view!
Any help is highly appreciated :)
Thx in advance
restorationIdentifier is a view controller property as well as a view property.
From the View Controller Class Reference documentation:
restorationIdentifier The identifier that determines whether the view controller supports state restoration.
This property indicates whether the view controller and its contents should be preserved and is used to identify the view controller during the restoration process. The value of this property is nil by default, which indicates that the view controller should not be saved. Assigning a string object to the property lets the system know that the view controller should be saved. In addition, the contents of the string are your way to identify the purpose of the view controller.
State restoration is hierarchical in nature. As I'm sure you know, if you don't set the view controller's restorationIdentifier property, its view will not be saved, even if its view's restorationIdentifier property is set.
Update:
You can programmatically set your view controller's restorationIdentifier when you initialize it:
- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
self = [super initWithNibName:nibName bundle:bundle];
if(self)
{
self.restorationIdentifier = #"MyViewControllerID";
}
}
Since you're not using Storyboards, you can't inspect your view controller's properties. This is where the view controller's restorationIdentifier property is displayed.
I'm trying to build a To-do list application. I have 2 tablesviews and one textfield. In the first tableview are the different projects, and when you click on one of them the associated todos appear in the second tableview. It's a pretty basic Master-detail I guess.
I set it all up with bindings.
Right now the way you add a task, is you click on an add button and it adds a row with a placeholder text that's editable.
But what I want, is the user to enter the task in the textfield, press add, and then it adds the todo with the name already set.
So basically I have TodoItem Class with a name property, and my question would be, how do I get the content of the nstextfield and assign it to the name property ?
I tried creating an outlet from the Todoitem class to the textfield, but xcode won't let me connect it....
Tell me if you need to see any code, but since I used bindings, there's almost nothing to show. Thanks!
… how do I get the content of the nstextfield and assign it to the name property ?
Translate that directly into Objective-C:
NSString *contentOfTheNSTextField = [myTextField stringValue];
myNewTask.name = contentOfTheNSTextField;
You'd do that in the action method that you've set both the button and the field to call.
I tried creating an outlet from the Todoitem class to the textfield, but xcode won't let me connect it....
To do this, the Todoitem would need to reside in the nib.
But, even if you could do that, why should the model object know about the text field? Carrying values between model and view is a controller's job.
BACKGROUND:
I have a tableview with a list of names. When you click on a name in the list, it displays additional detail information in another section of the window. Everything is connected and is working correctly.
However...
I would like to use Type Select with this table and have run into the following snag:
When I start typing a name (while the table is selected) it correctly highlights the appropriate name in the table BUT the detailed information to the right of the table does not change.
I know the reason is the code for changing the detail information is in an IBAction method which is only called when you click to select a name in the list, and uses the [sender clickedRow] call to get the index of the selected name.
I also suspect that I need to use the [tableView selectedRow] (since it is being selected, but you are not clicking on it) but I am not quite certain where or how to perform this check.
I'm also thinking that since "type select" isn't sending an action message, I won't be able to use [sender selectedRow] but rather will use [tableView selectedRow]...
QUESTION:
How can I tell when the selected row in a tableview has changed via type select?
Thanks!
Implement the tableViewSelectionDidChange: delegate method in your controller, and ensure that your controller is the NSTableView's delegate.
You could update your view with something like:
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
[relatedView updateInformationFromRow:[[aNotification object] selectedRow]];
}
From the documentation:
tableViewSelectionDidChange: Informs the delegate that the table
view’s selection has changed.
Within that, you would update your related view. You could use the aNotification's object, which will be a NSTableViewSelectionDidChangeNotification, again from the documentation, is:
Posted after an NSTableView object's selection changes. The
notification object is the table view whose selection changed. This
notification does not contain a userInfo dictionary.
(Abstract: bindings work in code, but not in IB)
I have a window managed by a NSWindowController. To the left of the window is a source view. To the right is a table view showing the elements of the currently selected source.
I have set up a NSTreeController within my window XIB. I want its contents to be used for the source view. It's selection will drive the table view.
I am trying to split this up using NSViewControllers. One view controller will load a NIB containing the source view. Another view controller will load the table view.
Seeing that I need access to the NSTreeController within the source view controller, I have set it to be the view controller's representedObject. (Actually for this setup to be done by the time awakeFromNib is called on the view controller, I have turned representedObject into an IBOutlet).
All works fine when I wire my source view up in code:
[outlineView bind:#"content"
toObject:sources
withKeyPath:#"arrangedObjects"
options:nil];
[outlineView bind:#"selectionIndexPaths"
toObject:sources
withKeyPath:#"selectionIndexPaths"
options:nil];
[[outlineView tableColumnWithIdentifier:#"Title"] bind:#"value"
toObject:sources
withKeyPath:#"arrangedObjects.title"
options:nil];
I am however unable to reproduce this using Interface Builder. Thing is, here the "controller key" textfield is grayed out. Thus I bind column's "value" to the file owner using a model keyPath of "representedObject.arrangedObjects.title". This does not show the desired behavior. Actually an exception is thrown: -[NSProxy doesNotRecognizeSelector:_mutatingNodes] called!
How can I use representedObject in IB?
Can I create a controller in IB which acts as proxy to representedObject?
Could I set-up a tree controller in the source view XIB which during NIB loading gets swapped out for the representedObject?
I moved away from using representedObject. It appears that is meant only for model objects.
I now pass in my tree controller using a custom outlet. I continued setting up and tearing down the bindings in code.
I’ve similar issues when I try to pass a reference to an object controller (NSTreeController in my case). I don’t think this is how Apple wants you to use their KVO-compatible controllers. The exceptions look like they’re XIB-unarchiving & timing-related.
The trick is not to pass the controllers, but to pass the underlying data and keep the selection in sync.
This way you can set up your bindings in a storyboard and won’t get any exceptions. You’ll have to set up a new instance of an object controller for every child view controller (copy & paste in Storyboard once you configured the first one works). For a detailed example take a look at another answer that gets much more into detail.
I'm having trouble converting my Cocoa project from a manually-synched interface model to a bindings model so that I don't have to worry about interface glue code.
I followed the CocoaDevCentral Cocoa Bindings tutorial to make sure that I had covered all the bases, but things aren't working correctly. I have a master-detail interface, but I'm having trouble even getting the master portion of the interface to work correctly. No data is showing up in the master column, even though I've set up the bindings model similar to how it is shown in the tutorial. I've made sure all my controllers and objects have -(id)key and -(void)setKey:(id)key methods so that they're bindings-compliant, I've created a ControllerAlias object in my nib, connected it to my controller, created an NSArrayController that binds to one of the NSMutableArrays from the class that ControllerAlias connects to, made sure to set the type of objects that are contained within the array, and then I've bound a table column to the NSArrayController.
I'm getting no errors whatsoever in the Console, and setting NSBindingDebugLogLevel to 1 doesn't produce any errors either, that would help me figure out what the problem is.
The only other thing I could think of to make sure that things are working correctly is to check that the NSMutableArray that connects to the NSArrayController actually has something in it, and it does.
Any suggestions? What other typical pitfalls are there with Cocoa bindings that I should check?
Have you put a breakpoint in your key: method to determine if it is getting called or not? If it isn't, then that would indicate that something isn't set up correctly for the binding in the table column (since you have verified that your array does have items in it).
I don't think that you need to create an Object Controller anymore (that tutorial is a bit out of date). Just create an Object in your NIB, and set its class to your Controller class. You can set up the bindings directly through it instead of the ObjectController.
To set up a binding, I do the following:
Create an instance of my controller in the NIB.
Create an NSArrayController, bind it to an array in my controller.
For each column in the table, bind the value to a member of an object in the array controller.
That should be all that you need to do - I think they've cleaned this up quite a bit since bindings were first introduced a few versions ago.
I've created a ControllerAlias object in my nib,
What is a “controller alias”? Is this a model, controller, or view?
connected it to my controller,
What do you mean?
created an NSArrayController that binds to one of the NSMutableArrays from the class that ControllerAlias connects to,
Classes don't have NSMutableArrays.
What property of the array controller did you bind?
What object did you bind it to?
What key path of that object did you bind it to?
… and then I've bound a table column to the NSArrayController.
What property of the table column did you bind?
Which property (key path) of the array controller did you bind it to?
So in my original code, I was modifying the array (which the NSArrayController was representing) in awakeFromNib, not in init, so the changes weren't being reflected in the interface since I wasn't modifying the array via a key-value observing method.
I changed the code from
theArray = [[NSMutableArray alloc] init];
[theArray addObject:newThing];
to:
theArray = [[NSMutableArray alloc] init];
NSMutableArray *bindingsCompliantArray = [self mutableArrayValueForKey:#"things"];
[bindingsCompliantArray addObject:newThing];
I think the other solution is to do the loading in the -(id)init method instead of the -(void)awakeFromNib method, but that required a larger refactor, so I didn't do that.
I figured this out by adding a button to create a new thing in the array list via the NSArrayController, and when I clicked the button, a new thing was added to the array and my existing array magically showed up as well.