GithubHelp home page GithubHelp logo

quickbirdeng / flippingnotch Goto Github PK

View Code? Open in Web Editor NEW
832.0 16.0 43.0 9.42 MB

FlippingNotch 🤙 - Dribble inspired animation https://dribbble.com/shots/4089014-Pull-To-Refresh-iPhone-X

License: MIT License

Swift 100.00%
swift animation notch dribble

flippingnotch's Introduction

FlippingNotch 🤙

Platform Swift 3.2

FlippingNotch is "pull to refresh/add/show" custom animation written Swift, using the iPhone X Notch. Heavily inspired by this Dribble project: https://dribbble.com/shots/4089014-Pull-To-Refresh-iPhone-X

alt text

What FlippingNotch is not

It is not a framework, it is just an Xcode project, embracing the notch.

Requirements

FlippingNotch is written in Swift 4.0 and requires an iPhone X Simulator/Device.

Tutorial

  1. Put a UICollectionView and constraint it in a ViewController.

The image below shows an example how to constraint it.

  1. Add a cell in the UICollectionView.

  1. Set up the UICollectionView in the ViewController by conforming to UICollectionViewDataSource.
class ViewController: UIViewController {

    // MARK: IBOutlets

    @IBOutlet var collectionView: UICollectionView!
    
    // MARK: Fileprivates
    fileprivate var numberOfItemsInSection = 1

    
    // MARK: Overrides

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.dataSource = self
    }
    
}
    
// MARK: UICollectionViewDataSource

extension ViewController: UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return numberOfItemsInSection
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        
        cell.layer.cornerRadius = 10
        cell.layer.masksToBounds = true
        
        return cell
    }
}
  1. The Notch View
  • Instantiate a view that represents the notch. The notchViewBottomConstraint is used to position the notchView into the view.
   fileprivate var notchView = UIView()
   fileprivate var notchViewBottomConstraint: NSLayoutConstraint!
   fileprivate var numberOfItemsInSection = 1
  • After instantiating the notchView, add it as a subview its parent view.  The notchView have a black background and rounded corners.  translatesAutoResizingMaskIntoConstraints needs to be set to false because we want to use auto layout for this view rather than frame-based layout.  Then, the notchView is constrained to the center of its parent view, with the same width as the notch, a height of (notch height - maximum scrolling offset what we want to give) and a bottom constrained to its parent view topAnchor + notch height.
private func configureNotchView() {
        self.view.addSubview(notchView)
        
        notchView.translatesAutoresizingMaskIntoConstraints = false
        notchView.backgroundColor = UIColor.black
        notchView.layer.cornerRadius = 20
        
        notchView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).activate()
        notchView.widthAnchor.constraint(equalToConstant: Constants.notchWidth).activate()
        notchView.heightAnchor.constraint(equalToConstant: Constants.notchHeight - 
                                                           Constants.maxScrollOffset).activate()
        notchViewBottomConstraint = notchView.bottomAnchor.constraint(equalTo: self.view.topAnchor, 
                                                                      constant: Constants.notchHeight)
        notchViewBottomConstraint.activate()
    }

The result in an iPhone 8:

  1. Reacting while scrolling

