8
8
9
9
import UIKit
10
10
11
+ public protocol AnimatedCardsViewDataSource : class {
11
12
12
- protocol AnimatedCardsViewDataSource : class {
13
-
14
13
}
15
14
16
15
public class AnimatedCardsView : UIView {
17
16
18
17
private var cardArray : [ UIView ] !
19
18
19
+ public weak var dataSource : AnimatedCardsViewDataSource ?
20
+
20
21
public struct Constants {
21
22
struct DefaultSize {
22
- static let width = 400.0
23
- static let height = 300 .0
23
+ static let width : CGFloat = 400.0
24
+ static let ratio : CGFloat = 3.0 / 4 .0
24
25
}
25
26
static let numberOfCards = 8
26
27
}
27
28
29
+ var frontCardTag = 1
30
+ var cardCount = 0
31
+ let maxVisibleCardCount = 8
32
+ let gradientBackgroundLayer = CAGradientLayer ( )
33
+ var gestureDirection : panScrollDirection = . Up
34
+
28
35
override init ( frame: CGRect ) {
29
36
cardArray = [ ]
30
37
super. init ( frame: frame)
31
- generateCards ( )
38
+ configure ( )
32
39
}
33
40
34
41
required public init ? ( coder aDecoder: NSCoder ) {
35
42
cardArray = [ ]
36
43
super. init ( coder: aDecoder)
37
44
backgroundColor = UIColor . yellowColor ( )
45
+ configure ( )
46
+ }
47
+
48
+ private func configure( ) {
38
49
generateCards ( )
50
+ let scrollGesture = UIPanGestureRecognizer ( target: self , action: " scrollOnView: " )
51
+ self . addGestureRecognizer ( scrollGesture)
52
+ cardCount = Constants . numberOfCards
53
+ relayoutSubViews ( )
39
54
}
40
55
41
56
@@ -53,20 +68,297 @@ public class AnimatedCardsView: UIView {
53
68
private func generateNewCardViewWithTagId( tagId: NSInteger ) -> UIView {
54
69
let view = UIView ( )
55
70
view. translatesAutoresizingMaskIntoConstraints = false
56
- view. tag = tagId
57
- view. backgroundColor = UIColor . purpleColor ( )
71
+ view. tag = tagId+ 1
72
+ switch tagId {
73
+ case 0 : view. backgroundColor = UIColor . purpleColor ( )
74
+ case 1 : view. backgroundColor = UIColor . redColor ( )
75
+ case 2 : view. backgroundColor = UIColor . blackColor ( )
76
+ case 3 : view. backgroundColor = UIColor . greenColor ( )
77
+ case 4 : view. backgroundColor = UIColor . brownColor ( )
78
+ case 5 : view. backgroundColor = UIColor . darkGrayColor ( )
79
+ case 6 : view. backgroundColor = UIColor . blueColor ( )
80
+ case 7 : view. backgroundColor = UIColor . orangeColor ( )
81
+ default : view. backgroundColor = UIColor . whiteColor ( )
82
+ }
58
83
return view
59
84
}
60
85
61
86
private func applyConstraintsToView( view: UIView ) {
62
87
view. addConstraints ( [
63
- NSLayoutConstraint ( item: view, attribute: . Width, relatedBy: . Equal, toItem: nil , attribute: . NotAnAttribute, multiplier: CGFloat ( 1.0 ) , constant: CGFloat ( Constants . DefaultSize. width) ) ,
64
- NSLayoutConstraint ( item: view, attribute: . Height, relatedBy: . Equal, toItem: nil , attribute: . NotAnAttribute , multiplier: CGFloat ( 1.0 ) , constant: CGFloat ( Constants . DefaultSize . height ) ) ,
88
+ NSLayoutConstraint ( item: view, attribute: . Width, relatedBy: . Equal, toItem: nil , attribute: . NotAnAttribute, multiplier: CGFloat ( 1.0 ) , constant: Constants . DefaultSize. width) ,
89
+ NSLayoutConstraint ( item: view, attribute: . Height, relatedBy: . Equal, toItem: view , attribute: . Width , multiplier: Constants . DefaultSize . ratio , constant: 0 ) ,
65
90
] )
66
91
view. superview!. addConstraints ( [
67
92
NSLayoutConstraint ( item: view, attribute: . CenterX, relatedBy: . Equal, toItem: view. superview, attribute: . CenterX, multiplier: CGFloat ( 1.0 ) , constant: 0 ) ,
68
93
NSLayoutConstraint ( item: view, attribute: . CenterY, relatedBy: . Equal, toItem: view. superview, attribute: . CenterY, multiplier: CGFloat ( 1.0 ) , constant: 0 ) ,
69
94
] )
70
95
}
96
+
97
+ public func flipUp( ) {
98
+ if frontCardTag == 1 {
99
+ return
100
+ }
101
+
102
+ guard let previousFrontView = viewWithTag ( frontCardTag - 1 ) else {
103
+ return
104
+ }
105
+
106
+ var flipUpTransform3D = CATransform3DIdentity
107
+ flipUpTransform3D. m34 = - 1.0 / 1000.0
108
+ flipUpTransform3D = CATransform3DRotate ( flipUpTransform3D, 0 , 1 , 0 , 0 )
109
+
110
+ previousFrontView. hidden = false
111
+ if let subView = previousFrontView. viewWithTag ( 10 ) {
112
+ subView. hidden = false
113
+ }
114
+
115
+ UIView . animateWithDuration ( 0.2 , animations: {
116
+ previousFrontView. layer. transform = flipUpTransform3D
117
+ } , completion: {
118
+ _ in
119
+ self . adjustUpViewLayout ( )
120
+ } )
121
+ }
122
+
123
+ public func flipDown( ) {
124
+ if frontCardTag > cardCount{
125
+ return
126
+ }
127
+
128
+ guard let frontView = viewWithTag ( frontCardTag) else {
129
+ return
130
+ }
131
+
132
+ if let subView = frontView. viewWithTag ( 10 ) {
133
+ subView. hidden = true
134
+ }
135
+
136
+ var flipDownTransform3D = CATransform3DIdentity
137
+ flipDownTransform3D. m34 = - 1.0 / 1000.0
138
+ //此处有个很大的问题,折磨了我几个小时。原来官方的实现有个临界问题,旋转180度不会执行,其他的角度则没有问题
139
+ flipDownTransform3D = CATransform3DRotate ( flipDownTransform3D, CGFloat ( - M_PI) * 0.99 , 1 , 0 , 0 )
140
+ UIView . animateWithDuration ( 0.3 , animations: {
141
+ frontView. layer. transform = flipDownTransform3D
142
+ } , completion: {
143
+ _ in
144
+
145
+ frontView. hidden = true
146
+ self . adjustDownViewLayout ( )
147
+
148
+ } )
149
+
150
+ }
151
+
152
+ // //MARK: Handle Screen Rotation
153
+ // override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
154
+ // super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
155
+ // coordinator.animateAlongsideTransition({
156
+ // _ in
157
+ // self.gradientBackgroundLayer.frame = self.view.bounds
158
+ // self.relayoutSubViews()
159
+ // }, completion: nil)
160
+ // }
71
161
72
162
}
163
+
164
+
165
+ // MARK: Handle Layout
166
+ extension AnimatedCardsView {
167
+ func relayoutSubViewWith( viewTag: Int , relativeIndex: Int , delay: NSTimeInterval , haveBorderWidth: Bool ) {
168
+ let width = Constants . DefaultSize. width
169
+ if let subView = self . viewWithTag ( viewTag) {
170
+
171
+ subView. layer. anchorPoint = CGPointMake ( 0.5 , 1 )
172
+
173
+ // if let nestedImageView = subView.viewWithTag(10) as? UIImageView{
174
+ // nestedImageView.image = cardImageAtIndex(viewTag - 1)
175
+ // }
176
+
177
+ subView. layer. zPosition = CGFloat ( 1000 - relativeIndex)
178
+ subView. alpha = calculateAlphaForIndex ( relativeIndex)
179
+
180
+ var borderWidth : CGFloat = 0
181
+ let filterSubViewConstraints = subView. constraints. filter ( { $0. firstAttribute == . Width && $0. secondItem == nil } )
182
+ if filterSubViewConstraints. count > 0 {
183
+ let widthConstraint = filterSubViewConstraints [ 0 ]
184
+ let widthScale = calculateWidthScaleForIndex ( relativeIndex)
185
+ widthConstraint. constant = widthScale * width
186
+ borderWidth = width * widthScale / 100
187
+ }
188
+
189
+ let filteredViewConstraints = self . constraints. filter ( { $0. firstItem as? UIView == subView && $0. secondItem as? UIView == self && $0. firstAttribute == . CenterY} )
190
+ if filteredViewConstraints. count > 0 {
191
+ let centerYConstraint = filteredViewConstraints [ 0 ]
192
+ let subViewHeight = calculateWidthScaleForIndex ( relativeIndex) * width * ( 1 / Constants. DefaultSize. ratio)
193
+ let YOffset = calculusYOffsetForIndex ( relativeIndex)
194
+ centerYConstraint. constant = subViewHeight/ 2 - YOffset
195
+ }
196
+
197
+ if haveBorderWidth{
198
+ subView. layer. borderWidth = borderWidth
199
+ } else {
200
+ subView. layer. borderWidth = 0
201
+ }
202
+
203
+
204
+ UIView . animateWithDuration ( 0.2 , delay: delay, options: UIViewAnimationOptions . BeginFromCurrentState, animations: {
205
+ self . layoutIfNeeded ( )
206
+ } , completion: nil )
207
+ }
208
+ }
209
+
210
+ func adjustUpViewLayout( ) {
211
+ if frontCardTag >= 2 {
212
+ let endCardTag = cardCount - frontCardTag > maxVisibleCardCount - 1 ? ( frontCardTag + maxVisibleCardCount - 1 ) : cardCount
213
+ let feed : UInt32 = 2
214
+ let randomRoll = arc4random_uniform ( feed)
215
+ switch randomRoll{
216
+ case 0 :
217
+ for var viewTag = frontCardTag; viewTag <= endCardTag; ++ viewTag{
218
+ let delay : NSTimeInterval = Double ( viewTag - frontCardTag) * 0.1
219
+ let relativeIndex = viewTag - frontCardTag + 1
220
+ relayoutSubViewWith ( viewTag, relativeIndex: relativeIndex, delay: delay, haveBorderWidth: true )
221
+ }
222
+ case 1 :
223
+ for var viewTag = endCardTag; viewTag >= frontCardTag; -- viewTag{
224
+ let delay : NSTimeInterval = Double ( cardCount - viewTag) * 0.1
225
+ let relativeIndex = viewTag - frontCardTag + 1
226
+ relayoutSubViewWith ( viewTag, relativeIndex: relativeIndex, delay: delay, haveBorderWidth: true )
227
+ }
228
+ default :
229
+ print ( " NOT YET " )
230
+ }
231
+
232
+ frontCardTag -= 1
233
+ }
234
+ }
235
+
236
+ func adjustDownViewLayout( ) {
237
+ frontCardTag += 1
238
+ let endCardTag = cardCount - frontCardTag > maxVisibleCardCount - 1 ? ( frontCardTag + maxVisibleCardCount - 1 ) : cardCount
239
+ if frontCardTag <= endCardTag{
240
+ for viewTag in frontCardTag... endCardTag{
241
+ let delay : NSTimeInterval = 0.1 * Double( viewTag - frontCardTag)
242
+ let relativeIndex = viewTag - frontCardTag
243
+ relayoutSubViewWith ( viewTag, relativeIndex: relativeIndex, delay: delay, haveBorderWidth: true )
244
+ }
245
+ }
246
+ }
247
+
248
+ func relayoutSubViews( ) {
249
+ let endCardTag = cardCount - frontCardTag > maxVisibleCardCount - 1 ? ( frontCardTag + maxVisibleCardCount - 1 ) : cardCount
250
+ if frontCardTag <= endCardTag{
251
+ for viewTag in frontCardTag... endCardTag{
252
+ if let subView = self . viewWithTag ( viewTag) {
253
+
254
+ let relativeIndex = viewTag - frontCardTag
255
+ let delay : NSTimeInterval = 0
256
+
257
+ subView. layer. borderColor = UIColor . whiteColor ( ) . CGColor
258
+ relayoutSubViewWith ( viewTag, relativeIndex: relativeIndex, delay: delay, haveBorderWidth: true )
259
+
260
+ }
261
+ }
262
+ }
263
+
264
+ //adjust hiddened views
265
+ if frontCardTag > 1 {
266
+ for viewTag in 1 ..< frontCardTag{
267
+ relayoutSubViewWith ( viewTag, relativeIndex: 0 , delay: 0 , haveBorderWidth: false )
268
+ }
269
+ }
270
+
271
+ UIView . animateWithDuration ( 0.1 , animations: {
272
+ self . layoutIfNeeded ( )
273
+ } )
274
+
275
+ }
276
+
277
+ //MARK: Helper Method
278
+ //f(x) = k * x + m
279
+ func calculateFactorOfFunction( x1: CGFloat , x2: CGFloat , y1: CGFloat , y2: CGFloat ) -> ( CGFloat , CGFloat ) {
280
+
281
+ let k = ( y1- y2) / ( x1- x2)
282
+ let m = ( x1*y2 - x2*y1) / ( x1- x2)
283
+
284
+ return ( k, m)
285
+ }
286
+
287
+ func calculateResult( argument x: Int , k: CGFloat , m: CGFloat ) -> CGFloat {
288
+ return k * CGFloat( x) + m
289
+ }
290
+
291
+ func calcuteResultWith( x1: CGFloat , x2: CGFloat , y1: CGFloat , y2: CGFloat , argument: Int ) -> CGFloat {
292
+ let ( k, m) = calculateFactorOfFunction ( x1, x2: x2, y1: y1, y2: y2)
293
+ return calculateResult ( argument: argument, k: k, m: m)
294
+ }
295
+
296
+ //I set the gap between 0Card and 1st Card is 35, gap between the last two card is 15. These value on iPhone is a little big, you could make it less.
297
+ //设定头两个卡片的距离为35,最后两张卡片之间的举例为15。不设定成等距才符合视觉效果。
298
+ func calculusYOffsetForIndex( indexInQueue: Int ) -> CGFloat {
299
+ if indexInQueue < 1 {
300
+ return CGFloat ( 0 )
301
+ }
302
+
303
+ var sum : CGFloat = 0.0
304
+ for i in 1 ... indexInQueue{
305
+ var result = calcuteResultWith ( 1 , x2: 8 , y1: 35 , y2: 15 , argument: i)
306
+ if result < 5 {
307
+ result = 5.0
308
+ }
309
+ sum += result
310
+ }
311
+
312
+ return sum
313
+ }
314
+
315
+ func calculateWidthScaleForIndex( indexInQueue: Int ) -> CGFloat {
316
+ let widthBaseScale : CGFloat = 0.5
317
+
318
+ var factor : CGFloat = 1
319
+ if indexInQueue == 0 {
320
+ factor = 1
321
+ } else {
322
+ factor = calculateScaleFactorForIndex ( indexInQueue)
323
+ }
324
+
325
+ return widthBaseScale * factor
326
+ }
327
+
328
+ //Zoom out card one by one.
329
+ //为符合视觉以及营造景深效果,卡片依次缩小
330
+ func calculateScaleFactorForIndex( indexInQueue: Int ) -> CGFloat {
331
+ if indexInQueue < 1 {
332
+ return CGFloat ( 1 )
333
+ }
334
+
335
+ var scale = calcuteResultWith ( 1 , x2: 8 , y1: 0.95 , y2: 0.5 , argument: indexInQueue)
336
+ if scale < 0.1 {
337
+ scale = 0.1
338
+ }
339
+
340
+ return scale
341
+ }
342
+
343
+ func calculateAlphaForIndex( indexInQueue: Int ) -> CGFloat {
344
+ if indexInQueue < 1 {
345
+ return CGFloat ( 1 )
346
+ }
347
+
348
+ var alpha = calcuteResultWith ( 6 , x2: 9 , y1: 1 , y2: 0.4 , argument: indexInQueue)
349
+ if alpha < 0.1 {
350
+ alpha = 0.1
351
+ } else if alpha > 1 {
352
+ alpha = 1
353
+ }
354
+
355
+ return alpha
356
+ }
357
+
358
+ func calculateBorderWidthForIndex( indexInQueue: Int , initialBorderWidth: CGFloat ) -> CGFloat {
359
+ let scaleFactor = calculateScaleFactorForIndex ( indexInQueue)
360
+ return scaleFactor * initialBorderWidth
361
+ }
362
+
363
+ }
364
+
0 commit comments