Adding a subview into view hierarchy - iphone

I'd like to have a view appear when the user clicks a button. The hierarchy I have looks like this:
MainWindow
-UIView
--ScrollView
---ScrollView.pages = UIViews
----UIView (from above assignment)
----TextView
----InfoButton
pages is an NSMutableArry of pageController objects. These hook to a nib. These nibs are the pages that user flicks through in the scroll view.
The InfoButton click is wired up like this:
- (IBAction) infoButton_click:(id)sender{
topView topViewViewController *topView = [[topViewViewController alloc] initWithNibName:#"TopView" bundle:[NSBundle mainBundle]];
//[self.navigationController pushViewController: topViewView animated:YES];
//[self.view addSubview: topViewView.view];
[super.view addSubview: topViewView.view];
[topViewView release];
}
InfoButton is on one of the pages in the ScrollView. I've commented out different code that has been tried. None of it adds the view. Nothing happens. Is there a way to get TopView as the top view in the hierarchy?

Is your goal to add the view as a subview, or to slide on a new view using the navigation controller? I'm going to assume the latter for the moment.
- (IBAction)infoButton_click:(id)sender
{
TopViewController *topViewController = [[TopViewController alloc] initWithNibName:#"TopView" bundle:nil];
[self.navigationController pushViewController:topViewController animated:YES];
[topViewController release];
}
This is correct if you actually have a navigationController. Make sure you actually do. When "nothing happens" in Cocoa, it usually means something is nil. You should check in the debugger or with NSLog() to see if any of these values are nil. It is possible (even likely), that your parent has a navigationController, but you do not.
Classes should always have a leading capital. Do not create a variable called "view" that is of class "UIViewController". This is a sure path to suffering. Objective-C is a dynamic language with limited compiler checks on types. Naming things correctly is critical to effective programming in ObjC.

Based on your comment to a previous answer, you want to present a modal view. You do this by creating a new view "modalView" and calling [topView presentModalViewController:modalView animated:YES].
In a future version of the iPhone OS, which of course I would be unable to comment upon if it were under NDA, you might be able to present a modal view controller with a flip transition by setting a property on the view controller to be presented, which would probably be called modalTransitionStyle or somesuch.

Related

viewwillappear and viewdidappear not called

this question is very frequent, but I am not able to solve it with any answers available.
I am working on iOS 5.1. My navigation controller is one tab amongst tab bar view controllers. There's a tableview, in which selecting of a row pushes new view controllers.
This problem occurs Only on selecting of the second row and only sometimes. It's not regular.
The Pushed view comes blank - viewWillAppear/viewDidAppear are not being called. On clicking the back button of the navigation bar - the root view's viewWillAppear/viewDidAppear are also not being called, making it blank.
I am pushing the view on select of first row/second row in exactly the same way. But the problem occurs only on the second row.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
switch (indexPath.row) {
case 0:
AViewController *aObj = [[AViewController alloc] init];
aObj.homeObj = self;
[self.navigationController pushViewController:aObj animated:YES];
[aObj release];
break;
case 1:
BViewController *bVCObj = [[BViewController alloc] init];
bVCObj.homeObj = self;
[self.navigationController pushViewController:bVCObj animated:YES];
[bVCObj release];
break;
default:
break;
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I have tried this and this but in vain.
viewDidLoad is being called on pushing the BViewController, However, viewWillAppear and viewDidAppear is not being called. Following is my viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
NSLog(#"nav stack: %#", [self.navigationController viewControllers]);
NSLog(#"nav stack: %#", [[self.navigationController visibleViewController] description]);
//some initialization and call of methods
}
It's not regular. Sometimes I get this scenario, and this continues until I close the app from the background and restart it. But sometimes it works just fine. I am just pushing my view controller to the nab stack.
As I mentioned in the comment, It's a regular navigation controller in tab bar controller.
How are you defining your views for AViewController and BViewController? Generally you'd use initWithNibName, e.g.
AViewController *aObj = [[AViewController alloc] initWithNibName:#"mynibname" bundle:nil]`
As Carl pointed out, you can apparently use just init (though I don't see this documented in the UIViewController Class Reference), but then the system will be very particular about the name of your NIB file. The documentation does say, though, that you can use initWithNibName and pass a nil for the NIB name, in which case it will try to find it for you. Personally, if you're having inconsistent results, though, I'd try using initWithNibName and explicitly pass the name of your NIB, and see if that rectifies the situation.
Or are you building your view programmatically with loadView in your two controllers? Then you need to show us those loadView routines (not to be confused with viewDidLoad).
But according to the documentation, you need to either specify your NIB or use loadView. See the View Management discussion in the UIViewController Class Reference.
Update:
Given your feedback, I have a couple of thoughts:
Needless to say, the problem is apparently not related to the above code. You need to broaden you search and show us more code. Perhaps show us your viewDidLoad of B?
Generally when you don't get these sorts of events, it's because the view controller hierarchy has gotten out of sync with the view hierarchy. The most common way that people do this is if they've done something like "[addSubview someNewController.view]" at some point. If you're using a view controller in any context either than (a) your app delegate's initial configuration; (b) presentViewController (or dismiss); or (c) pushViewController (or pop), then you might want to share what you've done.
As andreamazz pointed out, your comment, "My navigation controller is inside a view controller of the tab bar controller," is a little disturbing if one reads it literally. You can put navigation bar in a view controller's view, but you can't put a navigation controller in a view controller (unless you're doing view controller containment, which is a whole different beast). Equally concerning is where, in another one of your questions, you said, "Embedding a UINavigationController or UITabBarController (my case) in a UIViewController somehow interrupts with the calling of these methods." Thing is, you don't embed nav controllers in other view controllers (unless it is, itself, a container controller such as a tab view controller), but rather its the other way around. But if you literally mean that you have a controller that contains a nav controller, you have to show us how you're doing that (proper view controller containment?) because that's highly unusual.
It's unusual, but I've had projects get corrupted, ending up in weird states. At a minimum, I might suggest "Product" - "Clean" and rebuild. If problem persists, and you've isolated the problem to to B's NIB, then temporarily rename the it and build a quick and dirty one from scratch.

Removing a View from same view. Is it good practice.?

I have added following piece of code in a view.
- (IBAction)accept_clicked:(id)sender {
[self.view removeFromSuperview];
self.view = nil;
}
Once accept is clicked, I have removed the own view like this. It worked fine, anyhow is it fine to do like this or It should be removed from another view(parent)?
Don't do this (with self.view) - it doesn't looks good and you might face difficult to find problems. self.view is the main view associated with an UIViewController. So this view to be visible on the screen, you must have shown it somehow: either pushing it to a UINavigationController or presenting it modally with -presentViewController:animated:completion: (IOS5+) or - presentModalViewController:animated:. Showing a view by instantiating a view controller and adding its view to the current view controller's view is not a good practice also:
//Not good
MyViewController *mvc = [[MyViewController alloc] init];
[self.view addSubView:mvc.view];
In your particular case I suppose you are showing some terms and conditions (or something similar) and have an accept and deny button, your best solution would be to present your view controller from somewhere, implement a delegate method, so the presenting view controller can have the result and then in your -accept_clicked: method to use either [self dismissModalViewControllerAnimated:YES] or [self dismissViewControllerAnimated:completion:] (IOS5+),

InterfaceOrientation issues when popping a ViewController off the stack

I have the beginnings of my app working pretty well but I have one small issue that I can't figure out how to fix.
It's a view based app and I have a NavigationController in my appDelegate file which is pushing various ViewControllers as required. Going in the "forwards direction" everything works well, seems perfect actually, but the problem I have is when VC's are popped off the stack. This is the code I'm using to show the VC and then just using the back button to go back.
UINavigationController *iaxNC = [[UINavigationController alloc] init];
LogInViewController *logInVC = [[LogInViewController alloc] init];
[iaxNC pushViewController:logInVC animated:NO];
[logInVC release];
[_window addSubview:iaxNC.view];
[_window makeKeyAndVisible];
That loads my login view and then the code checks to see if there are any users before it loads the SetUp screen (if there are none) as follows:
setUpVC = [[SetUpViewController alloc] init];
setUpVC.firstUse = self.firstUse;
[self.navigationController pushViewController:setUpVC animated:YES];
[setUpVC release];
The problem happens when I hit the back button from the SetUp view to go back to the LogIn view.
I have a portrait and a landscape view for most of the VC's and these VC's are subclasses of Michael Tyson's TPMultiLayoutViewController. The portrait view is hooked up as *portraitView and also as *view, the landscape view is hooked up as *landscapeView.
The problem is this:
If I push a VC into view and change the orientation of the device before I hit the back button then when I do hit "Back" the previous VC is displayed in its original format (i.e. NOT rotated to the current orientation) it also appears that the lower left corner of the view is in the lower left of the screen. It then "sticks" like that until I rotate the device again and then all is good and functions as expected.
So the thing is I'm working through the contents of the TPMultiView subclass but I don't pretend to understand all of it yet (and therein probably lies my REAL problem) but as a stopgap solution is there a way to force the view of the "pusher" VC to appear in it's previous orientation (albeit briefly - and THEN allow it to rotate once it has been displayed) when the "pushee's" VC is popped?
Does that make sense?
SOLVED IT! The problem was I had included my own viewWillAppear method in LogInVC but I hadn't called [super viewWillAppear] so the effect of the superclass was never really going to work, was it! Thanks for the input. I can recommend the TPMultiView drop in subclass BTW works very nicely.
You should first verify your implementation of shouldAutoRotateToInterfaceOrientation: is correct for all your view controllers.
Then make sure the views of your view controller's have an appropriate autoresizingMask set. If you have created the view controllers and their views in Interface Builder you should set the resizing mask there.
Otherwise in your -loadView or -viewDidLoad methods you should have this line:
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight;

How to optimize performance in view controller navigation with UISegmentedControl and UITabBarController

On a project I'm working on, the design decision was to use a UISegmentControl at the top, with a UITabBarController on the bottom. The UISegmentControl has 3 choices for 3 different views. Currently, my coworker has added all 3 views to an NSArray when that particular tab is selected, and then based on the UISegmentControl, the view selected gets unhidden, and the other two are hidden. It seems to not follow Apple's guidelines of lazy loading and seems expensive since 3 viewDidLoads (where queries are made to a database) are getting all loaded at once. There is some lag because of it when the tab is selected for the first time, loading all 3 viewControllers at once.
Is there a better way to do this? I saw a simple example with just two viewControllers, and a button that would switch between the two views. That makes sense to me since you always know what your previous view was, and you can remove that view from the superview, present your new one, release your old one. But with 3 choices, I do not know how to keep track of my view hierarchy (since I could be on segment 0, showing view 0, and then go to segment 2, showing view 2). I am not sure how to check for the last view that was shown, and even if that's the best method. I'm thinking that if there is a better option to keep track of this, but still using the segment control, might as well do it now before the project gets more complex. Thanks!
I would suggest creating a root view controller whose job it is to manage the segment control and load the proper VC depending on which button in the segmented control is selected. The root VC's view would have a subView where the segmented control's VC views are inserted. Something like:
- (void)segmentAction:(id)sender
{
NSParameterAssert([sender isKindOfClass: [UISegmentedControl class]]);
switch ([sender selectedSegmentIndex]) {
case 0:
MYViewController1 *vc = [[MyViewController1 alloc] init];
self.segmentVC = vc;
self.segmentSubvew = vc.view;
[vc release];
break;
}
}
One thing people tend to get hung up on is that there needs to be only 1 VC per screenfull of content -- while that was originally what was recommended by Apple, they have since changed this recommendation. So, loading your segment specific VCs inside the SegmentManagerVC is perfectly acceptable.
You could further tweak this design for performance. For example, you could initially load the VC for the default selected segment and then lazy load the other two so they are already available when a different segment is selected. If you take this approach, though, be sure to hook up -didReceiveMemoryWarning to release the two VCs that aren't currently being viewed.
You could push/pop views onto the UINavigationControler stack. This would also support a "back" button if you wanted it.
[self.navigationController pushViewController:self.myVC animated:YES];
Link a method up to the SegmentedControl that pushes the appropriate ViewController when the corresponding segment is selected. The VC with your segmented control inside of it would need a reference to each segment's corresponding VC. viewDidLoad() will only be called once, and only when the view is pushed onto the navigation stack for the first time.
When you change views or want to go "back", you can pop the VC off the stack:
[self.navigationController popViewControllerAnimated:YES];
Is this the type of functionality which you were looking for?
Edit for Clarity
UIViewController References:
Each view will need a reference to the other two view's ViewControllers. This can be done like this: (assume that we are in "View1", and we also have "View2" and "View3":
View2Controller v2Controller = [[View2Controller alloc] initWithNibName:#"View2" bundle:nil];
View3Controller v2Controller = [[View3Controller alloc] initWithNibName:#"View3" bundle:nil];
The reference to self.navigationController should be declared in your app's delegate as:
UINavigationController* navigationController;
It can be initialized as:
[navigationController initWithRootViewController: rootViewController];
The RootViewController
rootViewController is the UIViewController that corresponds to your application's root view (whatever loads on startup). It is declared in the delegate as:
RootViewController* rootViewController;
And initialized as:
rootViewController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:nil];

Is parentViewController always a Navigation controller?

I was kind of scratching my head at this a week ago, and now with a little bit more Cocoa experience under my belt I feel like I have an inkling as to what might be going on.
I'm making an application that is driven by a UINavigationController. In the AppDelegate, I create an instance of this class, using "page 1" as the Root View Controller.
UINavigationController *aNavigationController = [[UINavigationController alloc]
initWithRootViewController:page1ViewController];
Now here's where I'm having the problem. From "page 1" I'd like to use a modal view controller that slides over the interface and then disappears once the user has made an edit. I do that using code like this, inside of Page1ViewController:
[self presentModalViewController:myModalViewController animated:YES];
When the Modal View Controller is gone, I want a value on "Page 1" to change based on what the user entered in the Modal View Controller. So, I wrote some code like this, which resides in the Modal View Controller:
[self.parentViewController dismissModalViewControllerAnimated:YES];
[self.parentViewController doSomethingPleaseWithSomeData:someData];
The update to page 1 wasn't happening, and it took me a long time to realize that the "doSomethingPleaseWithSomeData" message was not being sent to Page1ViewController, but the Navigation Controller.
Is this always to be expected when using Navigation Controllers? Did I perhaps configure something improperly? Is there an easy way to get at the View Controller that I want (in this case, Page1ViewController).
I would recommend using the delegation pattern to solve your problem. Create a property
#property (nonatomic, assign) id <MyModalViewDelegate> delegate;
And a corresponding protocol
#protocol MyModalViewDelegate
#optional
- (void)myModalViewControllerDidFinish:(MyModalViewController *)aModalViewController;
#end
When the user finishes with your view (e.g. taps the save button), send this message:
if ([self.delegate respondsToSelector:#selector(myModalViewControllerDidFinish:)])
[self.delegate myModalViewControllerDidFinish:self];
Now, set the delegate to the view controller that should manage the whole thing, and it will be notified when the view controller is finished. Note that you'll need your view controller to dismiss the modal view controller. But, logically, that makes sense, since it was the object that presented the modal view controller in the first place.
This is how Apple solves this problem in, for example, the UIImagePickerController and UIPersonPickerController.
There are a couple of ways you can handle this. The simplest is probably just to add a UIViewController property into myModalViewController and set it to page1Controller before you present it:
myModalViewController.logicalParent = self; //page1Controller
[self presentModalViewController:myModalViewController animated:YES];
Just make sure you add the appropriate instance variable #property, and #synthesize for logicalParent to myModalViewController, then you will have a way to communicate data back to the ViewController that triggered the modal dialog. This is also for passing data back and forth between different levels of navigation before you push and pop them on the stack.
The one important thing to worry about when doing this is that it is easy to get retain loops if you are not careful. Depending on exactly how you structure this you might need to use assign properties.
I just ran into this same problem. It definitely seems that if you put a UIViewController embedded in a NavigationController, then when, from that UIViewController you present another UIViewController modally, the presentee thinks that the presenter is the NavigationController. In other words, parentViewController is incorrect.
I bet this is a bug: either that, or the documentation seems incomplete. I will inquire.
Just ran into the same problem. I believe this is a bug. My scenario is the following:
A navigation hierarchy with A, B and C view controllers in this order. On C there's a button that would open a modal view controller called D. Once D is presented the navigation controller drops C from its hierarchy which is a terrible behavior. Once D gets dismissed, the navigation controller instantiates a new C type view controller and pushes it into its hierarchy to recover the original one. Terrible. My solution is hacking the navigation hierarchy this way (a very bad solution but works well. with a 2 dimension array you could implement stacking modals):
- (void)presentModalViewController:(UIViewController *)c {
[self.navigationHierarchy removeAllObjects];
[self.navigationHierarchy addObjectsFromArray:[navigation viewControllers]];
[navigation setViewControllers:[NSArray array] animated:YES];
[navigation presentModalViewController:c animated:YES];
}
- (void)dismissModalViewController {
[navigation dismissModalViewControllerAnimated:YES];
[navigation setViewControllers:[NSArray arrayWithArray:self.navigationHierarchy] animated:YES];
}
These two methods are defined where I maintain the main navigation hiererchy: the app delegate. navigation and navigationhierarchy are defined this way:
NSMutableArray *navigationHierarchy;
UINavigationController *navigation;