(Looks clearer in an iPhone 8 what we are trying to do)

  • We want to move down the notchView while scrolling

  • To do this, first we have to conform our ViewController to UICollectionViewDelegate and call scrollViewDidScroll delegate function. In there we write the logic to move the notchView down.
  • The scrollView should scroll until it reaches the maximum scrolling offset what we want to give
  • The bottom constrained of the notchView should be increased while scrolling.
  extension ViewController: UICollectionViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
    
        // Making sure that we contentOffset of the scrollView is max to maxScrollOffset
        scrollView.contentOffset.y = max(Constants.maxScrollOffset, scrollView.contentOffset.y)
        
        // Move down the notchView until we have reached our threshold
        notchViewTopConstraint.constant = Constants.notchTopOffset - min(0, scrollView.contentOffset.y)
    }
  1. Drop the view from the notch
  • When the scroll did end dragging we want to create the view that will be part of the flipping animation.

  • We create the animatableView, reset notchBottomConstraint, and move down the collectionView and drop the animatableView (notchView clone) with an animation and we round its corners.
  private func animateView() {
    
        // Create animatableView (notch clone)
        let animatableView = UIImageView(frame: notchView.frame)
        animatableView.backgroundColor = UIColor.black
        animatableView.layer.cornerRadius = self.notchView.layer.cornerRadius
        animatableView.layer.masksToBounds = true
        animatableView.frame = self.notchView.frame
        self.view.addSubview(animatableView)
        
        // Reset notchView bottom constraint
        notchViewBottomConstraint.constant = Constants.notchHeight
        
        // Move the collectionView down
        let flowLayout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
        let height = flowLayout.itemSize.height + flowLayout.minimumInteritemSpacing
        
        self.collectionView.transform = CGAffineTransform.identity.translatedBy(x: 0, y: -Constants.maxScrollOffset)

        // Dropping animation
        UIView.animate(withDuration: 0.3, delay: 0, options: [], animations: {
            let itemSize = flowLayout.itemSize
            animatableView.frame.size = CGSize(width: Constants.notchWidth, 
                                               height: (itemSize.height / itemSize.width) * Constants.notchWidth)

            // UIImage.fromColor(color), returns an image in a certain color
            animatableView.image = UIImage.fromColor(self.view.backgroundColor?.withAlphaComponent(0.2) ?? UIColor.black)
            animatableView.frame.origin.y = Constants.notchViewTopInset
            self.collectionView.transform = CGAffineTransform.identity.translatedBy(x: 0, y: height * 0.5)
        }) 
        
        // Animate the corners
        let cornerRadiusAnimation = CABasicAnimation(keyPath: "cornerRadius")
        cornerRadiusAnimation.fromValue = 16
        cornerRadiusAnimation.toValue = 10
        cornerRadiusAnimation.duration = 0.3
        animatableView.layer.add(cornerRadiusAnimation, forKey: "cornerRadius")
        animatableView.layer.cornerRadius = 10
    }
    
extension ViewController: UICollectionViewDelegate {
   
   ...

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if scrollView.contentOffset.y <= Constants.maxScrollOffset {
            animateView()
        }
    }
}
  1. Flip it
  • After dropping the view, a snapshot of the collectionview cell is taken, the image is set on the animatableView and it is flipped with an animation.

  private func animateView() {
   ...
   
        UIView.animate(withDuration: 0.3, delay: 0, options: [], animations: {
            
            ...
            
        }) { _ in
            
            // Snapshot the collectionView cell. 
            // It is easier to deal with an image of the cell than the cell itself
            // This is the reason why animatableView is an UIImageView and not a UIView.
            let item = self.collectionView.cellForItem(at: IndexPath(row: 0, section: 0))
            animatableView.image = item?.snapshotImage()
            
            // Flipping transition
            UIView.transition(with: animatableView, duration: 0.6, options: UIViewAnimationOptions.transitionFlipFromBottom, animations: {
                animatableView.frame.size = flowLayout.itemSize
                animatableView.frame.origin = CGPoint(x: (self.collectionView.frame.width - flowLayout.itemSize.width) / 2.0, 
                                                      y: self.collectionView.frame.origin.y - height * 0.5)
                self.collectionView.transform = CGAffineTransform.identity.translatedBy(x: 0, y: height)
                }, completion: { _ in
                    // Remove the animatableView
                    self.collectionView.transform = CGAffineTransform.identity
                    animatableView.removeFromSuperview()
                    
                    // Add an item in section
                    self.numberOfItemsInSection += 1
                    self.collectionView.reloadData()
                }
            )
        }
        
      ...
    }

Limitations

The animation works as expected only in iPhone X in portrait mode

TODO

  • Include the case when a NavigationBar is implemented.

Authors

flippingnotch's People

Contributors

jdisho avatar paralax 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  avatar  avatar

flippingnotch's Issues

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.