Bring back progressive loading
Loading the all the frames of a GIF at once into memory helped cut down on the memory footprint because we could eliminate the need to hold onto the source image. However, we see this break down when there are too many frames. The "almost_nailed_it.gif" GIF has 545 frames and would crash the app around 130 loaded. This brings back progressive loading with a max frame count of 50 to prevent this issue.
This commit is contained in:
parent
1c833b16f4
commit
a885c995e9
|
@ -7,16 +7,35 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
005656EB1A6EE471008A0ED1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 005656EA1A6EE471008A0ED1 /* Gifu.framework */; };
|
|
||||||
9D25870819BCCB0F00A55A18 /* mugen.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9D25870619BCCB0F00A55A18 /* mugen.gif */; };
|
9D25870819BCCB0F00A55A18 /* mugen.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9D25870619BCCB0F00A55A18 /* mugen.gif */; };
|
||||||
9D98823D19BC69CA00B790C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98823C19BC69CA00B790C6 /* AppDelegate.swift */; };
|
9D98823D19BC69CA00B790C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98823C19BC69CA00B790C6 /* AppDelegate.swift */; };
|
||||||
9D98823F19BC69CA00B790C6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98823E19BC69CA00B790C6 /* ViewController.swift */; };
|
9D98823F19BC69CA00B790C6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98823E19BC69CA00B790C6 /* ViewController.swift */; };
|
||||||
9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D98824319BC69CA00B790C6 /* Images.xcassets */; };
|
9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D98824319BC69CA00B790C6 /* Images.xcassets */; };
|
||||||
9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D98825919BC69F600B790C6 /* Main.storyboard */; };
|
9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D98825919BC69F600B790C6 /* Main.storyboard */; };
|
||||||
9D98826719BC874C00B790C6 /* FlatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98826619BC874C00B790C6 /* FlatButton.swift */; };
|
9D98826719BC874C00B790C6 /* FlatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98826619BC874C00B790C6 /* FlatButton.swift */; };
|
||||||
|
EA5789A01B20C5B100A9F7D1 /* almost_nailed_it.gif in Resources */ = {isa = PBXBuildFile; fileRef = EA57899F1B20C5B100A9F7D1 /* almost_nailed_it.gif */; };
|
||||||
|
EA5789A71B20C65E00A9F7D1 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA5789A61B20C65800A9F7D1 /* Gifu.framework */; };
|
||||||
|
EA5789A91B20C68B00A9F7D1 /* Gifu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EA5789A61B20C65800A9F7D1 /* Gifu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
EA9299291AE99E2900E22976 /* Runes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA9299281AE99E2900E22976 /* Runes.framework */; };
|
EA9299291AE99E2900E22976 /* Runes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA9299281AE99E2900E22976 /* Runes.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
EA5789A51B20C65800A9F7D1 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */;
|
||||||
|
proxyType = 2;
|
||||||
|
remoteGlobalIDString = 00B8C73E1A364DA400C188E7;
|
||||||
|
remoteInfo = Gifu;
|
||||||
|
};
|
||||||
|
EA5789AA1B20C68B00A9F7D1 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 00B8C73D1A364DA400C188E7;
|
||||||
|
remoteInfo = Gifu;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
00B8C7331A364D4C00C188E7 /* Embed Frameworks */ = {
|
00B8C7331A364D4C00C188E7 /* Embed Frameworks */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
@ -24,6 +43,7 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
|
EA5789A91B20C68B00A9F7D1 /* Gifu.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -31,7 +51,6 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
005656EA1A6EE471008A0ED1 /* Gifu.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Gifu.framework; path = "../../build/Debug-iphoneos/Gifu.framework"; sourceTree = "<group>"; };
|
|
||||||
9D25870519BCCB0F00A55A18 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
9D25870519BCCB0F00A55A18 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
9D25870619BCCB0F00A55A18 /* mugen.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = mugen.gif; sourceTree = "<group>"; };
|
9D25870619BCCB0F00A55A18 /* mugen.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = mugen.gif; sourceTree = "<group>"; };
|
||||||
9D98823719BC69CA00B790C6 /* gifu-demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "gifu-demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
9D98823719BC69CA00B790C6 /* gifu-demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "gifu-demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -40,6 +59,8 @@
|
||||||
9D98824319BC69CA00B790C6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
9D98824319BC69CA00B790C6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||||
9D98825919BC69F600B790C6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
9D98825919BC69F600B790C6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||||
9D98826619BC874C00B790C6 /* FlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FlatButton.swift; path = classes/FlatButton.swift; sourceTree = "<group>"; };
|
9D98826619BC874C00B790C6 /* FlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FlatButton.swift; path = classes/FlatButton.swift; sourceTree = "<group>"; };
|
||||||
|
EA57899F1B20C5B100A9F7D1 /* almost_nailed_it.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = almost_nailed_it.gif; sourceTree = "<group>"; };
|
||||||
|
EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Gifu.xcodeproj; path = ../Gifu.xcodeproj; sourceTree = "<group>"; };
|
||||||
EA9299281AE99E2900E22976 /* Runes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Runes.framework; path = ../Carthage/Build/iOS/Runes.framework; sourceTree = "<group>"; };
|
EA9299281AE99E2900E22976 /* Runes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Runes.framework; path = ../Carthage/Build/iOS/Runes.framework; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
@ -48,8 +69,8 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
005656EB1A6EE471008A0ED1 /* Gifu.framework in Frameworks */,
|
|
||||||
EA9299291AE99E2900E22976 /* Runes.framework in Frameworks */,
|
EA9299291AE99E2900E22976 /* Runes.framework in Frameworks */,
|
||||||
|
EA5789A71B20C65E00A9F7D1 /* Gifu.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -90,16 +111,25 @@
|
||||||
9D98823A19BC69CA00B790C6 /* Supporting Files */ = {
|
9D98823A19BC69CA00B790C6 /* Supporting Files */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
005656EA1A6EE471008A0ED1 /* Gifu.framework */,
|
|
||||||
9D25870519BCCB0F00A55A18 /* Info.plist */,
|
9D25870519BCCB0F00A55A18 /* Info.plist */,
|
||||||
|
EA57899F1B20C5B100A9F7D1 /* almost_nailed_it.gif */,
|
||||||
9D25870619BCCB0F00A55A18 /* mugen.gif */,
|
9D25870619BCCB0F00A55A18 /* mugen.gif */,
|
||||||
);
|
);
|
||||||
name = "Supporting Files";
|
name = "Supporting Files";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
EA5789A21B20C65800A9F7D1 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EA5789A61B20C65800A9F7D1 /* Gifu.framework */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
EA92992C1AE9AB2100E22976 /* Frameworks */ = {
|
EA92992C1AE9AB2100E22976 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */,
|
||||||
EA9299281AE99E2900E22976 /* Runes.framework */,
|
EA9299281AE99E2900E22976 /* Runes.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
|
@ -121,6 +151,7 @@
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
EA5789AB1B20C68B00A9F7D1 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = "gifu-demo";
|
name = "gifu-demo";
|
||||||
productName = "gifu-demo";
|
productName = "gifu-demo";
|
||||||
|
@ -152,6 +183,12 @@
|
||||||
mainGroup = 9D98822E19BC69CA00B790C6;
|
mainGroup = 9D98822E19BC69CA00B790C6;
|
||||||
productRefGroup = 9D98823819BC69CA00B790C6 /* Products */;
|
productRefGroup = 9D98823819BC69CA00B790C6 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
|
projectReferences = (
|
||||||
|
{
|
||||||
|
ProductGroup = EA5789A21B20C65800A9F7D1 /* Products */;
|
||||||
|
ProjectRef = EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */;
|
||||||
|
},
|
||||||
|
);
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
9D98823619BC69CA00B790C6 /* gifu-demo */,
|
9D98823619BC69CA00B790C6 /* gifu-demo */,
|
||||||
|
@ -159,11 +196,22 @@
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXReferenceProxy section */
|
||||||
|
EA5789A61B20C65800A9F7D1 /* Gifu.framework */ = {
|
||||||
|
isa = PBXReferenceProxy;
|
||||||
|
fileType = wrapper.framework;
|
||||||
|
path = Gifu.framework;
|
||||||
|
remoteRef = EA5789A51B20C65800A9F7D1 /* PBXContainerItemProxy */;
|
||||||
|
sourceTree = BUILT_PRODUCTS_DIR;
|
||||||
|
};
|
||||||
|
/* End PBXReferenceProxy section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
9D98823519BC69CA00B790C6 /* Resources */ = {
|
9D98823519BC69CA00B790C6 /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
EA5789A01B20C5B100A9F7D1 /* almost_nailed_it.gif in Resources */,
|
||||||
9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */,
|
9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */,
|
||||||
9D25870819BCCB0F00A55A18 /* mugen.gif in Resources */,
|
9D25870819BCCB0F00A55A18 /* mugen.gif in Resources */,
|
||||||
9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */,
|
9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */,
|
||||||
|
@ -202,6 +250,14 @@
|
||||||
};
|
};
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
EA5789AB1B20C68B00A9F7D1 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
name = Gifu;
|
||||||
|
targetProxy = EA5789AA1B20C68B00A9F7D1 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
9D98825119BC69CA00B790C6 /* Debug */ = {
|
9D98825119BC69CA00B790C6 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 11 MiB |
|
@ -9,7 +9,7 @@ class ViewController: UIViewController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
imageView.animateWithImage(named: "mugen.gif")
|
imageView.animateWithImage(named: "almost_nailed_it.gif")
|
||||||
|
|
||||||
UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: false)
|
UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,10 @@ public class AnimatableImageView: UIImageView, Animatable {
|
||||||
/// An `Animator` instance that holds the frames of a specific image in memory.
|
/// An `Animator` instance that holds the frames of a specific image in memory.
|
||||||
var animator: Animator?
|
var animator: Animator?
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
println("deinit animatable view")
|
||||||
|
}
|
||||||
|
|
||||||
/// A computed property that returns whether the image view is animating.
|
/// A computed property that returns whether the image view is animating.
|
||||||
public var isAnimatingGIF: Bool {
|
public var isAnimatingGIF: Bool {
|
||||||
return animator?.isAnimating ?? isAnimating()
|
return animator?.isAnimating ?? isAnimating()
|
||||||
|
@ -59,5 +63,9 @@ public class AnimatableImageView: UIImageView, Animatable {
|
||||||
public func stopAnimatingGIF() {
|
public func stopAnimatingGIF() {
|
||||||
animator?.pauseAnimation() ?? stopAnimating()
|
animator?.pauseAnimation() ?? stopAnimating()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func cleanup() {
|
||||||
|
animator = .None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,14 @@ class Animator: NSObject {
|
||||||
let delegate: Animatable
|
let delegate: Animatable
|
||||||
/// Maximum duration to increment the frame timer with.
|
/// Maximum duration to increment the frame timer with.
|
||||||
private let maxTimeStep = 1.0
|
private let maxTimeStep = 1.0
|
||||||
/// The total duration of the GIF image.
|
|
||||||
private var totalDuration: NSTimeInterval = 0.0
|
|
||||||
/// An array of animated frames from a single GIF image.
|
/// An array of animated frames from a single GIF image.
|
||||||
private var animatedFrames = [AnimatedFrame]()
|
private var animatedFrames = [AnimatedFrame]()
|
||||||
|
/// Maximum number of frames to load at once
|
||||||
|
private let maxNumberOfFrames = 50
|
||||||
|
/// The total number of frames in the GIF.
|
||||||
|
private var numberOfFrames = 0
|
||||||
|
/// A reference to the original image source.
|
||||||
|
private var imageSource: CGImageSourceRef
|
||||||
/// The index of the current GIF frame.
|
/// The index of the current GIF frame.
|
||||||
private var currentFrameIndex = 0
|
private var currentFrameIndex = 0
|
||||||
/// Time elapsed since the last frame change. Used to determine when the frame should be updated.
|
/// Time elapsed since the last frame change. Used to determine when the frame should be updated.
|
||||||
|
@ -34,43 +38,46 @@ class Animator: NSObject {
|
||||||
/// :param: data The raw GIF image data.
|
/// :param: data The raw GIF image data.
|
||||||
/// :param: delegate An `Animatable` delegate.
|
/// :param: delegate An `Animatable` delegate.
|
||||||
required init(data: NSData, delegate: Animatable) {
|
required init(data: NSData, delegate: Animatable) {
|
||||||
let imageSource = CGImageSourceCreateWithData(data, nil)
|
imageSource = CGImageSourceCreateWithData(data, nil)
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
super.init()
|
super.init()
|
||||||
attachDisplayLink()
|
attachDisplayLink()
|
||||||
curry(prepareFrames) <^> imageSource <*> delegate.frame.size
|
prepareFrames()
|
||||||
pauseAnimation()
|
pauseAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
println("deinit animator")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Frames
|
// MARK: - Frames
|
||||||
/// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`.
|
/// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`.
|
||||||
|
private func prepareFrames() {
|
||||||
|
numberOfFrames = Int(CGImageSourceGetCount(imageSource))
|
||||||
|
let framesToProcess = numberOfFrames > maxNumberOfFrames ? maxNumberOfFrames : numberOfFrames
|
||||||
|
animatedFrames.reserveCapacity(framesToProcess)
|
||||||
|
animatedFrames = reduce(0..<framesToProcess, []) { $0 + pure(prepareFrame($1)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads a single frame from an image source, resizes it, then returns an `AnimatedFrame`.
|
||||||
///
|
///
|
||||||
/// :param: imageSource The `CGImageSourceRef` image source to extract the frames from.
|
/// :param: index The index of the GIF image source to prepare
|
||||||
/// :param: size The size to use for the cached frames.
|
/// :returns: An AnimatedFrame object
|
||||||
private func prepareFrames(imageSource: CGImageSourceRef, size: CGSize) {
|
private func prepareFrame(index: Int) -> AnimatedFrame {
|
||||||
let numberOfFrames = Int(CGImageSourceGetCount(imageSource))
|
let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index)
|
||||||
animatedFrames.reserveCapacity(numberOfFrames)
|
let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil)
|
||||||
|
let size = delegate.frame.size
|
||||||
|
|
||||||
(animatedFrames, totalDuration) = reduce(0..<numberOfFrames, ([AnimatedFrame](), 0.0)) { accumulator, index in
|
let image = UIImage(CGImage: frameImageRef)
|
||||||
let accumulatedFrames = accumulator.0
|
let scaledImage: UIImage?
|
||||||
let accumulatedDuration = accumulator.1
|
|
||||||
|
|
||||||
let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index)
|
switch delegate.contentMode {
|
||||||
let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil)
|
case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size)
|
||||||
|
case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(size)
|
||||||
let image = UIImage(CGImage: frameImageRef)
|
default: scaledImage = image?.resize(size)
|
||||||
let scaledImage: UIImage?
|
|
||||||
|
|
||||||
switch delegate.contentMode {
|
|
||||||
case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size)
|
|
||||||
case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(size)
|
|
||||||
default: scaledImage = image?.resize(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
let animatedFrame = AnimatedFrame(image: scaledImage, duration: frameDuration)
|
|
||||||
|
|
||||||
return (accumulatedFrames + [animatedFrame], accumulatedDuration + frameDuration)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return AnimatedFrame(image: scaledImage, duration: frameDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the frame at a particular index.
|
/// Returns the frame at a particular index.
|
||||||
|
@ -78,23 +85,29 @@ class Animator: NSObject {
|
||||||
/// :param: index The index of the frame.
|
/// :param: index The index of the frame.
|
||||||
/// :returns: An optional image at a given frame.
|
/// :returns: An optional image at a given frame.
|
||||||
private func frameAtIndex(index: Int) -> UIImage? {
|
private func frameAtIndex(index: Int) -> UIImage? {
|
||||||
if index >= animatedFrames.count { return .None }
|
return animatedFrames[index % animatedFrames.count].image
|
||||||
return animatedFrames[index].image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`.
|
/// Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`.
|
||||||
///
|
///
|
||||||
/// :returns: An optional image at a given frame.
|
/// :returns: An optional image at a given frame.
|
||||||
func updateCurrentFrame() {
|
func updateCurrentFrame() {
|
||||||
if totalDuration == 0 { return }
|
if animatedFrames.count <= 1 { return }
|
||||||
|
|
||||||
timeSinceLastFrameChange += min(maxTimeStep, displayLink.duration)
|
timeSinceLastFrameChange += min(maxTimeStep, displayLink.duration)
|
||||||
var frameDuration = animatedFrames[currentFrameIndex].duration
|
var frameDuration = animatedFrames[currentFrameIndex % animatedFrames.count].duration
|
||||||
|
|
||||||
if timeSinceLastFrameChange >= frameDuration {
|
if timeSinceLastFrameChange >= frameDuration {
|
||||||
timeSinceLastFrameChange -= frameDuration
|
timeSinceLastFrameChange -= frameDuration
|
||||||
currentFrameIndex = ++currentFrameIndex % animatedFrames.count
|
let lastFrameIndex = currentFrameIndex
|
||||||
|
currentFrameIndex = ++currentFrameIndex % numberOfFrames
|
||||||
delegate.layer.setNeedsDisplay()
|
delegate.layer.setNeedsDisplay()
|
||||||
|
|
||||||
|
// load the next needed frame for progressive loading
|
||||||
|
if animatedFrames.count < numberOfFrames {
|
||||||
|
let nextFrameToLoad = (lastFrameIndex + animatedFrames.count) % numberOfFrames
|
||||||
|
animatedFrames[lastFrameIndex % animatedFrames.count] = prepareFrame(nextFrameToLoad)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +119,7 @@ class Animator: NSObject {
|
||||||
|
|
||||||
/// Resumes the display link.
|
/// Resumes the display link.
|
||||||
func resumeAnimation() {
|
func resumeAnimation() {
|
||||||
if totalDuration > 0 {
|
if animatedFrames.count > 1 {
|
||||||
displayLink.paused = false
|
displayLink.paused = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue