这节课分为两部分,一部分是Calculator的Demo,一部分是Views。

Demo的演示包括下面内容:

1、可编程性,添加一些API,作用是返回计算程序!API要保证程序向上兼容。计算程序是在Brain里操作数和运算符的组合。一旦获取到了计算程序,回到calculator类通过类方法来执行这个程序,也就是去计算运算结果,同时还会有一个类方法返回一个可读的计算程序的描述。计算程序,就是操作数和操作符的组合,把这个组合里的操作数和操作符弹栈并做相应的计算。API向上兼容就是说他没有改变Controller,程序一样能正常运行。

2、演示使用到了id类型、property 、数组的可变和不可变复制、内省、还有递归。用到id是,要用内省判断来包含id使用时不至于崩溃。id类型可以赋值给静态类型。

3、里面的描述的api留到作业了,应该就是把计算时的操作数和操作符组合成字符串,这样Controller就可以把这些操作显示到view上。

4、在runProgram中如果传进来的数组里包含有除number和string以外的东西怎么办?会返回0,所以不怕传进来的是垃圾数组。课程还讨论下nil判断包含的问题,代码可以保护很多nil的情况。

以下是CalculatorBrain.h文件的代码:

#import <Foundation/Foundation.h>

@interface CalculatorBrain : NSObject

-(void)pushOperand:(double)operand;
-(double)performOperation:(NSString *)operation;

@property (readonly) id program;

+(double)runProgram:(id)program;
+(NSString *)descriptionOfProgram:(id)program;

@end

CalculatorBrain.m文件的代码如下:

#import "CalculatorBrain.h"

@interface CalculatorBrain()

@property (nonatomic, strong) NSMutableArray *programStack;

@end

@implementation CalculatorBrain

@synthesize programStack = _programStack;

-(NSMutableArray *)programStack{
    if (_programStack == nil) {
        _programStack = [[NSMutableArray alloc] init];
    }
    return _programStack;
}

-(void)pushOperand:(double)operand{
    NSNumber *operandObject = [NSNumber numberWithDouble:operand];
    [self.programStack addObject:operandObject];
}

-(double)performOperation:(NSString *)operation{
    [self.programStack addObject:operation];
    return [CalculatorBrain runProgram:self.program];
}

-(id)program{
    return [self.programStack copy];
}

+(NSString *)descriptionOfProgram:(id)program{
    return @"Implement this in Assignment 2";
}

+(double)popOperandOffStack:(NSMutableArray *)stack{
    double result = 0;
    
    id topOfStack = [stack lastObject];
    if (topOfStack) {
        [stack removeLastObject];
    }
    if ([topOfStack isKindOfClass:[NSNumber class]]) {
        result = [topOfStack doubleValue];
    } else if ([topOfStack isKindOfClass:[NSString class]]){
        NSString *operation = topOfStack;
        if ([operation isEqualToString:@"+"]) {
            result = [self popOperandOffStack:stack] + [self popOperandOffStack:stack];
        }else if([operation isEqualToString:@"*"]){
            result = [self popOperandOffStack:stack] * [self popOperandOffStack:stack];
        }else if([operation isEqualToString:@"-"]){
            double subtrahend = [self popOperandOffStack:stack];
            result = [self popOperandOffStack:stack] - subtrahend;
        }else if([@"/" isEqualToString:operation]){
            double divisor = [self popOperandOffStack:stack];
            if (divisor) {
                result = [self popOperandOffStack:stack] / divisor;
            }
        }
    }
    
    return result;
}

+(double)runProgram:(id)program{
    NSMutableArray *stack;
    if ([program isKindOfClass:[NSArray class]]) {
        stack = [program mutableCopy];
    }
    return [self popOperandOffStack:stack];
}

@end

Views

view的概述

1、view就是屏幕上的一个矩形空间。

2、view是干什么的?它画出矩形空间,并处理其中的事件。

3、view的结构是分层的,一个view只能有一个父view,但可以有多个子view。子view的顺序是有关系的,在数组中的位置越高或者说是数字越大,就显示在后面,位置低的就显示在前面。

4、UIWindow是UIView的子类,在ios里UIWindow非常不重要几乎不会继承它,ios里全都是views在处理。

5、最顶层的view是管理屏幕的controller的view,这个view的层级结构一定要在xcode里图形化操作。

6、可以用代码实现分层,addSubView是往一个view里添加子view,但是把一个view拿出来就不是这么回事了,不能要求同一个view移除刚刚添加的view,要去到那个view,让它移除它自己。通过父view添加子view,但是移除要通过子view自己。

view的类型

view是个坐标系统,那么需要有东西描述坐标。

1、最基础的是CGFloat,一个默认的float或者double类型,图形化编程的时候永远要用CGFloat而不是float或double。

2、CGPoint是C结构体,包含有两个CGFloat数x和y。

3、CGSize含有两个CGFloat数width和height。

4、CGRect就是含有一个CGPoint和CGSize。这就是用来描述view绘图的4个主要类型。

view的坐标系统

1、iso里的坐标系统原点是在左上角,而不是数学里的左下角。

2、坐标单位是点而不是像素,点是种图形上的术语。点和像素唯一不一致的情况是iphone4的高清显示,它的分辨率太高了。所以UIView会有个property叫contentScaleFactor,它会返回一个点含有多少像素。

