2

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;
}
Ben Packard
  • 26,102
  • 25
  • 102
  • 183

3 Answers3

3

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:

  1. Use your view controller as the root view controller
  2. Present your view controller through one of the methods mentioned above
  3. 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).
  4. 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.
diegoreymendez
  • 1,997
  • 18
  • 20
  • 1
    Thank you - so my view is added, but the controller is essentially floating around unconnected? My goal is to use MainVC to load a paginated scrollview, and then add multiple instances of MyView -basically the same UI navigation weather app. So this rules out options 1 and 2 I think? 3 and 4 I need to play with for an hour. Maybe I am over doing this, is there some simpler way to set up a paginated scroll view (with repeatable content)? – Ben Packard May 29 '12 at 15:39
  • 1
    As per your question: yes, it's sort of floating around disconnected so to speak. As to what you can do: I would suggest you attempt to have a single view controller that configures and setups several views, and acts as the scroll view's delegate. It would take care of querying your model to know what to display, and setup the different views to show the proper data. Each view that needs to communicate an action back to the view controller can use blocks, a delegate, or callbacks, to do so. – diegoreymendez May 29 '12 at 16:04
  • To expand on this, my preferred method to organize iOS apps is to have my custom views offer a `setupWithSomeDataObject:` method that receives just what it needs to set up its appearance. I also use [blocks](http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html) which are (IMHO) a very elegant way to execute actions whenever my custom view needs to let the view controller know some important event was triggered. – diegoreymendez May 29 '12 at 16:09
  • One thing that confuses me - with other apps I have built, my common practice has been a 1:1 view:controller ratio. If I push a new type of view, it has it's own controller. Is the distinction just because I am not transitioning to a new view, I am adding new views. If so I guess what I am doing is equivalent to making a LabelController for every label on a view, a FieldController for each UITextField, etc. Which would clearly be madness. – Ben Packard May 29 '12 at 16:20
  • In iOS a view controller sort of relates to the controller for a "screen" worth of views. There's nothing wrong with having more view controllers, but I suggest that you try to avoid the 1:1 ration when possible - it will make your life much easier. – diegoreymendez May 29 '12 at 17:04
  • A note of interest: UIViewController in iOS is closer to the concept of NSWindowController in OS X (rather than NSViewController). – diegoreymendez May 29 '12 at 17:10
2

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.

Toastor
  • 8,980
  • 4
  • 50
  • 82
  • My goal is to separate out the view logic from the model logic. I could easily use a single view controller, and add all the view setup to loadView, but my feeling was that this is not very MVC - at least the view setup that is not dependent on the data object would be better in the UIView subclass right? And then I would expose just the parts that rely on the data object to the controller. Maybe I am way off. And then the second tricky part for me is reusability. That's why I am trying to use 2 views/controllers. I will re-read the apple MVC docs. – Ben Packard May 29 '12 at 15:56
  • 1
    The model part often doesn't contain any logic at all - the model class serves more or less as a data container most of the time. Yet it is still a class, a fact which, especially compared to a viewcontroller stuffed with logic, may be confusing. Concerning reusability, as a rule of thumb: try encapsulating logic up to the point, where you could take an UIView subclass (for example) out of a project and use it in another one without making a single change. Don't spread logic across classes if it isn't absolutely necessary. And most important: don't worry, it can be tricky but you'll master it! – Toastor May 29 '12 at 16:17
0

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

Niklas Berglund
  • 3,563
  • 4
  • 32
  • 32