Skip to content

Commit

Permalink
[PR #9] Added Custom image transition and modified completion behavior.
Browse files Browse the repository at this point in the history
  • Loading branch information
kcharwood authored and cnoon committed Sep 16, 2015
1 parent 2794904 commit 42c590e
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 41 deletions.
135 changes: 98 additions & 37 deletions Source/UIImageView+AlamofireImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,30 +38,56 @@ extension UIImageView {
case FlipFromLeft(NSTimeInterval)
case FlipFromRight(NSTimeInterval)
case FlipFromTop(NSTimeInterval)
case Custom(
duration: NSTimeInterval,
animationOptions: UIViewAnimationOptions,
animations: (UIImageView, Image) -> Void,
completion: (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:
return { $0.image = $1 }
}
}

private var completion: (Bool -> Void)? {
switch self {
case Custom(_, _, _, let completion):
return completion
default:
return nil
}
}
}
Expand Down Expand Up @@ -204,15 +230,56 @@ 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.
The `completion` closure is called after the image download and filtering are complete, but before the start of
the image transition. Please note it is no longer the responsibility of the `completion` closure to set the
image. It will be set automatically. If you require a second notification after the image transition completes,
use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when
the image transition is 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
image view will not change its image until the image request finishes.
- parameter filter: The image filter applied to the image after the image request is finished.
- parameter imageTransition: The image transition animation applied to the image when set.
- parameter completion: A closure to be executed when the image request finishes. The closure has no
return value and takes three arguments: the original request, the response from
the server and the result containing either the image or the error that occurred.
If the image was returned from the image cache, the response will be `nil`.
*/
public func af_setImageWithURL(
URL: NSURL,
placeholderImage: UIImage?,
filter: ImageFilter?,
imageTransition: ImageTransition,
completion: ((NSURLRequest?, NSHTTPURLResponse?, Result<UIImage>) -> Void)?)
{
af_setImageWithURLRequest(
URLRequestWithURL(URL),
placeholderImage: placeholderImage,
filter: filter,
imageTransition: imageTransition,
completion: completion
)
}

/**
Asynchronously downloads an image from the specified URL, applies the specified image filter to the downloaded
image and sets it once finished while executing the image transition.

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.

The `completion` closure is called after the image download and filtering are complete, but before the start of
the image transition. Please note it is no longer the responsibility of the `completion` closure to set the
image. It will be set automatically. If you require a second notification after the image transition completes,
use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when
the image transition is finished.

- parameter URLRequest: The URL request.
- parameter placeholderImage: The image to be set initially until the image request finished. If `nil`, the
image view will not change its image until the image request finishes.
- parameter filter: The image filter applied to the image after the image request is finished.
- parameter imageTransition: The image transition animation applied to the image when set.
- parameter completion: A closure to be executed when the image request finishes. The closure has no
return value and takes three arguments: the original request, the response from
the server and the result containing either the image or the error that occurred.
Expand Down Expand Up @@ -263,26 +330,20 @@ extension UIImageView {

strongSelf.af_activeRequest = nil

guard completion == nil else {
if let image = result.value {
completion?(request, response, result)
return
}

if let image = result.value {
switch imageTransition {
case .None:
strongSelf.image = image
default:
UIView.transitionWithView(
strongSelf,
duration: imageTransition.duration,
options: imageTransition.animationOptions,
animations: {
strongSelf.image = image
},
completion: nil
)
}
UIView.transitionWithView(
strongSelf,
duration: imageTransition.duration,
options: imageTransition.animationOptions,
animations: {
imageTransition.animations(strongSelf, image)
},
completion: imageTransition.completion
)
} else {
completion?(request, response, result)
}
}
)
Expand Down
66 changes: 62 additions & 4 deletions Tests/UIImageViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -326,14 +326,29 @@ 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 expectation9 = expectationWithDescription("image download should succeed")
imageView.imageObserver = {
imageTransitionsComplete = true
expectation9.fulfill()
}
ImageDownloader.defaultInstance.imageCache?.removeAllImages()
imageView.af_setImageWithURL(
URL,
imageTransition: .Custom(
duration: 0.5,
animationOptions: UIViewAnimationOptions(),
animations: { $0.image = $1 },
completion: nil
)
)
waitForExpectationsWithTimeout(timeout, handler: nil)

// Then
XCTAssertTrue(imageTransitionsComplete, "image transitions complete should be true")
XCTAssertNotNil(imageView.image, "image view image should not be nil")
Expand Down Expand Up @@ -373,7 +388,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")
}

Expand Down Expand Up @@ -408,6 +423,49 @@ class UIImageViewTestCase: BaseTestCase {
XCTAssertTrue(result?.isFailure ?? false, "result should be a failure case")
}

func testThatCompletionHandlerAndCustomTransitionHandlerAreBothCalled() {
// Given
let imageView = UIImageView()

let completionExpectation = expectationWithDescription("image download should succeed")
let transitionExpectation = expectationWithDescription("image transition should complete")

var completionHandlerCalled = false
var transitionCompletionHandlerCalled = false

var result: Result<UIImage>?

// When
imageView.af_setImageWithURL(
URL,
placeholderImage: nil,
filter: nil,
imageTransition: .Custom(
duration: 0.1,
animationOptions: UIViewAnimationOptions(),
animations: { $0.image = $1 },
completion: { _ in
transitionCompletionHandlerCalled = true
transitionExpectation.fulfill()
}
),
completion: { _, _, responseResult in
completionHandlerCalled = true
result = responseResult

completionExpectation.fulfill()
}
)

waitForExpectationsWithTimeout(timeout, handler: nil)

// Then
XCTAssertTrue(completionHandlerCalled, "completion handler called should be true")
XCTAssertTrue(transitionCompletionHandlerCalled, "transition completion handler 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() {
Expand Down Expand Up @@ -481,7 +539,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")
}
}

0 comments on commit 42c590e

Please sign in to comment.