#iOS開發(fā)#仿Snapchat主頁面切換視圖

因?yàn)樽罱昧艘幌耂napchat。發(fā)現(xiàn)他的主頁的上下左右滑動(dòng)都可以切換進(jìn)入一個(gè)新的視圖。覺得挺好玩的。所以就決定也照著寫一個(gè)。???????
想了很多解決方法都還是不能很好的將這個(gè)效果做出來。于是借鑒了網(wǎng)上的一些Demo。很僵硬。照抄根本沒辦法把這個(gè)代碼學(xué)習(xí)進(jìn)去。(? ̄?д ̄??)
所以想了想還是一邊學(xué)習(xí)一邊寫一篇文章做個(gè)記錄。這樣學(xué)習(xí)的過程也可能會(huì)清晰一點(diǎn)。

效果如下

未命名.gif

好了。我們進(jìn)入正題。

1. 那我們先看一下這個(gè)項(xiàng)目結(jié)構(gòu)。

WX20170319-181717@2x.png

2. 我們看一下Storyboard吧。

WX20170319-200906@2x.png

這個(gè)Storyboard中用到了我之前從沒有用過的一個(gè)控件。
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.

上面那段話截取蘋果的開發(fā)者平臺(tái)。大致的意思就是ContainerViewController可以在一個(gè)容器視圖控制器中添加多個(gè)視圖控制器。這里有一篇文章對(duì)這個(gè)控件分析的挺好的。大家可以看看。

ContainerViewCotroller中的視圖結(jié)構(gòu)是這樣的。它包含兩個(gè)ContainerView對(duì)應(yīng)的就是另外兩個(gè)VC的view。這邊還用到了Segue。并且給了Identifier。那么說明等會(huì)兒會(huì)對(duì)Segue做處理。

WX20170319-203130@2x.png

3. 輪到看代碼了。

我們先看最簡單的ContainerChildViewController。
定義了三個(gè)虛函數(shù)。因?yàn)槎紱]有實(shí)現(xiàn)三個(gè)方法所以我們也沒什么好看了。這三個(gè)方法具體是干什么的通過方法名我大致猜了一下可能是做這些事的。

/// 更新交互式過渡
- (void)updateInteractiveTransition:(CGFloat)progress;
/// 取消交互式過渡
- (void)cancelInteractiveTransition;
/// 完成交互式過渡
- (void)finishInteractiveTransition;

然后看看重量級(jí)的ContainerViewController。

ContainerViewController.h

#import <UIKit/UIKit.h>

@interface ContainerViewController : UIViewController

/// 過渡動(dòng)畫時(shí)長
@property(nonatomic, assign) CGFloat transitionAnimationDuration;
/// 上層視圖是否可見(只讀)
@property(nonatomic, assign, getter=isOverViewVisible, readonly) BOOL overViewVisible;
/// 交互進(jìn)度
@property(nonatomic, assign, getter=isInteractionInProgress, readonly) BOOL interactionInProgress;

/// 上層視圖消失
- (void)dismissOverViewController;
/// 上層視圖顯示
- (void)presentOverViewController;

@end

ContainerViewController.m

先看下實(shí)現(xiàn)文件里聲明的一下屬性。

/// 目前還不知道這個(gè)常量有什么用
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;
/// 交互進(jìn)度(讀寫)
@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;
/// 上層視圖容器距離頂部的約束(初始化為負(fù)的屏幕高度)
@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;
/// 上層視圖容器距離頂部的預(yù)估值
@property(nonatomic, assign) CGFloat overViewTopEstimatedValue;

@end

好了。接下來我們一個(gè)方法一個(gè)方法去看。這樣方便我們學(xué)習(xí)。

- (void)awakeFromNib
初始化overViewVisibleoverViewTopEstimatedValue

- (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
設(shè)置隱藏狀態(tài)欄。因?yàn)檫@個(gè)容器視圖控制器中有另外兩個(gè)子視圖控制器。所以隱藏狀態(tài)欄寫在這里。下面會(huì)有一個(gè)方法當(dāng)拖拽時(shí)回去調(diào)用setNeedsStatusBarAppearanceUpdate。所以系統(tǒng)會(huì)調(diào)用prefersStatusBarHidden重新獲取是否隱藏狀態(tài)欄。

- (BOOL)prefersStatusBarHidden
{
    /// 設(shè)置狀態(tài)欄顯示范圍
    static CGFloat const kVisibleStatusBarRange = -10.0;
    /// 當(dāng)上層視圖控制器距離上邊距只有10的時(shí)候狀態(tài)欄隱藏
    return (self.overViewTopEstimatedValue < kVisibleStatusBarRange);
}

