Getting objects from a window Cocoa - objective-c

What's the best way to get objects from a window. I have a sudoku-like grid of 81 NSTextFields and I would prefer to simply have an array of NSTextFields instead of 81 individual NSTextFields linked through IBOutlets.
For example: if there was a way to send a message to NSWindow such as getObject: (NSString*) title and then use a for-loop to add these NSTextFields to an NSMutableArray, that would be ideal. Any suggestions would be appreciated!
-Luke

You could do the following:
NSArray *subviews = [_window subViews];
for(NSView *subview in subviews)
{
if( [subview isKindOfClass:[NSTextField class]] )
{
[_textFields addObject:subview];
}
}
in -awakeFromNib
Now for the nagging, I don't think you should do it this way. Creating a custom NSView to act as a "Sudoku" view would be both easier to use in your code and better for performance of your application. Loading the Window with 81 textFields is quite heavy + uses a lot more memory.

It sounds to me like you would be much better off with an NSMatrix of NSTextFieldCells.
A matrix is a single object that you can reference with a single outlet connection (or other property), and it knows about rows and columns (so no need to convert those to and from linear indexes). You can also access its individual cells to configure them separately; for example, to set the filled-in values and disable those cells so the user can't change them.

Related

Combining UIWindows?

I am going to add 50 windows with 4 UIButtons (with text) and 1 UILabel (with text as well) to EVERY window. Do I need to do it this way? Is there a better way?
It's never a good idea to create additional windows if you can help it, especially on iOS where there is almost never a need to do so. You should create a UIViewController subclass and make it your root view controller (this is already set up in the single view application template). Then, make a subclass of UIView — let's call it "MyQuizView." "MyQuizView" should have a custom initializer that takes five NSStrings (one for the question, four for the answers) and an integer to determine which answer is the correct one. The UIViewController subclass can then instantiate 50 of these views handing them values from the model and make them its main view's subviews.
EDIT: Here's an example of a custom initializer for a UIView subclass.
- (id) initWithFrame:(CGRect)frame question:(NSString*)ques answers:(NSArray*)ans correctAnswer:(int)correctAns{
self = [super initWithFrame: frame];
if (self) {
self.question = ques;
self.answers = ans;
self.correctAnswerNumber = correctAns;
[self setup];
}
return self;
}
A custom initializer starts with init. It sets self to the return vale of its superclasses' designated initializer, then, if self is not nil, it initializes its state–usually using the arguments passed to do so. At the end it returns self. This one assumes you have the correct properties and calls a method called setup after setting the properties to the correct values, allowing you to use them to create labels and whatnot. Alternatively you could take the values passed in and use them to immediately create the labels and buttons, set up the target actions and place them as subviews, that way you wouldn't need to keep the arguments as properties. Each button can be given a numerical tag so that you know whether or not the answer was correct or not (based on the integer passed into the initializer, which you would have to store somewhere). This is all from memory, but hopefully it's correct.
Don't waste your time creating 50 windows (if this is a MacOS question) or views (if this is an iOS question). Wow, that'd be awful.
Instead, create one single view which has four buttons and at least one label.
You can then populate the string values for each of those items from your list of questions & answers. You can keep those questions either in a plist file or a CoreData database or some parseable flat file, etc. Connect the four buttons to the (game?) controller via "IBAction" methods.

Longpress On UI Element (Or How To Determine Which Element Is Pressed?)

I have an app that has a rather large help section in PDF form. It's getting to me that the PDF is so large and I can tell in instruments that it's putting a little too much strain on the processor, so I'm going to phase it out.
I figured, a lighter implementation would be using a UILongPressGestureRecognizer that I could attach to every UI element that would display specified text in maybe a popover or a UIMenuController indicating the function of the selected element.
So my question is this: How can I attach something like a tag to EVERY element in the view so it can be passed to a single method? When I tried tags, I found there was no way to access it through the (id)sender section of my method signature and thus there was no way to differentiate between the elements.
EDIT: to the person below: While you have solved my facepalm of a question as to determining the tags of views, how might one go about attaching gesture recognizers to a UIBarButtonItem so as to ascertain it's tag? Your implementations allow for a very nasty unrecognized selector because UIGestureRecognizers dont have a tag property.
You can derive the tag from an object passed in as the sender. Just have to check it's class and cast it appropriately. tag is a UIView property so we'll start there.
- (void)someMethod:(id)sender
{
if (![sender isKindOfClass:[UIView class]])
return;
UIView *senderView = (UIView *)sender;
NSInteger tag = senderView.tag;
}
You can access tags through the -(IBAction)xxxxxx:(id)sender; like so:
NSInteger tagValue = [sender tag];
But why can't you just connect the actions to your elements through Interface Builder?
What are the UI elements your using here?

Changing the view after addSubview, what is good practice?