3、view有3个和位置大小相关的property:其中一个叫做bounds,是个矩形,它是view自己的坐标系统下的绘画区域。不要在view的实现里面用另外两个。其它的是center和frame,它们是在父view坐标系统中你的view的中心点和容纳view的框,所以这两个只用来在父view中定位你的view。明确view的frame就是把它放到父view的坐标系的指定位置。bounds和frame完全不一样,因为view可以旋转也可以缩放。frame和center是关联的,设置一个另一个也变。bounds是用来画自己的。永远不要在view的实现里用frame和center,这两个是用来定位的。

创建views

怎么拖拽一个自定义的view呢?新建view需要继承UIView,但它不会自动出现在对象库的导航栏里。拖出一个通用view,就位于对象库的底部,但希望它是自定义的,就在identity inspector上设置。

如何在代码里实现新建一个UIView呢?只需要使用alloc和init,UIView的初始化也是用得最多的初始化,就是initWithFrame。例子如下:

CGRect labelRect = CGRectMake(20, 20, 50, 30);  
UILabel *label = [[UILabel alloc] initWithFrame:labelRect];   
label.text = @”Hello!”;  
[self.view addSubview:label];

view是个在controller中很重要的property,它是controller的顶级view。任何一个controller都有一个顶级view,它里面容纳了所有的view。

那么什么时候需要创建自己的UIView子类呢?指的是自己的自定义类什么时候需要用到?view是用来控制绘图和触摸事件。通常建议不要继承这些内置类型如UIButton,它们只被优化为拖出来用,而没有优化过继承和重载。怎么去绘图呢?有了view子类只需要重载drawRect方法,它的参数是个矩形,就是你要重绘的区域。永远不要去调用drawRect,因为drawRect不是让你调用的,系统会去调。那我怎么告诉系统需要重绘呢?发送两个消息:

- (void)setNeedsDisplay;  
- (void)setNeedsDisplayInRect:(CGRect)aRect;

可以认为初始化的时候的设置是一个点,然后系统查看所有需要重绘的东西,再把它们按顺序排列因为有些东西可能会重叠,最后非常高效的把需要画的东西绘制出来。这样做有两个好处,一是让系统依据层的情况最优化性能,二是如果property有一些setter,当你设置的时候需要重绘,这种情况也被最优化,所以你所有的setter都会调用self的setNeedsDisplay来重绘。每个setNeedsDisplay都被一起传过去,然后一次性画出来。

有了drawRect如何重载它来绘制我的自定义view?用核心图形framework,它是C接口,framework的基本概念是你创建一个环境,然后创建一些轨迹如直线,再设置它们的字体颜色样式等并描边或填充到轨迹里。绘制图片就是复制二进制码。环境决定了你在哪绘图,所以创建环境的方法决定了在哪绘图,使用drawRect时不用关心这个,因为进到drawRect会调用一个C函数,它会给你系统准备绘图的环境。关于这个环境注意的有一点,每次调用drawRect环境都是不一样的,所以不要把它保存起来,而是每次都去获取新的。使用uicolor对象的时候不用指出它的环境。

获取环境的代码:

CGContextRef context = UIGraphicsGetCurrentContext();

创建轨迹、描边填充及绘制的代码如下:

CGContextBeginPath(context);  
CGContextMoveToPoint(context, 75, 10);  
CGContextAddLineToPoint(context, 160, 150);  
CGContextAddLineToPoint(context, 10, 150);  
[[UIColor greenColor] setFill];   
[[UIColor redColor] setStroke];  
CGContextDrawPath(context,kCGPathFillStroke); //kCGPathFillStroke is a constant

图形状态

UIView可以设置透明的东西,透明度0是完全透明,1.0是完全不透明。UI还可以对整个view设置透明度。透明的开销不小。

@property CGFloat alpha  
@property BOOL opaque

设置隐藏属性,隐藏表示view仍在层级结构中,但是不显示也不响应点击。

顶层的在后面,底层的在前面,view可以重叠。

图形状态要小心的有一点就是子程序。

绘制文字,通常会用UILable来绘制文字,当你要画个文字,只要发个消息给NSString,比如drawAtPoint。因为UIKit,所以NSString也能实现UI上的功能,用了categories。

UIFont *myFont = [UIFont systemFontOfSize:12.0];  
UIFont *theFont = [UIFont fontWithName:@“Helvetica” size:36.0];  
NSArray *availableFonts = [UIFont familyNames];
NSString *text = ...;  
[text drawAtPoint:(CGPoint)p withFont:theFont]; // NSString instance method

UIImageView就相当于图片的UILable。有多个方法可以获取图片,最常用的是拖图片到xcode的resource文件夹,再直接通过图片名调用。

UIImage *image = [UIImage imageNamed:@“foo.jpg”];

还可以通过文件系统获取或者通过网络传过来的二进制数据。

UIImage *image = [[UIImage alloc] initWithContentsOfFile:(NSString *)fullPath];  
UIImage *image = [[UIImage alloc] initWithData:(NSData *)imageData];

还可以通过context之类的东西自己创建图片。

UIGraphicsBeginImageContext(CGSize);  
// draw with CGContext functions  
UIImage *myImage = UIGraphicsGetImageFromCurrentContext();  
UIGraphicsEndImageContext();

一旦获得UIImage,在drawRect里可以用这些方法画出来。

[image drawAtPoint:(CGPoint)p];  
[image drawInRect:(CGRect)r];  
[image drawAsPatternInRect:(CGRect)patRect;

drawAtPoint会按原大小绘图,drawInRect会缩放图片,drawAsPatternInRect会重复绘图以填满指定的矩形区域。

posted on 2012-12-16 12:29  写下一生的程序  阅读(1155)  评论(1编辑  收藏  举报