#iOS development # imitation Snapchat page view switch

Because I recently used Snapchat. Found that the top, bottom, left and right slides of his home page can be switched into a new view. I think it’s fun. So I decided to write one. The is
Think a lot of solutions, still can not be very good to do this effect. So I borrowed some Demo from the internet. Very stiff. Copy, there is no way to learn this code. (to)
So, think or study while writing an article, make a record. In this way, the process of learning may be clearer.

Effect is as follows

#iOS development # imitation Snapchat page view switch
is not named.Gif

Okay。 Let’s get to the point.

1. let’s take a look at the structure of the project first.

#iOS development # imitation Snapchat page view switch
WX20170319-181717@2x.png

2. let’s take a look at Storyboard.

#iOS development # imitation Snapchat page view switch
WX20170319-200906@2x.png

This Storyboard uses a control that I’ve never used before.
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.

The above paragraph intercepts Apple’s developer platform. Roughly, ContainerViewController can add more than one view controller in a container view controller. Here is an article on this control analysis quite well. Everyone can see.

The view structure in ContainerViewCotroller is like this. It contains two ContainerView, corresponding to two other VC’s view. Segue is also used here. And gave it to Identifier. Then the instructions will be dealt with by Segue later.

#iOS development # imitation Snapchat page view switch
WX20170319-203130@2x.png

3. it’s time to look at the code.

Let’s look at the simplest ContainerChildViewController.
defines three virtual functions. Because we didn’t implement the three method, so we didn’t look good either. What exactly are these three methods? I guess by the name of the method, I guess I could do it.

Just update interactive transition - (void) updateInteractiveTransition: (CGFloat) progress; / / / cancel interactive transition (void) - cancelInteractiveTransition; / / / interactive transition - (void) finishInteractiveTransition;

Then look at the heavyweight ContainerViewController.

ContainerViewController.h

#import < UIKit/UIKit.h> @interface; ContainerViewController: UIViewController / / / transition animation length @property (nonatomic, assign) CGFloat transitionAnimationDuration; / / / upper view is visible (read-only) @property (nonatomic, assign, getter=isOverViewVisible, readonly) BOOL overViewVisible @property (nonatomic; / / / interactive schedule assign, getter=isInteractionInProgress, readonly, BOOL, interactionInProgress); / / / upper view disappear - (void) dismissOverViewController; / / / upper view - (void) presentOverViewController; @end

ContainerViewController.m

First look at the implementation of the document declaration of the properties.

Just don't know yet what is the use of the constant static CGFloat const kActionButtonDisplacement = 55; / / / button minimum size static kActionButtonSmallSize = 50 CGFloat const @interface ContainerViewController (property); / / / upper view view controller @ (nonatomic, strong) ContainerChildViewController *overViewController @property; / / / view controller main view (nonatomic, strong) ContainerChildViewController *mainViewController; just upper view is visible (read and write) @property (nonatomic, assign, getter=isOverViewVisible, readwrite) BOOL overViewVisible; / / / interactive schedule (read and write) @property (nonatomic, assign, getter=isInteractionInProgress, readwrite) BOOL interactionInProgress; / / / is over (nonatomic, as @ property Sign BOOL shouldCompleteTransition @property); / / / upper view container view (nonatomic, weak) IBOutlet UIView *overViewContainer; @property / / / view of the container main view (nonatomic, weak) IBOutlet UIView *mainViewContainer; @property / / / in the middle of the circle button (nonatomic, weak) IBOutlet UIButton *actionButton; / / / upper view distance from the top of the container (for initialization constraint the negative screen height) @property (nonatomic, weak) IBOutlet NSLayoutConstraint *overViewTopConstraint; / / / button at the bottom of the circle in the middle distance constraint (initialized to 50) and @property (nonatomic, weak) IBOutlet NSLayoutConstraint *actionButtonBottomConstraint; / / / circle in the middle of the button width constraint (initialized to 50) and @property (nonatomic, weak) IBOutlet NSLayoutConstraint *actionBut TonWidthConstraint; intermediate / / / the initialization of the circle button at the bottom of the distance constraint (initialized to 50) and @property (nonatomic, assign) CGFloat originalActionButtonBottomConstraintConstant; / / / initialize the circle in the middle of the button width constraint (initialized to 50) and @property (nonatomic, assign) CGFloat originalActionButtonWidthConstraintConstant; the estimated value of @property / / / upper view distance from the top of the container (nonatomic, assign CGFloat overViewTopEstimatedValue; @end)