This may sound a newbie question, however I'm new to iOS dev.
Suppose with have this code.
UILabel* label = [[UILabel alloc] init];
...
[someScrollView addSubview:label];
...
label.text = #"Some Text";
is it good practice to modify the view after addSubview ?
Actually my concern is following probably, it is possible that label get released before reaching to label.text assignment, for instance in viewDidUnload, right ? and the assignment will fail.
Overall my questions are
it is good practice to modify views after addSubview ?
is it good practice to release view after addSubview, and later if I need to get any subview to look for it using following technique for (UIView *view in self.subviews) { if (...) ... } ?
It is fine to change properties of a view after you add it as a subview. Those properties will be applied (or animated) on the next turn of the runloop when UIKit renders stuff.
You should absolutely release your view after adding it as a subview IF you no longer need to own it. In other words, follow the memory management guidelines for all cocoa programming. Doing addSubview will cause the owing view to retain it (since it needs it). If you need to change a property on the view in the future though, you should retain it so you have access to it
Your code is fine as long as it is all in the same method and label is not reassigned during any of the ... sections.
Modifying a view before or after adding it to a subview makes no difference.
If you have allocated a view, then added it to a subview, and you don't wish to keep a separate reference to it, you should release it - this is standard memory management. The super view will retain its subviews.
To get hold of a reference to your subview again, your two options are:
Set the tag on the subview before adding it, then use viewWithTag: to get it later
Keep a reference to the subview as an instance variable (in this case, you wouldn't be releasing it after creating it, you'd release it on dealloc).
I’ve used both ways - modifying before and after and have not had any issues either way. The superview is retaining the subview, so if you don’t release the superview or set the subview to NIL somewhere, you’re pretty safe.
Yes, you need to release the view you added after addSubview, but easiest to do it like this:
UILabel* label = [[[UILabel alloc] init] autorelease];
Then it will be released automatically and you don’t have to worry about explicitly releasing it.

Objective-C, Set UITextField Value from this:

