Skip to content

Commit

Permalink
Fix borked frame preloading
Browse files Browse the repository at this point in the history
  • Loading branch information
Reda Lemeden committed May 1, 2016
1 parent 47ef2e2 commit aa2e6a1
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 87 deletions.
12 changes: 12 additions & 0 deletions Demo/demo/Images.xcassets/earth.dataset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"data" : [
{
"idiom" : "universal",
"filename" : "earth.gif"
}
]
}
Binary file added Demo/demo/Images.xcassets/earth.dataset/earth.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions Demo/demo/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="14F27" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="vXZ-lx-hvc">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="vXZ-lx-hvc">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Gifu-->
Expand Down
74 changes: 43 additions & 31 deletions GifuTests/GifuTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,70 @@ import ImageIO
@testable import Gifu

private let imageData = testImageDataNamed("mugen.gif")
private let staticImage = UIImage(data: imageData!)
private let staticImage = UIImage(data: imageData)!
private let preloadFrameCount = 20

class GifuTests: XCTestCase {
var animator: Animator?
var originalFrameCount: Int?
var preloadedFrameCount: Int?
var animator: Animator!
var originalFrameCount: Int!

override func setUp() {
super.setUp()
animator = Animator(data: imageData!, size: CGSizeZero, contentMode: .ScaleToFill, framePreloadCount: 20)
animator!.prepareFrames()
originalFrameCount = Int(CGImageSourceGetCount(animator!.imageSource))
preloadedFrameCount = animator!.animatedFrames.count
}

override func tearDown() {
super.tearDown()
animator = Animator(data: imageData, size: CGSizeZero, contentMode: .ScaleToFill, framePreloadCount: preloadFrameCount)
originalFrameCount = Int(CGImageSourceGetCount(animator.imageSource))
}

func testIsAnimatable() {
XCTAssertTrue(animator!.isAnimatable)
XCTAssertTrue(animator.isAnimatable)
}

func testFramePreload() {
XCTAssertLessThanOrEqual(preloadedFrameCount!, originalFrameCount!)
}
let expectation = expectationWithDescription("frameDuration")

func testFrameAtIndex() {
XCTAssertNotNil(animator!.frameAtIndex(preloadedFrameCount! - 1))
}
animator.prepareFrames {
let animatedFrameCount = self.animator.animatedFrames.count
XCTAssertEqual(animatedFrameCount, self.originalFrameCount)
XCTAssertNotNil(self.animator.frameAtIndex(preloadFrameCount - 1))
XCTAssertNil(self.animator.frameAtIndex(preloadFrameCount + 1)?.images)
XCTAssertEqual(self.animator.currentFrameIndex, 0)

func testFrameDurationPrecision() {
let image = animator!.frameAtIndex(5)
XCTAssertTrue((image!.duration - 0.05) < 0.00001)
}
self.animator.shouldChangeFrame(1.0) { hasNewFrame in
XCTAssertTrue(hasNewFrame)
XCTAssertEqual(self.animator.currentFrameIndex, 1)
expectation.fulfill()
}
}

func testFrameSize() {
let image = animator!.frameAtIndex(5)
XCTAssertEqual(image!.size, staticImage!.size)
waitForExpectationsWithTimeout(1.0) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}

func testPrepareFramesPerformance() {
let tempAnimator = Animator(data: imageData!, size: CGSizeZero, contentMode: .ScaleToFill, framePreloadCount: 50)
func testFrameInfo() {
let expectation = expectationWithDescription("testFrameInfoIsAccurate")

animator.prepareFrames {
let frameDuration = self.animator.frameAtIndex(5)?.duration ?? 0
XCTAssertTrue((frameDuration - 0.05) < 0.00001)

let imageSize = self.animator.frameAtIndex(5)?.size ?? CGSizeZero
XCTAssertEqual(imageSize, staticImage.size)

expectation.fulfill()
}

self.measureBlock() {
tempAnimator.prepareFrames()
waitForExpectationsWithTimeout(1.0) { error in
if let error = error {
print("Error: \(error.localizedDescription)")
}
}
}
}

private func testImageDataNamed(name: String) -> NSData? {
private func testImageDataNamed(name: String) -> NSData {
let testBundle = NSBundle(forClass: GifuTests.self)
let imagePath = testBundle.bundleURL.URLByAppendingPathComponent(name)
return NSData(contentsOfURL: imagePath)
return NSData(contentsOfURL: imagePath)!
}
15 changes: 8 additions & 7 deletions Source/AnimatableImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class AnimatableImageView: UIImageView {
}

@objc func onScreenUpdate() {
target?.updateFrame()
target?.updateFrameIfNeeded()
}
}

Expand Down Expand Up @@ -85,7 +85,7 @@ public class AnimatableImageView: UIImageView {

/// Updates the `image` property of the image view if necessary. This method should not be called manually.
override public func displayLayer(layer: CALayer) {
image = animator?.currentFrame
image = animator?.currentFrameImage ?? image
}

/// Starts the image view animation.
Expand All @@ -100,16 +100,17 @@ public class AnimatableImageView: UIImageView {
displayLink.paused = true
}

/// Reset the image view values
/// Reset the image view values.
public func prepareForReuse() {
stopAnimatingGIF()
animator = nil
}

/// Update the current frame with the displayLink duration
func updateFrame() {
if animator?.updateCurrentFrame(displayLink.duration) ?? false {
layer.setNeedsDisplay()
/// Update the current frame if needed.
func updateFrameIfNeeded() {
guard let animator = animator else { return }
animator.shouldChangeFrame(displayLink.duration) { hasNewFrame in
if hasNewFrame { self.layer.setNeedsDisplay() }
}
}

Expand Down
21 changes: 19 additions & 2 deletions Source/AnimatedFrame.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
/// Keeps a reference to an `UIImage` instance and its duration as a GIF frame.
struct AnimatedFrame {
/// The image that should be used for this frame.
let image: UIImage?
/// The duration that the frame image should be displayed.
let duration: NSTimeInterval

static func null() -> AnimatedFrame {
return AnimatedFrame(image: .None, duration: 0)
/// A placeholder frame with no image assigned.
/// Used to replace frames that are no longer needed in the animation.
var placeholderFrame: AnimatedFrame {
return AnimatedFrame(image: nil, duration: duration)
}

/// Whether the AnimatedFrame instance contains an image or not.
var isPlaceholder: Bool {
return image == .None
}

/// Takes an optional image and returns an non-placeholder `AnimatedFrame`.
///
/// - parameter image: An optional `UIImage` instance to be assigned to the new frame.
/// - returns: A non-placeholder `AnimatedFrame` instance.
func frameWithImage(image: UIImage?) -> AnimatedFrame {
return AnimatedFrame(image: image, duration: duration)
}
}

Loading

0 comments on commit aa2e6a1

Please sign in to comment.