Strategy for two mutually exclusive UIViews - objective-c

I have been doing spring cleaning in my app. I noticed something strange, that, when I tried to correct it, completely crashes my app.
There are two "paths" in my app; either you are in the "A" part of it or you are in the "B" part. From the "A" part you can go to the "B" part and the other way around.
I designed it so that the app delegate class has two methods; switchToAView and switchToBView
So being in the middle of the BView and calling the switchToAView method on the app delegate should completely release everything related to the BView and send the user to the AView.
I found out when switching to the AView the BView view was still retained.
Each view is held by a property.
interface;
#property(nonatomic, retain) UIView *viewA
#property(nonatomic, retain) UIView *viewB
implementation;
#synthesize viewA, viewB
in the appDelegate.
I put a breakpoint in the dealloc method of the two views. (viewA and viewB).
Now this happens;
When loading up viewA and from there switching to viewB, nothing is released(as expected). Then when I switch from viewB to viewA, viewA is first released (by the generated setter, as far as I can figure out).
and then viewA is initialized and displayed.
This works just fine but has the downside that both viewA and viewB is alive on the stack at the same time :/ this is not desirable as they are mutually exclusive. The viewA is only released when a new viewA comes along, however, viewA should be released when a viewB is added to the window
I then tried releasing the viewA in the switchToBView method.
This works fine when going from viewA to viewB. I checked with instruments and the retain count drops to 0 for the viewA. Now when switching back to the viewA, the app crashes, and I think it is because the:
self.viewA = newlyInstantiatedViewA;
setter, first sends a release message to the viewA, but I released viewA earlier, and this causes the crash.
I can't get my head around that using a setter this way, will cause a crash. ("message sent to deallocated instance" and breaking in the dealloc method of the last subview added to A when [super dealloc] is called)
Should I have chosen a different approach all together?
And how will I build something sturdy, that ensures only one view is kept alive at a time?
Sorry for the lengthy writing, I would have written less if I had more time:
Thanks you in advance for any solution or design suggestions:)

Should I have chosen a different
approach all together?
Quite definitely, you mixed view controller logic into your views and you have views mutually retaining each other in what appears to be a circular reference.
(1) The views should not have logic for loading other view. Loading and unloading views is the function of the view controller. A view should only be concerned with logic immediately relating to the display. It should not even store or manipulate any user data.
(2) View controllers should seldom call other view controllers. The view controller should be concerned with managing the view and reading and writing data from the view to the data modal.
(3) If you need to relate views, use a UINavigation controller. It's not just for strictly visually hierarchal view patterns. Plus you can hide the nav bar so the user never sees it. You need to have a view controller that pushes viewA and when you need viewB, call the nav to pop viewA and push viewB. To the user, it will look like the views are simply swapping our. See the utility flip view for a similar use.
Your app works temporarily because there is nothing in the language or API that prevents you from cramming almost the entire application logic into a view subclass. However, keeping such a design running and reliable is nearly impossible.

Related

Completely unload a ViewController at modalTransition?

normally after a modal transition, the second viewController runs further "behind" the visible ViewController in the background.
Is there a possibility to completely unload the second ViewController ??
If don't want to use push, because I want an animation on the transition...
Several points:
You are worrying about something that is not a worry. View controllers are lightweight, simple objects. The only heavyweight object is the view, and it is in fact removed from the interface when another view controller's view is presented in its place.
The game does not "run in the background". If it does, you're doing it wrong. You should be detecting in viewDidDisappear: that your view is no longer in the interface and stopping all the activity. That's what these sorts of events are for.
If you're still bent on making certain the view controller itself is destroyed when the game is not showing, then use a different architecture. For example, present the game controller. When the game controller is dismissed, it will be released and destroyed.

ViewDidLoad and UINavigationController in iOS?