Okay。 Next, we have a way, a way to see. It’s convenient for us to study.

– (void) awakeFromNib
initializes overViewVisible and overViewTopEstimatedValue.

- (void) awakeFromNib {[super awakeFromNib]; self.overViewVisible = NO; / / / can also use a self.overViewTopEstimatedValue = self.overViewTopConstraint.constant; self.overViewTopEstimatedValue = -[UIScreen for mainScreen].bounds.size.height;}

– (void) viewDidLoad
view to add drag gesture and initialization overViewTopConstraint,
originalActionButtonBottomConstraintConstant,
originalActionButtonWidthConstraintConstant.

- (void) viewDidLoad {[super viewDidLoad]; / / / add drag gesture [self addGestureRecogniserOnView:self.view]; self.overViewTopConstraint.constant -[UIScreen = mainScreen].bounds.size.height; self.originalActionButtonBottomConstraintConstant = self.actionButtonBottomConstraint.constant; self.originalActionButtonWidthConstraintConstant = self.actionButtonWidthConstraint.constant;}

– (BOOL) prefersStatusBarHidden
sets the hidden status bar. Because of this container, there are two other view controllers in the view controller. So hide the status bar and write it here. Here’s a way to call setNeedsStatusBarAppearanceUpdate when you drag and drop. So the system calls prefersStatusBarHidden to retrieve whether to hide the status bar.

- (BOOL) prefersStatusBarHidden {/ / / set the status bar shows the range of static CGFloat const kVisibleStatusBarRange = -10.0; / / / when the view controller from top status bar is hidden from return when only 10 (self.overViewTopEstimatedValue < kVisibleStatusBarRange);}

– – (void) prepareForSegue: (UIStoryboardSegue*) segue sender: (ID) sender
notifies the view controller that it is about to jump. Segue contains jump information.