- (void)prepareForSegue:(UIStoryboardSegue)segue sender:(id)sender*
通知視圖控制器即將要跳轉(zhuǎn)。segue中包含了跳轉(zhuǎn)的信息。

- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
    /// 通過segue.identifier去判斷跳轉(zhuǎn)至那個(gè)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
按鈕的點(diǎn)擊事件。

- (IBAction)actionButtonTouched:(id)sender
{
    /// 判斷上層視圖是否顯示
    if (self.isOverViewVisible)
    {
        /// 如果顯示上層視圖。當(dāng)點(diǎn)擊按鈕之后dismiss上層視圖
        [self dismissOverViewController];
    }
}

- (void)transformContentWithProgress:(CGFloat)progress
通過交互的進(jìn)度使內(nèi)容變形。

- (void)transformContentWithProgress:(CGFloat)progress
{
    /// 使上層視圖變形
    /// Y軸上的移動(dòng)量
    CGFloat translationY = progress * self.view.frame.size.height;
    /// CGAffineTransformMakeTranslation每次都會(huì)相對(duì)于初始的中心點(diǎn)做變化
    self.overViewContainer.transform = CGAffineTransformMakeTranslation(0, translationY);
#warning 這里為什么要賦值給overViewTopEstimatedValue???
    /// 將上層視圖的位置賦值給overViewTopEstimatedValue
    self.overViewTopEstimatedValue = self.overViewTopConstraint.constant + translationY;
    
    /// --------------------------------------------------------------------------------- ///
    /// 按鈕的形變
    
    /// 上層視圖是否顯示。如果顯示則為progress的絕對(duì)值。如果不顯示則為1 - progress
    /// 當(dāng)上層視圖顯示并且從下向上滑時(shí)。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 并不是常量。當(dāng)過渡完成時(shí)改變
    CGFloat actionButtonScale = newActionButtonSize / self.actionButtonWidthConstraint.constant;
    
    CGAffineTransform actionButtonScaleTransform = CGAffineTransformMakeScale(actionButtonScale, actionButtonScale);

    /// --------------------------------------------------------------------------------- ///
    /// 按鈕的偏移
    
    /// 因?yàn)槲覀兺瑫r(shí)讓按鈕偏移并且縮放。所以我們要將兩個(gè)形變拼起來
#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
過渡動(dòng)畫的時(shí)間。

- (CGFloat)transitionAnimationDuration
{
    return 0.25;
}

- (void)dismissOverViewController
上層視圖dismiss。

- (void)dismissOverViewController
{
    [self finishInteractiveTransition];
}

- (void)cancelInteractiveTransition
取消交互式過渡。

- (void)cancelInteractiveTransition
{
    /// 重新獲取上層視圖相對(duì)頂部的距離。
    self.overViewTopEstimatedValue = self.overViewTopConstraint.constant;
    
    /// 通知主視圖控制器與上層視圖控制器 取消交互式過渡。
    [self.mainViewController cancelInteractiveTransition];
    [self.overViewController cancelInteractiveTransition];
    
    /// 對(duì)弱引用self。避免循環(huán)引用。
    __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)
    {
        /// 完成后通知系統(tǒng)重新獲取狀態(tài)欄是否隱藏。
        [blockSelf setNeedsStatusBarAppearanceUpdate];
    };
    
    /// 執(zhí)行動(dòng)畫。
    [UIView animateWithDuration:[self transitionAnimationDuration]
                          delay:0
                        options:UIViewAnimationOptionCurveEaseInOut
                     animations:AnimationBlock
                     completion:CompletionBlock];
}

- (void)finishInteractiveTransition
完成交互式過渡。

