Skip to content

Commit

Permalink
Updated to Swift 3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
delannoyk committed Oct 30, 2016
1 parent 3f47ed9 commit c82f392
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 121 deletions.
112 changes: 55 additions & 57 deletions GIFRefreshControl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import ImageIO
var size: CGSize { get }
var frameCount: UInt { get }

func frameDurationForImageAtIndex(index: UInt) -> NSTimeInterval
func frameDurationForImage(at index: UInt) -> TimeInterval

subscript(index: UInt) -> UIImage { get }
}
Expand All @@ -28,54 +28,52 @@ import ImageIO
////////////////////////////////////////////////////////////////////////////

public class GIFAnimatedImage: NSObject, AnimatedImage {
private typealias ImageInfo = (image: UIImage, duration: NSTimeInterval)
private let images: [ImageInfo]
fileprivate typealias ImageInfo = (image: UIImage, duration: TimeInterval)
fileprivate let images: [ImageInfo]

public let size: CGSize

public var frameCount: UInt {
return UInt(images.count)
}

public init?(data: NSData) {
if let source = CGImageSourceCreateWithData(data, nil) {
let count = CGImageSourceGetCount(source)

images = (0..<count).map { i -> ImageInfo in
if let image = CGImageSourceCreateImageAtIndex(source, i, nil) {
let duration: NSTimeInterval = {
let info = CGImageSourceCopyPropertiesAtIndex(source, i, nil)
let gifInfo = unsafeBitCast(CFDictionaryGetValue(info, unsafeAddressOf(kCGImagePropertyGIFDictionary)), CFDictionary.self)
public init?(data: Data) {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
images = []
size = .zero
super.init()
return nil
}

var delay = unsafeBitCast(CFDictionaryGetValue(gifInfo, unsafeAddressOf(kCGImagePropertyGIFUnclampedDelayTime)), NSNumber.self)
if delay.doubleValue <= 0 {
delay = unsafeBitCast(CFDictionaryGetValue(gifInfo, unsafeAddressOf(kCGImagePropertyGIFDelayTime)), NSNumber.self)
}
let count = CGImageSourceGetCount(source)

return delay.doubleValue
}()
return (UIImage(CGImage: image), duration)
}
images = (0..<count).map { i -> ImageInfo in
guard let image = CGImageSourceCreateImageAtIndex(source, i, nil) else {
return (UIImage(), 0)
}

if let image = images.first {
size = image.image.size
}
else {
size = CGSize(width: 0, height: 100)
}
super.init()
let duration: TimeInterval = {
let info = CGImageSourceCopyPropertiesAtIndex(source, i, nil)
let gifInfo = unsafeBitCast(CFDictionaryGetValue(info, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque()), to: CFDictionary.self)

var delay = unsafeBitCast(CFDictionaryGetValue(gifInfo, Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()), to: NSNumber.self)
if delay.doubleValue <= 0 {
delay = unsafeBitCast(CFDictionaryGetValue(gifInfo, Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: NSNumber.self)
}
return delay.doubleValue
}()
return (UIImage(cgImage: image), duration)
}
else {
images = []
size = .zero
super.init()
return nil

if let image = images.first {
size = image.image.size
} else {
size = CGSize(width: 0, height: 100)
}
super.init()
}

public func frameDurationForImageAtIndex(index: UInt) -> NSTimeInterval {
public func frameDurationForImage(at index: UInt) -> TimeInterval {
return images[Int(index)].duration
}

Expand All @@ -96,13 +94,13 @@ private class GIFAnimatedImageView: UIImageView {
image = animatedImage?[0]
}
}
var animating = false
var animated = false
var lastTimestampChange = CFTimeInterval(0)

lazy var displayLink: CADisplayLink = {
let dl = CADisplayLink(target: self, selector: #selector(GIFAnimatedImageView.refreshDisplay))
dl.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
dl.paused = true
dl.add(to: .main, forMode: .commonModes)
dl.isPaused = true
return dl
}()

Expand All @@ -115,16 +113,16 @@ private class GIFAnimatedImageView: UIImageView {
}

override func startAnimating() {
if !animating {
displayLink.paused = false
animating = true
if !animated {
displayLink.isPaused = false
animated = true
}
}

@objc func refreshDisplay() {
if animating {
if animated {
if let animatedImage = animatedImage {
let currentFrameDuration = animatedImage.frameDurationForImageAtIndex(index)
let currentFrameDuration = animatedImage.frameDurationForImage(at: index)
let delta = displayLink.timestamp - lastTimestampChange

if delta >= currentFrameDuration {
Expand All @@ -136,9 +134,9 @@ private class GIFAnimatedImageView: UIImageView {
}

override func stopAnimating() {
if animating {
displayLink.paused = true
animating = false
if animated {
displayLink.isPaused = true
animated = false
}
}
}
Expand Down Expand Up @@ -178,7 +176,7 @@ public class GIFRefreshControl: UIControl {
private func commonInit() {
imageView.frame = bounds
imageView.clipsToBounds = true
imageView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
imageView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(imageView)
}

Expand All @@ -195,16 +193,16 @@ public class GIFRefreshControl: UIControl {
// MARK: Superview handling
////////////////////////////////////////////////////////////////////////////

public override func willMoveToSuperview(newSuperview: UIView?) {
public override func willMove(toSuperview newSuperview: UIView?) {
if let superview = superview as? UIScrollView {
superview.removeObserver(self, forKeyPath: "contentOffset")
}
super.willMoveToSuperview(newSuperview)
super.willMove(toSuperview: newSuperview)
}

public override func didMoveToSuperview() {
if let superview = superview as? UIScrollView {
superview.addObserver(self, forKeyPath: "contentOffset", options: [.New, .Old], context: nil)
superview.addObserver(self, forKeyPath: "contentOffset", options: [.new, .old], context: nil)
}
super.didMoveToSuperview()
}
Expand Down Expand Up @@ -235,7 +233,7 @@ public class GIFRefreshControl: UIControl {

public var animateOnScroll = true

public var animationDuration = NSTimeInterval(0.33)
public var animationDuration = TimeInterval(0.33)

public var animationDamping = CGFloat(0.4)

Expand All @@ -248,7 +246,7 @@ public class GIFRefreshControl: UIControl {
////////////////////////////////////////////////////////////////////////////

public func beginRefreshing() {
if let superview = superview as? UIScrollView where !refreshing {
if let superview = superview as? UIScrollView, !refreshing {
refreshing = true

//Saving inset
Expand All @@ -270,15 +268,15 @@ public class GIFRefreshControl: UIControl {
}

public func endRefreshing() {
if let superview = superview as? UIScrollView where refreshing {
if let superview = superview as? UIScrollView, refreshing {
forbidsOffsetChanges = false
refreshing = false

UIView.animateWithDuration(animationDuration,
UIView.animate(withDuration: animationDuration,
delay: 0,
usingSpringWithDamping: animationDamping,
initialSpringVelocity: animationVelocity,
options: UIViewAnimationOptions.CurveLinear,
options: UIViewAnimationOptions.curveLinear,
animations: { () -> Void in

if let contentInset = self.contentInset {
Expand All @@ -301,7 +299,7 @@ public class GIFRefreshControl: UIControl {
// MARK: KVO
////////////////////////////////////////////////////////////////////////////

public override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if !changingInset {
adaptShift()
}
Expand All @@ -314,7 +312,7 @@ public class GIFRefreshControl: UIControl {
////////////////////////////////////////////////////////////////////////////

private var expandedHeight: CGFloat {
let maxHeight = UIScreen.mainScreen().bounds.height / 5
let maxHeight = UIScreen.main.bounds.height / 5
let height = imageView.animatedImage?.size.height
return min(maxHeight, height ?? maxHeight)
}
Expand Down Expand Up @@ -348,9 +346,9 @@ public class GIFRefreshControl: UIControl {
imageView.index = index
}

if !superview.dragging && superview.decelerating && !forbidsOffsetChanges && forbidsInsetChanges {
if !superview.isDragging && superview.isDecelerating && !forbidsOffsetChanges && forbidsInsetChanges {
imageView.startAnimating()
sendActionsForControlEvents(.ValueChanged)
sendActions(for: .valueChanged)

beginRefreshing()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,11 @@
TargetAttributes = {
46798DA91B1CC4AD00510F1C = {
CreatedOnToolsVersion = 6.3.2;
LastSwiftMigration = 0810;
};
46798DBE1B1CC4AD00510F1C = {
CreatedOnToolsVersion = 6.3.2;
LastSwiftMigration = 0810;
TestTargetID = 46798DA91B1CC4AD00510F1C;
};
};
Expand Down Expand Up @@ -357,6 +359,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "be.delannoyk.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
Expand All @@ -368,6 +371,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "be.delannoyk.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
Expand All @@ -383,6 +387,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "be.delannoyk.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GIFRefreshControlExample.app/GIFRefreshControlExample";
};
name = Debug;
Expand All @@ -395,6 +400,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "be.delannoyk.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/GIFRefreshControlExample.app/GIFRefreshControlExample";
};
name = Release;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,6 @@ import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}

func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}


}

Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,35 @@ class ViewController: UIViewController, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()

refreshControl.animatedImage = GIFAnimatedImage(data: NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("giphy", withExtension: "gif")!)!)
refreshControl.contentMode = .ScaleAspectFill
refreshControl.addTarget(self, action: #selector(ViewController.refresh), forControlEvents: .ValueChanged)
refreshControl.animatedImage = GIFAnimatedImage(data: try! Data(contentsOf: Bundle.main.url(forResource: "giphy", withExtension: "gif")!))
refreshControl.contentMode = .scaleAspectFill
refreshControl.addTarget(self, action: #selector(ViewController.refresh), for: .valueChanged)
tableView.addSubview(refreshControl)
}


//MARK: Refresh

func refresh() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1) * Int64(NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.count += 1
self.refreshControl.endRefreshing()

self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forItem: 0, inSection: 0)],
withRowAnimation: UITableViewRowAnimation.None)
self.tableView.insertRows(at: [IndexPath(item: 0, section: 0)],
with: .none)
self.tableView.endUpdates()
}
}


//MARK: UITableViewDataSource

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return tableView.dequeueReusableCellWithIdentifier("TableViewCell")!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "TableViewCell")!
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,4 @@ import UIKit
import XCTest

class GIFRefreshControlExampleTests: XCTestCase {

override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}

func testExample() {
// This is an example of a functional test case.
XCTAssert(true, "Pass")
}

func testPerformanceExample() {
// This is an example of a performance test case.
self.measureBlock() {
// Put the code you want to measure the time of here.
}
}

}

0 comments on commit c82f392

Please sign in to comment.