解构革命的演变
背景
景观
演化
解构
那7参数法
Stretttch
为了在我们的实现中模仿这种行为,我们将提供一个view(SCSpringExpandingView
),以在两个不同的帧之间进行动画处理。折叠,未展开状态的视图框架将占据其父视图的整个宽度,并且高度匹配,从而为我们提供一个小的正方形视图。
- (CGRect)frameForCollapsedState
{
return CGRectMake(0.f, CGRectGetMidY(self.bounds) - (CGRectGetWidth(self.bounds) / 2.f),
CGRectGetWidth(self.bounds), CGRectGetWidth(self.bounds));
}
当我们将视图拉伸到展开状态时,我们将使用一个框架,该框架是超级视图的高度,但只有宽度的一半。我们还将移动水平原点,以使我们的视图保持在超级视图的中心内。
- (CGRect)frameForExpandedState
{
return CGRectMake(CGRectGetWidth(self.bounds) / 4.f, 0.f,
CGRectGetWidth(self.bounds) / 2.f, CGRectGetHeight(self.bounds));
}
为了使视图的角变圆,我们将cornerRadius
拉伸视图的图层的层设置为视图宽度的一半,使其在折叠时呈现圆形外观,在扩展时呈现圆形边缘。在修改框架的宽度时,我们需要在折叠状态和展开状态之间转换时更新此值,否则其中一种情况的边缘将变成圆角,这与视图的宽度相反。
- (void)layoutSubviews
{
[super layoutSubviews];
self.stretchingView.layer.cornerRadius = CGRectGetMidX(self.stretchingView.bounds);
}
现在剩下要做的就是使用我们的新朋友长名来在两个州之间建立动画animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:
。
我们已经看到了最前,这种方法使用的参数,但让我们快速浏览一下这两个事关对我们来说,usingSpringWithDamping
和initialSpringVelocity
。
usingSpringWithDamping``CGFloat
从0.0到1.0之间取一个值,并从物理意义上确定弹簧的强度。接近1.0的值将增加弹簧的强度并导致低振动。接近0.0的值会削弱弹簧的强度并导致高振动。
initialSpringVelocity
也要接受,CGFloat
但是传递的值将相对于动画过程中经过的距离。值1.0表示在1秒钟内遍历的动画距离,而值0.5表示在1秒钟内遍历的动画距离的一半。
尽管这些参数与物理属性相对应,但在大多数情况下还是感觉良好,请这样做。
[UIView animateWithDuration:0.5f
delay:0.0f
usingSpringWithDamping:0.4f
initialSpringVelocity:0.5f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.stretchingView.frame = [self frameForExpandedState];
} completion:NULL];
就是这样。只需一个方法调用和一些波动的魔术数字,我们就可以利用iOS 7中UIKit的动态基础。
三人一族
现在,我们已经创建了SCSpringExpandingView
,我们需要创建一个包含三个SCSpringExpandingView
s 的视图。叫它SCDragAffordanceView
。
的基本工作SCDragAffordanceView
是布局三个SCSpringExpandingView
,并提供一个接口,我们可以通过该接口进行下拉菜单交互。
要布局SCSpringExpandingView
,我们将覆盖layoutSubviews
并对齐每个视图框架,使其在边界上等距分布。
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat interItemSpace = CGRectGetWidth(self.bounds) / self.springExpandViews.count;
NSInteger index = 0;
for (SCSpringExpandView *springExpandView in self.springExpandViews)
{
springExpandView.frame = CGRectMake(interItemSpace * index, 0.f, 4.f,
CGRectGetHeight(self.bounds));
index++;
}
}
为了实现这一点,我们将遍历三个SCSpringExpandingView
s并主要基于progress
传入的是大于还是等于1.0来更新每个s的颜色,然后基于s 是否progress
足够大以使视图可以扩展。
- (void)setProgress:(CGFloat)progress
{
_progress = progress;
CGFloat progressInterval = 1.0f / self.springExpandViews.count;
NSInteger index = 0;
for (SCSpringExpandView *springExpandView in self.springExpandViews)
{
BOOL expanded = ((index * progressInterval) + progressInterval < progress);
if (progress >= 1.f)
{
[springExpandView setColor:[UIColor redColor]];
}
else if (expanded)
{
[springExpandView setColor:[UIColor blackColor]];
}
else
{
[springExpandView setColor:[UIColor grayColor]];
}
[springExpandView setExpanded:expanded animated:YES];
index++;
}
}
现在,我们已经涵盖了一些新的热点,让我们绕过一条人迹罕至的道路。
嵌套的UIScrollView
我们希望我们SCDragAffordanceView
始终在您身边,准备展示我们的菜单。要考虑的一个选择是将其添加为我们的子视图UITextView
基础上,并修改其垂直原点contentOffset
我们的UITextView
,但这种重载我们的责任UITextView
不仅仅是显示文本,只是感觉有点不对劲。
相反,让我们创建一个单独的实例UIScrollView
,我们的UITextView
和SCDragAffordanceView
将被添加为的子视图。
self.enclosingScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
self.enclosingScrollView.alwaysBounceHorizontal = YES;
self.enclosingScrollView.delegate = self;
[self.view addSubview:self.enclosingScrollView];
此处的关键行设置alwaysBounceHorizontal
为YES
。现在,无论contentSize
滚动视图如何,水平拖动始终将以预期的阻力继续超出界限。
如果我们嵌套UITextView
的水平内容大小没有超出其范围,那么我们将获得仅一个的效果UIScrollView
,同时在代码中分离关注点。
我们还希望成为滚动视图的委托,以便我们检测到滚动视图被拖动并相应地更新SCDragAffordanceView
的进度。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.isDragging)
{
self.menuDragAffordanceView.progress = scrollView.contentOffset.x /
CGRectGetWidth(self.menuDragAffordanceView.bounds);
}
}
最后,当我们收到scrollViewDidEndDragging:willDecelerate:
委托回调时,我们将使用在scrollViewDidScroll:
回调中计算出的相同进度来确定是否显示菜单视图控制器。如果没有,我们将进度设置回0.0。
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (self.menuDragAffordanceView.progress >= 1.f)
{
[self presentViewController:self.menuViewController
animated:YES
completion:NULL];
}
else
{
self.menuDragAffordanceView.progress = 0.f;
}
}
有了尘土飞扬的道路,让我们陷入下一个iOS 7热点问题。
UIViewControllerTransitioningDelegate
值得庆幸的是,在iOS 7中,Apple注意到了这种模式的出现,并从开发人员社区得到了另一个提示,它提供了一种干净,经过批准的方法,可以使用一组最少的协议来实现这一目标。现在,您可以通过实现UIViewControllerTransitioningDelegate
协议来定义自定义动画和视图控制器之间的交互式过渡。
该UIViewControllerTransitioningDelegate
协议声明了一些方法,这些方法使您可以返回动画师对象,这些对象定义了视图过渡的三个阶段之一:呈现,关闭和交互。我们的自定义过渡将定义展示和发布阶段。
在我们的视图控制器中,我们将声明我们遵守UIViewControllerTransitioningDelegate
协议并实现我们关心的两种方法animationControllerForPresentedController:presentingController:sourceController:
和animationControllerForDismissedController:
。
self.menuViewController = [[SCMenuViewController alloc] initWithNibName:nil bundle:nil];
创建此类的实例后,我们需要将其transitionDelegate
设置为我们的视图控制器,并将其设置为modalPresentationStyle
,UIModalPresentationCustom
以便transitioningDelegate
在出现时可以回调它。
self.menuViewController.modalPresentationStyle = UIModalPresentationCustom;
self.menuViewController.transitioningDelegate = self;
现在,当我们展示菜单视图控制器时,它将回调到其transitioningDelegate
(我们的视图控制器)以请求展示UIViewControllerAnimatedTransitioning
动画器对象。
UIViewControllerAnimatedTransitioning
为了向菜单视图控制器提供动画对象,我们将从创建一个普通的旧NSObject子类开始SCOverlayPresentTransition
,并声明其符合UIViewControllerAnimatedTransitioning
协议。在animationControllerForPresentedController:presentingController:sourceController:
委托回调中,我们将创建SCOverlayPresentTransition
对象的实例并返回它。
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[SCOverlayPresentTransition alloc] init];
}
对于解雇动画,我们将创建另一个名为NSObject的子类SCOverlayDismissTransition
,并在收到animationControllerForDismissedController:
委托回调时提供其实例。
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [[SCOverlayDismissTransition alloc] init];
}
我们现在和罢免过渡对象的实现包括两种方法,transitionDuration:
和animateTransition:
。transitionDuration:
您可能已经猜到的方法只是请求NSTimeInterval
来指定动画的持续时间。该animateTransition:
是在过渡的实质性工作。
该animateTransition:
方法的唯一参数是符合UIViewControllerContextTransitioning
协议的对象。从该对象中,我们可以提取驱动动画所需的对象和信息,包括过渡中涉及的视图控制器。它还提供了一些方法,用于通知框架我们已完成过渡。
UIViewController *presentingViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *overlayViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
一旦有了呈现和呈现的视图控制器,就需要将它们的视图添加为过渡的容器视图的子视图,以便它们都在动画期间出现。
UIView *containerView = [transitionContext containerView];
[containerView addSubview:presentingViewController.view];
[containerView addSubview:overlayViewController.view];
当前过渡的最后一部分是简单地为视图设置动画,但是我们愿意,然后通知transitionContext
对象我们是否已成功完成过渡。
overlayViewController.view.alpha = 0.f;
NSTimeInterval transitionDuration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:transitionDuration
animations:^{
overlayViewController.view.alpha = 0.9f;
} completion:^(BOOL finished) {
BOOL transitionWasCancelled = [transitionContext transitionWasCancelled];
[transitionContext completeTransition:transitionWasCancelled == NO];
}];
在SCOverlayDismissTransition
将遵循基本上相同的过程,尽管是在相反的方向。
现在,当我们显示菜单视图控制器时,它将使用我们的自定义过渡,将呈现视图控制器的视图保持在视图层次结构中。
闭幕
当我们即将迎来iOS App Store成立6周年之际,其应用前景已令人叹为观止。我们已经可以将应用视为经典的想法表明了它的移动速度。每年,开发人员都会获得一系列新的玩具,以用来构建出色的应用程序,但仍然有可观的空间UIScrollView
。
- 如果你感到怀旧的令人心醉的iOS 6天,还有的iOS 6中拉来刷新控制的一大克隆