I have been told that to access a UITextField in the setup i have with my app, i have to use a subView whatever this is (in a noob at objectivec) and i was given this
[subview isKindOfClass:[UITextField class]]
Where do i put this? And how can i use it to set the value of my UITextField? :D
Thanks!
Update: I want to set the value of my textfield, in the function that is called after my URLScheme - the scheme bit works as i have alerted out the url.. this is cool, now i need to set a textfield with a string?
Iterating through subviews and choosing based on type is not generally considered good design practice. You should reconsider how this is structured to have explicit access to the UITextField you want to access. Doing this with Apple's private classes in particular will deny you access to the App Store. At any rate, here's how you'd do what you want:
for(UIView *subview in myParentView){
if([subview isKindOfClass:[UITextField class]])
[(UITextField *)subview setText:#"MyString"];
}
This is still very little useful context, but you most likely want to create an outlet to your text field and just set the value using that reference (with the text property).

Is it possible to design NSCell subclasses in Interface Builder?

I'm trying to subclass NSCell for use in a NSTableView. The cell I want to create is fairly complicated so it would be very useful if I could design it in Interface Builder and then load the NSCell from a nib.
Is this possible? How do I do it?
The question was about a subclass of NSCell; the other answers seem to be doing something else, likely taking advantage of UITableViewCell being a view.
NSCell is not a view. While laying a custom cell out in IB would be a useful thing to be able to do, I think the answer is basically "no, this is not possible". When you subclass NSCell, you're pretty much just doing your own drawing. There isn't support subcells, or parameterized auto layout (ala NSView's springs and struts), which is I suspect what you're looking for.
The only caveat is that you could design an NSCell subclass that did do layout of sub-elements and provided parameters for setting those subelements and all tweakable parameters. Then, you would need to write an IB plugin to make that cell and accompanying inspector available at design time in IB.
This, however, is probably harder than writing a little custom app that does more or less the same thing. Put an NSCell in a control in the middle of a window, and make yourself UI for tweaking the parameters you're interested in. Bindings can make this pretty straightforward for positioning stuff (i.e. bind an x value to a slider), though you will not get direct manipulation of the elements of course. When you're done, you could archive your cell and load the archive at runtime in your real app, or you could just log out the properties and set them in code in your app.
Some answers in this thread have gone off topic because they're talking about Cocoa Touch, when the original question was about Cocoa - the 2 APIs are quite different in this regard and Cocoa Touch makes it easy because UITableViewCell is a view subclass. NSCell isn't, and that's the problem
For information, I had to do something very similar in NSOutlineView recently - which is basically the same, but a little harder if anything because you have to deal with disclosure / collapse of levels. If you're interested in the code, I posted about it here: http://www.stevestreeting.com/2010/08/08/cocoa-tip-using-custom-table-outline-cells-designed-in-ib/
HTH
As Ken says, NSCells and NSViews are different, and you can only lay out NSView hierarchies in NIB, not NSCells (which don't have any explicit hierarchy).
On the other hand, there's nothing preventing you from having a hierarchy of NSViews and using that to draw your NSCell -- you could add them as a subview of your cell's parent view, tell them to display, and remove them from the window and nobody would be the wiser.
In this case, using a NIB would work, although it seems like a ton of hassle. Typically I've just replaced the object that takes NSCells with a custom one that takes my NSViews, but that means writing your own mouse-handling code, which is very touchy.
On the other hand, my approach lets you bind the views' values in NIB, so you don't have to do any extra work, which is cool.
In IB, start an empty XIB. Now go to the pallete and drag in a UITableViewCell, double click to bring up and edit.
include only the custom UITableViewCell (no other UIViews or other top level controls) - make sure it's a real UITableViewCell in IB, or you cannot set a reuse identifier (as opposed to casting a UIView in IB as your custom UITableViewCell class).
Then you can add lables or whatever you like within the cell, as well as setting the reuse identifier or set whatever disclosure indicator you might like.
To use, you provide code like this in the tableView:cellForRow:atIndexPath: method:
YourCustomCellClass *cell = (YourCustomCellClass *)[tableView dequeueReusableCellWithIdentifier:<IDYouSetInXIBFile>];
if ( cell == nil )
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:<YourXIBName> owner:self options:nil];
id firstObject = [topLevelObjects objectAtIndex:0];
if ( [ firstObject isKindOfClass:[UITableViewCell class]] )
cell = firstObject;
else cell = [topLevelObjects objectAtIndex:1];
}
If you have any labels or other controls you want to reference in your code, wire them in IB to your custom cell class - NOT the file's owner, which you do not ever need to set using the above code (you can leave it as NSObject).
Edit: I note you are really looking for an NSCell answer, but the code approach to using IB should be identical in Cocoa with the Cocoa Touch code I used above as loadNibNamed is a standard Cocoa call.
Joar Wingfors wrote an article for Stepwise a few years back on a related topic, Subviews in TableView Rows.
The principal technique is to create an NSCell that can host an NSView. If you were to do this, you could then design an NSView subclass in Interface Builder that you could embed anywhere you need that specific cell.
Another possibility, if you can target Leopard, is to see whether you need to use an NSTableView or whether you can use an NSCollectionView. Collection views deal directly in terms of "item views" rather than in cells, so they're much more straightforward to design in Interface Builder.
I found some interesting examples which I do not totally understand, though.
GitX extends the NSTextFieldCell in their PBIconAndTextCell, referencing this post.
WWDC 2009 - Session 110 "Presenting User Data with Table Views and Browsers" talks "Adding subviews" and "Custom cell editors". (I do not have the source code, though.)
Display an NSTextfieldCell containing text and an image within a NSTableView, #70
Display an NSTextfieldCell containing text and an image within a NSTableView, #71
The last 2 examples work with NSTableViewDataSource and NSTableViewDelegate. I would like to use Bindings an ArrayController in the InterfaceBuilder to connect other UI elements like text fields.
I stumbled into another discussion where Abizern points out PXListView by Alex Rozanski which looks very promising!
I am trying to implement a solution for the problem myself. Please find my project on github and the latest rendering problems over here.
I do it like this:
/* example of a silly way to load a UITableViewCell from a standalone nib */
+ (CEntryTableViewCell *)cell
{
// TODO -- this is really silly.
NSArray *theObjects = [[NSBundle mainBundle] loadNibNamed:#"EntryTableViewCell" owner:self options:NULL];
for (id theObject in theObjects)
if ([theObject isKindOfClass:self])
return(theObject);
NSAssert(NO, #"Could not find object of class CEntryTableViewCell in nib");
return(NULL);
}
However it isn't very efficient and if you're loading lot of data it might hurt you. Of course you should be using a reuseIdentifier which should force this code to only run a handful of times per table.
1) Create NSViewController TableViewCell.h
2) Create in TableViewCell.h some procedures like
-(void)setText:(NSString *)text image:(NSImage *)image
3) In Main class #import "TableViewCell.h"
4) In Main class in -(NSView *)tableView:viewForTableColumn:row: write:
NSImage *img = //some image
TableViewCell *cell = [[TableViewCell alloc] initWithWindowNibName:#"TableViewCell"];
cell.view.init;
[cell setText:#"some text" image:img];
return cell;
Hope this will help =)
I want to provide a more modern approach here.
Starting with iOS 5, UITableView has a method
(void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
Once you registered your NIB containing your cell, just use
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
to get a new cell. If a cell is available for reuse, it will be returned, otherwise a new cell is automatically created, and in that case this means loaded from the NIB file.
Add your UITableViewCell to your tableviewcontroller and declare an IBOutlet property:
#interface KuguTableViewController : UITableViewController {
IBOutlet UITableViewCell *customTypeCell;
}
#property (readonly) UITableViewCell *customTypeCell;
... then in cellForRowAtIndexPath you can just use your cell and set it to be reused:
static NSString *CellIdentifier = #"CustomCell"
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = customTypeCell;
cell.reuseIdentifier = CellIdentifier;