• Stars
    star
    835
  • Rank 54,605 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created almost 7 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

FlippingNotch ๐Ÿค™ - Dribble inspired animation https://dribbble.com/shots/4089014-Pull-To-Refresh-iPhone-X

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

More Repositories

1

XCoordinator

๐ŸŽŒ Powerful navigation library for iOS based on the coordinator pattern
Swift
2,253
star
2

SwiftUI-Architectures

Three different architectures (Model-View, Redux, MVVM) for using SwiftUI implemented at the example of a chat app
Swift
682
star
3

opencv-android

Easy way to integrate OpenCv into your Android project via Gradle
Kotlin
673
star
4

PullToReach

PullToReach is a simple drag-and-drop solution for adding pull-to-reach functionality to your app
Swift
516
star
5

SurveyKit

Android library to create beautiful surveys (aligned with ResearchKit on iOS)
Kotlin
386
star
6

SwiftUI-Coordinators-Example

Sample app that showcases the use of the Coordinator Pattern in SwiftUI
Swift
223
star
7

CombineRxSwiftPerformance

A test suite comparing the performance of Combine and RxSwift
Swift
187
star
8

survey_kit

Flutter library to create beautiful surveys (aligned with ResearchKit on iOS)
Dart
124
star
9

XUI

XUI makes modular, testable architectures for SwiftUI apps a breeze!
Swift
116
star
10

XCoordinator-Example

XCoordinator-Example serves as an MVVM-C example app for XCoordinator
Swift
64
star
11

kotlin-snapshot-testing

Extensible Kotlin Multiplatform library to easily create Snapshot tests for Android and other Kotlin applications
Kotlin
61
star
12

XServiceLocator

Light-weight Service Locator / Dependency Injection library for Swift: providing objects with the dependencies they need throughout your whole iOS app.
Swift
54
star
13

yuvToMat

High-performance library for converting YUV_420_888 Android Camera images to OpenCV RGB Mats
Kotlin
53
star
14

NonEmptyCollections

A type-safe implementation for collections that cannot be empty. Life is too short for emptiness-checks!
Kotlin
53
star
15

DataKit

A Swift library to easily read and write binary formatted data using a modern, declarative interface.
Swift
42
star
16

flutter_platform_search

A platform-adaptive search for Flutter
Dart
36
star
17

flutter_pull_to_reach_demo

A new way to access controls in your flutter app that are normally hard to reach with your thumb
Dart
26
star
18

QBRepository

QBRepository is a simple implementation of the repository pattern for data access in Swift
Swift
16
star
19

DependencyInjectionPlayground

Playground showing different techniques for dependency injection in Swift
Swift
15
star
20

platform-independent-mvvm-android

An Android-MVVM example of how we create platform-independent ViewModels that can be reused on iOS/Desktop/Server
Kotlin
13
star
21

crc-swift

Swift
11
star
22

XCoordinator-Talks

Presentations & Workshops about the Coordinator pattern & XCoordinator
Swift
6
star
23

crc-kotlin

Kotlin
5
star
24

DoggyKotlinKoans

A list of entertaining koans/exercises to learn the basic features of Kotlin (including solutions).
Kotlin
3
star
25

mobile_hacknight_flutter

Dart
3
star
26

actions

JavaScript
2
star
27

HackNight-SwiftUI

Swift
1
star