FSPageViewTransformer.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // FSPagerViewTransformer.swift
  3. // FSPagerView
  4. //
  5. // Created by Wenchao Ding on 05/01/2017.
  6. // Copyright © 2017 Wenchao Ding. All rights reserved.
  7. //
  8. import UIKit
  9. @objc
  10. public enum FSPagerViewTransformerType: Int {
  11. case crossFading
  12. case zoomOut
  13. case depth
  14. case overlap
  15. case linear
  16. case coverFlow
  17. case ferrisWheel
  18. case invertedFerrisWheel
  19. case cubic
  20. }
  21. open class FSPagerViewTransformer: NSObject {
  22. open internal(set) weak var pagerView: FSPagerView?
  23. open internal(set) var type: FSPagerViewTransformerType
  24. open var minimumScale: CGFloat = 0.65
  25. open var minimumAlpha: CGFloat = 0.6
  26. @objc
  27. public init(type: FSPagerViewTransformerType) {
  28. self.type = type
  29. switch type {
  30. case .zoomOut:
  31. self.minimumScale = 0.85
  32. case .depth:
  33. self.minimumScale = 0.5
  34. default:
  35. break
  36. }
  37. }
  38. // Apply transform to attributes - zIndex: Int, frame: CGRect, alpha: CGFloat, transform: CGAffineTransform or transform3D: CATransform3D.
  39. open func applyTransform(to attributes: FSPagerViewLayoutAttributes) {
  40. guard let pagerView = self.pagerView else {
  41. return
  42. }
  43. let position = attributes.position
  44. let scrollDirection = pagerView.scrollDirection
  45. let itemSpacing = (scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height) + self.proposedInteritemSpacing()
  46. switch self.type {
  47. case .crossFading:
  48. var zIndex = 0
  49. var alpha: CGFloat = 0
  50. var transform = CGAffineTransform.identity
  51. switch scrollDirection {
  52. case .horizontal:
  53. transform.tx = -itemSpacing * position
  54. case .vertical:
  55. transform.ty = -itemSpacing * position
  56. }
  57. if (abs(position) < 1) { // [-1,1]
  58. // Use the default slide transition when moving to the left page
  59. alpha = 1 - abs(position)
  60. zIndex = 1
  61. } else { // (1,+Infinity]
  62. // This page is way off-screen to the right.
  63. alpha = 0
  64. zIndex = Int.min
  65. }
  66. attributes.alpha = alpha
  67. attributes.transform = transform
  68. attributes.zIndex = zIndex
  69. case .zoomOut:
  70. var alpha: CGFloat = 0
  71. var transform = CGAffineTransform.identity
  72. switch position {
  73. case -CGFloat.greatestFiniteMagnitude ..< -1 : // [-Infinity,-1)
  74. // This page is way off-screen to the left.
  75. alpha = 0
  76. case -1 ... 1 : // [-1,1]
  77. // Modify the default slide transition to shrink the page as well
  78. let scaleFactor = max(self.minimumScale, 1 - abs(position))
  79. transform.a = scaleFactor
  80. transform.d = scaleFactor
  81. switch scrollDirection {
  82. case .horizontal:
  83. let vertMargin = attributes.bounds.height * (1 - scaleFactor) / 2;
  84. let horzMargin = itemSpacing * (1 - scaleFactor) / 2;
  85. transform.tx = position < 0 ? (horzMargin - vertMargin*2) : (-horzMargin + vertMargin*2)
  86. case .vertical:
  87. let horzMargin = attributes.bounds.width * (1 - scaleFactor) / 2;
  88. let vertMargin = itemSpacing * (1 - scaleFactor) / 2;
  89. transform.ty = position < 0 ? (vertMargin - horzMargin*2) : (-vertMargin + horzMargin*2)
  90. }
  91. // Fade the page relative to its size.
  92. alpha = self.minimumAlpha + (scaleFactor-self.minimumScale)/(1-self.minimumScale)*(1-self.minimumAlpha)
  93. case 1 ... CGFloat.greatestFiniteMagnitude : // (1,+Infinity]
  94. // This page is way off-screen to the right.
  95. alpha = 0
  96. default:
  97. break
  98. }
  99. attributes.alpha = alpha
  100. attributes.transform = transform
  101. case .depth:
  102. var transform = CGAffineTransform.identity
  103. var zIndex = 0
  104. var alpha: CGFloat = 0.0
  105. switch position {
  106. case -CGFloat.greatestFiniteMagnitude ..< -1: // [-Infinity,-1)
  107. // This page is way off-screen to the left.
  108. alpha = 0
  109. zIndex = 0
  110. case -1 ... 0: // [-1,0]
  111. // Use the default slide transition when moving to the left page
  112. alpha = 1
  113. transform.tx = 0
  114. transform.a = 1
  115. transform.d = 1
  116. zIndex = 1
  117. case 0 ..< 1: // (0,1)
  118. // Fade the page out.
  119. alpha = CGFloat(1.0) - position
  120. // Counteract the default slide transition
  121. switch scrollDirection {
  122. case .horizontal:
  123. transform.tx = itemSpacing * -position
  124. case .vertical:
  125. transform.ty = itemSpacing * -position
  126. }
  127. // Scale the page down (between minimumScale and 1)
  128. let scaleFactor = self.minimumScale
  129. + (1.0 - self.minimumScale) * (1.0 - abs(position));
  130. transform.a = scaleFactor
  131. transform.d = scaleFactor
  132. zIndex = 0
  133. case 1 ... CGFloat.greatestFiniteMagnitude: // [1,+Infinity)
  134. // This page is way off-screen to the right.
  135. alpha = 0
  136. zIndex = 0
  137. default:
  138. break
  139. }
  140. attributes.alpha = alpha
  141. attributes.transform = transform
  142. attributes.zIndex = zIndex
  143. case .overlap,.linear:
  144. guard scrollDirection == .horizontal else {
  145. // This type doesn't support vertical mode
  146. return
  147. }
  148. let scale = max(1 - (1-self.minimumScale) * abs(position), self.minimumScale)
  149. let transform = CGAffineTransform(scaleX: scale, y: scale)
  150. attributes.transform = transform
  151. let alpha = (self.minimumAlpha + (1-abs(position))*(1-self.minimumAlpha))
  152. attributes.alpha = alpha
  153. let zIndex = (1-abs(position)) * 10
  154. attributes.zIndex = Int(zIndex)
  155. case .coverFlow:
  156. guard scrollDirection == .horizontal else {
  157. // This type doesn't support vertical mode
  158. return
  159. }
  160. let position = min(max(-position,-1) ,1)
  161. let rotation = sin(position*(.pi)*0.5)*(.pi)*0.25*1.5
  162. let translationZ = -itemSpacing * 0.5 * abs(position)
  163. var transform3D = CATransform3DIdentity
  164. transform3D.m34 = -0.002
  165. transform3D = CATransform3DRotate(transform3D, rotation, 0, 1, 0)
  166. transform3D = CATransform3DTranslate(transform3D, 0, 0, translationZ)
  167. attributes.zIndex = 100 - Int(abs(position))
  168. attributes.transform3D = transform3D
  169. case .ferrisWheel, .invertedFerrisWheel:
  170. guard scrollDirection == .horizontal else {
  171. // This type doesn't support vertical mode
  172. return
  173. }
  174. // http://ronnqvi.st/translate-rotate-translate/
  175. var zIndex = 0
  176. var transform = CGAffineTransform.identity
  177. switch position {
  178. case -5 ... 5:
  179. let itemSpacing = attributes.bounds.width+self.proposedInteritemSpacing()
  180. let count: CGFloat = 14
  181. let circle: CGFloat = .pi * 2.0
  182. let radius = itemSpacing * count / circle
  183. let ty = radius * (self.type == .ferrisWheel ? 1 : -1)
  184. let theta = circle / count
  185. let rotation = position * theta * (self.type == .ferrisWheel ? 1 : -1)
  186. transform = transform.translatedBy(x: -position*itemSpacing, y: ty)
  187. transform = transform.rotated(by: rotation)
  188. transform = transform.translatedBy(x: 0, y: -ty)
  189. zIndex = Int((4.0-abs(position)*10))
  190. default:
  191. break
  192. }
  193. attributes.alpha = abs(position) < 0.5 ? 1 : self.minimumAlpha
  194. attributes.transform = transform
  195. attributes.zIndex = zIndex
  196. case .cubic:
  197. switch position {
  198. case -CGFloat.greatestFiniteMagnitude ... -1:
  199. attributes.alpha = 0
  200. case -1 ..< 1:
  201. attributes.alpha = 1
  202. attributes.zIndex = Int((1-position) * CGFloat(10))
  203. let direction: CGFloat = position < 0 ? 1 : -1
  204. let theta = position * .pi * 0.5 * (scrollDirection == .horizontal ? 1 : -1)
  205. let radius = scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height
  206. var transform3D = CATransform3DIdentity
  207. transform3D.m34 = -0.002
  208. switch scrollDirection {
  209. case .horizontal:
  210. // ForwardX -> RotateY -> BackwardX
  211. attributes.center.x += direction*radius*0.5 // ForwardX
  212. transform3D = CATransform3DRotate(transform3D, theta, 0, 1, 0) // RotateY
  213. transform3D = CATransform3DTranslate(transform3D,-direction*radius*0.5, 0, 0) // BackwardX
  214. case .vertical:
  215. // ForwardY -> RotateX -> BackwardY
  216. attributes.center.y += direction*radius*0.5 // ForwardY
  217. transform3D = CATransform3DRotate(transform3D, theta, 1, 0, 0) // RotateX
  218. transform3D = CATransform3DTranslate(transform3D,0, -direction*radius*0.5, 0) // BackwardY
  219. }
  220. attributes.transform3D = transform3D
  221. case 1 ... CGFloat.greatestFiniteMagnitude:
  222. attributes.alpha = 0
  223. default:
  224. attributes.alpha = 0
  225. attributes.zIndex = 0
  226. }
  227. }
  228. }
  229. // An interitem spacing proposed by transformer class. This will override the default interitemSpacing provided by the pager view.
  230. open func proposedInteritemSpacing() -> CGFloat {
  231. guard let pagerView = self.pagerView else {
  232. return 0
  233. }
  234. let scrollDirection = pagerView.scrollDirection
  235. switch self.type {
  236. case .overlap:
  237. guard scrollDirection == .horizontal else {
  238. return 0
  239. }
  240. return pagerView.itemSize.width * -self.minimumScale * 0.6
  241. case .linear:
  242. guard scrollDirection == .horizontal else {
  243. return 0
  244. }
  245. return pagerView.itemSize.width * -self.minimumScale * 0.2
  246. case .coverFlow:
  247. guard scrollDirection == .horizontal else {
  248. return 0
  249. }
  250. return -pagerView.itemSize.width * sin(.pi*0.25*0.25*3.0)
  251. case .ferrisWheel,.invertedFerrisWheel:
  252. guard scrollDirection == .horizontal else {
  253. return 0
  254. }
  255. return -pagerView.itemSize.width * 0.15
  256. case .cubic:
  257. return 0
  258. default:
  259. break
  260. }
  261. return pagerView.interitemSpacing
  262. }
  263. }