这节课分为两部分,一部分是Calculator的Demo,一部分是Views。
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
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是个坐标系统,那么需要有东西描述坐标。
1、最基础的是CGFloat,一个默认的float或者double类型,图形化编程的时候永远要用CGFloat而不是float或double。
2、CGPoint是C结构体,包含有两个CGFloat数x和y。
3、CGSize含有两个CGFloat数width和height。
4、CGRect就是含有一个CGPoint和CGSize。这就是用来描述view绘图的4个主要类型。
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,这两个是用来定位的。
怎么拖拽一个自定义的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会重复绘图以填满指定的矩形区域。