斯坦福大学iOS公开课学习笔记(9)-AutoLayout

这一节课前边一小部分用来完成上一次的Demo,后边的部分主要用来讲AutoLayout,然后对之前的Atttibutor项目的代码进行了AutoLayout的处理。

多图预警!!!这节课中关于AutoLayout的部分截图会有点多。。

完成 Dropit

上一节课中的Dropit并没有完全完成,因为方块可以自由旋转,所以导致方块搭起来不稳,这个问题也会导致最后的爆炸方法没办法出发。所以这节课会对这部分功能进行一些优化。

除此之外,还会对方块增加一个吸附行为,以此来实现可以拖拽方块放置到我想要放置的位置上。

添加禁止旋转的属性

首先我们先对旋转进行一个控制,在之前的DropitBehavior中继续添加行为,添加一个UIDynamicItemBehavior并且同样的使用懒加载实现它。

1
@property (nonatomic, strong) UIDynamicItemBehavior *animationOptions;
1
2
3
4
5
6
7
8
9
10
- (UIDynamicItemBehavior *)animationOptions
{
if(!_animationOptions)
{
_animationOptions = [[UIDynamicItemBehavior alloc] init];
_animationOptions.allowsRotation = NO; //设置禁止旋转
}
return _animationOptions;
}

这样就可以让方块不会到处旋转,而且还可以顺利的出发爆炸的方法。

logo

添加吸附手势

添加吸附手势可以让我在方块掉落的过程中“抓住”它,并且可以控制他掉落到我想要落下的位置。

首先我们需要添加一个吸附行为UIAttachmentBehavior。然后我们需要知道哪一个方块是我正要进行操作的方块,所以我们需要在drop方法中获取正在掉落的方块。

1
2
@property (nonatomic, strong) UIAttachmentBehavior *attachment;
@property (nonatomic, strong) UIView *droppingView;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)drop
{
CGRect frame;
frame.origin = CGPointZero;
frame.size = DROP_SIZE;
int x = (arc4random()%(int)self.gameView.bounds.size.width) / DROP_SIZE.width;
frame.origin.x = x * DROP_SIZE.width;
UIView *dropView = [[UIView alloc] initWithFrame:frame];
dropView.backgroundColor = [self randColor];
[self.gameView addSubview:dropView];
self.droppingView = dropView;//获取正在掉落的view
[self.dropitBehavior addItem:dropView];
}

这样当我们进行操作的时候就会获取正在掉落的方块,如果同时有多个方块的时候我们会获取到最后一个方块。

接下来我需要拖动他,所以我需要实现一个拖动手势。

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
- (IBAction)pan:(UIPanGestureRecognizer *)sender
{
//获取手指的位置
CGPoint gesturePoint = [sender locationInView:self.gameView];
if(sender.state == UIGestureRecognizerStateBegan)
{
[self attachDroppingViewToPoint:gesturePoint];
}
else if(sender.state == UIGestureRecognizerStateChanged)
{
self.attachment.anchorPoint = gesturePoint;
}
else if(sender.state == UIGestureRecognizerStateEnded)
{
[self.animator removeBehavior:self.attachment];
}
}
- (void)attachDroppingViewToPoint:(CGPoint)anchorPoint
{
if(self.droppingView)
{
self.attachment = [[UIAttachmentBehavior alloc] initWithItem:self.droppingView attachedToAnchor:anchorPoint];
self.droppingView = nil;
[self.animator addBehavior:self.attachment];
}
}

这样我们就实现了吸附的功能,并且可以使用拖拽手势来改变下落的位子。

logo

接下来我们需要在我们的手指点击的位置和方块的位置之间添加一条线,让我们控制方块的动作更加明显。那么我们怎么来绘制这条线呢,我们需要添加一条UIBezierPath来绘制这个部分。所以我们新建一个view并且实现画线的功能。

1
2
3
4
5
@interface BezierView : UIView
@property (nonatomic ,strong) UIBezierPath *path;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import "BezierView.h"
@implementation BezierView
- (void)setPath:(UIBezierPath *)path
{
_path = path;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[self.path stroke];
}
@end

然后让我们的gameView使用这个新建的view。

1
@property (weak, nonatomic) IBOutlet BezierView *gameView;

接下来我们在每次手指改变在屏幕上的位置的时候重绘这条连接线。

1
2
3
4
5
6
7
8
9
10
11
UIView *droppingView = self.droppingView;
__weak ViewController *weakSelf = self; //防止循环引用
self.attachment.action = ^{
//创建连接线
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:weakSelf.attachment.anchorPoint];
[path addLineToPoint:droppingView.center];
weakSelf.gameView.path = path;
};

