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
时间: 2024-10-10 00:30:40