- (void)finishInteractiveTransition
{
    /// 通知主視圖控制器與上層視圖控制器完成交互式過渡
    [self.mainViewController finishInteractiveTransition];
    [self.overViewController finishInteractiveTransition];
    
    /// 判斷當(dāng)前進(jìn)度。如果上層視圖顯示則為 -1,否則為 1。
    CGFloat progress = (self.isOverViewVisible ? - 1 : 1);
    
    /// 重新獲取上層視圖相對(duì)頂部的距離。
    CGFloat newOverViewTopConstraintConstant =
    (self.isOverViewVisible ?
     -[UIScreen mainScreen].bounds.size.height :
     0);
    
    /// 重新獲取按鈕相對(duì)于底部的距離
    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)
    {
        /// 通過進(jìn)度發(fā)生形變
        [blockSelf transformContentWithProgress:progress];
    };
    
    void (^CompletionBlock)(BOOL finished) = ^void (BOOL finished)
    {
        /// 賦值新的約束
        blockSelf.overViewTopConstraint.constant =
        newOverViewTopConstraintConstant;
        
        blockSelf.actionButtonBottomConstraint.constant =
        newActionButtonBottomConstraintConstant;
        
        blockSelf.actionButtonWidthConstraint.constant =
        newActionButtonWidthConstraintConstant;
        
        /// 獲取新的上層視圖相對(duì)頂部的約束
        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*
手勢觸發(fā)的事件。

- (void)handleGestureRecognizer:(UIPanGestureRecognizer*)gesture
{
    /// 獲取手勢位移(位置的偏移量,所有點(diǎn)都相對(duì)于動(dòng)作起點(diǎn)的距離)。
    CGPoint translation = [gesture translationInView:self.view];
    /// 手勢移動(dòng)的速度。
    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;
   /// 滑動(dòng)距離的上限。
    static CGFloat const kTranslationLimit = 0.30;
    
    /// 通過判斷手勢的不同狀態(tài)來做不同事情。
    switch (gesture.state)
    {
            /// 剛開始拖拽時(shí)。
        case UIGestureRecognizerStateBegan:
            
            self.shouldCompleteTransition = NO;
            self.interactionInProgress = YES;
            
            break;
            /// 開始拖拽。
        case UIGestureRecognizerStateChanged:
            
            /// 判斷是否在交互中。
            if (self.isInteractionInProgress)
            {
                /// 如果滑動(dòng)速度太快。直接完成動(dòng)畫。
                if ((self.isOverViewVisible && velocity.y < -kVelocityLimit) ||
                    (!self.isOverViewVisible && velocity.y > kVelocityLimit))
                {
                    /// 完成過渡。
                    self.interactionInProgress = NO;
                    [self finishInteractiveTransition];
                }
                else
                {
                    /// 當(dāng)手勢完成的時(shí)候動(dòng)畫也會(huì)完成。
                    self.shouldCompleteTransition =
                    (self.isOverViewVisible ?
                     progress < -kTranslationLimit:
                     progress > kTranslationLimit);
                    
                    [self updateInteractiveTransition:progress];
                }
            }
            
            break;
        /// 手勢識(shí)別失敗或者取消的時(shí)候。
        case UIGestureRecognizerStateFailed:
        case UIGestureRecognizerStateCancelled:
            
            /// 如果在交互過程中的就直接取消。
            if (self.isInteractionInProgress)
            {
                self.interactionInProgress = NO;
                [self cancelInteractiveTransition];
            }
            
            break;
        /// 手勢結(jié)束之后。
        case UIGestureRecognizerStateEnded:
            
            /// 判斷是否在交互中。
            if (self.isInteractionInProgress)
            {
                self.interactionInProgress = NO;
                
                /// 判斷是否應(yīng)該完成移動(dòng)。
                if (self.shouldCompleteTransition)
                {
                    /// 如果是。完成交互。
                    [self finishInteractiveTransition];
                }
                else
                {
                    /// 如果不是。取消交互。
                    [self cancelInteractiveTransition];
                }
            }
            
            break;
            
        case UIGestureRecognizerStatePossible:
            // Do nothing
            break;
    }
}

即使這樣讀完了代碼但是還是對(duì)這個(gè)類理解還不夠通透。所以我決定再看一遍。并且重點(diǎn)看看一下疑問的地方。

我想先去搞清楚關(guān)于Transition這塊的幾個(gè)方法到底是做了什么事。
過了兩天。終于搞清楚了這個(gè)類到底做了什么事情。自己的水平不夠。加上又是閱讀別人的代碼真的是好累阿。不過好在學(xué)到了東西。
多說不說。先看看實(shí)現(xiàn)的效果。

Demo.gif

在原來的代碼上做了點(diǎn)修改。實(shí)現(xiàn)了四個(gè)方向的滑動(dòng)。雖然沒有上面的酷炫但是這個(gè)原理是差不多了。
那我們繼續(xù)看代碼吧。這回就直接看核心部分吧。

第一個(gè)核心的部分。

首先應(yīng)該是處理手勢的那部分。
通過三個(gè)方法去處理動(dòng)畫:
分別是:
1. - (void)updateInteractiveTransition:(CGFloat)progress
2. - (void)cancelInteractiveTransition
3. - (void)finishInteractiveTransition
這三個(gè)方法在處理手勢的時(shí)候都有用到。我們通過手勢的不同狀態(tài)與手勢的移動(dòng)距離去判斷分別調(diào)用什么方法。
我們先來看看觸發(fā)手勢時(shí)候發(fā)生了什么。
*- (void)handleGestureRecognizer:(UIPanGestureRecognizer )gesture

- (void)handleGestureRecognizer:(UIPanGestureRecognizer*)gesture
{

    /// 速度的上限。
    static CGFloat const kVelocityLimit = 2000.0;
   /// 滑動(dòng)距離的上限。
    static CGFloat const kTranslationLimit = 0.30;

    /// 獲取手勢位移(位置的偏移量,所有點(diǎn)都相對(duì)于動(dòng)作起點(diǎn)的距離)。
    CGPoint translation = [gesture translationInView:self.view];
    /// 手勢移動(dòng)的速度。
    CGPoint velocity = [gesture velocityInView:self.view];
    
    /// 獲取progress的值。
    CGFloat progress = translation.y / self.view.frame.size.height;

    /// 避免過度向上滑或者過度向下滑。
    /// 先判斷當(dāng)前所在頁面。
    /// 如果是在上層視圖。那么就是只能上滑。向上滑動(dòng) translation.y 為負(fù)數(shù)。所以取值范圍是 -1 到 0。
    /// 如果是在主視圖。道理同上。
    progress =
    (self.isOverViewVisible ?
     fmin(0.0, fmax(-1.0, progress)) :
     fmin(1.0, fmax(0.0, progress)));
    
    /// 通過判斷手勢的不同狀態(tài)來做不同事情。
    switch (gesture.state)
    {
            /// 剛開始拖拽時(shí)。
        case UIGestureRecognizerStateBegan:
            
            self.shouldCompleteTransition = NO;
            self.interactionInProgress = YES;
            
            break;
            /// 開始拖拽。
        case UIGestureRecognizerStateChanged:
            
            /// 判斷是否在交互中。
            if (self.isInteractionInProgress)
            {
                /// 判斷手勢的速度。
                /// 如果滑動(dòng)速度太快。直接完成動(dòng)畫。
                /// 不能用絕對(duì)值去判斷速度。
                /// 如果用絕對(duì)值判斷會(huì)導(dǎo)致在主頁面??焖傧蛏匣瑒?dòng)。也會(huì)觸發(fā)以下代碼。違反交互。
                if ((self.isOverViewVisible && velocity.y < -kVelocityLimit) ||
                    (!self.isOverViewVisible && velocity.y > kVelocityLimit))
                {
                    /// 完成過渡。
                    self.interactionInProgress = NO;
                    [self finishInteractiveTransition];
                }
                else
                {
                    /// 當(dāng)手勢完成的時(shí)候動(dòng)畫也會(huì)完成。
                    /// 通過已經(jīng)移動(dòng)過的比例去判斷是否需要完成動(dòng)畫。當(dāng)超過 kTranslationLimit 設(shè)置 shouldCompleteTransition 為YES。
                    self.shouldCompleteTransition =
                    (self.isOverViewVisible ?
                     progress < -kTranslationLimit:
                     progress > kTranslationLimit);

                    /// 通過進(jìn)度去更新過渡動(dòng)畫。
                    [self updateInteractiveTransition:progress];
                }
            }
            
            break;
        /// 手勢識(shí)別失敗或者取消的時(shí)候。
        case UIGestureRecognizerStateFailed:
        case UIGestureRecognizerStateCancelled:
            
            /// 如果在交互過程中的就直接取消。
            if (self.isInteractionInProgress)
            {
                self.interactionInProgress = NO;
                [self cancelInteractiveTransition];
            }
            
            break;
        /// 手勢結(jié)束之后。
        case UIGestureRecognizerStateEnded:
            
            /// 判斷是否在交互中。
            if (self.isInteractionInProgress)
            {
                self.interactionInProgress = NO;
                
                /// 判斷是否應(yīng)該完成移動(dòng)。
                if (self.shouldCompleteTransition)
                {
                    /// 如果是。完成交互。
                    [self finishInteractiveTransition];
                }
                else
                {
                    /// 如果不是。取消交互。
                    [self cancelInteractiveTransition];
                }
            }
            
            break;
            
        case UIGestureRecognizerStatePossible:
            // Do nothing
            break;
    }
}

然后我們?cè)倏纯赐瓿墒謩輨?dòng)畫的部分。
這個(gè)方法里面我刪掉了很多代碼。這樣可以方便學(xué)習(xí)。如果想用做一些很酷炫的動(dòng)畫就要靠各位自行發(fā)揮了。
- (void)finishInteractiveTransition