- (void) prepareForSegue: (UIStoryboardSegue*) segue sender: (ID sender) {/ / / by segue.identifier to determine the VC (jump to "embedOver" if [segue.identifier isEqualToString:@ ") {ContainerChildViewController *viewController = segue.destinationViewController; viewController.containerViewController = self; self.overViewController = segue.destinationViewController;} else (if [segue.identifier isEqualToString:@" embedMain "]) {ContainerChildViewController = segue.destinationViewController * viewController; viewController.containerViewController = self; self.mainViewController = segue.destinationViewController;}}

Click event of (IBAction) actionButtonTouched: (ID) sender
button.

- (IBAction) actionButtonTouched: (ID sender) {/ / / to determine whether to display the upper view if (self.isOverViewVisible) {/ / / if the upper view display. After clicking the button, the dismiss upper layer view [self dismissOverViewController];}}

– – (void) transformContentWithProgress: (CGFloat) progress
makes the content deformed by interactive progress.

- (void) transformContentWithProgress: (CGFloat progress) {/ / / / / / make the upper view morphing Y axis mobile CGFloat translationY = progress * self.view.frame.size.height; / / / CGAffineTransformMakeTranslation every time relative to the center point of the initial changes in self.overViewContainer.transform = CGAffineTransformMakeTranslation (0, translationY); #warning here why the assignment to overViewTopEstimatedValue??? / / / location assignment the upper view to overViewTopEstimatedValue self.overViewTopEstimatedValue = self.overViewTopConstraint.constant + translationY; / / / / / / / / / / / / --------------------------------------------------------------------------------- button deformation of the upper view is displayed. If shown, the absolute value of progress. If you do not display is 1 - progress / / / when the upper view display and from bottom to top. The value of progress from 0 to -1 NSLog (@ "%lf", progress); CGFloat value = (self.isOverViewVisible? Fabs (Progress): 1 - progress); / / / new size button. Maximum 80. Minimum 50. CGFloat newActionButtonSize = kActionButtonSmallSize + value * (self.originalActionButtonWidthConstraintConstant - kActionButtonSmallSize); / / / self.actionButtonWidthConstraint.constant here is not constant. When changing the CGFloat actionButtonScale = newActionButtonSize / self.actionButtonWidthConstraint.constant transition is complete; CGAffineTransform actionButtonScaleTransform = CGAffineTransformMakeScale (actionButtonScale, actionButtonScale); / / / / / / / / / / / / --------------------------------------------------------------------------------- shift button because we also let the button shift and zoom. So we should keep two deformation together #warning. Why is divided by 2??? CGFloat = compensationBecauseOfMakingScale (self.actionButtonWidthConstraint.constant - newActionButtonSize) / 2; CGFloat = actionButtonTranslationY (Progress * kActionButtonDisplacement) + compensationBecauseOfMakingScale; CGAffineTransform actionButtonTranslateTransform = CGAffineTransformMakeTranslation (0, actionButtonTranslationY); CGAffineTransform actionButtonTransform = CGAffineTransformConcat (actionButtonScaleTransform, actionButtonTranslateTransform); self.actionButton.transform = actionButtonTransform;}

– (CGFloat) transitionAnimationDuration
transition animation time.

- - (CGFloat) transitionAnimationDuration {return 0.25;}

– (void) dismissOverViewController
upper layer view dismiss.

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

– (void) cancelInteractiveTransition
cancels interactive transition.

- (void) cancelInteractiveTransition {/ / / regain the upper view at the top of the relative distance. Self.overViewTopEstimatedValue = self.overViewTopConstraint.constant; / / / notify the main view controller and upper view controller to cancel the interactive transition. [self.mainViewController cancelInteractiveTransition]; [self.overViewController cancelInteractiveTransition]; / / / of WeakReference self. Avoid circular references. __weak typeof (self) blockSelf = self; void (^AnimationBlock) (void) (void = ^void) {/ / / the deformation reduction. BlockSelf.overViewContainer.transform = CGAffineTransformIdentity; blockSelf.actionButton.transform = CGAffineTransformIdentity;}; void (^CompletionBlock) (BOOL finished) = ^void (BOOL finished) {/ / / completion notification system retrieves state bar is hidden. [blockSelf setNeedsStatusBarAppearanceUpdate];}; / / / animation execution. [UIView animateWithDuration:[self transitionAnimationDuration] delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:AnimationBlock completion:CompletionBlock];}

– (void) finishInteractiveTransition
complete interactive transition.

- (void) finishInteractiveTransition {/ / / notify the main view controller and upper view controller [self.mainViewController finishInteractiveTransition] [self.overViewController interactive transition; finishInteractiveTransition]; / / / to determine the current progress. If the upper view is displayed, -1, otherwise 1. CGFloat progress = (self.isOverViewVisible? - 1: 1); / / / to get upper view at the top of the relative distance. CGFloat = newOverViewTopConstraintConstant (self.isOverViewVisible? -[UIScreen mainScreen].bounds.size.height: 0); / / / retrieve button relative to the distance from the bottom of CGFloat = newActionButtonBottomConstraintConstant (self.isOverViewVisible? Self.originalActionButtonBottomConstraintConstant: self.originalActionButtonBottomConstraintConstant - kActionButtonDisplacement); / / / retrieve button width CGFloat (newActionButtonWidthConstraintConstant = self.isOverViewVisible? Self.originalActionButtonWidthConstraintConstant: kActionButtonSmallSize); __weak typeof (self) blockSelf = self (void (^AnimationBlock); void (void) = ^void) {/ / / the schedule occur The deformation of [blockSelf transformContentWithProgress:progress];}; void (^CompletionBlock) (BOOL finished) = ^void (BOOL finished) {/ / / new assignment constraint blockSelf.overViewTopConstraint.constant = newOverViewTopConstraintConstant; blockSelf.actionButtonBottomConstraint.constant = newActionButtonBottomConstraintConstant; blockSelf.actionButtonWidthConstraint.constant = newActionButtonWidthConstraintConstant; / / / get the upper view at the top of the relatively new constraint blockSelf.overViewTopEstimatedValue = newOverViewTopConstraintConstant; #warning here why deformation reduction??? / / / reduction the deformation of blockSelf.overViewContainer.transform = CGAff IneTransformIdentity; blockSelf.actionButton.transform = CGAffineTransformIdentity; / / / [blockSelf.view to refresh the page layoutIfNeeded]; blockSelf.overViewVisible = blockSelf.isOverViewVisible; [blockSelf setNeedsStatusBarAppearanceUpdate];}! [UIView animateWithDuration:[self transitionAnimationDuration] delay:0 options:UIViewAnimationOptionCurveEaseInOut; animations:AnimationBlock completion:CompletionBlock];}

– (void) addGestureRecogniserOnView: (UIView*) view
adds gestures to View.

- (void) addGestureRecogniserOnView: (UIView*) view UIPanGestureRecognizer [[UIPanGestureRecognizer alloc] initWithTarget:self {*panGesture = action:@selector (handleGestureRecognizer:)]; [view addGestureRecognizer:panGesture];}

– (void) handleGestureRecognizer: (UIPanGestureRecognizer*) gesture
gesture triggered events.

- (void) handleGestureRecognizer: (UIPanGestureRecognizer* gesture) {/ / / get the gesture displacement (offset position, all points relative to the starting point of the action distance). CGPoint translation = [gesture translationInView:self.view]; / / / gesture moving speed. CGPoint velocity = [gesture velocityInView:self.view]; / / / get the value of progress. CGFloat progress = translation.y / self.view.frame.size.height; / / / to avoid excessive slip or excessive downward slide. Progress = (self.isOverViewVisible? Fmin (0, Fmax (-1.0, progress): Fmin (1), Fmax (0, progress))); / / NSLog (@ "%f", progress); / / NSLog (@ "%f", translation.y); / / NSLog (@ "%f", velocity.y); / / / the speed limit. Static CGFloat const kVelocityLimit = 2000; / / / maximum sliding distance. Static CGFloat const kTranslationLimit = 0.30; / / / state through different gestures to do different things to judge. Switch (gesture.state) {/ / / first drag. Case UIGestureRecognizerStateBegan: self.shouldCompleteTransition = NO; self.interactionInProgress = YES; break; / / / start to drag. Case UIGestureRecognizerStateChanged: / / / to determine whether in the interaction. If (self.isInteractionInProgress) {/ / / if the sliding speed is too fast. Direct animation. (if (self.isOverViewVisible & & velocity.y < -kVelocityLimit (self.isOverViewVisible) ||! & & velocity.y > kVelocityLimit) {/ / / transition). Self.interactionInProgress = NO; [self finishInteractiveTransition];} else {/ / / when gestures animation will be complete. Self.shouldCompleteTransition = (self.isOverViewVisible? Progress < -kTranslationLimit: progress; > kTranslationLimit); [self updateInteractiveTransition:progress] break;}}; / / / when failure or cancellation of gesture recognition. Case UIGestureRecognizerStateFailed: case UIGestureRecognizerStateCancelled: / / / if in the interactive process is directly canceled. If (self.isInteractionInProgress) {self.interactionInProgress = NO; [self cancelInteractiveTransition] break;}; / / / after the end of the gesture. Case UIGestureRecognizerStateEnded: / / / to determine whether in the interaction. If (self.isInteractionInProgress) {self.interactionInProgress = NO; / / / to determine whether it should complete mobile. If (self.shouldCompleteTransition) {/ / / if. Complete interaction. [self finishInteractiveTransition];} else {/ / / if not. Cancel interaction. [self cancelInteractiveTransition] break case UIGestureRecognizerStatePossible:;}}; / / Do nothing break;}}

Even if you finish reading the code, you still don’t understand the class well enough. So I decided to see it again. And focus on the places of doubt.

I want to find out what the methods of Transition are.
after two days. Finally figured out what the class had done. Your level is not enough. Plus reading other people’s code, really tired. Fortunately, I learned something.
doesn’t say much. First look at the implementation effect.

#iOS development # imitation Snapchat page view switch
Demo.gif

made some changes on the original code. Sliding in four directions is achieved. Although not cool above but this principle is almost the same.
let’s go on with the code. Let’s look directly at the core this time.

The first core part.

First of all, it should be the part of the gesture processing.
handles animation in three ways:
, respectively:

1 - (void) updateInteractiveTransition: (CGFloat) progress** 2 - (void) cancelInteractiveTransition** 3 - (void) finishInteractiveTransition**

These three methods are useful when dealing with hand gestures. We use the different states of gestures and gestures to determine what distances are called.
, let’s start with what happens when triggered gestures.
– (void) handleGestureRecognizer: (UIPanGestureRecognizer *) gesture

- (void) handleGestureRecognizer: (UIPanGestureRecognizer* gesture) {/ / / speed limit. Static CGFloat const kVelocityLimit = 2000; / / / maximum sliding distance. Static CGFloat const kTranslationLimit = 0.30; / / / get gesture displacement (offset position, all points relative to the starting point of the action distance). CGPoint translation = [gesture translationInView:self.view]; / / / gesture moving speed. CGPoint velocity = [gesture velocityInView:self.view]; / / / get the value of progress. CGFloat progress = translation.y / self.view.frame.size.height; / / / to avoid excessive slip or excessive downward slide. First determine the current page / / /. Just if it is in the upper view. So it's only slippery. Slide upward, translation.y is negative. So the range is from -1 to 0. Just if it is in the main view. Same reason. Progress = (self.isOverViewVisible? Fmin (0, Fmax (-1.0, progress): Fmin (1), Fmax (0, progress))); / / / state through different gestures to do different things to judge. Switch (gesture.state) {/ / / first drag. Case UIGestureRecognizerStateBegan: self.shouldCompleteTransition = NO; self.interactionInProgress = YES; break; / / / start to drag. Case UIGestureRecognizerStateChanged: / / / to determine whether in the interaction. If (self.isInteractionInProgress) {/ / / to determine the speed of gesture. Just if the sliding speed is too fast. Direct animation. Can not be used to judge the absolute value of changing speed. If the absolute value judgment will always result in the main page. Quick upward slide. The following code is also triggered. Violation interaction. (if (self.isOverViewVisible & & velocity.y < -kVelocityLimit (self.isOverViewVisible) ||! & & velocity.y > kVelocityLimit) {/ / / transition). Self.interactionInProgress = NO; [self finishInteractiveTransition];} else {/ / / when gestures animation will be complete. The proportion of mobile / / / has to decide whether it is necessary to complete the animation. When more than kTranslationLimit is set, shouldCompleteTransition is YES. Self.shouldCompleteTransition = (self.isOverViewVisible? Progress < -kTranslationLimit: progress; > kTranslationLimit); / / / through the schedule to update the transition animation. [self updateInteractiveTransition:progress] break;}}; / / / when failure or cancellation of the gesture recognition. Case UIGestureRecognizerStateFailed: case UIGestureRecognizerStateCancelled: / / / if in the interactive process is directly canceled. If (self.isInteractionInProgress) {self.interactionInProgress = NO; [self cancelInteractiveTransition] break;}; / / / after the end of the gesture. Case UIGestureRecognizerStateEnded: / / / to determine whether in the interaction. If (self.isInteractionInProgress) {self.interactionInProgress = NO; / / / to determine whether it should complete mobile. If (self.shouldCompleteTransition) {/ / / if. Complete interaction. [self finishInteractiveTransition];} else {/ / / if not. Cancel interaction. [self cancelInteractiveTransition] break case UIGestureRecognizerStatePossible:;}}; / / Do nothing break;}}

Then we’ll look at the part of the gesture animation.
this method, I deleted a lot of code. This will make it easier for you to learn. If you want to do some very cool animation will rely on you to play.
– (void) finishInteractiveTransition

- (void) finishInteractiveTransition {/ / / because it is directly complete the animation so we progress we write directly to the value of a good finish. When the page is / / / upper view. Slide upward into the main page. Sliding upward is negative. So it's -1. When the page is when / / / the main view. Same reason. CGFloat progress = (self.isOverViewVisible? - 1: 1); / / / by judging the current page to obtain a new constraint. When the page is / / / upper view. We need to complete the transition from the top view to the main view. So the constraint is the negative screen height. When the page is when / / / the main view. Same reason. CGFloat = newOverViewTopConstraintConstant (self.isOverViewVisible? -[[UIScreen mainScreen] bounds].size.height: 0); __weak typeof (self) weakSelf = self; void (^AnimationBlock) (void) (void = ^void) {/ / / deformation. [weakSelf transformContentWithProgress:progress];}; void (^CompletionBlock) (BOOL finished) = ^void (BOOL finished) {/ / / get the new constraint value weakSelf.overViewTopConstraint.constant = newOverViewTopConstraintConstant; / / / reduction of deformation. WeakSelf.overViewContrainer.transform = CGAffineTransformIdentity; / / / refresh page. Make the constraint effective. [weakSelf.view layoutIfNeeded]; / / / get the currently displayed view. WeakSelf.overViewVisible = weakSelf.isOverViewVisible;}; / / /! [UIView animateWithDuration:[self transitionAnimationDuration] delay:0 options:UIViewAnimationOptionCurveEaseInOut animation executive animations:AnimationBlock completion:CompletionBlock];}

And part of the transition. The code for this part of
is simple. If the code above is understood, just look at it.
this part of the code is to restore the deformation back.
– (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) {/ / / the deformation reduction of weakSelf.overViewContrainer.transform = CGAffineTransformIdentity; weakSelf.actionButton.transform = CGAffineTransformIdentity;}; void (^CompletionBlock) (BOOL = void ^ (finished) BOOL finished) {[weakSelf}; [UIView setNeedsStatusBarAppearanceUpdate]; animateWithDuration:[self transitionAnimationDuration] delay:0 optio Ns:UIViewAnimationOptionCurveEaseInOut animations:AnimationBlock completion:CompletionBlock];}

There’s also the last one to go through the progress update transition.
this method is called when gestures change. Through the change of schedule, we also change the deformation. But the specific things are not done in this way. There is a way to do it alone.
– (void) updateInteractiveTransition: (CGFloat) progress

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

The second core part.

The second part of the core is the part that deals with deformation. This method is useful in both animation and animation updates. Changes in the view, and the changes in UI, are all done here. I’ve simplified this part of
a lot. It is convenient for you to study.
– (void) transformContentWithProgress: (CGFloat) progress

- (void) transformContentWithProgress: (CGFloat progress) {/ / / get mobile CGFloat translationY = progress * self.view.frame.size.height through the schedule; / / / the upper view sliding self.overViewContrainer.transform = CGAffineTransformMakeTranslation (0, translationY); / / / get the current distance. Use this parameter to determine whether the status bar disappears, self.overViewTopEstimatedValue = self.overViewTopConstraint.constant + translationY,}

summary

After reading the code. A look at the very beginning Meng force. Although there is nothing strange about it. The method used, what is still read, understand. But still do not understand the author's ideas. The hardest thing about computers is thinking. Then decided to practice it yourself. Start with the simplest. Then add a little bit of stuff. Finally, there are four directions in the above can move things. In fact, the whole project is simple. Just do two things. The first thing is deformation. The second thing is to update the constraint to refresh the page. But there was a lot of detail in it, at first it was unexpected. Thanks for the code.

Last

If there are some bad places, please say it directly.
if you don’t understand it, please ask it directly.
wants to make progress together. I hope iOS is getting better and better.