斯坦福大学iOS公开课笔记(7)--绘制视图和手势

这节课主要讲了iOS中的输入(Input)和输出(Output)。

输入是指用户和应用之间进行交互的部分,这节课中主要是使用手势UIGestureRecognizer来对应用内的控件进行控制。

输出是指应用展示给用户的东西,这节课中主要使用UIView进行输出。

然后在后边通过一个绘制扑克牌的Demo来进一步展示了如何使用手势和绘制视图进行输入和输出。

视图(UIView)

视图(UIView)是iOS中至关重要的部分,所有的东西都是通过视图来绘制,按钮UIButton是一个视图,标签UILabel是一个视图。视图在屏幕上是一块矩形区域,你可以通过坐标来对他的大小范围进行设置。

视图是层层嵌套的,每个视图都有一个父视图,和一个或多个子视图。要注意视图之间的层级关系,因为这会影响到显示的问题。

UIWindow

UIWindow在iOS中并不是很重要,因为iOS中只有一个UIWindow,而在Mac上就会有多个UIWindow。

self.view

每一个控制器UIViewController都会有一个自己的UIView,我们在控制器中可以通过self.view来获取他。获取到之后我们就可以对他添加一些子视图来处理我们的应用。

我们可以通过方法将UIView添加到某个UIView中,也可以将某一个UIView移除。

1
2
- (void)addSubview:(UIView *)aView; //添加一个子视图
- (void)removeFromSuperview; //将自身从父视图中移除

位置和大小

每一个UIView都有控制他位置和大小的坐标,设置合理的坐标才能让视图合理的显示在屏幕上。了解坐标我们就要先了解下边几个概念。

  • CGFloat

    浮点型,UIView坐标中所有的参数都是使用CGFloat浮点型进行设置的。

  • CGPoint

    一个C语言结构体,用来表示点坐标,含有两个参数x和y,使用x和y来确定一个坐标的横竖位置。

    1
    2
    CGPoint p = CGPointMake(10.0, 20.0);
    p.x += 20; //向右移动20个点。
  • CGSize

    一个C语言结构体,用来描述UIView的大小,含有两个参数width和height。

    1
    2
    CGSize s = CGSizeMake(100.0, 200.0);
    s.height += 100; //变高100个点。
  • CGRect

    一个C语言结构体,包含前面的CGPointCGSize

    1
    CGRect r = CGRectMake(10.0, 20.0, 100.0, 200.0);
  • 原点

    iOS中的原点在整个视图的左上角,横轴为X轴,越向右值越大。竖轴为Y轴,越向下值越大。这一点与笛卡尔坐标系不同。

    logo
  • bounds

    这是你的坐标系中绘制区域的原点,以及高度和宽度。

  • frame

    也是用来设置你的坐标系中绘制区域的位置和高度和宽度。只不过他是基于你的父视图的位置的。

  • center

    表示你所在的父视图的坐标中的中心位置,注意!没有属性可以得到你自身的中心,你只能使用自己的bounds的宽和高除以二获取自身的中心位置。

  • bounds和frame的区别

    bounds和frame虽然都是表示视图的位置和大小,但是他们之前还是有一些区别的,如图,当视图发生旋转之后,bounds还是((0,0) (200,250)),而frame则变为了((140,65) (320,320))。

    logo

创建一个视图

创建视图可以使用storyboard直接拖拽或者使用代码来alloc init生成一个视图

1
2
3
4
CGRect labelRect = CGRectMake(20,20,50,30);
UILabel *label = [[UILabel alloc] initWithFrame:labelRect];
label.text = @"Hello";
[self.view addSubView:label];

当我们要绘制一个自定义的视图的使用,只要重写实现UIView中的drawRect方法就可以了。系统会自动调用它。

注意:不要调用drawRect方法,只是重写并实现他如果想要重绘视图,调用setNeedsDisplay就可以了。

另外可以使用UIBezierPath来绘制你需要的图形,关于UIBezierPath的具体使用可以看这里

关于自定义视图的具体使用,下面的Demo中会写的清清楚楚,所以这边就不多说了。

UIGestureRecognizer

手势是iOS中的输入部分。添加手势只需要两个步骤

1.这一步一般由控制器完成,为自己添加一个手势识别器。
2.这一步一般由视图自己完成,处理手势完成后发生的事情。

UIPanGestureRecognizer 拖动手势

1
2
3
4
5
6
- (void)setPannableView:(UIView*)pannableView
{
_pannableView = pananbleView;
UIPanGestureRecognizer *pangr = [UIPanGestureRecognizer alloc] initWithTarget:pannableView action: @selector(pan:)];
[pannableView addGestureRecognnizer:panr];
}

UIPinchGestureReccognizer 捏合手势

1
2
@property CGFloat scale; 捏合手势距离
@property (readonly) CGFloat velocity; 每分钟变化的速度

UIRotationGestureRecgnizer 旋转手势

1
2
@property CGFloat rotation; 弧度
@property (readonly) CGFloat velocity; 每秒变化的速度

UISwipeGestureRecgnizer 滑动手势

