iOS: UIViewController doesn't display another UIViewController using storyboards - ios

I am tryin to display multiple UIViewController objects inside a single view. For the time being I want to display a single UIViewController object when the app loads. But the app screen appears blank, while it should be displaying a label inside the child view controller.
Here is what I did:
ParentViewController.h
#import <UIKit/UIKit.h>
#interface ParentViewController : UIViewController
{
UIViewController *child1Controller;
UIViewController *child2Controller;
}
#end
ParentViewController.m
#import "ParentViewController.h"
#import "Child1Controller.h"
#import "Child2Controller.h"
#interface ParentViewController ()
#end
#implementation ParentViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { ... }
- (void)viewDidLoad
{
child2Controller = [[Child2Controller alloc] init];
[self.view addSubview:child2Controller.view];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewDidUnload { ... }
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { ... }
#end
Then in the storyboard in interface builder
add 3 view controllers
assigned a class to each one of them ParentViewController, Child1Controller & Child2Controller
in Child2Controller object, added a UILabel inside View.
in Child2Controller.h defined the IBOutlet for UILabel and added a synthesize statement for the same in Child2Controller.m
finally in project-Info.plist set the main storyboard file
Did I miss something over here?

Starting from iOS 5 it's possible to take advantage of View Controller Containment. This is a new methodology that allows you to create a custom controller container like UINavigationController or UITabBarController.
In your case, this could be very useful. In fact, in your storyboard you could create the parent controller and the two child controllers. The parent could be linked to another scene while the two children are not linked. They are independent scenes that you can use within your parent controller.
For example in viewDidLoad method of your parent controller you could do the following:
- (void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard *storyboard = [self storyboard];
FirstChildController *firstChildScene = [storyboard instantiateViewControllerWithIdentifier:#"FirstChildScene"];
[self addChildViewController:firstChildScene];
[firstChildScene didMoveToParentViewController:self];
}
Then in your FirstChildController override didMoveToParentViewController
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// Add the view to the parent view and position it if you want
[[parent view] addSubview:[self view]];
CGRect newFrame = CGRectMake(0, 0, 350, 400);
[[self view] setFrame:newFrame];
}
And voilĂ ! You have a controller that contains one view that is managed by a child controller.
For further info see how-does-view-controller-containment-work-in-ios-5.
Hope it helps.

Related

iOS Delegate setup w/ storyboarding

Recently read this:
Passing Data between View Controllers
Which outlines delegate setup between two controllers. The problem I am running into is that I do not have a segue between the controllers. And I am not sure I wan ta segue between those two controllers, ie I do not want to change the view when a value updates, behind the scenes I just want another controller to be aware that the value did indeed change.
This is the step I am stumbling on from the link above:
The last thing we need to do is tell ViewControllerB that
ViewControllerA is its delegate before we push ViewControllerB on to
nav stack.
ViewControllerB *viewControllerB = [[ViewControllerB alloc]
initWithNib:#"ViewControllerB" bundle:nil]; viewControllerB.delegate =
self [[self navigationController] pushViewController:viewControllerB
animated:YES];
I am not using nibs, nor do I think a prepare for segue is the correct place to wire the delegate up. Is there some other place I am supposed to wire up the delegate?
here is my code:
SettingsVeiwController, want to let another controller know when the user updates the refresh rate field.
#import <UIKit/UIKit.h>
#protocol SettingsViewControllerDelegate <NSObject>
-(void) didUpdateRefreshRate:(NSString *)refreshRate;
#end
#interface SettingsViewController : UITableViewController <UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *refreshRateTextField;
#property (copy, nonatomic) NSNumber *refreshRate;
#property (weak, nonatomic) id <SettingsViewControllerDelegate> delegate;
#end
MainViewController, want to get updates when refreshRate changes,
#interface MainViewController ()
#end
#implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) didUpdateRefreshRate:(NSString *)refreshRate {
NSLog(#"This was returned from SettingsViewController %#",refreshRate);
}
#end
I think everything is setup correctly to work except telling SettingsViewController that the MainViewController is its delegate.
Probably most important is that this is a tabbed application. MainViewController is one tab, SettingsViewController is another. Might be a good way to set this up when using a tabbed application, ie how do I pass info/data between tabs. I assume it via delegates still just need to know where to wire them together.
A good place would be MainViewController viewDidLoad (assuming that MainViewController has nothing to do with the presentation of SettingsViewController). You just need to get the instance of SettingsViewController and set its delegate to self.
That said, I'm going to assume that SettingsViewController is presented after MainViewController, so no instance of SettingsViewController exists when MainViewControllers view is loaded. In that case, whichever controller has the responsibility of creating and presenting an instance of SettingsViewController needs to do something to tell MainViewController that it has done so. A good solution to this would be notifications as you want to keep cross coupling low and not teach this class about the requirements of MainViewController.
Define your own notification name:
#define SettingsViewControllerWasCreatedNotification #"SettingsViewControllerWasCreatedNotification"
Setup MainViewController as an observer:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(settingViewControllerShown:)
name:SettingsViewControllerWasCreatedNotification
object:nil];
}
After SettingsViewController is created, post the notification
[[NSNotificationCenter defaultCenter] postNotificationName:SettingsViewControllerWasCreatedNotification
object:settingsViewController];
The new settings view controller is the object. Now, your MainViewController will receive the callback and can add itself as the delegate:
- (void)settingViewControllerShown:(NSNotification *)note
{
SettingsViewController *settingsViewController = (SettingsViewController *)[note object];
settingsViewController.delegate = self;
}

