Where to define NSArray and where to define Button action? - objective-c

I have made an array of text strings and want to pull these out an into a label by EITHER swiping of pressing a button. So i have two different functions/methods, the button and the swipe method.
Where and how do I define the array so that these methods can refer to it? Should it be a 'extern NSArray' ?
I have uploaded the image of full code externally http://s1.postimg.org/b2e3m4v67/Sk_rmbillede_2014_05_11_kl_15_48_28.png
Not sure though if that's a violation of some rules here(?)

You want the quote to change on swipe/button press.
In your button press/swipe methods you're setting the text property of the VC's label property to something called Quoteselected. And it looks like Quoteselected is a random element of the array Quotes - or at least maybe it is, since that random number could be 6-10, and you don't have any objects in the Quotes array at those indices - so if those numbers are ever generated by the random function, your program will crash due to an index out of bounds error.
What you probably want to do is generate a new random number on each user interaction and then at that point change the value of Quoteselected to be the object at that index of the array. And then assign that to the label's text property.
As far as defining the Array - I wouldn't have done it the way you did. What you've got there is an "ivar", an instance variable. On iOS, those are typically properties. And since it's a "private" array that outside classes won't need to know about, I'd declare it as a part of the class extension.
So,
#interface BOViewController()
#property NSArray *quotes;
#end
Also note my capitalization changes, that's better style.
So now you've got an array property declared, but there's no data in it. It depends on how you created your View Controller instance. Assuming you did it in a storyboard, it would go in awakeFromNib: or viewDidLoad: (if you instantiated the VC automatically, you might put it in the initWithNibName: method).
- (void)viewDidLoad {
[super viewDidLoad];
self.quotes = #[#"Test", #"Number 3"...];
Then when you want to reference the array in other parts of the class:
self.label.text = self.quotes[0];
Note that your existing code should work, it's just not typical Cocoa coding style.

Related

How do I prevent NSMutableArray from losing data?

The first view of my app is a UITableView.
The user will choose an option and the next view will be another UITableView. Now the user can tap on an "add" button to be taken to another UIViewController to enter text in a UITextField. That text will then appear in the previous UITableViewCell when saved.
The issue I am having: if I back out to the main view and then go back to where I previously was, that inputed text is now gone.
How can I make sure that text is not being released or disappears like this?
You might want to store this array somewhere else in your project, like in an MVC (data model). You could create a new class for it that passes the information through the classes and stores the array in one place. Then once you add to the array, you could reference that class and call a method in that class to store the text in the array and whenever you load the table view it loads with that array in the class.
In my case, I would do this, but I would make everything class methods (where you cannot access properties or ivars) and just store the array in the user defaults / web service or wherever you need and retrieve and add/return it like this:
+ (NSMutableArray *)arrayOfSavedData {
return [[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"];
}
+ (void)addStringToArray: (NSString *)stringFromTextField {
[[[[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"] mutableCopy] addObject: stringFromTextField];
}
The mutableCopy part is important because arrays don't stay mutable after you store them into the user defaults
The reason the text is gone, is probably because you're instantiating new controllers when you go back to where you were. You can keep a strong reference to your controllers, and only instantiate one if it doesn't exist yet. Exactly how to do this depends on how you're moving between controllers -- whether you're doing it in code, or using a storyboard for instance.
This kind of issue is very frequent. When you move around multiple controllers and views.
Each time you load a new view and controllers are alloc+init, new set of values are assigned and previous values are not used!!!.
You can use SharedInstance/SingletonClass so that it is allocated & assigned once and does not get re created with new set of values.

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.

objective-c identifying contentView items

Is there a way to identify unique objects within the contentView? For example, I in my mainWin I have a NSView and 2 NSButtons. Using
[[mainWin contentView] subviews];
I can get all the objects within the mainWin. This works fine for my needs if the object is a subclass of NSView and I've given it a class, for example, in this case I've named the class vHUD and when I log the object it comes back as
<vHUD: 0x146e10>
This is fine for NSViews I am creating because for the most part if I am making them they are going to serve multiple purposes (content container, being toggled around screen, etc.) and a class should be necessary. I could subclass all the buttons (under NSObject) and go that way, but it seems like a sledgehammer approach if I end up having a lot of buttons. Is there a way I can uniquely identify all of the buttons with something descriptive in IB that can then be retrieved from the object itself? I tried "description" but that didn't return anything.
Have you tried using the tag instance variable in the NSView class?

Bindings vs IBOutlets in Objective-C/Cocoa

I'm going through a simple Objective-C/Cocoa program to try and learn the language and am getting a little confused about how some things are linked from the code I write to the interface builder.
For example, I have a simple NSString:
#property (assign) NSString *letters;
And in my interface builder, I have a text field and I use the text field's bindings to connect it to letters.
However, the example also has this:
#property (assign) IBOutlet NSArrayController *wordsController;
In the view I have a table that continuously changes and shows different words and those words are stored in an NSMutableArray. I suppose I can understand that I just can't bind the array to the the table because there are some more complexities. So in the Interface Builder I create an Array Controller and bind it to the table. In the Array Controller's bindings, I bind the Array Controller to the array of words.
I understand that the last thing I have to do is also bind the Array Controller to my NSArrayController object as well. I don't understand why I do this through the main controller object by making a connection between this outlet and the wordsController. In the Array Controller's bindings section there's a greyed out option, Content Object, which says "An NSArrayController that the NSArrayController treats as its content." Why wouldn't I set the binding here? What is the significance of it being an outlet and why is it different than my NSString letters?
Thanks
You are confusing bindings and IBOutlets. This is not unreasonable -- it's a lot of Control-dragging of connections and it can be hard to keep clear what's going on. Let me try to explain:
Bindings are a way to let Cocoa handle the mechanics of keeping a model (some collection of data, even something as simple as a single NSString) and a view (an object which displays on the screen) in sync. When you "bind" your NSString to a text field's value, you are asking the framework to communicate changes to the string or the text field "behind the scenes"; your object which owns the string gets notified to change the string's value when the text field changes, and vice versa.*
A similar situation applies to your mutable array, array controller, and table view. You're essentially right about the complications: the mutable array and the table view don't know how to talk to each other; the array controller stands in between and facilitates: ("Okay, tableView wants to know what to put in row i. Array, give me your object at index i." :) In the past, you would've had to write that code manually, and it looked very similar every time you did so.
That's what the bindings do. They are a way to reduce boilerplate code. The IBOutlet to the array controller gives your object a way to send messages to the array controller, if necessary. One simple example of why you might need to do this is to allow a menu item to trigger a method in the array controller; a document object or another controller might handle the action from the menu item and call the appropriate message on the array controller. You can also ask the array controller for its arrangedObjects to get, for example, the sorted and filtered version of its content array.
* One side note here is that your NSString property should almost certainly use retain, not assign. The object that contains this variable should be responsible for its memory.

How to refresh an NSTableVIew when you populate the NSMutableArray (bind) associated with it

I tried to bind my NSArraycontroller to an NSMutableArray; the array holds objects of type "iData" (it's a custom class). The class further contains some NSMutableString variables which are set as keys for KVC. Then I bound the my NSTableColumn to the NSArrayController and set key model paths of every column respectively.
When I try to populate the array, the GUI does not show anything. What did I forget?
So it's likely that you solved this long ago, but in case someone else stumbles across this question...
I am populating the array via NSMutableArray, but I am not sure how can I populate the array via NSArrayController, since I don't have the instance in my Controller.m class. Please tell me how can I resolve this issue.
It is possible that you were doing something like
[myData addObject:someObject];
However, your NSArrayController will not learn of this change to the NSMutableArray instance because addObject is not KVC compliant. You need to inform any object that is observing that your NSMutableArray instance has changed. There are at least two ways to do this. Assuming that your NSMutableArray instance property is named "myData", then you can do something like the following:
[self.willChangeValueForKey:#"myData"];
[myData addObject:someObject];
[self.didChangeValueForKey:#"myData"];
or
NSMutableArray *bindingsCompliantArray = [self mutableArrayValueForKey:#"myData"];
[bindingsCompliantArray addObject:someObject];
Another SO answer (linked) has a good explanation on what mutableArrayValueForKey actually does, but I recommend reading the Apple developer docs on key-value coding and key-value observation to help understand it.
I have dragged the NSController instance in my mainmenu.nib tray. Do i need to declare an IBOutLet NSArrayController in my Controller.h file and then connect it with the NSArrayController instance in the tray ?
You need a NSArrayController instance in your nib file, but you do not need an IBOutlet in your interface for the situation that you've described here. The NSArrayController should be bound to the key of your NSMutableArray (myData in my example) and it sounds like you already have your table columns bound correctly.
Although Stephen's answer is probably "the way to go", I think the OP's original question "How do I insert/delete/manage my NSMutableArray using the NSArrayController, deserves a simpler and more direct answer:
NSArrayController provides a full and rich set of methods and even IBActions to fulfill almost anything you want on the managed NSMutableArray, with all the niceties of handling things "through filters", through selection, and "keeping sorting rules"
e.g. insert a new item so that it is inserted according to the current sort-descriptions.
Here's an excerpt from these methods (open NSArrayController.h for the full set) and remember that the 'content' is your NSMutableArray, while 'arrangedObjects' is an array provided by the NSArrayController that applies filtering and sorting to the content, "on its way" to the display in the NSTableView.
- (BOOL)addSelectedObjects:(NSArray *)objects;
- (BOOL)removeSelectedObjects:(NSArray *)objects;
- (IBAction)add:(nullable id)sender; // overridden to add a new object to the content objects and to the arranged objects
- (IBAction)remove:(nullable id)sender; // overridden to remove the selected objects
- (IBAction)insert:(nullable id)sender;
- (void)addObject:(id)object; // overridden to add to the content objects and to the arranged objects if all filters currently applied are matched
- (void)addObjects:(NSArray *)objects;
- (void)insertObject:(id)object atArrangedObjectIndex:(NSUInteger)index; // inserts into the content objects and the arranged objects (as specified by index in the arranged objects) - will raise an exception if the object does not match all filters currently applied
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes;
- (void)removeObjectAtArrangedObjectIndex:(NSUInteger)index; // removes from the content objects and the arranged objects (as specified by index in the arranged objects)
- (void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet *)indexes;
- (void)removeObject:(id)object; // removes from the content objects and the arranged objects (if currently contained)
- (void)removeObjects:(NSArray *)objects;
All this, of course, is for direct programmatic control over the content, and not "automagically" via Cocoa-Bindings.