首页 > 代码库 > [Stanford 2011] hapiness

[Stanford 2011] hapiness

From:view6-view7(00:17)

1.Introduction:

   Implement two gestures:pinch and pan.

   We first implement the pinch. We add delegate to faceView(the view) that allowed faceView to get the data,which is the degree(-1 to 1) of the smile face. Then the Controller can set self as the  delegate, and provide the smile degree(-1 to 1)  for view using the model.

   Another thing is adding the pan updown gesture to control the happiness.

2. Visual Effects:

     Pan:fig(1),fig(2),fig(3)

     Pinch:fig(4)

3. The codes:

创建faceView的类型必须是CocoaTouch下的Obj-C class类型,创建为UIView子类

//faceView.h#import <UIKit/UIKit.h>@class faceView;//只是告诉编译器,faceView类是存在的,对于protocol也可以有比如@protocol FaceViewDataSource;@protocol FaceViewDataSource<NSObject>//把faceView的笑脸程度委托给任何想要设置它的人-(float)smileForfaceView:(faceView*)sender;//在委托的方法里,当我们获取数据和把某个东西委托给另一个东西的时候,我们几乎都会把自己(sender)传过去,因为这样就不需要再回到faceView去问。因此任何时候做一个委托或者数据源,你都会把你自己作为发送者传递过去,这和target action相类似。//因为此句使用了faceView,而faceView在这之前还没定义,所以必须用@class faceView;声明类(即,前向引用)。@end@interface faceView : UIView@property(nonatomic) CGFloat scale;  //scale表示缩放的程度,是为了实现缩放的手势,缩放和笑没有关系,所以手势的处理就放在view里,这样其他的controller也可以使用我的view的缩放功能了。-(void)pinch:(UIPinchGestureRecognizer *)gesture; //手势处理方法是public的,因为要让所有使用faceView的人知道它有缩放功能@property(nonatomic,weak)IBOutlet id<FaceViewDataSource> dataSource;//如果有人想控制笑脸程度,就得把自己设为FaceView的数据源@end//faceView.m#import "faceView.h"@implementation faceView@synthesize dataSource=_dataSource;@synthesize scale=_scale;#define DEFAULT_SCALE 0.90  //笑脸的大小是view短边大小的90%-(CGFloat)scale    //getter{       if (!_scale)      {return DEFAULT_SCALE ;}     else     {return _scale;}}-(void)setScale:(CGFloat)scale  //setter{          if(scale !=_scale)   //if条件表示scale改变了。为了更高效,只有在scale改变的时候才自动重绘           {                 _scale=scale;                     [self setNeedsDisplay];        }}-(void)pinch:(UIPinchGestureRecognizer *)gesture  //手势处理(这之前需要把缩放手势添加到view上,添加需要在controller里进行,处理在view里做,添加在faceView的outlet的setter里),缩放处理方法,在模拟器上用option键测试{          if ((gesture.state == UIGestureRecognizerStateChanged)||(gesture.state == UIGestureRecognizerStateEnded))           {            self.scale *= gesture.scale;             gesture.scale = 1;  //重设scale为1,这样每次比较的就是上次的变化        } }-(void)setup{      self.contentMode=UIViewContentModeRedraw;//在initWithFrame和awakeFromNib里必须有此句,则在controller里自动旋转YES后,则旋转后才能重绘。}//其实有简单的不用写代码的方法,就是在storyboard里选中view,然后在右边Attributes Inspector里Mode里选Redraw就可以了。
-(void)awakeFromNib{ [self setup];}- (id)initWithFrame:(CGRect)frame //model自动从initWithFrame开始,确保你在这里面写的东西也放到了awakeFromNib里 {//用代码创建UIView对象,用initWithFrame做初始化;用xib创建UIView对象用awakeFromNib做其他的初始化工作. awakeFromNib是在UIVIEW中用,viewDIdLoad是在UIVIEWCONTROLLER里面用 self = [super initWithFrame:frame]; if (self) { // self.contentMode = UIViewContentModeRedraw; // 不会起作用,因为view离开storyboard之后,initWithFrame不会被调用,所以用setup方法,在awakeFromNib里调用。这样当view改变的时候就会重绘了,不管是调用init还是在view之外 [self setup]; } return self;} -(void) drawCircleAtPoint:(CGPoint)p withRadius:(CGFloat)radius inContext:(CGContextRef)context{ UIGraphicsPushContext(context); //子程序在修改context之前要pushContext,在最后要pop回来。可以在中间做任何事,比如改变线条颜色,这样做就不会破坏外面的context。 CGContextBeginPath(context); CGContextAddArc(context, p.x, p.y, radius, 0, 2*M_PI, YES); //开始角度0,结束角度2pai, YES表示顺时针 CGContextStrokePath(context); //这里没有设置线条填充颜色或线宽,需要在调用的地方设置,即drawRect UIGraphicsPopContext();}-(void)drawRect:(CGRect)rect{ CGContextRef context =UIGraphicsGetCurrentContext(); //需要contex来调用其他的绘图方法。 // draw face [circle] CGPoint midPoint; midPoint.x=self.bounds.origin.x+self.bounds.size.width/2; // midPoint是整个view的中心点 midPoint.y=self.bounds.origin.y+self.bounds.size.height/2; CGFloat size=self.bounds.size.width/2; //找出view的短边 if (self.bounds.size.height<self.bounds.size.width) size=self.bounds.size.height/2; size *= self.scale; //笑脸的大小开始是整个view中短边的90%大小,后面随着scale大小来变大变小 CGContextSetLineWidth(context, 5.0); //设置线宽 [[UIColor blueColor]setStroke]; //设置线条颜色 [self drawCircleAtPoint:midPoint withRadius:size inContext:context]; //画大圆,表示脸的轮廓 // draw eyes [2 circle] #define EYE_H 0.30 #define EYE_V 0.30 #define EYE_RADIUS 0.10 CGPoint eyePoint; //左眼睛的中心点 eyePoint.x=midPoint.x-size*EYE_H; eyePoint.y=midPoint.y-size*EYE_V; [self drawCircleAtPoint:eyePoint withRadius:size*EYE_RADIUS inContext:context]; eyePoint.x +=size * EYE_H * 2; //右眼
[self drawCircleAtPoint:eyePoint withRadius:size*EYE_RADIUS inContext:context]; // no nose // draw mouth ,用贝塞尔曲线画mouth,就是在两点之间画条线,然后通过一个控制点调整这条线 #define MOUTH_H 0.45 #define MOUTH_V 0.40 #define MOUTH_SMILE 0.25 CGPoint mouthStart; //嘴的左边点 mouthStart.x = midPoint.x - size * MOUTH_H; mouthStart.y = midPoint.y + size * MOUTH_V; CGPoint mouthEnd = mouthStart; //嘴的右边点 mouthEnd.x += size * MOUTH_H * 2; CGPoint mouthCP1=mouthStart; //控制点1 mouthCP1.x += size * MOUTH_H * 2/3; CGPoint mouthCP2=mouthEnd; //控制点2 mouthCP2.x -= size * MOUTH_H * 2/3; //float smile=1.0; //smile表示移动控制点,0表示在中间,1表示控制点下移,因为下面是进行加法操作。所以1.0表示笑脸,-1.0表示哭脸 float smile = [self.dataSource smileForfaceView:self];//使用委托 if(smile<-1)smile = -1; if(smile>1)smile = 1; CGFloat smileOffset=MOUTH_SMILE * size * smile; mouthCP1.y += smileOffset; mouthCP2.y += smileOffset; CGContextBeginPath(context); CGContextMoveToPoint(context, mouthStart.x, mouthStart.y);//移动到左边起始点 CGContextAddCurveToPoint(context, mouthCP1.x, mouthCP1.y, mouthCP2.x, mouthCP2.y, mouthEnd.x, mouthEnd.y); CGContextStrokePath(context); //描边 }@end
//HappinessViewController.h#import <UIKit/UIKit.h>@interface HappinessViewController : UIViewController@property (nonatomic) int happiness; // 0 is sad,100 is very happy.model里的幸福度和view里的表达方式不一样,可以演示controller如何把model翻译给view听@end //HappinessViewController.m#import "HappinessViewController.h" #import "faceView.h"@interface HappinessViewController()<FaceViewDataSource>//私有实现此协议@property (nonatomic,weak) IBOutlet faceView *faceView;//有了faceView(model),在controller创建一个它的outlet,然后就可以在storyboard里拖出来了。 (在storyboard里拖出通用view填充整个storyboard,在identity inspector里把它的类改成faceView,然后stroryboard里的view就是faceView了)(注意拖的时候stroryboard的view上有个黄色图标代表controller)@end@implementation HappinessViewController@synthesize happiness=_happiness;@synthesize faceView=_faceView;-(void) setHappiness:(int)happiness//model改变,重绘view,要实现的方法{      _happiness=happiness;     [self.faceView setNeedsDisplay]; //happiness一旦被设置,faceView就会被重绘。因为我们的view就是反映了幸福度。}-(void)setFaceView:(faceView *)faceView //在setter里添加手势识别!当系统把faceView和Controller连起来的时候,这是最佳的放置手势识别的时机。{         _faceView=faceView;         [self.faceView addGestureRecognizer:[[UIPinchGestureRecognizer alloc] initWithTarget:self.faceView  action:@selector(pinch:)]]; //这里的target就是这个手势的处理者,也就是self.faceView     [self.faceView addGestureRecognizer:[[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handleHappinessGesture:)]];     self.faceView.dataSource =self;//控制器把自己设置为委托}          //添加手势识别到faceView,faceView会通过pinch来处理手势。-(void)handleHappinessGesture:(UIPanGestureRecognizer*)gesture {          if ((gesture.state==UIGestureRecognizerStateChanged)||(gesture.state==UIGestureRecognizerStateEnded))     {                  CGPoint translation=[gesture translationInView:self.faceView];                    self.happiness -= translation.y/2;                  [gesture setTranslation:CGPointZero inView:self.faceView];         }}-(float)smileForfaceView:(faceView *)sender //The controller,part of its job is to interpret the data in the model ,for the views.
{
return (self.happiness-50)/50.0; //笑脸程度-1到1,The model‘s happiness is 0-100,此句为转化}-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation //实现自动旋转,但运行发现没有调用drawRect重绘,需要在faceView.m里设置initWithFrame方法{ return YES;}@end

 

[Stanford 2011] hapiness