1
2
@property UISwipeGestureRecognizerDirection direction 滑动方向
@property NSUInteger numberOfTouchesRequired; 几只手指来完成

UITapGestureRecognizer 点击手势

1
2
@property NSUInteger numberOfTapsReqired;几次点击
@property NSUInteger numberOfTouchesRequired; 几只手指来完成

绘制纸牌Demo

这个绘制纸牌的Demo中运用了之前所讲的绘制和手势的知识。我们会使用到文字和图片的绘制以及滑动(swip)手势和捏合(pinch)手势。

logo

创建view

首先我们新建一个UIView,在共有方法里声明他的花色和大小以及是否正面向上的属性,并重写他们的setter方法,在他们的值发生改变的时候调用setNeedDisplay方法来更新样式。

1
2
3
4
5
6
7
@interface PlayingCardView : UIView
@property (nonatomic) NSUInteger rank;
@property (nonatomic ,copy) NSString *suit;
@property (nonatomic) BOOL faceUp;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@implementation PlayingCardView
- (void)setRank:(NSUInteger)rank
{
_rank = rank;
[self setNeedsDisplay];
}
- (void)setSuit:(NSString *)suit
{
_suit = suit;
[self setNeedsDisplay];
}
- (void)setFaceUp:(BOOL)faceUp
{
_faceUp = faceUp;
[self setNeedsDisplay];
}
@end

设置比例

在绘制卡片之前我们先通过几个方法和宏定义来做一些适配性的东西保证卡片在不同的大小下都可以完美的显示出来。

![logo](斯坦福大学iOS公开课笔记(7)-绘制视图和手势/card_big.png)

![logo](斯坦福大学iOS公开课笔记(7)-绘制视图和手势/card_small.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define CORNER_FONT_STANDARD_HEIGHT 180.0
#define CORNER_RADIUS 12.0
- (CGFloat)cornerScaleFactor
{
return self.bounds.size.height/CORNER_FONT_STANDARD_HEIGHT;
}
- (CGFloat)cornerRadius
{
return CORNER_RADIUS * [self cornerScaleFactor];
}
- (CGFloat)cornerOffSet
{
return [self cornerRadius]/3.0;
}

绘制

绘制部分主要是通过重写-(void)drawRect:(CGRect)rect方法来实现

绘制图片部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- (void)drawRect:(CGRect)rect
{
//使用UIBezierPath绘制一个圆角。
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:[self cornerRadius]];
[roundedRect addClip];
[[UIColor whiteColor] setFill];
UIRectFill(self.bounds); //调用C
[[UIColor blackColor] setStroke];
[roundedRect stroke];
if(self.faceUp)
{
//根据扑克牌的名字找到对应的图片
UIImage *faceImage = [UIImage imageNamed:[NSString stringWithFormat:@"%@%@",self.suit,[self rankAsString]]];
if(faceImage)
{
//将图像部分按比例缩小
CGRect imagrRect = CGRectInset(self.bounds,
self.bounds.size.width * (1.0 - self.faceCardScaleFcator),
self.bounds.size.height * (1.0 - self.faceCardScaleFcator));
//绘制图片
[faceImage drawInRect:imagrRect];
}
else
{
[self drawPips];
}
//绘制文字
[self drawCorners];
}
else
{
[[UIImage imageNamed:@"cardBcak"] drawInRect:self.bounds];
}
}

绘制文字部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)drawCorners
{
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter;
UIFont *cornerFont = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
cornerFont = [cornerFont fontWithSize:cornerFont.pointSize * [self cornerScaleFactor]];
NSAttributedString *cornerText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n%@",[self rankAsString],self.suit] attributes:@{NSFontAttributeName : cornerFont ,NSParagraphStyleAttributeName : paragraphStyle}];
//绘制文字
CGRect textBounds;
textBounds.origin = CGPointMake([self cornerOffSet], [self cornerOffSet]);
textBounds.size = [cornerText size];
[cornerText drawInRect:textBounds];
//旋转
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, self.bounds.size.width, self.bounds.size.height);
CGContextRotateCTM(context, M_PI);
[cornerText drawInRect:textBounds];
}

添加滑动手势

滑动手势是使用storyboard中脱线的方式来实现的,和添加一个UIButton的点击事件差不多,将添加的滑动手势拖入到控制器中并实现方法就可以了。

1
2
3
4
5
- (IBAction)swipe:(id)sender {
self.playingCardView.faceUp = !self.playingCardView.faceUp;
}

添加捏合手势

捏合手势是使用代码添加的,在view中添加手势的响应方法并把它放到公有文件中。

1
2
3
4
5
6
7
8
9
- (void)pinch:(UIPinchGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateChanged ||
gesture.state == UIGestureRecognizerStateEnded)
{
self.faceCardScaleFcator *= gesture.scale;
gesture.scale = 1.0;
}
}
1
- (void)pinch:(UIPinchGestureRecognizer *)gesture;

然后在控制器中将它添加到view中

1
[self.playingCardView addGestureRecognizer:[[UIPinchGestureRecognizer alloc] initWithTarget:self.playingCardView action:@selector(pinch:)]];

以上就是斯坦福大学iOS公开课第七课的笔记。