iOS 11 App Store Transition
Just another attempt to simulate App Store’s Card transition:
Implementation details are available in slides under the MobileConf
folder, in the last section (‘5 Phases of Interaction’).
My previous repo is here. The new one is a total rewrite. It has better effect/performance, better code organization, and has fixes for some issues found in the previous repo.
All is done with native APIs (UIViewControllerAnimatedTransitioning
, etc.), no external libraries. This is NOT a library to install or ready to use, it’s an experiementation/demo project to show how such App Store presentation might work.
Transition/PresentCardAnimator
: Animation code for presentation,Transition/DismissCardAnimator
: Animation code for dismissal,Transition/CardPresentationController
: Blur effect view and overall aspect of the presentation,ViewControllers/CardDetailViewController
: Interactive shrinking pan gesture code.ViewControllers/HomeViewController
: Home page, preparation code before presentation is at collectionView’s didSelect delegate method.Misc/StatusBarAnimatableViewController
: Status bar animation (quick & dirty though, need to inherit from this parent vc.)Here are some implementation details:
collectionView.delaysContentTouch = false
(it’s true
by default, to prevent premature cell highlighting, e.g., on table view).touchesBegan
and touchesCancellled/Ended
..allowsUserInteraction
is needed in animation options, so that you can always continue to scroll immediately while the unhighlighted animation is taking place.cardCell.layer.removeAllAnimations
. Also prevent any future highlighting animation with a flag.cardCell.layer.presentation().frame
, then convert it to screen coordinates.CardDetailViewController
)'s view and position it with AutoLayout at the original card cell’s position.// Animate constraints on the same view with different animation curves
UIView.animate(withDuration: 0.6 * 0.8) {
self.widthAnchor.constant = 200
self.heightAnchor.constant = 320
self.targetView.layoutIfNeeded()
}
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: {
self.topAnchor.constant = -200
self.targetView.layoutIfNeeded()
}) { (finished) in ... }
scrollView
’s pan.
dragDownMode
begins, save the starting drag point to calculate dragging progress, as it usually begins on .change
, not .began
.UIScreenEdgePanGestureRecognizer
.dragDownPan.require(toFail: leftEdgePan)
scrollView.panGestureRecognizer.require(toFail: leftEdgePan)
a.require(toFail: b)
is confusingly named. It actually means a
must wait for b
to fail first before it can start. So just read it like a.wait(toFail: b)
when you see that.var draggingDownToDismiss = false // A flag to check mode
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if draggingDownToDismiss || (scrollView.isTracking && scrollView.contentOffset.y < 0) {
draggingDownToDismiss = true
scrollView.contentOffset = .zero // * This is important to make it stick at the top
}
scrollView.showsVerticalScrollIndicator = !draggingDownToDismiss
}
UIViewPropertyAnimator
:let shrinking = UIViewPropertyAnimator(duration: 0, curve: .linear, animations: {
self.view.transform = .init(scaleX: 0.8, y: 0.8)
self.view.layer.cornerRadius = 16
})
shrinking.pauseAnimation()
progress/fractionComplete
of the animator by understaning when corresponding gestures are began! Use a combination of gesture.translation(in: _)
and gesture.location(in: nil)
, etc.shrinking!.pauseAnimation()
shrinking!.isReversed = true
// Disable gesture until reverse closing animation finishes.
gesture.isEnabled = false
shrinking!.addCompletion { [unowned self] (pos) in
self.didCancelDismissalTransition()
gesture.isEnabled = true
}
shrinking!.startAnimation()
If you’re interested in a more visual guide to ‘5 Phases of Interaction’, checkout MobileConf/slides