Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/QuickBirdEng/FlippingNotch
FlippingNotch π€ - Dribble inspired animation https://dribbble.com/shots/4089014-Pull-To-Refresh-iPhone-X
https://github.com/QuickBirdEng/FlippingNotch
animation dribble notch swift
Last synced: 3 months ago
JSON representation
FlippingNotch π€ - Dribble inspired animation https://dribbble.com/shots/4089014-Pull-To-Refresh-iPhone-X
- Host: GitHub
- URL: https://github.com/QuickBirdEng/FlippingNotch
- Owner: QuickBirdEng
- License: mit
- Created: 2018-01-18T13:52:21.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2018-03-02T09:57:03.000Z (over 6 years ago)
- Last Synced: 2024-07-27T18:53:13.135Z (4 months ago)
- Topics: animation, dribble, notch, swift
- Language: Swift
- Homepage:
- Size: 9.19 MB
- Stars: 834
- Watchers: 16
- Forks: 43
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# FlippingNotch π€
[![Platform](https://img.shields.io/badge/Platform-iOS-lightgrey.svg)]()
[![Swift 3.2](https://img.shields.io/badge/Swift-4.0-orange.svg)](https://swift.org)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](https://cdn.dribbble.com/users/793057/screenshots/4089014/iphone-x-pull-to-refresh.gif)
### 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.
2. **Add a cell in the UICollectionView.**
3. **Set up the UICollectionView in the ViewController by conforming to UICollectionViewDataSource.**
``` swift
class ViewController: UIViewController {// MARK: IBOutlets
@IBOutlet var collectionView: UICollectionView!
// MARK: Fileprivates
fileprivate var numberOfItemsInSection = 1
// MARK: Overridesoverride func viewDidLoad() {
super.viewDidLoad()collectionView.dataSource = self
}
}
// MARK: UICollectionViewDataSourceextension 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
}
}```
4. **The Notch View**
- Instantiate a view that represents the notch. The `notchViewBottomConstraint` is used to position the notchView into the view.``` swift
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.``` swift
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:5. **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.
``` swift
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)
}
```6. **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.
``` swift
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()
}
}
}
```
7. **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.
``` swift
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
* **Joan Disho** - [QuickBird Studios](http://www.quickbirdstudios.com)