How to assign the mainController and a custom view

2019-09-08 08:12发布

问题:

I am another rookie in iPhone programming and I have a very basic question which I can not answer.

First of all, I am writing code in xcode 3.1.4, so that I can learn the basics on an old platform and hopefully in the near future this will allow me to create portable apps (in the sense that I will be supporting iPhone 3 as well). Unfortunately though, from what I understand, apple has stripped the documentation that came with xcode 3.1.4 and this makes it hard to guess what is the right way of doing things, since the current documentation and examples suggest routes more relevant to iOS 5 (e.g. storyboard). So, even though I appear to understand the whole concept of MVC, and although my first real toy app is actually functioning it is certainly not managing the memory correctly as the relevant dealloc methods are not used (the relevant NSLogs that I have are not shown). For the learning process I am following the course material that is online from CS193P in Winter of 2010.

To the specifics now. I want to create a single-window app (Polygon, Assignment 3). Let's forget about the interface builder; all views/subviews will be created programmatically. For that I create a custom UIViewController. I also create a custom UIView in order to describe a custom subview of my single view that I want to present. (It is very likely that we disagree with my approach here already but for now I seriously believe that what I am trying here is conceptually right). In the app delegate (.h) I declare my mainController and this is all I really declare. The file is the following:

#import <UIKit/UIKit.h>
#import "MainViewController.h"

@interface MySimpleHelloPolyAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MainViewController * mainController;
}

@property (nonatomic, retain) IBOutlet UIWindow * window;
@property (nonatomic, assign) IBOutlet MainViewController * mainController;

@end

On runtime, I want mainController to allocate the necessary memory for the main view presented to the user and also add a subview of my custom UIView (this will be the canvas for drawing polygons). So, essentially I want to treat window as a container, the view of mainController to actually present the main view to the user, and to that view I will also have attached a subview with an instance of our custom canvas (custom view). Below is the AppDelegate.m file:

#import "MySimpleHelloPolyAppDelegate.h"

@implementation MySimpleHelloPolyAppDelegate

@synthesize window, mainController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    [window setBackgroundColor:[UIColor blueColor]]; // I do not want to see 'blue' on runtime

    // On the creation of the MainViewController subclass I indicated that I want a nib for my new controller
    // simply because this is the recommended way by apple. Apparently this should be an overkill for a simple
    // one-window app, but nevertheless it should be ok.
    mainController = [[MainViewController alloc] initWithNibName:nil bundle:nil];
    [window addSubview:mainController.view];

    [window makeKeyAndVisible];
}

- (void)dealloc {
    NSLog(@"(AppDelegate): Dealloc is called");
    [mainController.view removeFromSuperview];
    [mainController release];
    [window release];
    [super dealloc];
}

@end

Let's see the MainViewController.h:

#import <UIKit/UIKit.h>
#import "Polygon.h"
#import "Canvas.h"

@interface MainViewController : UIViewController {
    IBOutlet UILabel * numSidesTextLabel;
    IBOutlet UILabel * numSidesValueLabel;
    IBOutlet UILabel * advancedOptionsLabel;
    IBOutlet UILabel * polygonNameLabel;
    IBOutlet UISwitch * advancedOptionsSwitch; // Not supported yet
    IBOutlet UIButton * increaseButton;
    IBOutlet UIButton * decreaseButton;
    IBOutlet Canvas * myCanvas;
    Polygon * myPolygon;
}

@property (nonatomic, retain) IBOutlet UILabel * numSidesTextLabel;
@property (nonatomic, retain) IBOutlet UILabel * numSidesValueLabel;
@property (nonatomic, retain) IBOutlet UIButton * increaseButton;
@property (nonatomic, retain) IBOutlet UIButton * decreaseButton;
@property (nonatomic, retain) IBOutlet UIView * myCanvas;
@property (nonatomic, retain) IBOutlet Polygon * myPolygon;

- (IBAction) increase;
- (IBAction) decrease;
- (void) updateInterface;

@end

Now let's see the MainViewController.m:

#import "MainViewController.h"

@implementation MainViewController

@synthesize numSidesTextLabel, numSidesValueLabel, decreaseButton, increaseButton, myCanvas, myPolygon;

- (IBAction) decrease {
    [myPolygon setNumberOfSides:([myPolygon numberOfSides]-1)];
    [self updateInterface];
}

- (IBAction) increase {
    [myPolygon setNumberOfSides:([myPolygon numberOfSides]+1)];
    [self updateInterface];
}