- (void)finishInteractiveTransition
{
    /// 因?yàn)槭侵苯油瓿蓜?dòng)畫所以我們 progress 我們直接寫完成的值就好了。
    /// 當(dāng)當(dāng)前頁面是上層視圖的時(shí)候。是向上滑動(dòng)進(jìn)入主頁面。向上滑動(dòng)為負(fù)。所以是-1。
    /// 當(dāng)當(dāng)前頁面是主視圖的時(shí)候。道理同上。
    CGFloat progress = (self.isOverViewVisible ? - 1 : 1);
    
    /// 通過判斷當(dāng)前頁面來獲取新的約束。
    /// 當(dāng)當(dāng)前頁面是上層視圖的時(shí)候。我們需要完成上層視圖到主視圖的過渡。所以約束是 負(fù)的屏幕高度。
    /// 當(dāng)當(dāng)前頁面是主視圖的時(shí)候。道理同上。
    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];
        
        /// 獲得當(dāng)前顯示的視圖。
        weakSelf.overViewVisible = !weakSelf.isOverViewVisible;
        
    };
    
    /// 執(zhí)行動(dòng)畫
    [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];
}

還有最后一個(gè)通過進(jìn)度更新過渡。
這個(gè)方法是在手勢變化的時(shí)候調(diào)用的。通過進(jìn)度的改變我們也隨之改變形變。但是具體的事情卻不是在這個(gè)方法里做的。單獨(dú)有一個(gè)方法去做這個(gè)事情。
- (void)updateInteractiveTransition:(CGFloat)progress

