@@ -176,4 +176,184 @@ class UIKitBehavioralTests: XCTestCase {
176
176
" Expected \( keyPath. rawValue) not to generate any animations. " )
177
177
}
178
178
}
179
+
180
+ // MARK: .beginFromCurrentState option behavior
181
+ //
182
+ // The following tests indicate that UIKit treats .beginFromCurrentState differently depending
183
+ // on the key path being animated. This difference is in line with whether or not a key path is
184
+ // animated additively or not.
185
+ //
186
+ // > See testSomePropertiesImplicitlyAnimateAdditively and
187
+ // > testSomePropertiesImplicitlyAnimateButNotAdditively for a list of which key paths are
188
+ // > animated which way.
189
+ //
190
+ // Notably, ONLY non-additive key paths are affected by the beginFromCurrentState option. This
191
+ // likely became the case starting in iOS 8 when additive animations were enabled by default.
192
+ // Additive animations will always animate additively regardless of whether or not you provide
193
+ // this flag.
194
+
195
+ func testDefaultsAnimatesOpacityNonAdditivelyFromItsModelLayerState( ) {
196
+ UIView . animate ( withDuration: 0.1 ) {
197
+ self . view. alpha = 0.5
198
+ }
199
+
200
+ RunLoop . main. run ( until: . init( timeIntervalSinceNow: 0.01 ) )
201
+
202
+ let initialValue = self . view. layer. opacity
203
+
204
+ UIView . animate ( withDuration: 0.1 ) {
205
+ self . view. alpha = 0.2
206
+ }
207
+
208
+ XCTAssertNotNil ( view. layer. animationKeys ( ) ,
209
+ " Expected an animation to be added, but none were found. " )
210
+ guard let animationKeys = view. layer. animationKeys ( ) else {
211
+ return
212
+ }
213
+ XCTAssertEqual ( animationKeys. count, 1 ,
214
+ " Expected only one animation to be added, but the following were found: "
215
+ + " \( animationKeys) . " )
216
+ guard let key = animationKeys. first,
217
+ let animation = view. layer. animation ( forKey: key) as? CABasicAnimation else {
218
+ return
219
+ }
220
+
221
+ XCTAssertFalse ( animation. isAdditive, " Expected the animation not to be additive, but it was. " )
222
+
223
+ XCTAssertTrue ( animation. fromValue is Float ,
224
+ " The animation's from value was not a number type: "
225
+ + String( describing: animation. fromValue) )
226
+ guard let fromValue = animation. fromValue as? Float else {
227
+ return
228
+ }
229
+
230
+ XCTAssertEqualWithAccuracy ( fromValue, initialValue, accuracy: 0.0001 ,
231
+ " Expected the animation to start from \( initialValue) , "
232
+ + " but it did not. " )
233
+ }
234
+
235
+ func testBeginFromCurrentStateAnimatesOpacityNonAdditivelyFromItsPresentationLayerState( ) {
236
+ UIView . animate ( withDuration: 0.1 ) {
237
+ self . view. alpha = 0.5
238
+ }
239
+
240
+ RunLoop . main. run ( until: . init( timeIntervalSinceNow: 0.01 ) )
241
+
242
+ let initialValue = self . view. layer. presentation ( ) !. opacity
243
+
244
+ UIView . animate ( withDuration: 0.1 , delay: 0 , options: . beginFromCurrentState, animations: {
245
+ self . view. alpha = 0.2
246
+ } , completion: nil )
247
+
248
+ XCTAssertNotNil ( view. layer. animationKeys ( ) ,
249
+ " Expected an animation to be added, but none were found. " )
250
+ guard let animationKeys = view. layer. animationKeys ( ) else {
251
+ return
252
+ }
253
+ XCTAssertEqual ( animationKeys. count, 1 ,
254
+ " Expected only one animation to be added, but the following were found: "
255
+ + " \( animationKeys) . " )
256
+ guard let key = animationKeys. first,
257
+ let animation = view. layer. animation ( forKey: key) as? CABasicAnimation else {
258
+ return
259
+ }
260
+
261
+ XCTAssertFalse ( animation. isAdditive, " Expected the animation not to be additive, but it was. " )
262
+
263
+ XCTAssertTrue ( animation. fromValue is Float ,
264
+ " The animation's from value was not a number type: "
265
+ + String( describing: animation. fromValue) )
266
+ guard let fromValue = animation. fromValue as? Float else {
267
+ return
268
+ }
269
+
270
+ XCTAssertEqualWithAccuracy ( fromValue, initialValue, accuracy: 0.0001 ,
271
+ " Expected the animation to start from \( initialValue) , "
272
+ + " but it did not. " )
273
+ }
274
+
275
+ func testDefaultsAnimatesPositionAdditivelyFromItsModelLayerState( ) {
276
+ UIView . animate ( withDuration: 0.1 ) {
277
+ self . view. layer. position = CGPoint ( x: 100 , y: self . view. layer. position. y)
278
+ }
279
+
280
+ RunLoop . main. run ( until: . init( timeIntervalSinceNow: 0.01 ) )
281
+
282
+ let initialValue = self . view. layer. position
283
+
284
+ UIView . animate ( withDuration: 0.1 ) {
285
+ self . view. layer. position = CGPoint ( x: 20 , y: self . view. layer. position. y)
286
+ }
287
+
288
+ let displacement = initialValue. x - self . view. layer. position. x
289
+
290
+ XCTAssertNotNil ( view. layer. animationKeys ( ) ,
291
+ " Expected an animation to be added, but none were found. " )
292
+ guard let animationKeys = view. layer. animationKeys ( ) else {
293
+ return
294
+ }
295
+ XCTAssertEqual ( animationKeys. count, 2 ,
296
+ " Expected two animations to be added, but the following were found: "
297
+ + " \( animationKeys) . " )
298
+ guard let key = animationKeys. first ( where: { $0 != " position " } ) ,
299
+ let animation = view. layer. animation ( forKey: key) as? CABasicAnimation else {
300
+ return
301
+ }
302
+
303
+ XCTAssertTrue ( animation. isAdditive, " Expected the animation to be additive, but it wasn't. " )
304
+
305
+ XCTAssertTrue ( animation. fromValue is CGPoint ,
306
+ " The animation's from value was not a point type: "
307
+ + String( describing: animation. fromValue) )
308
+ guard let fromValue = animation. fromValue as? CGPoint else {
309
+ return
310
+ }
311
+
312
+ XCTAssertEqualWithAccuracy ( fromValue. x, displacement, accuracy: 0.0001 ,
313
+ " Expected the animation to have a delta of \( displacement) , "
314
+ + " but it did not. " )
315
+ }
316
+
317
+ func testBeginFromCurrentStateAnimatesPositionAdditivelyFromItsModelLayerState( ) {
318
+ UIView . animate ( withDuration: 0.1 ) {
319
+ self . view. layer. position = CGPoint ( x: 100 , y: self . view. layer. position. y)
320
+ }
321
+
322
+ RunLoop . main. run ( until: . init( timeIntervalSinceNow: 0.01 ) )
323
+
324
+ let initialValue = self . view. layer. position
325
+
326
+ UIView . animate ( withDuration: 0.1 , delay: 0 , options: . beginFromCurrentState, animations: {
327
+ self . view. layer. position = CGPoint ( x: 20 , y: self . view. layer. position. y)
328
+ } , completion: nil )
329
+
330
+ let displacement = initialValue. x - self . view. layer. position. x
331
+
332
+ XCTAssertNotNil ( view. layer. animationKeys ( ) ,
333
+ " Expected an animation to be added, but none were found. " )
334
+ guard let animationKeys = view. layer. animationKeys ( ) else {
335
+ return
336
+ }
337
+ XCTAssertEqual ( animationKeys. count, 2 ,
338
+ " Expected two animations to be added, but the following were found: "
339
+ + " \( animationKeys) . " )
340
+ guard let key = animationKeys. first ( where: { $0 != " position " } ) ,
341
+ let animation = view. layer. animation ( forKey: key) as? CABasicAnimation else {
342
+ return
343
+ }
344
+
345
+ XCTAssertTrue ( animation. isAdditive, " Expected the animation to be additive, but it wasn't. " )
346
+
347
+ XCTAssertTrue ( animation. fromValue is CGPoint ,
348
+ " The animation's from value was not a point type: "
349
+ + String( describing: animation. fromValue) )
350
+ guard let fromValue = animation. fromValue as? CGPoint else {
351
+ return
352
+ }
353
+
354
+ XCTAssertEqualWithAccuracy ( fromValue. x, displacement, accuracy: 0.0001 ,
355
+ " Expected the animation to have a delta of \( displacement) , "
356
+ + " but it did not. " )
357
+ }
358
+
179
359
}
0 commit comments