<repo_name>AbdoHema2016/FlippingBookPages<file_sep>/RW - Paper/BooksViewController.swift
/*
* Copyright (c) 2015 Razeware LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

import UIKit

class BooksViewController: UICollectionViewController {

    var transition: BookOpeningTransition?
    //1
    var interactionController: UIPercentDrivenInteractiveTransition?
    //2
    var recognizer: UIGestureRecognizer? {
        didSet {
            if let recognizer = recognizer {
                collectionView?.addGestureRecognizer(recognizer)
            }
        }
    }
    var books: Array<Book>? {
        didSet {
            collectionView?.reloadData()
        }
    }
	
    override func viewDidLoad() {
        super.viewDidLoad()
        books = BookStore.sharedInstance.loadBooks(plist: "Books")
        recognizer = UIPinchGestureRecognizer(target: self, action: "handlePinch:")
        
    }
    // MARK: Gesture recognizer action
    func handlePinch(recognizer: UIPinchGestureRecognizer) {
        switch recognizer.state {
        case .began:
            //1
            interactionController = UIPercentDrivenInteractiveTransition()
            //2
            if recognizer.scale >= 1 {
                //3
                if recognizer.view == collectionView {
                    //4
                    var book = self.selectedCell()?.book
                    //5
                    self.openBook(book: book)
                }
                //6
            } else {
                //7
                navigationController?.popViewController(animated: true)
            }
        case .changed:
            //8
            if transition!.isPush {
                //9
                var progress = min(max(abs((recognizer.scale - 1)) / 5, 0), 1)
                //10
                interactionController?.update(progress)
                //11
            } else {
                //12
                var progress = min(max(abs((1 - recognizer.scale)), 0), 1)
                //13
                interactionController?.update(progress)
            }
        case .ended:
            //14
            interactionController?.finish()
            //15
            interactionController = nil
        default:
            break
        }
    }

    
    // MARK: Helpers
    func selectedCell() -> BookCoverCell? {
        if let indexPath = collectionView?.indexPathForItem(at: CGPoint(x: collectionView.contentOffset.x + collectionView!.bounds.width / 2, y: collectionView.bounds.height / 2)){
            if let cell = collectionView.cellForItem(at: indexPath) as? BookCoverCell {
                return cell
                
            }
        }
        return nil
    }
    func openBook(book: Book?) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "BookViewController") as! BookViewController
        vc.book = selectedCell()?.book
        // UICollectionView loads it's cells on a background thread, so make sure it's loaded before passing it to the animation handler
        //1
        vc.view.snapshotView(afterScreenUpdates: true)
        DispatchQueue.main.async {
            self.navigationController?.pushViewController(vc, animated: true)
            return
        }
        
    }
//    func openBook(book: Book?) {
//        let vc = storyboard?.instantiateViewController(withIdentifier: "BookViewController") as! BookViewController
//        vc.book = book
//        // UICollectionView loads it's cells on a background thread, so make sure it's loaded before passing it to the animation handler
//        DispatchQueue.global(qos: .background).async {
//
//            // Background Thread
//
//            DispatchQueue.main.async {
//               self.navigationController?.pushViewController(vc, animated: true)
//            }
//        }
//
//    }
    
}
extension BooksViewController {
    func animationControllerForPresentController(vc: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 1
        var transition = BookOpeningTransition()
        // 2
        transition.isPush = true
        transition.interactionController = interactionController
        // 3
        self.transition = transition
        // 4
        return transition
    }
    func animationControllerForDismissController(vc: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        var transition = BookOpeningTransition()
        transition.isPush = false
        transition.interactionController = interactionController
        self.transition = transition
        return transition
    }
}

// MARK: UICollectionViewDelegate

extension BooksViewController {
    

    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        var book = books?[indexPath.row]
        openBook(book: book)
    }
    
}

// MARK: UICollectionViewDataSource

extension BooksViewController {
    

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }
    
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if let books = books {
            return books.count
        }
        return 0
    }
    
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        var cell = collectionView .dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath as IndexPath) as! BookCoverCell
        
        cell.book = books?[indexPath.row]
        
        return cell
    }
    
}
<file_sep>/README.md
# FlippingBookPages

That is a swift 4.2 implementation for Raywenderlich tutorial article for How to Create an iOS Book Open Animation built 
originaly with swift 2 .

https://www.raywenderlich.com/1719-how-to-create-an-ios-book-open-animation-part-1
https://www.raywenderlich.com/1718-how-to-create-an-ios-book-open-animation-part-2

![Alt Text](https://media.giphy.com/media/daULImZeyUFscd6wsL/giphy.gif)

<file_sep>/RW - Paper/CustomNavigationControllerViewController.swift
//
//  CustomNavigationControllerViewController.swift
//  RW - Paper
//
//  Created by Abdelrahman-Arw on 12/10/19.
//  Copyright © 2019 -. All rights reserved.
//

import UIKit
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //1
        delegate = self
    }
    
    //2
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationController.Operation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if operation == .push {
            if let vc = fromVC as? BooksViewController {
                return vc.animationControllerForPresentController(vc: toVC)
            }
        }
        
        if operation == .pop {
            if let vc = toVC as? BooksViewController {
                return vc.animationControllerForDismissController(vc: vc)
            }
        }
        
        return nil
    }
    func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if let animationController = animationController as? BookOpeningTransition {
            return animationController.interactionController
        }
        return nil
    }
}
<file_sep>/RW - Paper/BookLayout.swift
//
//  BookLayout.swift
//  RW - Paper
//
//  Created by Abdelrahman-Arw on 12/10/19.
//  Copyright © 2019 -. All rights reserved.
//

import UIKit
private let PageWidth: CGFloat = 362
private let PageHeight: CGFloat = 568
private var numberOfItems = 0
class BookLayout: UICollectionViewFlowLayout {

    override func prepare() {
        super.prepare()
        collectionView?.decelerationRate = UIScrollView.DecelerationRate.fast
        numberOfItems = collectionView!.numberOfItems(inSection: 0)
        collectionView?.isPagingEnabled = true
    }
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
    override var collectionViewContentSize: CGSize {
        return CGSize(width: (CGFloat(numberOfItems / 2)) * collectionView!.bounds.width, height: collectionView!.bounds.height)

    }
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var array: [UICollectionViewLayoutAttributes?] = []
       // var array = super.layoutAttributesForElements(in: rect) as! [UICollectionViewLayoutAttributes]
        //2
        for i in 0 ... max(0, numberOfItems - 1) {
            //3
            var indexPath = NSIndexPath(item: i, section: 0)
            //4
            var attributes = layoutAttributesForItem(at: indexPath as IndexPath)
            if attributes != nil {
                //5
                array += [attributes]
            }
        }
        //6
        return array as? [UICollectionViewLayoutAttributes]
    }
    //MARK: - Attribute Logic Helpers
    
    func getFrame(collectionView: UICollectionView) -> CGRect {
        var frame = CGRect()
        
        frame.origin.x = (collectionView.bounds.width / 2) - (PageWidth / 2) + collectionView.contentOffset.x
        frame.origin.y = (collectionViewContentSize.height - PageHeight) / 2
        frame.size.width = PageWidth
        frame.size.height = PageHeight
        
        return frame
    }
    func getRatio(collectionView: UICollectionView, indexPath: NSIndexPath) -> CGFloat {
        //1
        let page = CGFloat(indexPath.item - indexPath.item % 2) * 0.5
        
        //2
        var ratio: CGFloat = -0.5 + page - (collectionView.contentOffset.x / collectionView.bounds.width)
        
        //3
        if ratio > 0.5 {
            ratio = 0.5 + 0.1 * (ratio - 0.5)
            
        } else if ratio < -0.5 {
            ratio = -0.5 + 0.1 * (ratio + 0.5)
        }
        
        return ratio
    }
    func getAngle(indexPath: NSIndexPath, ratio: CGFloat) -> CGFloat {
        // Set rotation
        var angle: CGFloat = 0
        
        //1
        if indexPath.item % 2 == 0 {
            // The book's spine is on the left of the page
            angle = (1-ratio) * CGFloat(-M_PI_2)
        } else {
            //2
            // The book's spine is on the right of the page
            angle = (1 + ratio) * CGFloat(M_PI_2)
        }
        //3
        // Make sure the odd and even page don't have the exact same angle
        angle += CGFloat(indexPath.row % 2) / 1000
        //4
        return angle
    }
    func makePerspectiveTransform() -> CATransform3D {
        var transform = CATransform3DIdentity
        transform.m34 = 1.0 / -2000
        return transform
    }
    func getRotation(indexPath: NSIndexPath, ratio: CGFloat) -> CATransform3D {
        var transform = makePerspectiveTransform()
        var angle = getAngle(indexPath: indexPath, ratio: ratio)
        transform = CATransform3DRotate(transform, angle, 0, 1, 0)
        return transform
    }
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        //1
        var layoutAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
        
        //2
        var frame = getFrame(collectionView: collectionView!)
        layoutAttributes.frame = frame
        
        //3
        var ratio = getRatio(collectionView: collectionView!, indexPath: indexPath as NSIndexPath)
        
        //4
        if ratio > 0 && indexPath.item % 2 == 1
            || ratio < 0 && indexPath.item % 2 == 0 {
            // Make sure the cover is always visible
            if indexPath.row != 0 {
                return nil
            }
        }
        //5
        var rotation = getRotation(indexPath: indexPath as NSIndexPath, ratio: min(max(ratio, -1), 1))
        layoutAttributes.transform3D = rotation
        
        //6
        if indexPath.row == 0 {
            layoutAttributes.zIndex = Int.max
        }
        
        return layoutAttributes
    }
 
    
}
<file_sep>/RW - Paper/BooksLayout.swift
//
//  BooksLayout.swift
//  RW - Paper
//
//  Created by Abdelrahman-Arw on 12/10/19.
//  Copyright © 2019 -. All rights reserved.
//

import UIKit
private let PageWidth: CGFloat = 362
private let PageHeight: CGFloat = 568
class BooksLayout: UICollectionViewFlowLayout {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        scrollDirection = UICollectionView.ScrollDirection.horizontal
        itemSize = CGSize(width: PageWidth, height: PageHeight)
        minimumLineSpacing = 10
    }
    override func prepare() {
        super.prepare()
        
        //The rate at which we scroll the collection view.
        //1
        collectionView?.decelerationRate = UIScrollView.DecelerationRate.fast
        
        //2
        collectionView?.contentInset = UIEdgeInsets(
            top: 0,
            left: collectionView!.bounds.width / 2 - PageWidth / 2,
            bottom: 0,
            right: collectionView!.bounds.width / 2 - PageWidth / 2
        )
    }
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        var array = super.layoutAttributesForElements(in: rect) as! [UICollectionViewLayoutAttributes]
        //2
        for attributes in array {
            //3
            var frame = attributes.frame
            //4
            var distance = abs(collectionView!.contentOffset.x + collectionView!.contentInset.left - frame.origin.x)
            //5
            var scale = 0.7 * min(max(1 - distance / (collectionView!.bounds.width), 0.75), 1)
            //6
            attributes.transform = CGAffineTransform(scaleX: scale, y: scale)
        }
        
        return array
    }
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true
    }
}