- (void)updateInteractiveTransition:(CGFloat)progress
{
    [self setNeedsStatusBarAppearanceUpdate];
    
    /// 形變
    [self transformContentWithProgress:progress];
}

第二個(gè)核心的部分。

第二個(gè)核心的部分就是處理形變的部分。這個(gè)方法在完成動(dòng)畫更新動(dòng)畫中都有用到。視圖的下滑之類的UI上的變化都是在這里做的。
這一部分我也簡化了很多。方便各位去學(xué)習(xí)。
- (void)transformContentWithProgress:(CGFloat)progress

- (void)transformContentWithProgress:(CGFloat)progress
{
    /// 通過進(jìn)度獲取移動(dòng)量
    CGFloat translationY = progress * self.view.frame.size.height;
    /// 使上層視圖滑動(dòng)
    self.overViewContrainer.transform = CGAffineTransformMakeTranslation(0, translationY);
    /// 獲取當(dāng)前的距離。通過這個(gè)參數(shù)去判斷狀態(tài)欄是否消失
    self.overViewTopEstimatedValue = self.overViewTopConstraint.constant + translationY;
}

總結(jié)

  看了這份代碼之后。一開始看看的很懵逼。雖然這里面沒有用到一切比較奇怪的東西。用到的方法什么的都還是看的懂的。但是還是沒明白作者的思路。計(jì)算機(jī)最難的是思想嘛。
  于是之后決定還是自己實(shí)踐一下好了。從最簡單的開始。然后一點(diǎn)點(diǎn)加?xùn)|西上去。最后就有了上面那個(gè)四個(gè)方向都可以移動(dòng)的東西。其實(shí)整個(gè)項(xiàng)目也挺簡單。就是做了兩件事。第一件事就是形變。第二件是就是更新約束刷新頁面。但是這里面有很多細(xì)節(jié)的地方一開始是想不到的。
  感謝這份代碼。

最后

如果有些的不好的地方請(qǐng)各位直接說。
如果有不明白的地方也請(qǐng)各位直接問。
希望共同進(jìn)步。希望iOS這個(gè)圈子越來越好。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評(píng)論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,315評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評(píng)論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,189評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,388評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,613評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評(píng)論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,112評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,334評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容

  • 翻譯自“View Controller Programming Guide for iOS”。 1 彈出視圖控制器...
    lakerszhy閱讀 3,590評(píng)論 2 20
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,548評(píng)論 6 30
  • 前言的前言 唐巧前輩在微信公眾號(hào)「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各項(xiàng)指標(biāo)...
    VincentHK閱讀 5,403評(píng)論 3 44
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 3,905評(píng)論 1 22
  • 1.對(duì)于類繼承的內(nèi)存占用問題,derived類擁有base類的數(shù)據(jù)所占的內(nèi)存空間,并同時(shí)擁有自己數(shù)據(jù)成員所占的內(nèi)存...
    0ffa31abb8f8閱讀 158評(píng)論 0 0