第三节课没什么重要内容,直接跳过。
这节课前半节课, 其实是对第二课的深入,第二课讲了一个简易的计算器,这节课让这个计算器“可编程”。所谓的“可编程”就是可以解析变量并计算,比如我传入”3+2”,就可以得到结果5。 老师在课堂上只实现了部分功能,完整的是作为学生的作业。下半节课讲关于view的一些概念。
一 上半节课
1 增加两个类方法作为公共的API, 为了向上兼容,上节课的那两个也还保留。如下:
@interface calculatorBrain : NSObject
- (void)pushOperand:(double)operand;
- (double)performOperation:(NSString *)operation;
@property (nonatomic, readonly)id program;
+ (double)runProgram:(id)program;
+ (NSString*) descriptionOfProgram:(id)program;
@end
2 让内部的数据堆栈更加通用。
原来的堆栈只能存操作数,不能存操作符,这里要让它也能存操作符。如下(为了增加可读性,把它的名字也改了):
@interface calculatorBrain ()
@property (nonatomic, strong)NSMutableArray *programStack; //可以存操作数和操作符。
@end
-(void)pushOperand:(double)operand
{
[self.programStack addObject:[NSNumber numberWithDouble:operand]];
}
-(double)performOperation:(NSString *)operation
{
[self.programStack addObject:operation];//操作符也进栈
return [calculatorBrain runProgram:self.programStack];
}
3 popOperand方法就用不到了:
//-(double)popOperand
//{
// NSNumber *operandObject = [self.operandStack lastObject];
//
// //注意这里,如果operandObject为nil, 下面执行remove会让程序崩溃,所以要加上判断
// //lastObject方法不用判断
// if (operandObject)
// {
// [self.operandStack removeLastObject];
// }
//
// return [operandObject doubleValue];
//}
4 为program实现getter.
Program是只读属性,只要实现getter就行了,我们打算用这个getter返回内部的programStack。 但是有个问题,programStack是内部的一个变量,我们不希望别人破坏它,所以只是返回它的一个copy,如下:
-(id)program
{
return [self.program copy];
}
而且,对一个NSMutableArray做copy,得到的是一个NSArray。 是不可改变的。 如果想要得到可变的,要用mutableCopy。Stackoverflow上有如下的解释:
mutableCopy alwaysreturns a mutable result.
copy alwaysreturns an immutable result。
5 runProgram做了什么
代码如下:
+ (double)runProgram:(id)program
{
NSMutableArray *stack;
if ([program isKindOfClass:[NSArray class]])
{
//注意这个判断条件是不能少的,并不是id类型都有mutableCopy方法。
stack = [program mutableCopy];
}
return [self popOperandOffStack:stack];
}
+ (double)popOperandOffStack:(NSMutableArray *)stack
{
double result = 0;
//注意类型是id, 因为取出来的可能是操作数,也可能是操作符。
id topStack= [stack lastObject];
if(topStack)
{
[stack removeLastObject];
}
if([topStack isKindOfClass:[NSNumber class]])
{
result = [topStack doubleValue];
}
else if([topStackisKindOfClass:[NSString class]])
{
NSString*operation = topStack;
if([operation isEqualToString:@"+"]) //只实现加法
{
result = [self popOperandOffStack:stack] +[self popOperandOffStack:stack];
}
}
return result;
}
源码下载地址:
http://download.csdn.net/detail/pony_maggie/7314481
二 下半节课
首先讲到创建一个view的方法示例,这里注意一下self.view的用法,self指的是当前的controller, self.view就是这个controller的top level view.
另外,要补充一点,这节课里老师不断的强调简单这个词,他的意思是,最好的代码一定是最少的的代码实现同样的功能。果然是apple出来的人,提倡简单之美.
接着,什么时候我们需要自定义一个view(custom view), 如何自定义一个view?
主要是两个情况,一是要重画view, 二是重定义touch事件.
重画的方法马上就说了,重载drawRect方法就可以了,给个实现的示例,注意这里实现调用的都是基于C的API, 回想一下第一节课讲IOS架构时的内容,融会贯通一下。
//绘制一个方块
-(void)drawRect:(CGRect)rect {
// Drawing code.
//获得处理的上下文
CGContextRef context =UIGraphicsGetCurrentContext();
//设置线条样式
CGContextSetLineCap(context, kCGLineCapSquare);
//设置线条粗细宽度
CGContextSetLineWidth(context,1.0);
//设置颜色
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
//开始一个起始路径
CGContextBeginPath(context);
//起始点设置为(0,0):注意这是上下文对应区域中的相对坐标,
CGContextMoveToPoint(context, 0,0);
//设置下一个坐标点
CGContextAddLineToPoint(context,100, 100);
//设置下一个坐标点
CGContextAddLineToPoint(context,0, 150);
//设置下一个坐标点
CGContextAddLineToPoint(context,50, 180);
//连接上面定义的坐标点
CGContextStrokePath(context);
}
记得不要主动调用drawRect方法,它是系统调用的,你只要实现就行了。 当然你可以通知系统你改了drawRect,用下面两个方法:
setNeedsDisplay
setNeedsDisplayInRect
drawRect只会被调用一次, 因为draw是很expensive的(请原谅我装逼,卖弄英文),比如我自己定义了一个view, 这个view里面有一些属性,然后外部通过setter访问了这些属性, 那这个view的drawRect也只会被调用一次,并不是你调用一次setter,drawRect就调用一次.
如何在自定义视图上写文字?
有两种方式, 一种最直观的方法,用UILabel,生成一个UILabel的实例,设置它的text属性,非常简单。
另一种方式是自己”画文字”, 首先定义字体:
UIFont *myFont = [UIFontsystemFontOfSize:12.0];
UIFont *theFont = [UIFont fontWithName:@”Helvetica”size:36.0];
然后是NSString:
NSString *text = “abc”;
[text drawAtPoint:(CGPoint)pwithFont:theFont];
这个看起来有点奇怪,NSString是foundation kit里的对象,怎么能用来”画图”呢,关键在于drawAtPoint这个方法,这个方法通过category机制,加到UIKit里,所以它其实算是NSString在UI框架里的一个扩展.