How do I prevent NSMutableArray from losing data? - objective-c

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.

Related

How to remove object from NSPageController arrangedObjects?

How to add/remove objects in the page controller dynamically? All examples out there set the arrangedObjects property during the awakeFromNib method once during the application lifetime.
In my application I have NSMutableArray of NSObjects that I display in the main window using NSPageController. For each NSObject I create corresponding ViewController in the delegate's - (NSViewController *) pageController:(NSPageController *) pageController
viewControllerForIdentifier:(NSString *) identifier method.
During program execution objects will get added/removed asynchronously to/from the mutable array.
At every event I set the page controller's arrangedObjects to the mutable array to account for the change - display new objects and stop showing removed ones.
When objects are added it all works fine. But when removing objects the associated views/view controllers remain in memory.
How can I "reset" the page controller so that it forgets everything about the removed objects?
Looks like the page controller keeps references (snapshots) of the view controllers of removed objects. And when adding another objects their views are messed up.
Try using a NSPageController in History Mode as described here
How do you implement NSPageController to navigate through a webView's history
and here
https://developer.apple.com/library/mac/documentation/AppKit/Reference/NSPageController_Class/
In this case you do NOT need to implement
pageController:identifierForObject:
and
pageController:viewControllerForIdentifier
and no need to set
arrangedObjects
But you NEED to call
navigateForwardToObject
each time you want to open a new page, this method makes NSPageController clear forward history and open a new page.
Also make something like adding a new view to your pageController.view with a new page in this method.
Also I made view replacement in
didTransitionToObject

Where to define NSArray and where to define Button action?

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.

What method should I use to delete tableview currently selected object for Cocoa Application

What method should I use to delete the currently selected object in a table view?
The objects are put into an array and a controller is used to get the object in the array to display on the table view. But from what I can see in NSMutableArrays method list I cannot see a method to delete a currently selected object.
Do I need a protocol for this, if so what is it?
NSMutableArray has a method you can use. Do something like:
[dataArray removeObjectAtIndex:indexPath.row];
Once the object is removed, you can call
[tableView reloadData];
to refresh the UI.
The array is just a container of data, it has no concept of a selected item. The table view is just the presentation of the data, it has no concept of the underlying data, though it does understand the selection (to some extent).
It is the responsibility of your controller to bind these together. So, when the delete option is selected, it knows (or gets from the table view) the selection and deletes the item from the array.
The array supports this with the removeObjectAtIndex:, but your controller needs to get the index.

Adding array from one view to another and retaining values

I have four views, with four arrays. As soon as I navigate from one view, I add that particular array to my master array.
Initially this master array has nothing, and is allocated when the app runs using 'init' method. It does not have a view and it is an array from a subclass of NSObject.
Finally the master array should consist of {viewarray1, viewarray2, viewarray3, viewarray4}.
Each array is added to the master array when navigating to the next view.
So is there anything wrong in my implementation ? Is it okay to allocate masterArray in the init method? Each time I add an object to masterArray, I NSLog it and it displays (null)
How can I have the master array retain values for the whole app??
Some Information on the Code:
I initialize my master array in another class, in the -(init) method
masterArray = [[NSMutableArray alloc] init ];
While adding an object to MasterArray from another view, I reference that class, and I create an object for the class, and add it as a property and synthesize it. I then use
[self.thatClassObject.masterArray addObject:self.viewArray1];
There are two ways you could go about initializing it that I can think of offhand. First, you could alloc/init the array in the app delegate. This will ensure it's created before the rest of the views get a chance to add to it.
EDIT: There's only really one way I can think to do this as Josh Caswell pointed out a good fact that class initializations won't work for this situation. You're best off calling alloc/init for the array either in the app delegate or whichever view is made key window first. You'll have to reference it to the other classes from there

How can I load data for an NSTableView without blocking the interface?

I'm initializing a simple interface, with an NSTableView bound to an array controller (which manages an array of dictionaries). I want to load the content for the array in the background (it's a very time-consuming process), updating the table view every 100 or 1000 elements. The idea is that the interface is available and responsive. I can't figure out how to also trigger an update / refresh afterwards. The table remains empty. Can anyone offer pointers?
My current approach is:
// In init for my app controller. This seems to work well, but I've tried other methods here.
[self performSelectorInBackground:#selector(loadTable) withObject:nil];
- (void)loadTable {
tracks = [[NSMutableArray alloc] initWithCapacity:[masters count]];
// ... create each object one-by-one. Add it to tracks.
for (... in ...) {
[tracks addObject:newObject];
}
// Now I don't know what to do next. The table remains empty.
// Things I've tried (though possibly not in all combinations with the
// method above):
// 1. With a suitably-defined reloadData method, which just reloads
// the table view and sets needs display.
[self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
// 2. Reload directly.
[tv reloadData];
[tv setNeedsDisplay];
}
If I just load the data directly, and don't try to do that in the background, everything works fine, but it takes almost 30s.
You have the table columns (I assume you meant) bound to an array controller, so that's where the table view gets its data from. The table view may very well be asking for updated arrays, but it's asking the array controller, which doesn't know anything has changed.
The array controller won't simply turn around and ask you for fresh data; that would imply it exists solely to make it harder for you to bind the table view to your array, and that isn't the case. It's a controller; its job is to own (a copy of) the array and maintain its order and the user's selection of some subset of its objects.
Therefore, you need the array controller to find out when you add items to your array. The best way to make this happen is to bind the array controller's contentArray to a property of your controller, and update that property in a KVO-compliant manner.
That means:
Create the mutable array in your init method. (And, of course, release it in dealloc.)
Implement the array accessor methods, plus addTracksObject: and removeTracksObject: (which are technically set accessor methods, so KVO will ignore them for an array property) for your convenience.
To add a track, send yourself an addTracksObject: message. You should respond to that by sending yourself an insertObject:inTracksAtIndex: message (with [self countOfTracks] for the index, unless you want to do an insort), and you should respond to insertObject:inTracksAtIndex: by sending your tracks array an insertObject:atIndex: message.
As I mentioned, KVO will ignore addFooObject: and removeFooObject: when foo is an NSArray property, considering those only NSSet-property accessors, so you need to implement them on top of insertObject:inFooAtIndex: and removeObjectFromFooAtIndex: because those are array accessors, which means KVO will react to them.
Step 3, as I just described it, will be pretty slow, because it will cause the array controller to re-fetch your property and the table view to re-fetch the array controller's arrangedObjects at least once each for every row you add.
So, you should maintain your batch-adding behavior with this alternate step 3:
Implement insertTracks:atIndexes:, and pass it an array of one batch of (e.g., 100 or 1000) tracks and an index set formed by [NSIndexSet indexSetWithRange:(NSRange){ [self countOfTracks], countOfBatch }]. You'll also need to implement removeTracksAtIndexes:, only because KVO will ignore each insert method if you don't also have its counterpart.
You probably should have the array controller set to attempt to preserve the selection, so as not to frustrate the user too much while you're still bringing in rows.
Also, you may want to create the objects on a background thread, periodically sending yourself another batch to add using a main-thread perform. I'm ordinarily an advocate of doing things on the main thread run loop whenever possible, but this sort of thing could easily make your interface laggy while your periodic load builds up another batch.
You need to call setNeedsDisplay:YES on your table view on the main thread. Don't call it from a background thread. All Cocoa UI calls must be done on the main thread or weird things happen.