views:

1426

answers:

3

Without using Interface builder or xib files, what is the correct way to instantiate two classes which inherit from UIView such that they can switch between themselves using UIButtons located on the views themselves?

I think this involves setting up a UIViewController from the app delegate and adding two instances of my classes which implement UIView into the controller (perhaps from inside the controller?)

I'm also not sure how to raise events from UIButtons on the custom UIViews to switch the views. I suspect I would need to add a method to the view controller but I'm not sure how to get a reference to the view controller from inside the scope of my UIView.

Also, I'm wondering that,if the use of a UIViewController is necessary, should the switch method could be in the scope of the main app delegate?

Some code examples would be great!

A: 

Use Interface Builder. It's there for a reason.

NSResponder
To be fair, there are many reasons to avoid Interface Builder. I myself never use it if I can help it, and know other developers who feel the same way. There is something to be said for having all your app behavior in code both for development and debugging.
Victorb
+4  A: 

If you want to get it done in code, here is an example I just drummed up using lazy loaded UI elements. I'm only making one button here and swapping it between whichever view is active. It's slightly awkward, but it reduces the amount of code necessary to demonstrate this.

I've created two UIViews to represent your custom classes, one with a blue background and one with a red. The button swaps between the two. If you have a unique button already in each of your custom views, you just need to either expose those buttons as properties of your UIView subclasses so your view controller can access them, or add the view controller as a target for the button's action from within your UIView's loading code.

I've tested this code in my simulator and it seems to work fine, but you really should try to understand what's going on here so you can implement it yourself.

ToggleViewController.h:

#import <UIKit/UIKit.h>
@interface ToggleViewController : UIViewController {
    UIView *firstView;
    UIView *secondView;
    UIButton *button;
}
- (void)addButton;
- (void)toggleViews;
@property (nonatomic, readonly) UIView* firstView;
@property (nonatomic, readonly) UIView* secondView;
@property (nonatomic, readonly) UIButton* button;
@end

ToggleViewController.m:

#import "ToggleViewController.h"
@implementation ToggleViewController

// assign view to view controller
- (void)loadView {
    self.view = self.firstView;
}

// make sure button is added when view is shown
- (void)viewWillAppear:(BOOL)animated {
    [self addButton];

}

// add the button to the center of the view
- (void)addButton {
    [self.view addSubview:self.button];
    button.frame = CGRectMake(0,0,150,44);
    button.center = self.view.center;
}

// to toggle views, remove button from old view, swap views, then add button again
- (void)toggleViews {
    [self.button removeFromSuperview];
    self.view = (self.view == self.firstView) ? self.secondView : self.firstView;
    [self addButton];
}

// generate first view on access
- (UIView *)firstView {
    if (firstView == nil) {
        firstView = [[UIView alloc] initWithFrame:CGRectZero];
        firstView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        firstView.backgroundColor = [UIColor redColor];
    }
    return firstView;
}

// generate second view on access
- (UIView *)secondView {
    if (secondView == nil) {
        secondView = [[UIView alloc] initWithFrame:CGRectZero];
        secondView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        secondView.backgroundColor = [UIColor blueColor];
    }
    return secondView;
}

// generate button on access
- (UIButton *)button {
    if (button == nil) {

        // create button
        button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];

        // set title
        [button setTitle:@"Toggle Views" 
                forState:UIControlStateNormal];

        // set self as a target for the "touch up inside" event of the button
        [button addTarget:self 
                   action:@selector(toggleViews) 
         forControlEvents:UIControlEventTouchUpInside];
    }
    return button;
}

// clean up
- (void)dealloc {
    [button release];
    [secondView release];
    [firstView release];
    [super dealloc];
}

@end
Victorb
@Victorb thanks this is great stuff. By the way I had to use initWithFrame:[[UIScreen mainScreen] applicationFrame] my firstView method instead of CGRectZero to get it to work in the simulator any ideas why?
PeanutPower
In my test I was adding the VC to a tab bar controller so the frame was already established. My guess is that whatever container you used didn't have the right frame set. The CGRectZero frame should be changed by the superview when it lays out its subviews since the autoresizingMask is set. But if you got the frame to work your way, then go with it.
Victorb
+4  A: 

Your main problem is that you don't conceptually understand the role of UIViewControllers versus UIViews. Most people don't when they first start out.

Views are stupid and ideally, they should be composed of generic objects. They contain virtually none of the logic of the interface. They do not know or care about the existence of other views. The only logic you put in views is logic that pertains to the immediate and generic functioning of the view itself, regardless of the data it displays or the state of other parts of the app. You seldom need to subclass UIView. This is why views can be completely configured in Interface builder without any code.

ViewControllers contain the logic of the interface and connect the interface to the data (but they do not contain or logically manipulate the data.) They are "intelligent" and highly customized. The viewControllers do understand the place of the view in the context of the app. The viewControllers load and configure the views either from nib or programmatically. The viewControllers control when the views are displayed or hidden and it what order. The viewControllers determine what action is taken in response to events and what data gets displayed where.

VictorB's example code shows how this is all done pragmatically. The important thing to note is that the viewController and view are entirely separate objects from two entirely separate classes. There is no overlap and no need to subclass UIView. All the customization is in the controller.

All this is because of the MVC design patter. It decouples the interface from the data model, making them both modular and independent of each other. This makes it easy to design, debug, and reuse each independent module.

TechZen
@TechZen does that mean I shouldn't subclass UIView and implement custom core graphics drawrect code in there? Imagine for example the scenario of a chess game board and another view for say a menu screen. Does the board drawing code belong in a UIViewController?
PeanutPower
Actually drawing the view is part of the view and should be implemented there. Deciding what to draw, how much to draw etc should be function of the view controller passing along data from the data model. In the case of chess game, you might have a custom draw method in the view to handle the actual board itself but the pieces and their placement would be the responsibility of the view controller and the data model. Views should be as generic and as minimal as possible. For example, if you had a view that drew a board for chess game, you should be able to use it in a checkers game.
TechZen