diff --git a/Source/UIImageView+AlamofireImage.swift b/Source/UIImageView+AlamofireImage.swift index 60435ef1..8ea8894f 100644 --- a/Source/UIImageView+AlamofireImage.swift +++ b/Source/UIImageView+AlamofireImage.swift @@ -38,30 +38,50 @@ extension UIImageView { case FlipFromLeft(NSTimeInterval) case FlipFromRight(NSTimeInterval) case FlipFromTop(NSTimeInterval) + case Custom(NSTimeInterval, UIViewAnimationOptions, ((UIImageView, Image) -> (Void)), ((Bool) -> Void)?) private var duration: NSTimeInterval { switch self { - case None: return 0.0 - case CrossDissolve(let duration): return duration - case CurlDown(let duration): return duration - case CurlUp(let duration): return duration - case FlipFromBottom(let duration): return duration - case FlipFromLeft(let duration): return duration - case FlipFromRight(let duration): return duration - case FlipFromTop(let duration): return duration + case None: return 0.0 + case CrossDissolve(let duration): return duration + case CurlDown(let duration): return duration + case CurlUp(let duration): return duration + case FlipFromBottom(let duration): return duration + case FlipFromLeft(let duration): return duration + case FlipFromRight(let duration): return duration + case FlipFromTop(let duration): return duration + case Custom(let duration, _, _, _): return duration } } private var animationOptions: UIViewAnimationOptions { switch self { - case None: return .TransitionNone - case CrossDissolve: return .TransitionCrossDissolve - case CurlDown: return .TransitionCurlDown - case CurlUp: return .TransitionCurlUp - case FlipFromBottom: return .TransitionFlipFromBottom - case FlipFromLeft: return .TransitionFlipFromLeft - case FlipFromRight: return .TransitionFlipFromRight - case FlipFromTop: return .TransitionFlipFromTop + case None: return .TransitionNone + case CrossDissolve: return .TransitionCrossDissolve + case CurlDown: return .TransitionCurlDown + case CurlUp: return .TransitionCurlUp + case FlipFromBottom: return .TransitionFlipFromBottom + case FlipFromLeft: return .TransitionFlipFromLeft + case FlipFromRight: return .TransitionFlipFromRight + case FlipFromTop: return .TransitionFlipFromTop + case Custom(_, let animationOptions, _, _): return animationOptions + } + } + + private var animations: ((UIImageView, Image) -> (Void)) { + switch self { + case Custom(_, _,let animations, _): return animations + default: + let animation: ((UIImageView, Image) -> (Void)) = {$0.image = $1} + return animation + } + } + + private var completion: ((Bool) -> Void)? { + switch self { + case Custom(_, _, _, let completion): return completion + default: + return nil } } } @@ -204,9 +224,11 @@ extension UIImageView { If the image is cached locally, the image is set immediately. Otherwise the specified placehoder image will be set immediately, and then the remote image will be set once the image request is finished. - If a `completion` closure is specified, it is the responsibility of the closure to set the image of the image - view before returning. If no `completion` closure is specified, the default behavior of setting the image is - applied. + If an 'imageTransition` other than `None` is specified, the `completion` closure will be called just before the start of transition, and the image will not yet be set on the image view. If `None` is specified, the completion closure will be called after the image as has been set on the image view. + + Note that it is no longer the responsibility of the `completion` closure to set the image. It will be set automatically. + + If you require being notified when the image transition has completion, use a `.Custom` image transition and specify a completion closure. That closure will be called when the transition animation has finished. - parameter URL: The URL used for the image request. - parameter placeholderImage: The image to be set initially until the image request finished. If `nil`, the @@ -263,26 +285,23 @@ extension UIImageView { strongSelf.af_activeRequest = nil - guard completion == nil else { - completion?(request, response, result) - return - } - if let image = result.value { switch imageTransition { case .None: strongSelf.image = image + completion?(request, response, result) default: + completion?(request, response, result) UIView.transitionWithView( strongSelf, duration: imageTransition.duration, options: imageTransition.animationOptions, - animations: { - strongSelf.image = image - }, - completion: nil + animations: {imageTransition.animations(strongSelf, image)}, + completion: imageTransition.completion ) } + } else { + completion?(request, response, result) } } ) diff --git a/Tests/UIImageViewTests.swift b/Tests/UIImageViewTests.swift index 5d6d1db4..dc71fc7f 100644 --- a/Tests/UIImageViewTests.swift +++ b/Tests/UIImageViewTests.swift @@ -326,14 +326,22 @@ class UIImageViewTestCase: BaseTestCase { let expectation8 = expectationWithDescription("image download should succeed") imageView.imageObserver = { - imageTransitionsComplete = true expectation8.fulfill() } - ImageDownloader.defaultInstance.imageCache?.removeAllImages() imageView.af_setImageWithURL(URL, imageTransition: .FlipFromTop(0.1)) waitForExpectationsWithTimeout(timeout, handler: nil) + let customTransition = UIImageView.ImageTransition.Custom(0.5, UIViewAnimationOptions(), {$0.image = $1}, nil) + let expectation9 = expectationWithDescription("image download should succeed") + imageView.imageObserver = { + imageTransitionsComplete = true + expectation9.fulfill() + } + ImageDownloader.defaultInstance.imageCache?.removeAllImages() + imageView.af_setImageWithURL(URL, imageTransition: customTransition) + waitForExpectationsWithTimeout(timeout, handler: nil) + // Then XCTAssertTrue(imageTransitionsComplete, "image transitions complete should be true") XCTAssertNotNil(imageView.image, "image view image should not be nil") @@ -373,7 +381,7 @@ class UIImageViewTestCase: BaseTestCase { // Then XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") - XCTAssertNil(imageView.image, "image view image should be nil when completion handler is not nil") + XCTAssertNotNil(imageView.image, "image view image should be not be nil when completion handler is not nil") XCTAssertTrue(result?.isSuccess ?? false, "result should be a success case") } @@ -408,6 +416,49 @@ class UIImageViewTestCase: BaseTestCase { XCTAssertTrue(result?.isFailure ?? false, "result should be a failure case") } + func testThatCompletionHandlerAndCustomTransitionHandlerAreBothCalled() { + // Given + let imageView = UIImageView() + + let URLRequest: NSURLRequest = { + let request = NSMutableURLRequest(URL: URL) + request.addValue("image/*", forHTTPHeaderField: "Accept") + return request + }() + + let transitionExpectation = expectationWithDescription("image transition should complete") + var transitionCompletionHandlerCalled = false + let customTransition = UIImageView.ImageTransition.Custom(0.5, UIViewAnimationOptions(), {$0.image = $1}, { (_) in + transitionCompletionHandlerCalled = true + transitionExpectation.fulfill() + }) + + let expectation = expectationWithDescription("image download should succeed") + var completionHandlerCalled = false + var result: Result? + + // When + imageView.af_setImageWithURLRequest( + URLRequest, + placeholderImage: nil, + filter: nil, + imageTransition: customTransition, + completion: { _, _, responseResult in + completionHandlerCalled = true + result = responseResult + expectation.fulfill() + } + ) + + waitForExpectationsWithTimeout(timeout, handler: nil) + + // Then + XCTAssertTrue(completionHandlerCalled, "completion handler called should be true") + XCTAssertTrue(transitionCompletionHandlerCalled, "transition completion hanlder called should be true") + XCTAssertNotNil(imageView.image, "image view image should be not be nil when completion handler is not nil") + XCTAssertTrue(result?.isSuccess ?? false, "result should be a success case") + } + // MARK: - Cancellation func testThatImageDownloadCanBeCancelled() { @@ -481,7 +532,7 @@ class UIImageViewTestCase: BaseTestCase { // Then XCTAssertFalse(completion1Called, "completion 1 called should be false") XCTAssertTrue(completion2Called, "completion 2 called should be true") - XCTAssertNil(imageView.image, "image view image should be nil when completion handler is not nil") + XCTAssertNotNil(imageView.image, "image view image should not be nil when completion handler is not nil") XCTAssertTrue(result?.isSuccess ?? false, "result should be a success case") } }