最后为了让线在我们松手的时候消失,我们在拖动的状态改为UIGestureRecognizerStateEnded的时候删除这条线。

1
self.gameView.path = nil;

这样我们的这部分需求就完成了。

logo

AutoLayout

AutoLayout可以帮助你来确定view在屏幕上显示的位置,是使用规则来确定view的位置而不是使用固定的数字例如(20,20,100,100)。

比如说:现在我的屏幕上有两个UILabelThing 1和Thing 2,当我的屏幕处于垂直状态的时候我可以很好的显示他们两个的位置。

logo

但是当我把屏幕横过来的时候我发现Thing 2不见了。

logo

其实Thing 2还在那里,只是因为屏幕的高度不够,他超出了范围,所以没办法显示了。

那么接下来我们就需要对他添加AutoLayout来控制Thing 1和Thing 2的位置了。我希望Thing 1可以永远处于屏幕的左上角,Thing 2可以永远处于屏幕的右上角,无论是在横屏还是竖屏或者是在不同尺寸的设备上显示。

在Storyboard中一共有三种不同的方法来设置AutoLayout约束

  • 1.使用蓝色辅助线,并且根据系统建议来设置约束。
  • 2.使用底部按钮
  • 3.使用control拖拽

使用蓝色辅助线

当我们在storyboard中添加控件的时候,会自动产生一些蓝色的辅助线,这些辅助线可以帮我们更好的选择控件应该摆放的位置,根据蓝色辅助线的位置确定好之后,再通过底部的 Reset to suggested constants方法就可以建立AutoLayout的约束了。

根据蓝色辅助线摆放位置:

logo

点击Reset to suggested constants方法

logo

通过这样设置之后无论在横屏还是竖屏的状态下Thing 1和Thing 2都会在屏幕的左上角和右下角了。

logo
logo

使用底部按钮

现在我在storyboard又添加了一个Bad Thing,对他没有使用蓝色辅助线。

logo
logo

可以看得出来没有做约束的Bad Thing的位置始终都没有变。

但是即使没有使用蓝色辅助线,当我们点击点击Reset to suggested constants方法的时候,storyboard依然会对Bad Thing进行约束。但是添加了之后我们发现,虽然有了约束,但是根本就是不对。

logo
logo

那么第二种方法,我们可以使用底部的按钮对控件添加约束。

logo
logo

在添加了约束之后我们发现屏幕上出现了两条黄色的线。

logo

这是因为我们给Bad Thing设定了约束但是并没有更新Frame,这时候点击黄色的线可以看到左侧大纲中出现了警告的内容,我们点击黄色的小三角,选择 Update framers 就可以修正这个问题了。

logo

修正了之后,我们就可以看到无论什么情况下,Bad Thing都会在屏幕的中间了。

logo
logo

使用control拖拽

这种方法是使用control在两个目标对象之间进行拖拽,添加约束关系。和建立storyboard与代码之间的联系一样,只需要按住control键,然后将Bad Thing的线拖到Thing 2上。就可以建立一个Bad Thing与Thing 2之间的约束。

logo
logo

这样建立好约束之后,Bad Thing就会始终存在于Thing 2的上边。

logo
logo

AutoLayout Demo

AutoLayout的应用是对之前的Attribute进行处理,目的是希望在横屏之后也会有一个很好的表现。Attribute相关的课程在这里

之前我们完成的Attribute是这样的。

logo

在横屏之后他是这样的

logo

这肯定不是我们想要的结果。甚至我们都没办法点击按钮了。所以我们要对他们进行一些修改。
那么我们使用之前提到的第一种设置autolayout的方法,因为之前我们是使用蓝色辅助线,所以直接点击 Reset to suggested constants按钮就可以了。

logo

设置之后运行一下看看,发现比刚刚好多了,除了紫色的部分太大了,其他的一切都还可以,至少不影响我的正常使用了。

但是我们肯定不能就这样结束了,我们还需要对按钮进行一些调整,那么我们选中四个按钮,然后在底部按钮设置他们为相同宽度。

logo

之后再运行,我们发现,完美了!一些都是想象中的样子,按钮也平分在了屏幕上。

logo

但是这样就结束了吗??当然不是了,不然让你学啥。仔细看控制台,我们会发现控制台跟以前不一样了,这里打印出了好多的错误。

logo

这些错误仔细看就会发现,他说他没办法在运行时同时满足约束。

再仔细看我们会发现在约束里,我们对按钮进行了硬编码,我们固定了他的宽度就是64,但是屏幕变宽的时候按钮的宽度就不是64了。所以解决这个方法我们讲约束为64的部分去掉。

这时候在运行我们就会发现,不会再出现报错了。


以上就是第九课的全部内容了。