因为最近用了一下Snapchat。发现他的主页的上下左右滑动都可以切换进入一个新的视图。觉得挺好玩的。所以就决定也照着写一个。ʕʘ̅͜ʘ̅ʔ
想了很多解决方法都还是不能很好的将这个效果做出来。于是借鉴了网上的一些Demo。很僵硬。照抄根本没办法把这个代码学习进去。(๑‾᷆д‾᷇๑)
所以想了想还是一边学习一边写一篇文章做个记录。这样学习的过程也可能会清晰一点。
效果如下
好了。我们进入正题。
1. 那我们先看一下这个项目结构。
2. 我们看一下Storyboard吧。
这个Storyboard中用到了我之前从没有用过的一个控件。
ContainerViewController
Container view controllers are a way to combine the content from multiple
view controllers into a single user interface. Container view controllers
are most often used to facilitate navigation and to create new user interface types
based on existing content.
Examples of container view controllers in UIKit include UINavigationController,
UITabBarController, and UISplitViewController, all of which facilitate
navigation between different parts of your user interface.
上面那段话截取苹果的开发者平台。大致的意思就是ContainerViewController可以在一个容器视图控制器中添加多个视图控制器。这里有一篇文章对这个控件分析的挺好的。大家可以看看。
ContainerViewCotroller中的视图结构是这样的。它包含两个ContainerView对应的就是另外两个VC的view。这边还用到了Segue。并且给了Identifier。那么说明等会儿会对Segue做处理。
3. 轮到看代码了。
我们先看最简单的ContainerChildViewController。
定义了三个虚函数。因为都没有实现三个方法所以我们也没什么好看了。这三个方法具体是干什么的通过方法名我大致猜了一下可能是做这些事的。
/// 更新交互式过渡
- (void)updateInteractiveTransition:(CGFloat)progress;
/// 取消交互式过渡
- (void)cancelInteractiveTransition;
/// 完成交互式过渡
- (void)finishInteractiveTransition;
然后看看重量级的ContainerViewController。
ContainerViewController.h
#import <UIKit/UIKit.h>
@interface ContainerViewController : UIViewController
/// 过渡动画时长
@property(nonatomic, assign) CGFloat transitionAnimationDuration;
/// 上层视图是否可见(只读)
@property(nonatomic, assign, getter=isOverViewVisible, readonly) BOOL overViewVisible;
/// 交互进度
@property(nonatomic, assign, getter=isInteractionInProgress, readonly) BOOL interactionInProgress;
/// 上层视图消失
- (void)dismissOverViewController;
/// 上层视图显示
- (void)presentOverViewController;
@end
ContainerViewController.m
先看下实现文件里声明的一下属性。
/// 目前还不知道这个常量有什么用
static CGFloat const kActionButtonDisplacement = 55.0;
/// 按钮的最小尺寸
static CGFloat const kActionButtonSmallSize = 50.0;
@interface ContainerViewController()
/// 上层视图的视图控制器
@property(nonatomic, strong) ContainerChildViewController *overViewController;
/// 主视图的视图控制器
@property(nonatomic, strong) ContainerChildViewController *mainViewController;
/// 上层视图是否可见(读写)
@property(nonatomic, assign, getter=isOverViewVisible, readwrite) BOOL overViewVisible;
/// 交互进度(读写)
@property(nonatomic, assign, getter=isInteractionInProgress, readwrite) BOOL interactionInProgress;
/// 是否完成过度
@property(nonatomic, assign) BOOL shouldCompleteTransition;
/// 上层视图的视图容器
@property(nonatomic, weak) IBOutlet UIView *overViewContainer;
/// 主视图的视图容器
@property(nonatomic, weak) IBOutlet UIView *mainViewContainer;
/// 中间的圆圈按钮
@property(nonatomic, weak) IBOutlet UIButton *actionButton;
/// 上层视图容器距离顶部的约束(初始化为负的屏幕高度)
@property(nonatomic, weak) IBOutlet NSLayoutConstraint *overViewTopConstraint;
/// 中间的圆圈按钮距离底部的约束(初始化为50)
@property(nonatomic, weak) IBOutlet NSLayoutConstraint *actionButtonBottomConstraint;
/// 中间的圆圈按钮的宽度约束(初始化为50)
@property(nonatomic, weak) IBOutlet NSLayoutConstraint *actionButtonWidthConstraint;
/// 初始化的中间的圆圈按钮距离底部的约束(初始化为50)
@property(nonatomic, assign) CGFloat originalActionButtonBottomConstraintConstant;
/// 初始化中间的圆圈按钮的宽度约束(初始化为50)
@property(nonatomic, assign) CGFloat originalActionButtonWidthConstraintConstant;
/// 上层视图容器距离顶部的预估值
@property(nonatomic, assign) CGFloat overViewTopEstimatedValue;
@end
好了。接下来我们一个方法一个方法去看。这样方便我们学习。
- (void)awakeFromNib
初始化overViewVisible与overViewTopEstimatedValue。
- (void)awakeFromNib
{
[super awakeFromNib];
self.overViewVisible = NO;
/// 也可以通过 self.overViewTopEstimatedValue = self.overViewTopConstraint.constant; 获取
self.overViewTopEstimatedValue = -[UIScreen mainScreen].bounds.size.height;
}
- (void)viewDidLoad
为视图添加拖拽手势并且初始化
overViewTopConstraint、originalActionButtonBottomConstraintConstant、
originalActionButtonWidthConstraintConstant。
- (void)viewDidLoad
{
[super viewDidLoad];
/// 添加拖拽手势
[self addGestureRecogniserOnView:self.view];
self.overViewTopConstraint.constant =
-[UIScreen mainScreen].bounds.size.height;
self.originalActionButtonBottomConstraintConstant =
self.actionButtonBottomConstraint.constant;
self.originalActionButtonWidthConstraintConstant =
self.actionButtonWidthConstraint.constant;
}
- (BOOL)prefersStatusBarHidden
设置隐藏状态栏。因为这个容器视图控制器中有另外两个子视图控制器。所以隐藏状态栏写在这里。下面会有一个方法当拖拽时回去调用setNeedsStatusBarAppearanceUpdate。所以系统会调用prefersStatusBarHidden重新获取是否隐藏状态栏。
- (BOOL)prefersStatusBarHidden
{
/// 设置状态栏显示范围
static CGFloat const kVisibleStatusBarRange = -10.0;
/// 当上层视图控制器距离上边距只有10的时候状态栏隐藏
return (self.overViewTopEstimatedValue < kVisibleStatusBarRange);
}
- (void)prepareForSegue:(UIStoryboardSegue)segue sender:(id)sender*
通知视图控制器即将要跳转。segue中包含了跳转的信息。
- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
/// 通过segue.identifier去判断跳转至那个VC
if ([segue.identifier isEqualToString:@"embedOver"])
{
ContainerChildViewController *viewController = segue.destinationViewController;
viewController.containerViewController = self;
self.overViewController = segue.destinationViewController;
}
else if ([segue.identifier isEqualToString:@"embedMain"])
{
ContainerChildViewController *viewController = segue.destinationViewController;
viewController.containerViewController = self;
self.mainViewController = segue.destinationViewController;
}
}
- (IBAction)actionButtonTouched:(id)sender
按钮的点击事件。
- (IBAction)actionButtonTouched:(id)sender
{
/// 判断上层视图是否显示
if (self.isOverViewVisible)
{
/// 如果显示上层视图。当点击按钮之后dismiss上层视图
[self dismissOverViewController];
}
}
- (void)transformContentWithProgress:(CGFloat)progress
通过交互的进度使内容变形。
- (void)transformContentWithProgress:(CGFloat)progress
{
/// 使上层视图变形
/// Y轴上的移动量
CGFloat translationY = progress * self.view.frame.size.height;
/// CGAffineTransformMakeTranslation每次都会相对于初始的中心点做变化
self.overViewContainer.transform = CGAffineTransformMakeTranslation(0, translationY);
#warning 这里为什么要赋值给overViewTopEstimatedValue???
/// 将上层视图的位置赋值给overViewTopEstimatedValue
self.overViewTopEstimatedValue = self.overViewTopConstraint.constant + translationY;
/// --------------------------------------------------------------------------------- ///
/// 按钮的形变
/// 上层视图是否显示。如果显示则为progress的绝对值。如果不显示则为1 - progress
/// 当上层视图显示并且从下向上滑时。progress的值从0到-1
NSLog(@"%lf", progress);
CGFloat value =
(self.isOverViewVisible ?
fabs(progress) :
1.0 - progress);
/// 按钮的新大小。最大为80。最小为50。
CGFloat newActionButtonSize =
kActionButtonSmallSize + value *
(self.originalActionButtonWidthConstraintConstant - kActionButtonSmallSize);
/// 这里的 self.actionButtonWidthConstraint.constant 并不是常量。当过渡完成时改变
CGFloat actionButtonScale = newActionButtonSize / self.actionButtonWidthConstraint.constant;
CGAffineTransform actionButtonScaleTransform = CGAffineTransformMakeScale(actionButtonScale, actionButtonScale);
/// --------------------------------------------------------------------------------- ///
/// 按钮的偏移
/// 因为我们同时让按钮偏移并且缩放。所以我们要将两个形变拼起来
#warning 这边为什么除以2.0???
CGFloat compensationBecauseOfMakingScale = (self.actionButtonWidthConstraint.constant - newActionButtonSize) / 2.0;
CGFloat actionButtonTranslationY =
(progress * kActionButtonDisplacement) +
compensationBecauseOfMakingScale;
CGAffineTransform actionButtonTranslateTransform =
CGAffineTransformMakeTranslation(0, actionButtonTranslationY);
CGAffineTransform actionButtonTransform =
CGAffineTransformConcat(actionButtonScaleTransform, actionButtonTranslateTransform);
self.actionButton.transform = actionButtonTransform;
}
- (CGFloat)transitionAnimationDuration
过渡动画的时间。
- (CGFloat)transitionAnimationDuration
{
return 0.25;
}
- (void)dismissOverViewController
上层视图dismiss。
- (void)dismissOverViewController
{
[self finishInteractiveTransition];
}
- (void)cancelInteractiveTransition
取消交互式过渡。
- (void)cancelInteractiveTransition
{
/// 重新获取上层视图相对顶部的距离。
self.overViewTopEstimatedValue = self.overViewTopConstraint.constant;
/// 通知主视图控制器与上层视图控制器 取消交互式过渡。
[self.mainViewController cancelInteractiveTransition];
[self.overViewController cancelInteractiveTransition];
/// 对弱引用self。避免循环引用。
__weak typeof(self) blockSelf = self;
void (^AnimationBlock)(void) = ^void (void)
{
/// 将形变还原。
blockSelf.overViewContainer.transform = CGAffineTransformIdentity;
blockSelf.actionButton.transform = CGAffineTransformIdentity;
};
void (^CompletionBlock)(BOOL finished) = ^void (BOOL finished)
{
/// 完成后通知系统重新获取状态栏是否隐藏。
[blockSelf setNeedsStatusBarAppearanceUpdate];
};
/// 执行动画。
[UIView animateWithDuration:[self transitionAnimationDuration]
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:AnimationBlock
completion:CompletionBlock];
}
- (void)finishInteractiveTransition
完成交互式过渡。
- (void)finishInteractiveTransition
{
/// 通知主视图控制器与上层视图控制器完成交互式过渡
[self.mainViewController finishInteractiveTransition];
[self.overViewController finishInteractiveTransition];
/// 判断当前进度。如果上层视图显示则为 -1,否则为 1。
CGFloat progress = (self.isOverViewVisible ? - 1 : 1);
/// 重新获取上层视图相对顶部的距离。
CGFloat newOverViewTopConstraintConstant =
(self.isOverViewVisible ?
-[UIScreen mainScreen].bounds.size.height :
0);
/// 重新获取按钮相对于底部的距离
CGFloat newActionButtonBottomConstraintConstant =
(self.isOverViewVisible ?
self.originalActionButtonBottomConstraintConstant :
self.originalActionButtonBottomConstraintConstant -
kActionButtonDisplacement);
/// 重新获取按钮的宽度
CGFloat newActionButtonWidthConstraintConstant =
(self.isOverViewVisible ?
self.originalActionButtonWidthConstraintConstant :
kActionButtonSmallSize);
__weak typeof(self) blockSelf = self;
void (^AnimationBlock)(void) = ^void (void)
{
/// 通过进度发生形变
[blockSelf transformContentWithProgress:progress];
};
void (^CompletionBlock)(BOOL finished) = ^void (BOOL finished)
{
/// 赋值新的约束
blockSelf.overViewTopConstraint.constant =
newOverViewTopConstraintConstant;
blockSelf.actionButtonBottomConstraint.constant =
newActionButtonBottomConstraintConstant;
blockSelf.actionButtonWidthConstraint.constant =
newActionButtonWidthConstraintConstant;
/// 获取新的上层视图相对顶部的约束
blockSelf.overViewTopEstimatedValue = newOverViewTopConstraintConstant;
#warning 这里为什么要将形变还原???
/// 还原形变
blockSelf.overViewContainer.transform = CGAffineTransformIdentity;
blockSelf.actionButton.transform = CGAffineTransformIdentity;
/// 刷新页面
[blockSelf.view layoutIfNeeded];
blockSelf.overViewVisible = !blockSelf.isOverViewVisible;
[blockSelf setNeedsStatusBarAppearanceUpdate];
};
[UIView animateWithDuration:[self transitionAnimationDuration]
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:AnimationBlock
completion:CompletionBlock];
}
- (void)addGestureRecogniserOnView:(UIView)view*
给View添加手势。
- (void)addGestureRecogniserOnView:(UIView*)view
{
UIPanGestureRecognizer *panGesture =
[[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handleGestureRecognizer:)];
[view addGestureRecognizer:panGesture];
}
- (void)handleGestureRecognizer:(UIPanGestureRecognizer)gesture*
手势触发的事件。
- (void)handleGestureRecognizer:(UIPanGestureRecognizer*)gesture
{
/// 获取手势位移(位置的偏移量,所有点都相对于动作起点的距离)。
CGPoint translation = [gesture translationInView:self.view];
/// 手势移动的速度。
CGPoint velocity = [gesture velocityInView:self.view];
/// 获取progress的值。
CGFloat progress = translation.y / self.view.frame.size.height;
/// 避免过度向上滑或者过度向下滑。
progress =
(self.isOverViewVisible ?
fmin(0.0, fmax(-1.0, progress)) :
fmin(1.0, fmax(0.0, progress)));
// NSLog(@"%f", progress);
// NSLog(@"%f", translation.y);
// NSLog(@"%f", velocity.y);
/// 速度的上限。
static CGFloat const kVelocityLimit = 2000.0;
/// 滑动距离的上限。
static CGFloat const kTranslationLimit = 0.30;
/// 通过判断手势的不同状态来做不同事情。
switch (gesture.state)
{
/// 刚开始拖拽时。
case UIGestureRecognizerStateBegan:
self.shouldCompleteTransition = NO;
self.interactionInProgress = YES;
break;
/// 开始拖拽。
case UIGestureRecognizerStateChanged:
/// 判断是否在交互中。
if (self.isInteractionInProgress)
{
/// 如果滑动速度太快。直接完成动画。
if ((self.isOverViewVisible && velocity.y < -kVelocityLimit) ||
(!self.isOverViewVisible && velocity.y > kVelocityLimit))
{
/// 完成过渡。
self.interactionInProgress = NO;
[self finishInteractiveTransition];
}
else
{
/// 当手势完成的时候动画也会完成。
self.shouldCompleteTransition =
(self.isOverViewVisible ?
progress < -kTranslationLimit:
progress > kTranslationLimit);
[self updateInteractiveTransition:progress];
}
}
break;
/// 手势识别失败或者取消的时候。
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateCancelled:
/// 如果在交互过程中的就直接取消。
if (self.isInteractionInProgress)
{
self.interactionInProgress = NO;
[self cancelInteractiveTransition];
}
break;
/// 手势结束之后。
case UIGestureRecognizerStateEnded:
/// 判断是否在交互中。
if (self.isInteractionInProgress)
{
self.interactionInProgress = NO;
/// 判断是否应该完成移动。
if (self.shouldCompleteTransition)
{
/// 如果是。完成交互。
[self finishInteractiveTransition];
}
else
{
/// 如果不是。取消交互。
[self cancelInteractiveTransition];
}
}
break;
case UIGestureRecognizerStatePossible:
// Do nothing
break;
}
}
即使这样读完了代码但是还是对这个类理解还不够通透。所以我决定再看一遍。并且重点看看一下疑问的地方。
我想先去搞清楚关于Transition这块的几个方法到底是做了什么事。
过了两天。终于搞清楚了这个类到底做了什么事情。自己的水平不够。加上又是阅读别人的代码真的是好累阿。不过好在学到了东西。
多说不说。先看看实现的效果。
在原来的代码上做了点修改。实现了四个方向的滑动。虽然没有上面的酷炫但是这个原理是差不多了。
那我们继续看代码吧。这回就直接看核心部分吧。
第一个核心的部分。
首先应该是处理手势的那部分。
通过三个方法去处理动画:
分别是:
1. - (void)updateInteractiveTransition:(CGFloat)progress
2. - (void)cancelInteractiveTransition
3. - (void)finishInteractiveTransition
这三个方法在处理手势的时候都有用到。我们通过手势的不同状态与手势的移动距离去判断分别调用什么方法。
我们先来看看触发手势时候发生了什么。
*- (void)handleGestureRecognizer:(UIPanGestureRecognizer )gesture
- (void)handleGestureRecognizer:(UIPanGestureRecognizer*)gesture
{
/// 速度的上限。
static CGFloat const kVelocityLimit = 2000.0;
/// 滑动距离的上限。
static CGFloat const kTranslationLimit = 0.30;
/// 获取手势位移(位置的偏移量,所有点都相对于动作起点的距离)。
CGPoint translation = [gesture translationInView:self.view];
/// 手势移动的速度。
CGPoint velocity = [gesture velocityInView:self.view];
/// 获取progress的值。
CGFloat progress = translation.y / self.view.frame.size.height;
/// 避免过度向上滑或者过度向下滑。
/// 先判断当前所在页面。
/// 如果是在上层视图。那么就是只能上滑。向上滑动 translation.y 为负数。所以取值范围是 -1 到 0。
/// 如果是在主视图。道理同上。
progress =
(self.isOverViewVisible ?
fmin(0.0, fmax(-1.0, progress)) :
fmin(1.0, fmax(0.0, progress)));
/// 通过判断手势的不同状态来做不同事情。
switch (gesture.state)
{
/// 刚开始拖拽时。
case UIGestureRecognizerStateBegan:
self.shouldCompleteTransition = NO;
self.interactionInProgress = YES;
break;
/// 开始拖拽。
case UIGestureRecognizerStateChanged:
/// 判断是否在交互中。
if (self.isInteractionInProgress)
{
/// 判断手势的速度。
/// 如果滑动速度太快。直接完成动画。
/// 不能用绝对值去判断速度。
/// 如果用绝对值判断会导致在主页面。快速向上滑动。也会触发以下代码。违反交互。
if ((self.isOverViewVisible && velocity.y < -kVelocityLimit) ||
(!self.isOverViewVisible && velocity.y > kVelocityLimit))
{
/// 完成过渡。
self.interactionInProgress = NO;
[self finishInteractiveTransition];
}
else
{
/// 当手势完成的时候动画也会完成。
/// 通过已经移动过的比例去判断是否需要完成动画。当超过 kTranslationLimit 设置 shouldCompleteTransition 为YES。
self.shouldCompleteTransition =
(self.isOverViewVisible ?
progress < -kTranslationLimit:
progress > kTranslationLimit);
/// 通过进度去更新过渡动画。
[self updateInteractiveTransition:progress];
}
}
break;
/// 手势识别失败或者取消的时候。
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateCancelled:
/// 如果在交互过程中的就直接取消。
if (self.isInteractionInProgress)
{
self.interactionInProgress = NO;
[self cancelInteractiveTransition];
}
break;
/// 手势结束之后。
case UIGestureRecognizerStateEnded:
/// 判断是否在交互中。
if (self.isInteractionInProgress)
{
self.interactionInProgress = NO;
/// 判断是否应该完成移动。
if (self.shouldCompleteTransition)
{
/// 如果是。完成交互。
[self finishInteractiveTransition];
}
else
{
/// 如果不是。取消交互。
[self cancelInteractiveTransition];
}
}
break;
case UIGestureRecognizerStatePossible:
// Do nothing
break;
}
}
然后我们再看看完成手势动画的部分。
这个方法里面我删掉了很多代码。这样可以方便学习。如果想用做一些很酷炫的动画就要靠各位自行发挥了。
- (void)finishInteractiveTransition
- (void)finishInteractiveTransition
{
/// 因为是直接完成动画所以我们 progress 我们直接写完成的值就好了。
/// 当当前页面是上层视图的时候。是向上滑动进入主页面。向上滑动为负。所以是-1。
/// 当当前页面是主视图的时候。道理同上。
CGFloat progress = (self.isOverViewVisible ? - 1 : 1);
/// 通过判断当前页面来获取新的约束。
/// 当当前页面是上层视图的时候。我们需要完成上层视图到主视图的过渡。所以约束是 负的屏幕高度。
/// 当当前页面是主视图的时候。道理同上。
CGFloat newOverViewTopConstraintConstant = (self.isOverViewVisible ? -[[UIScreen mainScreen] bounds].size.height : 0);
__weak typeof(self) weakSelf = self;
void (^AnimationBlock)(void) = ^void (void)
{
/// 形变。
[weakSelf transformContentWithProgress:progress];
};
void (^CompletionBlock)(BOOL finished) = ^void (BOOL finished)
{
/// 获取新的约束值
weakSelf.overViewTopConstraint.constant = newOverViewTopConstraintConstant;
/// 还原形变。
weakSelf.overViewContrainer.transform = CGAffineTransformIdentity;
/// 刷新页面。使约束生效。
[weakSelf.view layoutIfNeeded];
/// 获得当前显示的视图。
weakSelf.overViewVisible = !weakSelf.isOverViewVisible;
};
/// 执行动画
[UIView animateWithDuration:[self transitionAnimationDuration]
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:AnimationBlock
completion:CompletionBlock];
}
还有取消过渡的部分。
这一部分的代码很简单。如果上面的代码能理解这边就看一眼就明白了。
这一部分代码就是将形变还原回去。
- (void)cancelInteractiveTransition
- (void)cancelInteractiveTransition
{
self.overViewTopEstimatedValue = self.overViewTopConstraint.constant;
[self.mainVC cancelInteractiveTransition];
[self.overVC cancelInteractiveTransition];
__weak typeof(self) weakSelf = self;
void (^AnimationBlock)(void) = ^void (void)
{
/// 将形变还原
weakSelf.overViewContrainer.transform = CGAffineTransformIdentity;
weakSelf.actionButton.transform = CGAffineTransformIdentity;
};
void (^CompletionBlock)(BOOL finished) = ^void (BOOL finished)
{
[weakSelf setNeedsStatusBarAppearanceUpdate];
};
[UIView animateWithDuration:[self transitionAnimationDuration]
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:AnimationBlock
completion:CompletionBlock];
}
还有最后一个通过进度更新过渡。
这个方法是在手势变化的时候调用的。通过进度的改变我们也随之改变形变。但是具体的事情却不是在这个方法里做的。单独有一个方法去做这个事情。
- (void)updateInteractiveTransition:(CGFloat)progress
- (void)updateInteractiveTransition:(CGFloat)progress
{
[self setNeedsStatusBarAppearanceUpdate];
/// 形变
[self transformContentWithProgress:progress];
}
第二个核心的部分。
第二个核心的部分就是处理形变的部分。这个方法在完成动画与更新动画中都有用到。视图的下滑之类的UI上的变化都是在这里做的。
这一部分我也简化了很多。方便各位去学习。
- (void)transformContentWithProgress:(CGFloat)progress
- (void)transformContentWithProgress:(CGFloat)progress
{
/// 通过进度获取移动量
CGFloat translationY = progress * self.view.frame.size.height;
/// 使上层视图滑动
self.overViewContrainer.transform = CGAffineTransformMakeTranslation(0, translationY);
/// 获取当前的距离。通过这个参数去判断状态栏是否消失
self.overViewTopEstimatedValue = self.overViewTopConstraint.constant + translationY;
}
总结
看了这份代码之后。一开始看看的很懵逼。虽然这里面没有用到一切比较奇怪的东西。用到的方法什么的都还是看的懂的。但是还是没明白作者的思路。计算机最难的是思想嘛。
于是之后决定还是自己实践一下好了。从最简单的开始。然后一点点加东西上去。最后就有了上面那个四个方向都可以移动的东西。其实整个项目也挺简单。就是做了两件事。第一件事就是形变。第二件是就是更新约束刷新页面。但是这里面有很多细节的地方一开始是想不到的。
感谢这份代码。
最后
如果有些的不好的地方请各位直接说。
如果有不明白的地方也请各位直接问。
希望共同进步。希望iOS这个圈子越来越好。