- (void) updateInterface {
    int sides = [myPolygon numberOfSides];

    UIColor * myOceanColor = [UIColor colorWithRed:0.00 green:0.333 blue:0.557 alpha:1.00];
    [increaseButton setTitleColor:myOceanColor forState:UIControlStateNormal];
    [increaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
    [decreaseButton setTitleColor:myOceanColor forState:UIControlStateNormal];
    [decreaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];

    numSidesValueLabel.text = [NSString stringWithFormat:@"%d", sides];
    decreaseButton.enabled = YES; increaseButton.enabled = YES;

    if (sides == MAX_NUM_SIDES_CONSTANT) increaseButton.enabled = NO;
    else if (sides == MIN_NUM_SIDES_CONSTANT) decreaseButton.enabled = NO;

    [myCanvas updateState:sides withName:[myPolygon name]];
    [myCanvas setNeedsDisplay];
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
    }
    return self;
}

- (void)loadView {
    self.view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 20.0, 320.0, 460.0)];
    self.view.backgroundColor = [UIColor whiteColor];
}

- (void)viewDidLoad {
    CGRect tempFrame;
    UIColor * oceanColor = [UIColor colorWithRed:0.000 green:0.333 blue:0.557 alpha:1.000];

    // Draw the labels
    tempFrame = CGRectMake(20.0, 20.0, 150.0, 20.0);
    numSidesTextLabel = [[UILabel alloc] initWithFrame:tempFrame];
    numSidesTextLabel.text = @"Number of sides:";
    numSidesTextLabel.textAlignment = UITextAlignmentLeft;
    [self.view addSubview:numSidesTextLabel];
    [numSidesTextLabel release];

    tempFrame = CGRectMake(160.0, 20.0, 40.0, 20.0);
    numSidesValueLabel = [[UILabel alloc] initWithFrame:tempFrame];
    numSidesValueLabel.text = @"-----";
    numSidesValueLabel.textAlignment = UITextAlignmentLeft;
    [self.view addSubview:numSidesValueLabel];
    [numSidesValueLabel release];

    tempFrame = CGRectMake(20.0, 125.0, 160.0, 20.0);
    advancedOptionsLabel = [[UILabel alloc] initWithFrame:tempFrame];
    advancedOptionsLabel.text = @"Advanced options";
    advancedOptionsLabel.textAlignment = UITextAlignmentLeft;
    [self.view addSubview:advancedOptionsLabel];
    [advancedOptionsLabel release];

    // Draw the advanced switch
    tempFrame = CGRectMake(205.0, 120.0, 120.0, 60.0);
    advancedOptionsSwitch = [[UISwitch alloc] initWithFrame:tempFrame];
    [advancedOptionsSwitch setOn:NO animated:YES];
    [self.view addSubview:advancedOptionsSwitch];
    [advancedOptionsSwitch release];

    // Decrease Button
    decreaseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];    // autoreleased
    [decreaseButton setTitle:@"Decrease" forState:UIControlStateNormal];
    [decreaseButton setTitleColor:oceanColor forState:UIControlStateNormal];
    [decreaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
    decreaseButton.frame = CGRectMake(20.0, 60.0, 110.0, 40.0);
    [decreaseButton addTarget:self action:@selector(decrease) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:decreaseButton];

    // Increase Button
    increaseButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];    // autoreleased
    [increaseButton setTitle:@"Increase" forState:UIControlStateNormal];
    [increaseButton setTitleColor:oceanColor forState:UIControlStateNormal];
    [increaseButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateDisabled];
    increaseButton.frame = CGRectMake(190.0, 60.0, 110.0, 40.0);
    [increaseButton addTarget:self action:@selector(increase) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:increaseButton];

    // Initialize the polygon
    myPolygon = [[Polygon alloc] init];
    numSidesValueLabel.text = [NSString stringWithFormat:@"%d", [myPolygon numberOfSides]];

    // Prepare the canvas
    CGRect myCanvasFrame = CGRectMake(20.0, 160.0, 280.0, 280.0);
    myCanvas = [[Canvas alloc] initWithFrame:myCanvasFrame withNumSides:[myPolygon numberOfSides] withPolygonName:[myPolygon name]];
    [self.view addSubview:myCanvas];
    [myCanvas release];

    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    numSidesTextLabel = nil;
    numSidesValueLabel = nil;
    advancedOptionsLabel = nil;
    myPolygon = nil;
    myCanvas = nil;
    [self.view release]; self.view = nil;
}

- (void)dealloc {
    NSLog(@"(MainViewController): Dealloc is called");
    [numSidesTextLabel removeFromSuperview];
    [numSidesValueLabel removeFromSuperview];
    [advancedOptionsLabel removeFromSuperview];
    [myPolygon release];
    [myCanvas removeFromSuperview];     // This should generate additional output
    [super dealloc];
}

@end

Now let's have a look on the Canvas.h file:

#import <UIKit/UIKit.h>
#import "PrepDefs.h"

@interface Canvas : UIView {
    IBOutlet UILabel * polygonNameLabel;
    int currentStateNumSides;
    CGPoint center;
}

@property (nonatomic, retain) UILabel * polygonNameLabel;
@property (nonatomic, assign) int currentStateNumSides;
@property (nonatomic, assign) CGPoint center;

+ (NSArray *) pointsForPolygonInRect:(CGRect) rect numberOfSides:(int) numberOfSides;

- (id) initWithFrame:(CGRect)frame withNumSides:(int)sides withPolygonName:(NSString *) name;
- (void) updateState:(int)sides withName:(NSString *)name;

@end

The Canvas.m file:

#import "Canvas.h"

@implementation Canvas

@synthesize polygonNameLabel, currentStateNumSides, center;

+ (NSArray *) pointsForPolygonInRect:(CGRect)rect numberOfSides:(int)numberOfSides { 
    CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0); 
    float radius = 0.9 * center.x; 
    NSMutableArray *result = [NSMutableArray array]; 
    float angle = (2.0 * M_PI) / numberOfSides; 
    float exteriorAngle = M_PI - angle; 
    float rotationDelta = angle - (0.5 * exteriorAngle); 

    for (int currentAngle = 0; currentAngle < numberOfSides; currentAngle++) { 
            float newAngle = (angle * currentAngle) - rotationDelta; 
            float curX = cos(newAngle) * radius; 
            float curY = sin(newAngle) * radius; 
            [result addObject:[NSValue valueWithCGPoint:CGPointMake(center.x + curX, center.y + curY)]]; 
    } 

    return result; 
} 

- (id) initWithFrame:(CGRect)frame withNumSides:(int)sides withPolygonName:(NSString *)name {
    if (self = [super initWithFrame:frame]) {
            // Initialization code
            [self setBackgroundColor:[UIColor brownColor]];
            currentStateNumSides = sides;
            center = CGPointMake ([self bounds].size.width / 2.0, [self bounds].size.height / 2.0);

            polygonNameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, center.y - 10.0, [self bounds].size.width, 20.0)];
            polygonNameLabel.text = name;
            polygonNameLabel.textAlignment = UITextAlignmentCenter;
            polygonNameLabel.backgroundColor = [UIColor clearColor];
            [self addSubview:polygonNameLabel];
            [polygonNameLabel release];
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame {
    NSLog(@"(Canvas, -initWithFrame): You should always use initWithFrame:withNumSides:withPolygonName:");
    return [self initWithFrame:frame withNumSides:DEFAULT_NUM_SIDES withPolygonName:DEFAULT_POLYGON_NAME];
}

- (void)drawRect:(CGRect)rect {
    // Drawing code
    NSArray * points = [Canvas pointsForPolygonInRect:[self bounds] numberOfSides:[self currentStateNumSides]];

    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    [[UIColor blackColor] set];
    UIRectFrame ([self bounds]);

    CGContextBeginPath (currentContext);
    for (int i = 0; i < [points count]; i++) {
            CGPoint currentPoint;
            NSValue * currentValue;
            currentValue = [points objectAtIndex:i];
            currentPoint = [currentValue CGPointValue];
            //NSLog(@"(%.1f, %.1f)", currentPoint.x, currentPoint.y);
            if (i == 0)
                    CGContextMoveToPoint(currentContext, currentPoint.x, currentPoint.y);
            else
                    CGContextAddLineToPoint(currentContext, currentPoint.x, currentPoint.y);
    }
    CGContextClosePath(currentContext);

    [[UIColor yellowColor] setFill];
    [[UIColor blackColor] setStroke];

    CGContextDrawPath(currentContext, kCGPathFillStroke);
}

- (void) updateState:(int)sides withName:(NSString *)name {
    currentStateNumSides = sides;
    polygonNameLabel.text = name;
}

- (void)dealloc {
    NSLog(@"(Canvas): Calling dealloc");
    [polygonNameLabel removeFromSuperview]; polygonNameLabel = nil;
    [super dealloc];
}

@end

The PrepDefs.h file:

#ifndef __PREP_DEFS_H__
#define __PREP_DEFS_H__

#define MIN_NUM_SIDES_CONSTANT 3
#define DEFAULT_NUM_SIDES 5
#define MAX_NUM_SIDES_CONSTANT 12

#define DEFAULT_POLYGON_NAME @"Pentagon"

#endif

Finally the files for the Polygon class. The Polygon.h file:

#import <Foundation/Foundation.h>
#import "PrepDefs.h"

@interface Polygon : NSObject {
    int numberOfSides;
    int minimumNumberOfSides;
    int maximumNumberOfSides;
}

