I'm interested in the way I am retaining my properties and the aspects of memory management. This is for a simple application that edits the information of a class that is stored in a table. There are 3 ViewControllers.
A list view (list all classes)
a detail view of the selected item
(lists properties of selected class)
an edit view (lists single property
of selected class and allows it to be
edited)
This is how it is structured at present, what do you think?
ListViewController
#property (nonatomic, retain) NSMutableArray *pools;
#property (nonatomic, retain) PoolFacilityEditController *childController;
To add a new class instance to the table you click an add button that runs this method..
//Loads up the editPoolFacility controller to add a new pool
-(void)add {
PoolFacilityEditController *editController = self.childController;
PoolFacility *aPoolFacility = [[PoolFacility alloc] init];
[self.pools addObject:aPoolFacility];
[aPoolFacility release];
editController.thePoolFacility = aPoolFacility;
editController.pools = self.pools;
[self.navigationController pushViewController:editController animated:YES];
}
The next controller is now loaded up and here are its interesting instance variables. Wise or not I have chose to just assign the pool to the new controller rather than retain. I don't want to unnecessarily retain.
detail View
#property (nonatomic, assign) PoolFacility *thePoolFacility; (assigned in the above add method)
#property (nonatomic, assign) NSMutableArray *pools; (also assigned in the add method)
The detail view has a method that does the following..
- (void)viewWillAppear:(BOOL)animated {
//Pass the copy onto the child controller
if (self.childController.thePoolFacility != self.thePoolFacility) {
self.childController.thePoolFacility = self.thePoolFacility;
}
}
The pool is passed onto the detail edit controller so it knows the pool it is editing.
Now a user clicks on an individual bit of pool information (e.g name) and the detail view controller pops up. It allows the editing of individual properties.
It's interesting properties look like this:
#property (nonatomic, retain) PoolFacility *thePoolFacilityCopy;
#property (nonatomic, assign) PoolFacility *thePoolFacility;
And it creates a copy to edit in case the user changes the values and then wants to cancel. If the user presses save it copies the values from the copy into the non-copy.
- (void)viewWillAppear:(BOOL)animated {
PoolFacility *poolCopy = [self.thePoolFacility copy];
self.thePoolFacilityCopy = poolCopy;
[poolCopy release];
}
If save or cancel is pressed the view is popped.
And then we're back to the middle view that displays all the fields.
Now if the user presses save I just poptheviewcontroller and we're back to the list view. OR if the user presses cancel I run this method.
-(void)cancel {
[self.pools removeObject:self.thePoolFacility];
[self.navigationController popViewControllerAnimated:YES];
}
So to summarize
I am assigning a property throughout different view controllers rather than retaining it.
Also my view controllers are only loaded once and are not deallocated when they 'dissapear'
I hope this made some sense! My question is.. Is this a good way of doing it?
Thanks,
Dan
I didn't see a specific question here, so I'll just make some general critiques.
In iPhone OS, Cancel buttons are common on dialogs meant to add a new item, but much less so on edit dialogs. In fact, the only example of a Cancel button on an Edit dialog I can think of is in the Clock app's Alarm panel. So don't worry about copying the PoolFacility and copying the changes back when it's saved; just make the Cancel button only be visible for new objects (or use the Trash icon--canceling a new pool and deleting an existing one are actually the same action the way things are designed right now).
As you have things now, there's no danger of an object being deallocated at the wrong time. However, if you ever change the storage method--for example, making the app lazily load PoolFacility objects from the disk--it will come back to bite you. Write it properly today and you'll save yourself pain tomorrow. The proper way is to make the thePoolFacility a retained property and release it in your dealloc method. (If you keep managing the pools list the way you currently do, you should do the same thing with it.)
Speaking of which, you don't show how existing PoolFacility objects are loaded. Where do they come from? If there's some kind of database access going on, you may find it helpful to have PoolFacility send notifications when an object is created, updated or deleted, and then observe and react to the appropriate notifications as needed. All of the apps I've written that store user data take this approach; I've found it very handy and flexible.
Since there's only one pool list and it's needed by multiple controllers, there's no shame in storing it in your app delegate instead of passing it around. Better yet, write a FacilityList singleton object that manages the list. This could allow you to take a lot of logic out of your controllers. Generally, you should put everything you can into your models, except the stuff that interacts with the screen. That means that when Apple makes the iTablet or releases the Apple TV SDK--or just when you decide to make a Mac version or redo the user interface--you can bring as much of your app as possible over unmodified.
Related
I am following this tutorial: http://www.appcoda.com/ios-programming-sidebar-navigation-menu/
However, I need to be able to access the side menu from the classes view controller as seen in the image . While my first view controller is a login screen that the user needs to login to a a server with. To fix the problem I have made a property inside the reveal view controller #property (strong, atomic, retain) NSString *SWSegueFrontIdentifier;. Then I use the getters and setters to assign which view controller is should to/from. I assign the first value sw_first in view controller and everything is fine. In ViewDidLoad of the classmates view controller, seen in the second image, i assign sw_front. However, even when that is set in the same function, if i try to print the property from the reveal controller, it ends up returning null. However, if I manually switch the SWSegueFrontIdentifier inside the view controller to sw_front it segues just fine, but ends up skipping my view controller, which is my login controller. I just need a way for the side menu to be accessed from my classmates view controller and have not had any luck, because it seems most things on www.cocoacontrols.com are made to use without storyboards and I can't seem to get them to work.
This code returns null:
[self.revealViewController setSWSegueFrontIdentifier:#"sw_front"];
tempMenuButton.target = self.revealViewController;
tempMenuButton.action = #selector(revealToggle:);
NSLog(#"reveal view controller prop2: %#", self.revealViewController.SWSegueFrontIdentifier);
Any ideas on what I can do to get this to work?
EDIT: Only thing that I can think of is some sort of memory retention problem, but I can't seem to figure it out.
after declaring the property
#property (strong, atomic, retain) NSString *SWSegueFrontIdentifier;
ave you used a synthesize to create the getters and setters
#synthesize SWSegueFrontIdentifier = _SWSegueFrontIdentifier;
and then you just do it like this
self.revealViewController.setSWSegueFrontIdentifier =#"sw_front";
This is what I came up with.
I have stumbled upon a problem in my application where my music player view controller needs to retain the delegate (which is a cloud based storage with songs) to keep the song playlist, until the song from a new folder is selected.
So, when the user taps a song in some folder, I assign the delegate to that ViewController so even when it is pushed from the view, it stays in the memory so the music player can play next and previous songs. But when the user selects the song from another folder(ViewController), I set the music player delegate to nil, and assign the delegate to that new ViewController.
Is this solution acceptable?
Code:
MusicPlayerViewController has:
#property (nonatomic, strong) id <MusicPlayerViewDelegate> delegate;
View Controller in which the songs will be loaded from cloud storage folders has this called when tapped on cell(song):
musicPlayerViewController.delegate = nil;
musicPlayerViewController.delegate = self;
There is no fundamental problem with retaining (holding a strong reference to) a delegate. It is unusual, but not unprecedented. NSURLConnection does it. It creates a retain loop that can be very useful if correctly managed. It's just up to you to make sure that the object will release its delegate in a deterministic way so that the retain loop is broken.
BUT... the specific case that you're discussing here sounds like you have an MVC problem and that your view controller is doing something it shouldn't be.
I assign the delegate to that ViewController so even when it is pushed from the view, it stays in the memory so the music player can play next and previous songs.
If you're saying that you cannot play music unless a certain view controller is in memory, then the view controller probably has an incorrect responsibility. The view controller should manage the view. That should be independent of actually playing music. See https://stackoverflow.com/a/5228317/97337 for discussion of how a music-playing system might be broken out in MVC.
In the example you are proposing, the delegate should be actually weak considering the music player stays as the same instances and the view controller gets set / unset to something different.
If the delegate were strong, your Viewcontroller will not get released unless the Musicplayer gets deallocated and in most cases a Viewcontroller should get released when it's view is no longer in use. As Rob mentioned, you seem to have a MVC problem.
I'm building an app the works similar to the iPhone Notes app.
My app consist of two screens, first screen is a UITableView listing all the records. The second screen appears when you either click on one of the records or click an add button. This second view contains a UITextView where the user can add/edit the text for that record.
Both screens have a View Controller. The MyListViewController loads the records into the UITableView. When the user clicks on a record I create an instance of the MyEditViewController and push it using the pushViewController method of the Navigation Controller.
MyListViewController -> MyEditViewController
My question is which controller should handle the CRUD logic, should it be the parent controller (i.e. MyListViewController) or the edit controller (i.e. MyEditViewController)?
One thing to note is that you should be able to delete a record from MyListController by swiping a table cell and selecting delete.
You can should also be able to delete from the MyEditViewController by clicking on a delete icon.
I'm basically trying to duplicate the Notes app but am unsure what is best practice in terms of where the CRUD logic should go.
In the scenario that you describe, the best pattern to use, would be the delegate pattern.
Simply make a delegate for your MyEditViewController and make it's delegate your MyListViewController.
You define a delegate as a protocol. So in your MyEditViewController.h put in this:
#class MyEditViewController;
#protocol MyEditViewControllerDelegate <NSObject>
#required
- (void)myEditViewController:(MyEditViewController *)controller didSaveNote:(BOOL)save;
#end
and add this to your already existing MyEditViewController.h code.
#interface MyEditViewController : UIViewController
....
#property (nonatomic, retain) id <MyEditViewControllerDelegate> delegate;
#end
In your MyEditViewController.m code when you push weather the save or the cancel button you send the following message:
[self.delegate myEditViewController:self didSaveNote:YES]
or
[self.delegate myEditViewController:self didSaveNote:YES]
depending on you pushed save or cancel.
In your MyListViewController.h you adopt your newly created protocol like this:
#interface MyListViewController : UIViewController <MyEditViewControllerDelegate>
and in your MyListViewController.m you remember two things. First to implement the required delegate method:
#implementation MyListViewController
...
- (void)myEditViewController:(MyEditViewController *)controller didSaveNote:(BOOL)save
{
// Do business logic here depending on the value of save
}
and the last thing is setting your MyListViewController to the delegate of your MyEditViewController like this:
MyEditViewController *myEditViewController = [[MyEditViewController alloc] initWithNibNamed:#"MyEditViewController" bundle:nil];
[myEditViewController setDelegate:self];
That way you handle all CRUD logic in your MyListViewController and that way you can update the Table View accordingly.
I have recently developed an application that has very similar requirements. I think you should be very clear about your Model, Views & Controllers.
Model is the non-UI part of your application, the management of Notes in your case. I created a singleton object, say NotesManager, whose shared instance can be accessed from anywhere in my code. Something like [NotesManager sharedInstance]. In my application, the view controller does not read/enumerate the documents directory's contents (because thats not its job), the NotesManager does. The List view controller asks the notes manager for the notes to display. [[NotesManager sharedInstance] notesFromDocsDir];
Views are the UI part of your application. In this case, it would be the table view & the note's edit view.
Controllers are the ones that act as the link between your Views & Model. As you know, there is the ListViewController & the EditViewController.
Now, there are two types of interactions:
The first one originates from the UI & has to update the Model. For example, the user taps delete or save. In my application, i do something like [[NotesManager sharedInstance] deleteNote:Note]. You can do this from both the View controllers.
The second one originates from the Model end & updates your UI. For example, in my application, I have enabled iTunesSharing & hence a user can add/delete a note via iTunes. When such an event occurs, my UI has to update itself to reflect the current state of the documents directory. To accomplish this, the NotesManager dispatches an NSNotification. The Controller(s) registers for these notifications & updates the view.
Now for your original question, the CRUD methods reside in the NotesManager. They can be called by the Controllers or by NotesManager itself, when it detects something has changed.
HTH,
Akshay
Both. And neither.
You should use a model to store/provide the data.
ViewController's should control the views and pass instruction to the model to save changes etc.
I would do the business logic in the model - simply call the methods in the model from the viewcontrollers.
Seeing as your child viewcontroller is doing the editing it should be that which instructs the model in that instance.
The parent viewcontroller should instruct the model when you handle the deletion of the data.
I'd say that a good solution is to implement all these operations in your Model. Say, you have a class called Note that can handle CRUD operations. You will also need something like NoteCollection that will provide valid data for your table view.
MyEditViewController will always deal with a single note that should handle operations like save and delete. These should update its state in your note collection.
I have two view controllers, one is MainViewController, the other is SetupViewController. I want a UILabel on MainViewController to set the text to the contents of a UITextField from the SetupViewController when a button is pressed in the SetupViewController.
In SetupViewController, I have this in the IBAction:
- (IBAction)donePressed:(id)sender {
MainViewController *mvc = [[MainViewController alloc] init];
[mvc.testLabelOnMVC setText:testTextFieldOnSVC.text];
[release mvc];
}
testLabelOnMVC (and testTextFieldOnSCV, with respective terms) is
#property (nonatomic, retain) UILabel *testLabelOnMVC;
and is also synthesized.
Every time I try, it doesn't work. Nothing happens, nothing changes. I have no errors or warnings. Can anyone help me out?
The view of your MainViewController does not exist until you reference the MainViewController's view property (which forces viewDidLoad to execute). You must reference the view (or otherwise force the view to be constructed) before you attempt to modify any UI objects in the MainViewController.
You are allocating a new MainViewController when you press the button, then you are setting the text of the label on this new controller, not on the MainViewController that your app is showing.
To fix this, create either and IBOutlet or iVar that points to the original MainViewController and set the text on that instead.
Easiest way is to create a #property in the main view controller and write the text in there. Then just read it in the second MVC's viewDidLoad.
The only views that MainViewController should worry about are the ones that it owns; it shouldn't be trying to access the view hierarchy managed by SetupViewController. Likewise, SetupViewController should not directly modify views in MainViewController's view graph.
The right way to do what you're asking is for the two controllers to talk to each other, either directly or via the data model. For example, let's say that your MainViewController instantiates SetupViewController. If that's the case, it'd be natural for mvc to set itself as svc's delegate, so that svc sends it a messages like -setupController:didUpdateTestStringTo:. MainViewController's implementation of that method could then save the new test string somewhere and update it's testLabel field.
Another example: MainViewController instantiates SetupViewController. SetupViewController contains a field where the user can enter a new value for the test string. Before exiting, SetupViewController writes the contents of that field into NSUserDefaults or some other common data storage. When control returns to MainViewController, that object reads the shared data and updates itself as necessary, including setting the new value for testLabel.
There are other variations on the same theme, but the common thread here is that neither view controller has to directly access views that it doesn't own.
You can change the text of the label if the view is already loaded. Instead of initializing the viewcontroller, retrieve it from the view stack if you are using navigation controller.
I dont know if your viewController is already loaded or not.
This is for an addition to a legacy iPhone app with the architecture already defined (years ago, by somebody else.)
The main limitation is that the functionality of the main menu system is based on configuration files, so I can't call any specific initialisation code from the main menu.
This means that the view I am developing is stand-alone, and has to somehow manage its states with the information from the system.
Further, on each screen there is a "Settings" button, taking the user to a Settings pane, that is pushed on the navigation stack on top of "my" view. When the user closes the settings pane, my view reappears, as per normal navigation.
OK, so here is my problem:
When the user enters my view from the menu I want it to be reset so all input fields are empty.
If the user goes to the settings screen and returns to my screen, I want all previous input to be preserved, i.e., not reset to empty fields.
If the user then goes back to the main menu, and re-enters my screen, fields should be empty again.
Is there a robust, documented and preferably simple way to know if I should reset the fields in this scenario?
Can you check the navigation stack to see if the settings page is currently on the stack?
- (void)viewWillAppear:(BOOL)aAnimated
{
[super viewWillAppear:aAnimated];
NSArray* stack = [[self navigationController] viewControllers];
UIViewController* last = [stack lastObject];
}
Presumably viewDidDisappear is called when your view is hidden for some reason. You could presumably get the UIWindow and work up the view chain to find where your view is in the chain (if it is at all) and what is hiding it.
Non-trivial, though, and the sort of thing that will likely need to be "maze bright" vs robust.
(Though if a navigation controller is consistently used it becomes much simpler.)
In the view controller in question, how about you set up a delegate.
id delegate;
#property (nonatomic, assign) id delegate;
for the header, then synthesize in the implementation.
Whenever you push to this view controller, set self as the delegate from the pushing view. Then in this view controller, you can perform a check in the viewDidLoad or viewDidAppear: (or wherever you feel it would be necessary) with something like the following:
if ([self.delegate isKindOfClass:[SomeClass class]]) {
// now you can find which class sent to this view;
}
That should do the trick, so I hope it helps you out
EDIT: considering you are switching views without always using a nav controller, the above won't be valid all the time. In that case, you are probably better off using an internal property as well as an outlet to your settings pane. So in this view controller, you'll want something like this in the header:
BOOL shouldReset;
#property (readwrite) BOOL shouldReset;
In your viewDidLoad, you'll want to initialize this as shouldReset = YES. You should also put this in your viewDidDisappear: since it is your default behavior. When you present the settings pane, give the settings an outlet to the current view controller so you can, from within the settings (when you press the back button) set [self.otherViewController setShouldReset:NO]. Then in your viewDidAppear: for the original view controller in question, you can check to see if it should reset its fields or not