GithubHelp home page GithubHelp logo

appstoreios11interactivetransition's Introduction

iOS 11 App Store Transition

Just another attempt to simulate App Store's Card transition:

demo

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.

Overview

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.

Features (that you might not know exist)

  • Status bar animation
  • Very responsive card cell highlighting animation
  • Card bouncing up animation (Two animations at work: spring for moving to place, linear for card expansion)
  • Damping and duration depends on how far the card needs to travel on screen
  • Drag down to dismiss when reach the top of content page
    • Scroll back up to cancel the dismissal!
  • Left screen edge pan to dismiss

Interesting code

  • 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.)

TODOs/Defects

  • Fix layout/top area on iPhone X
  • Support continuous video/gif playing from home to detail page (This requires some work to use a whole view controller as a card cell content from the first page!)
  • Add blurry close button at the top right of detail page
  • Perfecting card bouncing up animation (still can't figure out how to achieve that smooth bounciness like the App Store.)

Here are some implementation details:

5 Phases of Interaction

1. Highlighting

  • The card cell needs to be very responsive to touch, so we must set collectionView.delaysContentTouch = false (it's true by default, to prevent premature cell highlighting, e.g., on table view).
  • Put scaling down animation in 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.

2. Before Presenting

  • Need to stop all animations, using cardCell.layer.removeAllAnimations. Also prevent any future highlighting animation with a flag.
  • Get current card frame (that is currently animated scaling down) with cardCell.layer.presentation().frame, then convert it to screen coordinates.
  • Get presented view controller (CardDetailViewController)'s view and position it with AutoLayout at the original card cell's position.
  • Hide original card cell's position.

3. Presenting*

  • Simply animating frame/AutoLayout constraints with Spring animation won't work.
  • Best alternative (that I can think of right now) is to animate with two different animation curves: linear for card expansion, and spring for moving up to place.

Wait, how to animate different AutoLayout constraints with different animation curves?

  • Turns out you can animate different constraints in two animation blocks, like this:
// 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 ... }

4. Interactively Dismissing

  • Need to handle left screen edge pan and drag down pan.
    • For drag down we'll add a new pan gesture. Make it able to detect simultaneously with scrollView's pan.
      • This means we need to carefully handle when the dragDownMode begins, save the starting drag point to calculate dragging progress, as it usually begins on .change, not .began.
    • For left edge pan just use UIScreenEdgePanGestureRecognizer.
  • Give priority to left edge pan by:
dragDownPan.require(toFail: leftEdgePan)
scrollView.panGestureRecognizer.require(toFail: leftEdgePan)
  • Note that the method 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.
  • To smoothly transition to shrinking mode when reach the top of scroll view, just use scrollView's delegate:
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
}
  • Handle shrinking on drag using 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()
  • Carefully handle 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.
  • Reverse animation on drag down pan gesture ended/cancelled:
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()

5. Dismissing

  • Just do animation back to original cell's position.

If you're interested in a more visual guide to '5 Phases of Interaction', checkout MobileConf/slides

Weird Bugs

  • This is hard to explain, but there's some space on card view top edge during presentation despite constant 0 of their topAnchors. What's weirder is that it's already unintentionally fixed by setting a top anchor's constant to value >= 1 (or <= -1). Setting it to any values in the range of (-1, 1) doesn't work.
  • Blur effect view in the back seems to not showing up properly when we're in dismissal pan mode (especially on iOS 12). But sometimes it happens on iOS 11 too! Proobably due to my incomplete understanding of viewWillAppear/beginTransition/redraw life cycle.

appstoreios11interactivetransition's People

Contributors

aunnnn avatar choiminseok avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

appstoreios11interactivetransition's Issues

Support continuous video/gif playing from home to detail page (This requires some work to use a whole view controller as a card cell content from the first page!)

Hey @aunnnn

I opened this thread to have more insight by discussing the idea behind what Apple might have done. And to track the updates if we could figure out the same and implement.

Do u think they are hiding part of the whole detail page in the listing cell?
because, If we observe very carefully, their video/gif from the listing continues even after they are expanded to the full screen (detail page) & on dismissal shrinks back to where it came from with hiding rest of the detail part.

If we could make the whole view controller as a part of the cell, would that be feasible?
We could think about how can we achieve this one rendering part of view controller into the card cell and on-demand execute the full-screen rendering.

Presenting on top CardDetailViewController

As you can see from the screenshot below, there is a glitch when you present a CardDetailViewController on top of another CardDetailViewController

UIApplication.topViewController()?.present(cardDetailViewController, animated: true, completion: nil)

img_0042

Highlight animation clashes with MDCSwipeToChoose

Great job on this animation. I'm trying to incorporate this with MDCSwipeToChoose. When I press and pan the view, the unhighlight animation doesn't seem to put the view back in place.

Also when pressing and holding, the card highlights then unhighlights (the very last part of the video, I'm still holding onto the card while it unhighlights)

PS none of these issues happen with a collectionview cell. I'm pretty sure MDCSwipeToChoose is interfering because of its gesture recognizers.

RPReplay_Final1549989268.MP4.zip

Awesome job!!! Let's address the iPhone X

Greetings, this is by far the best custom implementation of this feature I have seen.

The non X devices look near perfect, but as you listed in the readme, there is a issue with the iPhone X in the top layout, as well as the end of the transition back to the Today view.

I'm looking into it, and I was wondering if you have any ideas for places to start.

Thanks!!

app store

An example with tableview in detail screen

Hey @aunnnn,

Great job, this is the only implementation is near perfect so far done for appstore like animation.

I am trying to use your idea, implemented the same but when i am using the tableview i need to match the card cell from listing to inner tableview top header uiview which cannot have contraint. but both are same card content view.

Somehow it works, but weired presentation animation goes to top, then bounces and lands to the screen from left to right.

Please have a look at this and suggest any workaround @aunnnn ๐Ÿ™๐Ÿป waiting for your response.
https://youtu.be/NeYoFmCMz6k?t=3s

Library

@aunnn Can we please have a library for this?

Shadow Applied Doesn't Animate

Wondering how I can get the shadow I have applied to my CardCollectionViewCell to animate nicely back with the view. Currently my shadow just flashes back into place when it is done successfully popping back to home

Decoupling Animation logic from ViewController

How would you present the CardDetailViewController from a button lets say? In my case I have a button lets say called "TEST" and that match Cards title.

I have modified your implementation to support fromCell: SomeType and am able to present it from any way that matches that class. In the case of a button, maybe I should just instantiate SomeClass programmatically and add it to VC only to use it to present from?

Some of the dismiss animation is done in the VC, It would make sense to decouple that from the VC so it allows any VC to be presented with this animation

Weird CardDetailViewController behavior

I have a similar setup as you, with CardView and a class called SwipeCardView.

But in the CardDetailViewController when I set the class to cardContentView: CardView, the controller doesn't load properly. The VC isn't scrollable and the content under the card doesn't not load.

Now if I leave everything the same but set cardContentView: SwipeCardView, everything works as expected. I have been scratching my head as to why one works but the other doesn't.

Any thoughts on this would be appreciated.

@IBDesignable final class CardView: UIView, NibLoadable {
    
    @IBOutlet weak var symbolLabel: UILabel!
    @IBOutlet weak var companyNameLabel: UILabel!
    @IBOutlet weak var exchangeLabel: UILabel!
    
    @IBOutlet weak var chartView: LineChartView!
    
    @IBOutlet weak var highlightOneImageView: UIImageView!
    @IBOutlet weak var highlightOneTitleLabel: UILabel!
    @IBOutlet weak var highlightOneSubtitleLabel: UILabel!
    
    @IBOutlet weak var highlightTwoImageView: UIImageView!
    @IBOutlet weak var highlightTwoTitleLabel: UILabel!
    @IBOutlet weak var highlightTwoSubtitleLabel: UILabel!
    
    @IBOutlet weak var highlightThreeImageView: UIImageView!
    @IBOutlet weak var highlightThreeTitleLabel: UILabel!
    @IBOutlet weak var highlightThreeSubtitleLabel: UILabel!
    
    var card: Card! {
        didSet {
            self.setCardInfo()
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        fromNib()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        fromNib()
    }
    
    private func setCardInfo() {
        self.setInfo()
        self.setHighlights()
        self.makeChart()
    }
    
    private func setInfo() {
        self.symbolLabel.text = card.symbol
        self.companyNameLabel.text = card.companyName
        self.exchangeLabel.text = card.exchange
    }
  
    .......
}

protocol ResetAbleTransform where Self: UIView  {
    func resetTransform()
}

class SwipeCardView: ResetAbleTransform {
    
    // Our custom view from the XIB file
    var cardView: CardView!
    var card: Card! {
        didSet {
            if self.cardView != nil {
                self.cardView.card = card
            }
        }
    }
    
    var disabledHighlightedAnimation = false
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        loadCardViewFromNib()
    }
    
    init(frame: CGRect, card: Card, options: MDCSwipeToChooseViewOptions?) {
        super.init(frame: frame, options: options)
        self.card = card
        
        loadCardViewFromNib()
        self.cardView.card = card
        
        self.addCenterMotionEffectsXY(withOffset: motionOffset)
    }
    
    func loadCardViewFromNib() {
        self.cardView = CardView(frame: self.frame)
        self.addSubview(self.cardView)
        self.sendSubviewToBack(self.cardView)
        self.cardView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            self.cardView.topAnchor.constraint(equalTo: self.topAnchor),
            self.cardView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            self.cardView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            self.cardView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            ])

        self.translatesAutoresizingMaskIntoConstraints = true
        self.autoresizingMask = [UIView.AutoresizingMask.flexibleHeight, UIView.AutoresizingMask.flexibleWidth]
    }
    
    func freezeAnimations() {
        disabledHighlightedAnimation = true
        layer.removeAllAnimations()
    }
    
    func unfreezeAnimations() {
        disabledHighlightedAnimation = false
    }
    
    func resetTransform() {
//        transform = .identity
    }
    
    // Make it appears very responsive to touch
//    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
//        super.touchesBegan(touches, with: event)
//        animate(isHighlighted: true)
//    }
//
//    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
//        super.touchesEnded(touches, with: event)
//        animate(isHighlighted: false)
//    }
//
//    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
//        super.touchesCancelled(touches, with: event)
//        animate(isHighlighted: false)
//    }
//
//    private func animate(isHighlighted: Bool, completion: ((Bool) -> Void)? = nil) {
//
//        guard !disabledHighlightedAnimation else { return }
//
//        let animationOptions: UIView.AnimationOptions = Constants.isEnabledAllowsUserInteractionWhileHighlightingCard
//            ? [.allowUserInteraction] : []
//        if isHighlighted {
//            UIView.animate(withDuration: 1,
//                           delay: 0,
//                           usingSpringWithDamping: 1,
//                           initialSpringVelocity: 0,
//                           options: animationOptions, animations: {
//                            self.transform = .init(scaleX: Constants.cardHighlightedFactor, y: Constants.cardHighlightedFactor)
//            }, completion: completion)
//        } else {
//            UIView.animate(withDuration: 1,
//                           delay: 0,
//                           usingSpringWithDamping: 1,
//                           initialSpringVelocity: 0,
//                           options: animationOptions, animations: {
//                            self.transform = .identity
//            }, completion: completion)
//        }
//    }
}

Something went wrong when I added a Tabbar

PresentCardAnimator.swift
line 78: ctx.viewController(forKey: .from)! as! HomeViewController, ----------Thread 1: signal SIGABRT
line 79: ctx.viewController(forKey: .to)! as! CardDetailViewController
@aunnnn
And it went well when I tried the AppStoreiOS11InteractiveTransition_old

extended card view

how can I show different text in extended card view for different cards?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.