Overriding loadView causes viewDidLoad and loadView to fire everytime a VC appears

My view heirarchy sits on several custom "root" UIViewController subclasses. I'm trying to set a custom self.view for one of my root VC subclasses. There, I am doing:
MyRootViewController_With_Scroll.h
// Import lowest level root VC
#import "MyRootViewController.h"
// my custom scroll view I want to use as self.view
#class MyScrollView;
#interface MyRootViewController_With_Scroll : MyRootViewController {
}
#property (strong) MyScrollView *view;
#end
MyRootViewController_With_Scroll.m
#import MyRootViewController_With_Scroll.h;
#interface MyRootViewController_With_Scroll ()
#end
#implementation MyRootViewController_With_Scroll
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView
{
NSLog(#"loading view");
CGRect windowSize = [UIScreen mainScreen].applicationFrame;
MyScrollView *rootScrollView = [MyScrollView scrollerWithSize:windowSize.size];
self.view = rootScrollView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// Getter and setter for self.view
- (MyScrollView *)view
{
return (MyScrollView *)[super view];
}
- (void)setView:(MyScrollView *)view
{
[super setView:view];
}
According to the iOS6 docs, viewDidLoad in only suppose to fire once per view controller for the entire lifecycle of the app.
What am I doing wrong here? What is causing my view controllers to repeatedly call loadView/viewDidLoad? Strangely, my apps "home screen" VC loads the view just once, but all its subsequent views in the navigation heirachy fires loadView everytime they appear. What is going on?
edit I've changed the property to strong. Same issue happens.
edit 2 I've stopped overriding loadView and its still happening. Now I'm really confused.
This is expected behaviour. If you're popping view controllers off a navigation stack, and nothing else has a reference to them, then they're going to get deallocated. Therefore when it appears again, it will be a new instance, so it has to perform loadView and so on all over again. Include self in your logging, you should see that it is a different object each time.
You've also redefined the view controller's view property as weak - if you are re-using the view controller objects, then this will be nilled out as soon as the view has no superview.
Prior to iOS 6, a view controller that was mid-way in your navigation stack would get its view unloaded under memory pressure:
root --> VC1 --> VC2
VC2 is on screen, a memory warning is received. VC1 would unload its view. When you pop VC2 off the stack, VC1 would call loadView again. This no longer happens.
However, if you've popped back to VC1, and nothing has a strong reference to VC2, then VC2 is deallocated. When you push another VC2 onto the stack, this is a new instance and loadView will be called again. Presumably you are creating these new instances of VC2 in code, so you should be able to tell that you are creating a new instance.
Thats because you have weak view property. So it get realloced all the time. Also, I don't think that you need to override view property at all.

UIViewController containment

I'm trying to use the UIViewController containment feature in one of my projects. The app is meant to be used in landscape mode only.
I add UIViewController A as a child of UIViewController B and add A's main view as a subview of one of B's views. I'm also saving a reference to A in B:
#interface BViewController : UIViewController
#property (retain, nonatomic) AViewController *aVC;
#end
#implementation BViewController : UIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.aVC = [self.storyBoard instantiateViewControllerWithIdentifier:#"A"];
[self addChildViewController:self.aVC];
[self.myContainerView addSubview:self.aVC.view];
}
#end
The problem I have is that landscape orientation is not being respected. I did some debugging and found a solution, but I fear is not ideal as it's more of a hack:
In B:
- (void)viewDidLoad
{
[super viewDidLoad];
self.aVC = [self.storyBoard instantiateViewControllerWithIdentifier:#"A"];
[self addChildViewController:self.aVC];
[self.myContainerView addSubview:self.aVC.view];
[self.aVC didMoveToParentViewController:self];
}
In A:
- (void)didMoveToParentViewController:(UIViewController *)parentVC
{
// Interchange width and height
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.**height**, self.view.frame.size.**width**);
}
Am I missing something here?
Your code:
self.aVC = [self.storyBoard instantiateViewControllerWithIdentifier:#"A"];
[self addChildViewController:self.aVC];
[self.myContainerView addSubview:self.aVC.view];
was always wrong. You MUST send didMoveToParentViewController: to the child controller after adding it to the parent. See my discussion here:
http://www.apeth.com/iOSBook/ch19.html#_container_view_controllers
As for the rotation, it is probable that you're just doing all this too early. The app starts out in portrait and hasn't rotated yet to landscape when viewDidLoad is called. I give solutions to this problem here:
http://www.apeth.com/iOSBook/ch19.html#_rotation
Note the suggestion there that you wait until didRotateFromInterfaceOrientation: to finish setting up your view's appearance. I think you might be facing here the same issues I'm describing there.

viewWillAppear is not fired

I have setup a basic test app that displays a view containing a label, with no use of IB. I want to use a custom UIView subclass AND custom UIViewController subclass.
This will run as anticipated, but the MyViewController's viewWillAppear and other similar delegates do not fire.
What am I missing to make these fire? In previous projects (using IB), these would fire just fine.
Here is the complete code:
AppDelegate - loads a 'MainVC' view controller and sets it as the root controller
#import "AppDelegate.h"
#import "MainVC.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize mainVC = _mainVC;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.mainVC = [[MainVC alloc] init];
self.window.rootViewController = self.mainVC;
[self.window makeKeyAndVisible];
return YES;
}
MainVC - creates a 'MyViewController' which allocates the 'MyView' (it also passes down the frame size that should be used for the view)
#import "MainVC.h"
#import "MyViewController.h"
#implementation MainVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
MyViewController *controller = [[MyViewController alloc] init];
CGRect frame;
frame.origin.x = 5;
frame.origin.y = 5;
frame.size.width = self.view.frame.size.width - (2 * 5);
frame.size.height = self.view.frame.size.height - (2 * 5);
controller.startingFrame = frame;
[self.view addSubview:controller.view];
}
return self;
}
MyViewController - creates the MyView
#import "MyViewController.h"
#import "MyView.h"
#implementation MyViewController
#synthesize startingFrame;
- (void)loadView{
self.view = [[MyView alloc] initWithFrame:startingFrame];
}
- (void)viewWillAppear:(BOOL)animated{
NSLog(#"appearing"); //doesn't do anything
}
- (void)viewDidAppear:(BOOL)animated{
NSLog(#"appeared"); //doesn't do anything
}
MyView
#import "MyView.h"
#implementation MyView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor whiteColor];
label = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 150, 40)];
[label setText:#"Label"];
[self addSubview:label];
}
return self;
}
Your mistake: You're setting a root view controller and then adding another's view controller view on top of that. While the second view is added to the view hierarchy, its view controller remains "unwired" this way. In fact if you check on your MainViewController's parentViewController, you will notice it's nil.
Why: The viewWillAppear method will be sent only to the root view controller or to view controllers in the hierarchy of the root view controller (those that were presented using presentModalViewController:animated: or presentViewController:animated:completion:).
Solutions: to solve it you have a few options:
Use your view controller as the root view controller
Present your view controller through one of the methods mentioned above
Keep your code as it is and manually wire those events to child view controllers (beware of this method though, as I believe the events you mention are automatically forwarded under iOS 5 - you can easily check this out).
If I recall properly another way to make these event get forwarded to your view controller is to add your view controller's view to the window, rather than to the parent view.
There's a number of very basic things that went wrong:
you're doing your whole setup in initWithNibNamed: for your MainViewController, yet you're creating it calling just init. So your setup will never happen
you're implementing a second VC (MyViewController), apparently just to create myView, which you then add to your rootVCs hierarchy. Not good! Only a single VC (in your case MainViewController) should be responsible to create and manage the views in its hierarchy
don't do VC controller setup in loadView, like you did in MyViewController. In your case it is the only way to make things work, because MyVC never actually gets fully up and running, but the approach is wrong - you're basically forcing the View Controller to set up the view, although the controller itself is never in control of anything
There's a few more things, but those are the most important ones - it appears like it would be a good idea for you to read about the whole basic concept of the Model - View - Controller concept again. Next, you should be digging through the class references for both UIViewController and UIView.
Even if you would get the results you desire at last using your current approach, it wouldn't help you in the long run, because you wouldn't learn to use the involved elements properly.
Methods are not invoked on a view controller inside another view controller. If developing for iOS 5 only then check out UIViewController Containment which can be used to solve this. If you want your application to be compatible with previous iOS versions you can forward method invocations to your child view controller. Personally I prefer subclassing SFContainerViewController which handles this automatically: https://github.com/krzysztofzablocki/SFContainerViewController

Load UITableViewController programmatically and add view as subview

I want to load a UITableViewController inside a UIView because I want to change the view on button click (like UITabBar but with my own buttons). I'm using a storyboard and have defined a TableViewController with custom class "InitialTableViewController" and identifier "InitialView".
My code look like this:
#import "MyViewController.h"
#import "InitialTableViewController.h"
#interface MyViewController ()
#end
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
InitialTableViewController *tableControl = [self.storyboard instantiateViewControllerWithIdentifier:#"InitialView"];
[[self view] addSubview:[tableControl view]];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
#end
The view starts and I can see my table but the code inside "InitialTableViewController" doesn't work.
What can I do?
Well, it would be easier to just have a normal UIViewController with a UIView as root of the Nib and then put a UITableView. My answer is based on your need to have buttons on that UIView.

Resources