I have an app which is based on a UINavigationController. There is a menu screen with buttons that segue (push onto navigation controller stack) to one of 9 other "sub-screens". None of these sub-screens segue to any other screen. When a user is done inputing data on a "sub-screen" they can press a done button which will pop back to the original menu screen. (If you're having difficulty picturing this, imagine a tree like storyboard where there is one root ViewController and then 9 leaf viewControllers).
Ok, so with that setup I have a few questions about how viewDidLoad works.
~ First, is viewDidLoad supposed to be called every time we transition to a sub-screen. For example, suppose I go from the menu screen to sub-screen "B", back to the menu screen and then back to sub-screen "B". Should B's viewDidLoad method be called twice? If not, why might mine be getting called twice?
~ Second, assuming that it will get called each time, what do I do if I have a lot of long operations that need to be performed exactly one time for each sub-screen? Where should I put them (if I put them in viewDidLoad it would happen multiple times if the user kept going back and forth between this page and the menu)?
To answer your questions:
Yes, in general B's viewDidLoad method should be called each time that it is pushed onto the UINavigationController's stack. This is because each time that B is popped off of the stack it is typically released, and each time that you go to B a new instance of B is created.
There could be numerous ways to handle this type of situation. It is hard to tell what is right for you without seeing exactly what you are trying to do. One way would be to create a singleton object that handles the processing. The reason this might be better than handling it within your UIViewController is that a singleton can live throughout the lifetime of the application, whereas UIViewControllers typically have a relatively short lifespan. Singleton objects can be created just once and they can manage whatever operations and data that you need to persist through the lifetime of your application.
1.
viewDidLoad is called when the view is loaded, and viewWillAppear is called when the view becomes visible.
If your viewDidLoad is called several times that means that you are loading the view every time you are showing it and releasing it every time you are popping it. If you post some code I could help you identify the problem better.
What you could do is something like this:
In your "root" viewController class, declare each "leaf" ViewController as a member, lets say they are called leafController1, leafController2 etc and create retain-properties for them.
#interface YourRootViewController : UIViewController {
LeafController1Class *leafController1;
LeafController2Class *leafController2;
// ...
}
#property (nonatomic, retain) LeafController1Class *leafController1;
#property (nonatomic, retain) LeafController2Class *leafController2;
// ...
#end
In the ViewDidLoad of your top ViewController, init all the leaf-controllers using "initWithNibName" etc (or whatever you are doing to create them). Retain their instances like so:
self.leafController1 = [[[LeafController1Class alloc] initWithNibName:#"LeafController1NibName" bundle:nil] autorelease];
When the user presses a button, push the correct leaf to the navigationcontroller:
[myNavigationController pushViewController:leafController1 animated:YES];
When you pop the leaf controllers now, they will be kept in memory since you retained them.
This way your viewDidLoad will only be called once for each leaf, just as long as you always push the same instace of the viewcontroller to your navigationcontroller.
2.
Heavy code related to the view should be executed when the view has been loaded, i.e. triggered by viewDidLoad. But also it might be a good idea to keep other classes that hold info about your application which are not viewcontrollers and separate from the UI. Heavy computations is better made in the background, or when the app is loading for the first time.
viewWillAppear gets called every time the view appears. viewDidLoad ONLY gets called when the view is constructed - so for example after a view controller initFromNibNamed call when the view is accessed. viewWillAppear is called anytime your view controller was not in view but comes into view - so when your view controller is pushed, viewWillAppear is called. So you might think your viewDidLoad is being called twice, but in reality it's probably not. So you should put the methods in viewDidLoad. What are you doing that takes a long time?

Adding NSNotification observers to view controllers on a storyboard

I am putting a test app together to demonstrate how users can be notified when something happens in the app (using NSNotifications). The user should be notified by an unobtrusive banner at the top of the screen regardless of what view controller is displayed at the time. I have the code already to draw the banner, but i am having problems setting up the NSNotification observers.
I have a storyboard with two viewcontrollers. How do I reference their init methods so i can add observers in for the NSNotification posts?
View controllers in a storyboard will be init'ed using initWithCoder:. You would usually have a separate set up method that is called from this, and from initWithNibName:bundle: just to cover you for all use cases.
Or, start observing in viewDidLoad and stop in viewDidUnload.

UITextView: Must I always resignFirstResponder?

Must I always resignFirstResponder for a UITextView? Or, will this happen automatically when its view controller disappears?
I'm asking because I'm having an issue similar to iPhone Objective-C: Keyboard won't hide with resignFirstResponder, sometimes, where the keyboard stays up even when the nav controller pushes and pops other view controllers. The keyboard works, and when I hit done, it unfocuses the UITextView (i.e., the cursor disappears), but the keyboard stays up.
I never found out why this is happening, but maybe it's due to not doing resignFirstResponder before pushing another view controller, but I thought it was optional?
At a total guess, the UITextView has a reference to the view controller (as its delegate) but does not retain it. When you go to the next screen, the controller is dealloced and then the UITextView (which has perhaps been retained by something else) tries to call back to the dealloced controller and crashes. When you call resignFirstResponder, you reverse the order this happens, and therefore no crash.
The way round this to add a textView.delegate = nil call in your view controller's dealloc method - obviously put it before you release the text view.
The contract between a UITextView and it's delegate says that the delegate will send -resignFirstResponder when the text view is done editing. This informs the framework that the view is done editing, fires the events relating to that (willEndEditing and didEndEditing), and allows other parts of the responder hierarchy to react accordingly. Failing to do so might work, but it's not following the contract (that's all a protocol is) it agreed to.
I don't think you have to because the Xcode Sample UICatalog UITextField doesn't call resignFirstResponder before the TextViewController is popped.
The reason the keyboard got stuck for me is that I was having the same view controller present two view controllers modally at the same time, one after the other. UIKit didn't like that.
Calling resignFirstResponder makes sure that the text property contains the actual text shown in the control.
Depending on the state this is not always necessary, but if your controls have resigned first responder, you know that you're working with valid data.

Are modal view controllers the preferred way to replace the entire interface on an iPad?

Specifically, I have something like a game, with a menu screen made out of standard components. I want a button to switch to another view controller that the user will interact with for a while, then return to the menu screen. It seems like having the menu controller present the 'game' mode as a modal view controller is the most straightforward solution, but is this the best way to essentially replace the entire view? Is the whole menu (which may later become a deep nav or split controller) kept in memory as long as the modal controller is in front, and is this something I should bother to worry about?
There are really two parts to this question:
Which method of transitioning from one view to the next in an iPad application provides the best experience to the user?
Which method of transitioning from one view to the next is easiest to implement and best handles memory management?
I'm not going to try to address the first part of this question other than to point you to Apple's 'iPad Human Interface Guidelines' which says (among other things):
Reduce Full-Screen Transitions
Closely associate visual transitions with the content that’s changing. Instead of swapping in a whole new screen when some embedded information changes, try to update only the areas of the user interface that need it. As a general rule, prefer transitioning individual views and objects, not the screen. In most cases, flipping the entire screen is not recommended.
When you perform fewer full-screen transitions, your application has greater visual stability, which helps people keep track of where they are in their task. You can use UI elements such as split view and popover to lessen the need for full-screen transitions.
http://developer.apple.com/library/ios/prerelease/#documentation/General/Conceptual/iPadHIG/DesignGuidelines/DesignGuidelines.html
However, in your case I'd have thought a full-screen transition is entirely appropriate (but then I'm not a user experience expert).
In answer to the second part, yes displaying a new view controller modally seems like a good approach to take.
By default both the objects used by the menu view and those used by the modal view will be kept in memory - but the great thing about using UIViewController sub-classes is that they've got some default memory management built-in. If your application receives a memory warning whilst the modal view is being presented in full-screen mode, the menu view controller's views will be removed and it's 'viewDidUnload' method will be called. So in your implementation of this method you should release any objects you don't need and then recreate them as needed in the menu view controller's viewDidLoad method (which will be called again before the menu view is shown).
This is explained in more detail in the UIViewController class reference:
When a low-memory warning occurs, the UIViewController class purges its views if it knows it can reload or recreate them again later. If this happens, it also calls the viewDidUnload method to give your code a chance to relinquish ownership of any objects that are associated with your view hierarchy, including objects loaded with the nib file, objects created in your viewDidLoad method, and objects created lazily at runtime and added to the view hierarchy. Typically, if your view controller contains outlets (properties or raw variables that contain the IBOutlet keyword), you should use the viewDidUnload method to relinquish ownership of those outlets or any other view-related data that you no longer need.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html