@property int numberOfSides;
@property int minimumNumberOfSides;
@property int maximumNumberOfSides;
@property (readonly) float angleInDegrees;
@property (readonly) float angleInRadians;
@property (readonly) NSString * name;

- (void) setNumberOfSides:(int)numSides;
- (void) setMinimumNumberOfSides:(int)minNumSides;
- (void) setMaximumNumberOfSides:(int)maxNumSides;

- (id) initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min maximumNumberOfSides:(int)max;

@end

The Polygon.m file:

#import "Polygon.h"

@implementation Polygon

@synthesize numberOfSides, minimumNumberOfSides, maximumNumberOfSides, angleInDegrees, angleInRadians, name;

- (void) setNumberOfSides:(int)numSides {
    if ((numSides >= [self minimumNumberOfSides]) && (numSides <= [self maximumNumberOfSides])) numberOfSides = numSides;
    else NSLog(@"PolygonShape [setNumberOfSides]: Assignment out of bounds");
}

- (void) setMinimumNumberOfSides:(int)minNumSides {
    if (minNumSides >= MIN_NUM_SIDES_CONSTANT) minimumNumberOfSides = minNumSides;
    else NSLog(@"PolygonShape [setMinimumNumberOfSides]: Assignment out of bounds");
}

- (void) setMaximumNumberOfSides:(int)maxNumSides {
    if (maxNumSides <= MAX_NUM_SIDES_CONSTANT) maximumNumberOfSides = maxNumSides;
    else NSLog(@"PolygonShape [setMaximumNumberOfSides]: Assignment out of bounds");
}

- (float) angleInDegrees {
    int sides = [self numberOfSides];
    return ((float) (180.0 * ((double) (sides - 2)) / ((double) sides)));
}

- (float) angleInRadians {
    int sides = [self numberOfSides];
    return ((double) M_PI) * ((double) (sides - 2.0)) / ((double) sides);
}

- (NSString *) name {
    NSString * s;

    switch ([self numberOfSides]) {
            case 3: s = [NSString stringWithString:@"Triangle"]; break;
            case 4: s = [NSString stringWithString:@"Square"]; break;
            case 5: s = [NSString stringWithString:@"Pentagon"]; break;
            case 6: s = [NSString stringWithString:@"Hexagon"]; break;
            case 7: s = [NSString stringWithString:@"Heptagon"]; break;
            case 8: s = [NSString stringWithString:@"Octagon"]; break;
            case 9: s = [NSString stringWithString:@"Enneagon"]; break;
            case 10: s = [NSString stringWithString:@"Decagon"]; break;
            case 11: s = [NSString stringWithString:@"Hendecagon"]; break;
            case 12: s = [NSString stringWithString:@"Dodecagon"]; break;
            default: NSLog(@"PolygonShape [name]: I should never enter here."); s = nil; break;
    }

    return s;
}

- (id) init {
    return [self initWithNumberOfSides:DEFAULT_NUM_SIDES minimumNumberOfSides:MIN_NUM_SIDES_CONSTANT maximumNumberOfSides:MAX_NUM_SIDES_CONSTANT];
}

- (id) initWithNumberOfSides:(int)sides minimumNumberOfSides:(int)min maximumNumberOfSides:(int)max {
    if (self = [super init]) {
            // Cautiously initialize everything to zero
            numberOfSides = 0;
            minimumNumberOfSides = 0;
            maximumNumberOfSides = 0;

            // Attempt the actual assignment
            [self setMaximumNumberOfSides:max];
            [self setMinimumNumberOfSides:min];
            [self setNumberOfSides:sides];
    }

    return self;
}

- (NSString *) description {
    return [NSString stringWithFormat:@"Hello I am a %d-sided polygon (aka a %@) with angles of %.3f degrees (%.6f radians)", [self numberOfSides], [self name], [self angleInDegrees], [self angleInRadians]];
}

- (void) dealloc {
    NSLog(@"(Polygon): Dealloc is called");
    [super dealloc];
}

@end

After all the above, I believe the questions are:

  1. Why dealloc functions are not used? Where do I actually have leaks?
  2. When I initialize my mainController, should I pass as parameter the name of the nib file that was created when I created the custom class for the mainController?
  3. Should I initialize the mainController and its view differently?
  4. Should I use awakeFromNib instead of loadView or viewDidLoad?
  5. Other comments that you have regarding the entire approach? Such as good or bad programming practices for one-screen applications? It really is a small program, so, I believe this is not an open-ended question. If however you think that this is an open-ended question, feel free not to answer. This is probably a naive question by a rookie to some veterans.

I really appreciate all the time that you spend on this and I am looking forward to your feedback.