123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- //
- // FSPagerViewTransformer.swift
- // FSPagerView
- //
- // Created by Wenchao Ding on 05/01/2017.
- // Copyright © 2017 Wenchao Ding. All rights reserved.
- //
- import UIKit
- @objc
- public enum FSPagerViewTransformerType: Int {
- case crossFading
- case zoomOut
- case depth
- case overlap
- case linear
- case coverFlow
- case ferrisWheel
- case invertedFerrisWheel
- case cubic
- }
- open class FSPagerViewTransformer: NSObject {
-
- open internal(set) weak var pagerView: FSPagerView?
- open internal(set) var type: FSPagerViewTransformerType
-
- open var minimumScale: CGFloat = 0.65
- open var minimumAlpha: CGFloat = 0.6
-
- @objc
- public init(type: FSPagerViewTransformerType) {
- self.type = type
- switch type {
- case .zoomOut:
- self.minimumScale = 0.85
- case .depth:
- self.minimumScale = 0.5
- default:
- break
- }
- }
-
- // Apply transform to attributes - zIndex: Int, frame: CGRect, alpha: CGFloat, transform: CGAffineTransform or transform3D: CATransform3D.
- open func applyTransform(to attributes: FSPagerViewLayoutAttributes) {
- guard let pagerView = self.pagerView else {
- return
- }
- let position = attributes.position
- let scrollDirection = pagerView.scrollDirection
- let itemSpacing = (scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height) + self.proposedInteritemSpacing()
- switch self.type {
- case .crossFading:
- var zIndex = 0
- var alpha: CGFloat = 0
- var transform = CGAffineTransform.identity
- switch scrollDirection {
- case .horizontal:
- transform.tx = -itemSpacing * position
- case .vertical:
- transform.ty = -itemSpacing * position
- }
- if (abs(position) < 1) { // [-1,1]
- // Use the default slide transition when moving to the left page
- alpha = 1 - abs(position)
- zIndex = 1
- } else { // (1,+Infinity]
- // This page is way off-screen to the right.
- alpha = 0
- zIndex = Int.min
- }
- attributes.alpha = alpha
- attributes.transform = transform
- attributes.zIndex = zIndex
- case .zoomOut:
- var alpha: CGFloat = 0
- var transform = CGAffineTransform.identity
- switch position {
- case -CGFloat.greatestFiniteMagnitude ..< -1 : // [-Infinity,-1)
- // This page is way off-screen to the left.
- alpha = 0
- case -1 ... 1 : // [-1,1]
- // Modify the default slide transition to shrink the page as well
- let scaleFactor = max(self.minimumScale, 1 - abs(position))
- transform.a = scaleFactor
- transform.d = scaleFactor
- switch scrollDirection {
- case .horizontal:
- let vertMargin = attributes.bounds.height * (1 - scaleFactor) / 2;
- let horzMargin = itemSpacing * (1 - scaleFactor) / 2;
- transform.tx = position < 0 ? (horzMargin - vertMargin*2) : (-horzMargin + vertMargin*2)
- case .vertical:
- let horzMargin = attributes.bounds.width * (1 - scaleFactor) / 2;
- let vertMargin = itemSpacing * (1 - scaleFactor) / 2;
- transform.ty = position < 0 ? (vertMargin - horzMargin*2) : (-vertMargin + horzMargin*2)
- }
- // Fade the page relative to its size.
- alpha = self.minimumAlpha + (scaleFactor-self.minimumScale)/(1-self.minimumScale)*(1-self.minimumAlpha)
- case 1 ... CGFloat.greatestFiniteMagnitude : // (1,+Infinity]
- // This page is way off-screen to the right.
- alpha = 0
- default:
- break
- }
- attributes.alpha = alpha
- attributes.transform = transform
- case .depth:
- var transform = CGAffineTransform.identity
- var zIndex = 0
- var alpha: CGFloat = 0.0
- switch position {
- case -CGFloat.greatestFiniteMagnitude ..< -1: // [-Infinity,-1)
- // This page is way off-screen to the left.
- alpha = 0
- zIndex = 0
- case -1 ... 0: // [-1,0]
- // Use the default slide transition when moving to the left page
- alpha = 1
- transform.tx = 0
- transform.a = 1
- transform.d = 1
- zIndex = 1
- case 0 ..< 1: // (0,1)
- // Fade the page out.
- alpha = CGFloat(1.0) - position
- // Counteract the default slide transition
- switch scrollDirection {
- case .horizontal:
- transform.tx = itemSpacing * -position
- case .vertical:
- transform.ty = itemSpacing * -position
- }
- // Scale the page down (between minimumScale and 1)
- let scaleFactor = self.minimumScale
- + (1.0 - self.minimumScale) * (1.0 - abs(position));
- transform.a = scaleFactor
- transform.d = scaleFactor
- zIndex = 0
- case 1 ... CGFloat.greatestFiniteMagnitude: // [1,+Infinity)
- // This page is way off-screen to the right.
- alpha = 0
- zIndex = 0
- default:
- break
- }
- attributes.alpha = alpha
- attributes.transform = transform
- attributes.zIndex = zIndex
- case .overlap,.linear:
- guard scrollDirection == .horizontal else {
- // This type doesn't support vertical mode
- return
- }
- let scale = max(1 - (1-self.minimumScale) * abs(position), self.minimumScale)
- let transform = CGAffineTransform(scaleX: scale, y: scale)
- attributes.transform = transform
- let alpha = (self.minimumAlpha + (1-abs(position))*(1-self.minimumAlpha))
- attributes.alpha = alpha
- let zIndex = (1-abs(position)) * 10
- attributes.zIndex = Int(zIndex)
- case .coverFlow:
- guard scrollDirection == .horizontal else {
- // This type doesn't support vertical mode
- return
- }
- let position = min(max(-position,-1) ,1)
- let rotation = sin(position*(.pi)*0.5)*(.pi)*0.25*1.5
- let translationZ = -itemSpacing * 0.5 * abs(position)
- var transform3D = CATransform3DIdentity
- transform3D.m34 = -0.002
- transform3D = CATransform3DRotate(transform3D, rotation, 0, 1, 0)
- transform3D = CATransform3DTranslate(transform3D, 0, 0, translationZ)
- attributes.zIndex = 100 - Int(abs(position))
- attributes.transform3D = transform3D
- case .ferrisWheel, .invertedFerrisWheel:
- guard scrollDirection == .horizontal else {
- // This type doesn't support vertical mode
- return
- }
- // http://ronnqvi.st/translate-rotate-translate/
- var zIndex = 0
- var transform = CGAffineTransform.identity
- switch position {
- case -5 ... 5:
- let itemSpacing = attributes.bounds.width+self.proposedInteritemSpacing()
- let count: CGFloat = 14
- let circle: CGFloat = .pi * 2.0
- let radius = itemSpacing * count / circle
- let ty = radius * (self.type == .ferrisWheel ? 1 : -1)
- let theta = circle / count
- let rotation = position * theta * (self.type == .ferrisWheel ? 1 : -1)
- transform = transform.translatedBy(x: -position*itemSpacing, y: ty)
- transform = transform.rotated(by: rotation)
- transform = transform.translatedBy(x: 0, y: -ty)
- zIndex = Int((4.0-abs(position)*10))
- default:
- break
- }
- attributes.alpha = abs(position) < 0.5 ? 1 : self.minimumAlpha
- attributes.transform = transform
- attributes.zIndex = zIndex
- case .cubic:
- switch position {
- case -CGFloat.greatestFiniteMagnitude ... -1:
- attributes.alpha = 0
- case -1 ..< 1:
- attributes.alpha = 1
- attributes.zIndex = Int((1-position) * CGFloat(10))
- let direction: CGFloat = position < 0 ? 1 : -1
- let theta = position * .pi * 0.5 * (scrollDirection == .horizontal ? 1 : -1)
- let radius = scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height
- var transform3D = CATransform3DIdentity
- transform3D.m34 = -0.002
- switch scrollDirection {
- case .horizontal:
- // ForwardX -> RotateY -> BackwardX
- attributes.center.x += direction*radius*0.5 // ForwardX
- transform3D = CATransform3DRotate(transform3D, theta, 0, 1, 0) // RotateY
- transform3D = CATransform3DTranslate(transform3D,-direction*radius*0.5, 0, 0) // BackwardX
- case .vertical:
- // ForwardY -> RotateX -> BackwardY
- attributes.center.y += direction*radius*0.5 // ForwardY
- transform3D = CATransform3DRotate(transform3D, theta, 1, 0, 0) // RotateX
- transform3D = CATransform3DTranslate(transform3D,0, -direction*radius*0.5, 0) // BackwardY
- }
- attributes.transform3D = transform3D
- case 1 ... CGFloat.greatestFiniteMagnitude:
- attributes.alpha = 0
- default:
- attributes.alpha = 0
- attributes.zIndex = 0
- }
- }
- }
-
- // An interitem spacing proposed by transformer class. This will override the default interitemSpacing provided by the pager view.
- open func proposedInteritemSpacing() -> CGFloat {
- guard let pagerView = self.pagerView else {
- return 0
- }
- let scrollDirection = pagerView.scrollDirection
- switch self.type {
- case .overlap:
- guard scrollDirection == .horizontal else {
- return 0
- }
- return pagerView.itemSize.width * -self.minimumScale * 0.6
- case .linear:
- guard scrollDirection == .horizontal else {
- return 0
- }
- return pagerView.itemSize.width * -self.minimumScale * 0.2
- case .coverFlow:
- guard scrollDirection == .horizontal else {
- return 0
- }
- return -pagerView.itemSize.width * sin(.pi*0.25*0.25*3.0)
- case .ferrisWheel,.invertedFerrisWheel:
- guard scrollDirection == .horizontal else {
- return 0
- }
- return -pagerView.itemSize.width * 0.15
- case .cubic:
- return 0
- default:
- break
- }
- return pagerView.interitemSpacing
- }
-
- }
|