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.
Related
I try to restore a simple UIViewController that I pushed from my initial view controller. The first one is preserved, but the second one just disappear when relaunched. I don't use storyboard. I implement the protocol in every view controller and add the restorationIdentifier and restorationClass to each one of them.
The second viewController inherit from a third viewController and is initialized from a xib file. I'm not sure if I need to implement the UIViewControllerRestoration to this third since I don't use it directly.
My code looks like typically like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.restorationIdentifier = #"EditNotificationViewController";
self.restorationClass = [self class];
}
return self;
}
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
}
+(UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
EditNotificationViewController* envc = [[EditNotificationViewController alloc] initWithNibName:#"SearchFormViewController" bundle:nil];
return envc;
}
Should perhaps the navigationController be subclassed so it too can inherit from UIViewControllerRestoration?
UPDATE:
Ok, it seems like pushing from UIViewController to UIViewController works. But pushing from UIViewController to UITableViewController and vice versa, don't work. The app crashes if the tableview implements the UIViewControllerRestoration protocol. If I don't implement the protocol, UIViewControllers pushed from the tableView is not preserved.
How should one treat UITableViewControllers to preserve them without a crash?
UPDATE:
No crashing no more.. It was due to a memory bug specific for my app. But the tableView is still not preserved, neither the pushed viewController.
UPDATE:
The reason my tableview did not restore properly was because I deleted my datasource in the background.. But I still have a problem with modal viewController not showing up when I try to push it on another navigationController than self.navigationController. I set the restoration identifier to this modal navigationController, but the view does not show up.
The modal transition needs to be backed by a UINavigationController which has a restorationIdentifier and either a restoration class or a corresponding implementation in the app delegates method viewControllerWithRestorationIdentifierPath. That solved the last piece of the problem.
I have issues understanding how the view inside the storyboard is targeted.
http://imageshack.us/photo/my-images/27/uye0.jpg/
Here is an image of a storyboard containing 2 view objects (Correct me if I am wrong).
Now, I want to change the background color of one of the views, to my understanding I do this like this:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor redColor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
If this is implemented in to the project that I linked above, this does nothing. The "viewDidLoad" never executes to my understanding.
The only view controller is named viewController, so my question is, how do I make viewControllers for both of the views? And how do I distinguish between the views when creating the view controllers?
And what identifier is used for this?
EDIT 1
I added
NSLog(#"Here") after viewDidLoad, and it does execute, but it never changes the background color of the view
You need to create a new objetive C class to contain the new view controller and then link that class to the other view in the storyboard:
http://i.stack.imgur.com/UHNvs.png
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
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.
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.