Is there an 'indirect' Cocoa function like in Excel? - cocoa

i'm building an app in Xcode where there are a 81 textviews in the NIB, each with a sequential name, so box1, box2, box3, box4 etc.
When doing data manipulation i want to be able to use the data in each box to add to an array for example. What i would like to be able to do is put this in a loop, so for example something like:
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i=1; i<82; i++) {
[array addObject: [Indirect("box" & i).text];
}
Similarly when outputting back to the textviews, i want to be able to loop from the array rather than referring to each textview independently. so something like:
for (int i=1; i<82; i++) {
indirect("box" & i).text = [array objectAtIndex:i];
}
Any ideas? Sorry if this is obvious - fairly new to the game.

Consider the MVC design pattern. Your calculation shouldn't be based directly off the views (the UI) but rather off some state in the controller, which is set by the views. Each time a field is edited, it notifies your controller via target/action or via Cocoa Bindings. When that happens, the controller updates your data model (in your case, that means it updates the computation and probably reflects the result in another part of the UI - the "total" field).

In Cocoa, there are two ways to do it:
Add all of the fields to an array in awakeFromNib. Enjoy writing 82 addObject: messages.
Remove the fields from the nib and create them in a loop in code, adding each one to an array. (This is what I'd do.)
Once they're in an array, you can refer to them by index, same as you do with the strings.
But you mention that you're accessing the fields' text property. This only exists in Cocoa Touch, not in Cocoa. If you're using Cocoa Touch, then you have a third option:
Replace your 82 outlets with an outlet collection.
The value of an outlet collection property is an array, so you get to create your fields in the nib but still refer to them by index into the array in the code.
On the other hand, I'd probably still create them in code, even though I'm more pro-nib than most Cocoa Touch devs. Part of it is habit (I'm still almost entirely a Mac developer), but part of it is the DRY principle. If I create the fields in a loop in code, I can describe all of the fields exactly once, along with the ways in which they differ. I won't have the risk of changing one field and forgetting (or even just having) to update the others, or of going to change all the fields (again) and forgetting to change one.

I would handle this using the tags: you can set them from 1 to 81 in the nib (look for the field under Control).
Then in -awakeFromNib you can call [self viewWithTag:i] inside a for loop.
It's definitely less work than individual outlets, and I think even simpler than an outlet collection – filling in the number means you don't have to connect outlets for all the text fields.

Related

Binding a NSArrayController to a NSPopupButton & NSTextField

What I want to accomplish seems like it should be fairly straightforward. I have placed a sample project here.
I have a NSArrayController filled with an array of NSDictionaries.
[[self controller] addObject:#{ #"name" : #"itemA", #"part" : #"partA" }];
[[self controller] addObject:#{ #"name" : #"itemB", #"part" : #"partB" }];
[[self controller] addObject:#{ #"name" : #"itemC", #"part" : #"partC" }];
I am populating a NSPopupButton with the items in this array based on the 'name' key. This is easily accomplished with the following bindings
I would then like to populate a NSTextField with the text in the 'part' key based on the current selection of the NSPopupButton. I have setup the following binding:
With these bindings alone, the text field does display 'partC'.
However, if I change the value of the NSPopupMenu, what the text field shows does not change.
I thought this would simply be a matter of setting up the 'Selected Object' binding on the NSPopupButton
but that isn't working. I end up with the proxy object in my menu for some strange reason (providing the reason why would be a bonus).
So, what do I need to do to make this work?
Don't use "Selected Object" in this case. Bind the pop-up's "Selected Index" binding to the NSArrayController's selectionIndex Controller Key. Tried it out on your sample project and it works.
EDIT:
You asked why it's appropriate to use selectionIndex over selectedObject. First some background:
When binding a popup menu, there are three virtual "Collections" you can bind: Content is the abstract "list of things that should be in the menu" -- you must always specify Content. If you specify neither Content Objects, nor Content Values, then the collection of values bound to Content will be used as the "objects" and the strings returned by their -description methods will be used as the "values". In other words, the Content Values are the strings displayed in the pop-up and the Content Objects are the things they correspond to (which are possibly not strings, and which might not have a -description method suitable for generating the text in the pop-up). What's important to realize here is that there are potentially three different 'virtual arrays' in play here: The array for Content, the array for Content Objects (which may be different) and the array for Content Values (which may also be different). They will all have the same number of values, and typically, the Content Objects and Content Values will be functions (in the mathematical sense) of the corresponding items in the Content array.
The next thing that's important to realize is that part of NSArrayController's purpose in life is to keep track of the user's selection. This is only mildly (if at all) interesting in the case of a pop-up, but starts to become far more interesting in the case of an NSTableView. Internally, NSArrayController keeps track of this by keeping an NSIndexSet containing the indexes in the Content array that are selected at any given time. From there, selection state is exposed in several different ways for your convenience:
selectionIndexes is as described - an NSIndexSet containing the indexes of the selected items in the Content array
selectionIndex is a convenient option for applications that do not support multiple selection. It can be thought of as being equivalent to arrayController.selectionIndexes.firstIndex.
selectedObject is also useful in single selection cases, and corresponds conceptually to ContentObjectsArray[arrayController.selectionIndexes.firstIndex]
selection returns a special object (opaque to the consumer) that brokers reads and writes back to the underlying object (or objects in the case of multiple selection) in the Content Array of the array controller. It exists to enable editing multiple objects at a time in multiple selection cases, and to provide support for other special cases. (You should think of this property as read-only; Since its type is opaque to the consumer, you could never make a suitable new value to write into it. It's meaningful to make calls like: -[arrayController.selection setValue: myObject forKey: #"modelKey"], but it's not meaningful to make calls like -[arrayController setValue: myObject forKey: #"selection"]
With that understanding of the selection property, let's take a step back and see why it's not the right thing to use in this case. NSPopUpButton tries to be smart: You've provided it with a list of things that should be in the menu via the Content and Content Values bindings. Then you've additionally told it that you want to bind its Selected Object to the the NSArrayController's selection property. You're probably thinking of this as a "write only" binding -- i.e. "Dear pop-up, please take the user's selection and push it into the arrayController", but the binding is really bi-directional. So when the bindings refresh, the popup first populates the menu with all the items from the Content/Content Values bindings, and then it says, "Oh, you say the value at arrayController.selection is my Selected Object. That's odd -- it's not in the list of things bound with my Content/Content Values bindings. I'd better add it to the list for you! I'll do that by calling -description on it, and plunking that string into the menu for you." But the object you get from that Selected Object binding is the opaque selection object described above (and you can see from the outcome that it is of class _NSControllerObjectProxy, a private-to-AppKit class as hinted by the leading underscore).
In sum, that's why binding your popup's Selected Object binding to the array controller's selection controller key is the wrong thing to do here. Sad to say, but as I'm sure you've discovered, the documentation for Cocoa bindings only begins to scratch the surface, so don't feel bad. I've been working with Cocoa bindings pretty much daily, in a large-scale project, for several years now, and I still feel like there are a lot of use cases I don't yet fully understand.

Database Results in Cocoa

I am creating an application that has to interact with server data and then display the results from a database accordingly. I am writing the client side app in Cocoa.
Example: A user logs on to a web application. They have some options for filing a web report. Choices: single line and multiple line. They can choose how many of these fields they have for various variables that they want to input. This is then saved in a MYSQL database for later use.
Example (part 2): The client side application fetches the data in the MYSQL databases and then displays it. The problem is that it is a variable number of fields and a variable number of types.
In other words, the database basically stores if we want to display a NSSecureTextField, NSTextField, etc. and then displays that on the screen. As I pointed out above, the problem is that they are allowed to choose how many and the type of the element they want - so I am not quite sure how to transfer this to code.
And just to clarify, I am not attempting to build an online Interface Builder. Simply a online way to input data which has a variable amount of fields, and various types of these fields. I have the online system created, but I am having difficulty with the client side part.
Any help would be greatly appreciated!
I'm not sure I understand what you're asking for. Isn't it pretty trivial to figure out how many NSTextFields the user wants and then have a little for() loop to create them? You'll probably want to keep track of the textfields, so I would probably do it like this:
NSMutableDictionary * interfaceElements = [[NSMutableDictionary alloc] init];
for (NSInteger i = 0; i < numberOfTextFields; ++i) {
//this is just to make a frame that's indented 10px
//and has 10px between it and the previous NSTextField (or window edge)
NSRect frame = NSMakeRect(10, (i*22 + (i+1)*10), 100, 22);
NSTextField * newField = [[NSTextField alloc] initWithFrame:frame];
//configure newField appropriately
[[myWindow contentView] addSubview:newField];
[interfaceElements setObject:newField forKey:#"someUniqueIdentifier"];
[newField release];
}
The dictionary of course would not be local to this method, but I think you get the idea.
Alternatively, you might be able to coerce NSMatrix into automating the layout for you.
If you're writing a client application for the iPhone, then I would highly recommend looking to the Settings Application Schema reference for inspiration. If you're unfamiliar with this, here's a brief introduction: The iPhone allows developers to move their preferences area from the actual app to the Settings app. This is done by creating a settings bundle and building a plist in a very specific way. Settings.app then discovers that plist, parses it, and builds an interface according to the definition it contains. You can do switches, textfields (even secure ones), sliders, groups, and a couple other kinds of interface elements.

How should I remove all items from an NSTableView controlled by NSArrayController?

I'm using an NSArrayController, NSMutableArray and NSTableView to show a list of my own custom objects (although this question probably applies if you're just showing a list of vanilla NSString objects too).
At various points in time, I need to clear out my array and refresh the data from my data source. However, just calling removeAllObjects on my NSMutableArray object does not trigger the KVO updates, so the list on screen remains unchanged.
NSArrayController has no removeAllObjects method available, which seems really weird. (It does have addObject, which I use to add the objects, ensuring the KVO is triggered and the UI is updated.)
The cleanest way I've managed to cause this happen correctly is:
[self willChangeValueForKey:#"myArray"];
[myArray removeAllObjects];
[self didChangeValueForKey:#"myArray"];
...so I'm kind of having to do the KVO notification manually myself (this is in my test app class, that contains the myArray property, which is NSMutableArray, as mentioned.)
This seems wrong - is there a better way? From my googling it seems a few people are confused by the lack of removeAllObjects in NSArrayController, but haven't seen any better solutions.
I have seen this solution:
[self removeObjectsAtArrangedObjectIndexes:
[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(0, [[self arrangedObjects] count])]];
but this looks even more unpleasant to me. At least my solution is at least marginally self-documenting.
Did Apple not notice that sometimes people might want to empty a list control being managed via an NSArrayController object? This seems kind of obvious, so I think I must be missing something...
Aside: of course, if I add new items to the array (via NSArrayController), then this triggers a KVO update with the NSArrayController/NSTableView, but:
Sometimes I don't put any items in the list, because there are none. So you just see the old items.
This is a bit yucky anyway.
You don't remove items from a table view. It doesn't have any items—it just displays another object's items.
If you bound the array controller's content array binding to an array property of some other object, then you should be working with that property of that object. Use [[object mutableArrayValueForKey:#"property"] removeAllObjects].
If, on the other hand, you haven't bound the array controller's content array binding, then you need to interact with its content directly. Use [[arrayController mutableArrayValueForKey:#"content"] removeAllObjects]. (You could also work with arrangedObjects instead of content. If one doesn't work, try the other—I've only ever done things the first way, binding the array controller to something else.)
Had this problem as well and solved it this way:
NSArrayController* persons = /* your array controller */;
[[persons content] removeAllObjects];
Swift
#IBOutlet var acLogs: NSArrayController!
acLogs.removeObjects(acLogs.content as! [AnyObject])
worked for me.
Solution in Swift:
if let ac = arrayController
{
let range:NSRange = NSMakeRange(0, ac.arrangedObjects.count);
let indexSet:NSIndexSet = NSIndexSet(indexesInRange: range);
ac.removeObjectsAtArrangedObjectIndexes(indexSet);
}
Just an update that works in Swift 4:
let range = 0 ..< (self.arrayController.arrangedObjects as AnyObject).count
self.arrayController.remove(atArrangedObjectIndexes: IndexSet(integersIn: range))

Refresh Cocoa-Binding - NSArrayController - ComboBox

in my application I made a very simple binding. I have a NSMutableArray bound to a NSArrayController. The controller itself is bound to a ComboBox and it shows all the content of the NSMutableArray. Works fine.
The problem is : The content of the Array will change. If the user makes some adjustments to the app I delete all the items in the NSMuteableArray and fill it with new and different items.
But the binding of NSMutableArray <-> NSArrayController <-> NSComboBox does not refresh.
No matter if I remove all objects from the Array the ComboBox still shows the same items.
What is wrong here? Is my approach wrong or do I only need to tell the binding to refresh itself? I did not find out how to do that.
You're likely "editing the array behind the controller's back", which subverts the KVO mechanism.
You said:
I have a NSMutableArray bound to a NSArrayController.
How? Where does the array live? In a document, accessible via a KVC/KVO compliant -myArray / -setMyArray: set of accessors?
I'll bet you're directly telling the "myArray" ivar to -removeAllObjects, right? How will these KVC/KVO accessors "know" the array has changed?
The answer is, they don't. If you're really replacing the whole array, you'll want to tell your document (or whoever owns the array) to -setMyArray: to a whole new array. This will trigger the proper KVO calls.
... but then, you don't really need a mutable array, do you? If you only want to replace individual items in the array, you'll want to use indexed accessors:
(Documentation - see the Collection Accessor Patterns for To-Many Properties section)
http://tinyurl.com/yb2zkr5
Try this (using ARC/OS X 10.7):
in header file, define the arrayInstance and the arrayController
#property (weak) IBOutlet NSArrayController *arrayController;
#property (strong) NSArray *arrayInstance; // for the array instance
then in implementation
#synthesize arrayController = _arrayController;
#synthesize arrayInstance = _arrayInstance;
_arrayInstance = ....... // What ever the new array will be
[_arrayController setContent:_arrayInstance];
This will force the arrayController to update the content and display correctly.
Another but 2 line of code solution would be:
[self willChangeValueForKey:#"arrayInstance"];
_arrayInstance = ....... // What ever the new array will be
[self didChangeValueForKey:#"arrayInstance"];
Think the first looks more obvious, the second more KVO-like.
KVC/KVO compliance seems to be the problem. You should create the new array and update the reference with the new object by using the generated accessor methods. You may otherwise fire KVO messages about the array being updated to inform the bindings, that the contents of the array have changed.
Christian

Best way to handle multiple NSTableView(s)

What is considered the best way of handling multiple NSTableViews without using Cocoa Bindings?
In my app, I have two NSTableViews that are sufficiently closely related that I'm using the same object as the delegate and dataSource for both. The problem is both tableViews invoke the same methods. I currently discriminate between the two tableViews on the basis of NSControl -tag.
The deeper I get into this code, the uglier the use of -tag looks. I end up creating largely duplicate code to distinguish between the tableViews in each delegate/dataSource method. The code ends up being distinctly non-object oriented.
I could create a separate object to handle one or the other tableView, but the creation of said object would be a largely artificial construct just to provide a distinct delegate/dataSource.
Is everyone just using Cocoa Bindings now? I'm avoiding Bindings as I would like to hone my Cocoa skills on techniques that are transferrable between Mac OS and iPhone.
Every delegate/dataSource method for NSTableView passes the instance of NSTableView that's calling it as the first parameter (except for the ones that pass NSNotification objects, in which case the NSNotification's object is the table view instance). Some examples include:
- (int)numberOfRowsForTableView:(NSTableView*)aTableView;
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(NSInteger)rowIndex
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
If you're using one controller object as a delegate/data source for multiple tables, you can just use that parameter to differentiate between them.
for the method :
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
you can use :
NSTableView *theTable = (NSTableView *)[aNotification object];
if(theTable==listeDesMots)
...
It sounds like you should be using a different delegate object for each view, but the same data source. In other words a single model for distinct view and controller objects.
I don't think this is an artificial distinction because the objects have sufficiently different purposes, but you want to use the same data. The bigger rule you are violating now is that each object should have a single purpose. Each objects' purpose could be to retreive and display the data in a specific way.
Good Luck!

Resources