Updated 31 August 2020
Flipping Notch is not a framework, it is just an Xcode project, embracing the notch.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
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 } } |
4. The Notch View
notchViewBottomConstraint
is used to position the notchView into the view.
1 2 3 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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)
scrollViewDidScroll
delegate function. In there we write the logic to move the notchView down.the maximum scrolling offset what we want to give
1 2 3 4 5 6 7 8 9 10 |
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
notchBottomConstraint
, and move down the collectionView
and drop the animatableView (notchView clone) with an animation and we round its corners.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
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
collectionview cell
is taken, the image is set on the animatableView
and it is flipped with an animation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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() } ) } ... } |
The animation works as expected only in iPhone X in portrait mode
For more Blogs please click here
If you have more details or questions, you can reply to the received confirmation email.
Back to Home
Be the first to comment.