diff --git a/.gitignore b/.gitignore index c0d8c9a..9135f02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,38 @@ +# OS X Finder +.DS_Store + +# Xcode per-user config *.mode1 *.mode1v3 *.mode2v3 *.perspective *.perspectivev3 *.pbxuser -*.xccheckout xcuserdata +*.xccheckout + +# Build products build/ *.o *.LinkFileList *.hmap + +# Automatic backup files *~.nib/ *.swp *~ *.dat *.dep -.DS_Store + +# Cocoapods +Pods + +# Carthage Carthage/Build + +# AppCode specific files +.idea/ +*.iml + +Gifu.framework.zip +*.xcscmblueprint diff --git a/.gitmodules b/.gitmodules index e69de29..bc544a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Carthage/Checkouts/Runes"] + path = Carthage/Checkouts/Runes + url = https://github.com/thoughtbot/Runes.git diff --git a/Cartfile b/Cartfile deleted file mode 100644 index 0cbf88c..0000000 --- a/Cartfile +++ /dev/null @@ -1 +0,0 @@ -github "thoughtbot/Runes" >= 1.2.2 diff --git a/Cartfile.private b/Cartfile.private new file mode 100644 index 0000000..e67aa45 --- /dev/null +++ b/Cartfile.private @@ -0,0 +1 @@ +github "thoughtbot/Runes" >= 3.0.0 diff --git a/Cartfile.resolved b/Cartfile.resolved index 31a671a..a64dbe4 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1 +1 @@ -github "thoughtbot/Runes" "v2.0.0" +github "thoughtbot/Runes" "v3.0.0" diff --git a/Carthage/Checkouts/Runes b/Carthage/Checkouts/Runes new file mode 160000 index 0000000..cded739 --- /dev/null +++ b/Carthage/Checkouts/Runes @@ -0,0 +1 @@ +Subproject commit cded739a780fd50c6f888d94802700b104e30f92 diff --git a/Demo/Default-568h@2x.png b/Demo/Default-568h@2x.png new file mode 100644 index 0000000..0891b7a Binary files /dev/null and b/Demo/Default-568h@2x.png differ diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 10e3682..2dcbcb2 100755 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -7,61 +7,21 @@ objects = { /* Begin PBXBuildFile section */ - 9D25870819BCCB0F00A55A18 /* mugen.gif in Resources */ = {isa = PBXBuildFile; fileRef = 9D25870619BCCB0F00A55A18 /* mugen.gif */; }; + 007380231B279644008DAD5C /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 007380221B279644008DAD5C /* Default-568h@2x.png */; }; 9D98823D19BC69CA00B790C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98823C19BC69CA00B790C6 /* AppDelegate.swift */; }; 9D98823F19BC69CA00B790C6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D98823E19BC69CA00B790C6 /* ViewController.swift */; }; 9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D98824319BC69CA00B790C6 /* Images.xcassets */; }; 9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D98825919BC69F600B790C6 /* Main.storyboard */; }; - 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 */; }; /* 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 */ - 00B8C7331A364D4C00C188E7 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - EA5789A91B20C68B00A9F7D1 /* Gifu.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ + 007380221B279644008DAD5C /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 9D25870519BCCB0F00A55A18 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9D25870619BCCB0F00A55A18 /* mugen.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = mugen.gif; sourceTree = ""; }; - 9D98823719BC69CA00B790C6 /* gifu-demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "gifu-demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 9D98823719BC69CA00B790C6 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9D98823C19BC69CA00B790C6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = classes/AppDelegate.swift; sourceTree = ""; }; 9D98823E19BC69CA00B790C6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = classes/ViewController.swift; sourceTree = ""; }; 9D98824319BC69CA00B790C6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 9D98825919BC69F600B790C6 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; - 9D98826619BC874C00B790C6 /* FlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FlatButton.swift; path = classes/FlatButton.swift; sourceTree = ""; }; - EA57899F1B20C5B100A9F7D1 /* almost_nailed_it.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = almost_nailed_it.gif; sourceTree = ""; }; - EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Gifu.xcodeproj; path = ../Gifu.xcodeproj; sourceTree = ""; }; - EA9299281AE99E2900E22976 /* Runes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Runes.framework; path = ../Carthage/Build/iOS/Runes.framework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -69,8 +29,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EA9299291AE99E2900E22976 /* Runes.framework in Frameworks */, - EA5789A71B20C65E00A9F7D1 /* Gifu.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -80,7 +38,6 @@ 9D98822E19BC69CA00B790C6 = { isa = PBXGroup; children = ( - EA92992C1AE9AB2100E22976 /* Frameworks */, 9D98823919BC69CA00B790C6 /* Source */, 9D98823819BC69CA00B790C6 /* Products */, ); @@ -89,7 +46,7 @@ 9D98823819BC69CA00B790C6 /* Products */ = { isa = PBXGroup; children = ( - 9D98823719BC69CA00B790C6 /* gifu-demo.app */, + 9D98823719BC69CA00B790C6 /* Demo.app */, ); name = Products; sourceTree = ""; @@ -99,7 +56,6 @@ children = ( 9D98823C19BC69CA00B790C6 /* AppDelegate.swift */, 9D98823E19BC69CA00B790C6 /* ViewController.swift */, - 9D98826619BC874C00B790C6 /* FlatButton.swift */, 9D98825919BC69F600B790C6 /* Main.storyboard */, 9D98824319BC69CA00B790C6 /* Images.xcassets */, 9D98823A19BC69CA00B790C6 /* Supporting Files */, @@ -111,51 +67,30 @@ 9D98823A19BC69CA00B790C6 /* Supporting Files */ = { isa = PBXGroup; children = ( + 007380221B279644008DAD5C /* Default-568h@2x.png */, 9D25870519BCCB0F00A55A18 /* Info.plist */, - EA57899F1B20C5B100A9F7D1 /* almost_nailed_it.gif */, - 9D25870619BCCB0F00A55A18 /* mugen.gif */, ); name = "Supporting Files"; sourceTree = ""; }; - EA5789A21B20C65800A9F7D1 /* Products */ = { - isa = PBXGroup; - children = ( - EA5789A61B20C65800A9F7D1 /* Gifu.framework */, - ); - name = Products; - sourceTree = ""; - }; - EA92992C1AE9AB2100E22976 /* Frameworks */ = { - isa = PBXGroup; - children = ( - EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */, - EA9299281AE99E2900E22976 /* Runes.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 9D98823619BC69CA00B790C6 /* gifu-demo */ = { + 9D98823619BC69CA00B790C6 /* Demo */ = { isa = PBXNativeTarget; - buildConfigurationList = 9D98825319BC69CA00B790C6 /* Build configuration list for PBXNativeTarget "gifu-demo" */; + buildConfigurationList = 9D98825319BC69CA00B790C6 /* Build configuration list for PBXNativeTarget "Demo" */; buildPhases = ( 9D98823319BC69CA00B790C6 /* Sources */, 9D98823419BC69CA00B790C6 /* Frameworks */, 9D98823519BC69CA00B790C6 /* Resources */, - 00B8C7331A364D4C00C188E7 /* Embed Frameworks */, - EA92992B1AE99E5600E22976 /* ShellScript */, ); buildRules = ( ); dependencies = ( - EA5789AB1B20C68B00A9F7D1 /* PBXTargetDependency */, ); - name = "gifu-demo"; + name = Demo; productName = "gifu-demo"; - productReference = 9D98823719BC69CA00B790C6 /* gifu-demo.app */; + productReference = 9D98823719BC69CA00B790C6 /* Demo.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -164,7 +99,8 @@ 9D98822F19BC69CA00B790C6 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0600; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Kaishin & Co"; TargetAttributes = { 9D98823619BC69CA00B790C6 = { @@ -183,60 +119,26 @@ mainGroup = 9D98822E19BC69CA00B790C6; productRefGroup = 9D98823819BC69CA00B790C6 /* Products */; projectDirPath = ""; - projectReferences = ( - { - ProductGroup = EA5789A21B20C65800A9F7D1 /* Products */; - ProjectRef = EA5789A11B20C65800A9F7D1 /* Gifu.xcodeproj */; - }, - ); projectRoot = ""; targets = ( - 9D98823619BC69CA00B790C6 /* gifu-demo */, + 9D98823619BC69CA00B790C6 /* Demo */, ); }; /* 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 */ 9D98823519BC69CA00B790C6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EA5789A01B20C5B100A9F7D1 /* almost_nailed_it.gif in Resources */, 9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */, - 9D25870819BCCB0F00A55A18 /* mugen.gif in Resources */, + 007380231B279644008DAD5C /* Default-568h@2x.png in Resources */, 9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - EA92992B1AE99E5600E22976 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(SRCROOT)/../Carthage/Build/iOS/Runes.framework", - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/usr/local/bin/carthage copy-frameworks"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 9D98823319BC69CA00B790C6 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -244,20 +146,11 @@ files = ( 9D98823F19BC69CA00B790C6 /* ViewController.swift in Sources */, 9D98823D19BC69CA00B790C6 /* AppDelegate.swift in Sources */, - 9D98826719BC874C00B790C6 /* FlatButton.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - EA5789AB1B20C68B00A9F7D1 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = Gifu; - targetProxy = EA5789AA1B20C68B00A9F7D1 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin XCBuildConfiguration section */ 9D98825119BC69CA00B790C6 /* Debug */ = { isa = XCBuildConfiguration; @@ -279,6 +172,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -340,16 +234,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../Carthage/Build/iOS", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = demo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu.demo; PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -357,16 +248,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(SRCROOT)/../Carthage/Build/iOS", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = demo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu.demo; PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; @@ -382,7 +270,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 9D98825319BC69CA00B790C6 /* Build configuration list for PBXNativeTarget "gifu-demo" */ = { + 9D98825319BC69CA00B790C6 /* Build configuration list for PBXNativeTarget "Demo" */ = { isa = XCConfigurationList; buildConfigurations = ( 9D98825419BC69CA00B790C6 /* Debug */, diff --git a/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100755 index 8e593f5..0000000 --- a/Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Demo/demo/Images.xcassets/AppIcon.appiconset/Contents.json b/Demo/demo/Images.xcassets/AppIcon.appiconset/Contents.json index 171085b..400567d 100755 --- a/Demo/demo/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Demo/demo/Images.xcassets/AppIcon.appiconset/Contents.json @@ -6,17 +6,32 @@ "filename" : "New icon-Small@2x.png", "scale" : "2x" }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, { "size" : "40x40", "idiom" : "iphone", "filename" : "New icon-Small-40@2x.png", "scale" : "2x" }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, { "size" : "60x60", "idiom" : "iphone", "filename" : "New icon-60@2x.png", "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" } ], "info" : { diff --git a/Demo/demo/Images.xcassets/Contents.json b/Demo/demo/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Demo/demo/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Demo/demo/Images.xcassets/LaunchImage.launchimage/Contents.json b/Demo/demo/Images.xcassets/LaunchImage.launchimage/Contents.json deleted file mode 100755 index c79ebd3..0000000 --- a/Demo/demo/Images.xcassets/LaunchImage.launchimage/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "orientation" : "portrait", - "idiom" : "iphone", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "2x" - }, - { - "orientation" : "portrait", - "idiom" : "iphone", - "subtype" : "retina4", - "extent" : "full-screen", - "minimum-system-version" : "7.0", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Demo/demo/Images.xcassets/mugen.dataset/Contents.json b/Demo/demo/Images.xcassets/mugen.dataset/Contents.json new file mode 100644 index 0000000..10d518f --- /dev/null +++ b/Demo/demo/Images.xcassets/mugen.dataset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "data" : [ + { + "idiom" : "universal", + "filename" : "mugen.gif" + } + ] +} \ No newline at end of file diff --git a/Demo/demo/mugen.gif b/Demo/demo/Images.xcassets/mugen.dataset/mugen.gif old mode 100755 new mode 100644 similarity index 100% rename from Demo/demo/mugen.gif rename to Demo/demo/Images.xcassets/mugen.dataset/mugen.gif diff --git a/Demo/demo/Images.xcassets/nailed.dataset/Contents.json b/Demo/demo/Images.xcassets/nailed.dataset/Contents.json new file mode 100644 index 0000000..41c1a00 --- /dev/null +++ b/Demo/demo/Images.xcassets/nailed.dataset/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "data" : [ + { + "idiom" : "universal", + "filename" : "nailed.gif" + } + ] +} diff --git a/Demo/demo/almost_nailed_it.gif b/Demo/demo/Images.xcassets/nailed.dataset/nailed.gif similarity index 100% rename from Demo/demo/almost_nailed_it.gif rename to Demo/demo/Images.xcassets/nailed.dataset/nailed.gif diff --git a/Demo/demo/Info.plist b/Demo/demo/Info.plist index 8506ad1..dd791df 100755 --- a/Demo/demo/Info.plist +++ b/Demo/demo/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - co.kaishin.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -18,8 +18,6 @@ 1.0 CFBundleSignature ???? - UIViewControllerBasedStatusBarAppearance - CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/Demo/demo/Main.storyboard b/Demo/demo/Main.storyboard index ce1c3a9..d478106 100755 --- a/Demo/demo/Main.storyboard +++ b/Demo/demo/Main.storyboard @@ -1,14 +1,14 @@ - + - - + + - + @@ -17,77 +17,54 @@ - - + + - - - - + + - - - - + + - - - + - + + + + + + diff --git a/Demo/demo/classes/AppDelegate.swift b/Demo/demo/classes/AppDelegate.swift index f9d74e6..708917c 100755 --- a/Demo/demo/classes/AppDelegate.swift +++ b/Demo/demo/classes/AppDelegate.swift @@ -9,4 +9,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } } - diff --git a/Demo/demo/classes/FlatButton.swift b/Demo/demo/classes/FlatButton.swift deleted file mode 100755 index f9af111..0000000 --- a/Demo/demo/classes/FlatButton.swift +++ /dev/null @@ -1,48 +0,0 @@ -import UIKit - -class FlatButton: UIButton { - - let horizontalPadding: CGFloat = 14.0 - - var buttonColor: UIColor? - - override init(frame: CGRect) { - super.init(frame: frame) - customizeAppearance() - } - - required init(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - customizeAppearance() - } - - override func drawRect(rect: CGRect) { - layer.borderColor = tintColor.CGColor - setTitleColor(tintColor, forState: UIControlState.Normal) - setTitleColor(UIColor.whiteColor(), forState: UIControlState.Selected) - } - - func customizeAppearance() { - let containsEdgeInsets = !UIEdgeInsetsEqualToEdgeInsets(contentEdgeInsets, UIEdgeInsetsZero) - contentEdgeInsets = containsEdgeInsets ? contentEdgeInsets : UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - layer.borderWidth = 2.0 - layer.borderColor = tintColor.CGColor - layer.cornerRadius = frame.size.height / 2.0 - layer.masksToBounds = true - } - - override var tintColor: UIColor! { - get { - if let color = buttonColor { - return color - } else { - return super.tintColor - } - } - - set { - super.tintColor = newValue - buttonColor = newValue - } - } -} \ No newline at end of file diff --git a/Demo/demo/classes/ViewController.swift b/Demo/demo/classes/ViewController.swift index e6584f9..cfb5db7 100755 --- a/Demo/demo/classes/ViewController.swift +++ b/Demo/demo/classes/ViewController.swift @@ -2,37 +2,19 @@ import UIKit import Gifu class ViewController: UIViewController { - @IBOutlet weak var imageView: AnimatableImageView! - @IBOutlet weak var button: FlatButton! - + override func viewDidLoad() { super.viewDidLoad() imageView.animateWithImage(named: "mugen.gif") - - UIApplication.sharedApplication().setStatusBarStyle(.LightContent, animated: false) } - @IBAction func toggleAnimation(button: UIButton) { + @IBAction func toggleAnimation(sender: AnyObject) { if imageView.isAnimatingGIF { imageView.stopAnimatingGIF() - button.layer.backgroundColor = UIColor.whiteColor().CGColor - button.setTitleColor(UIColor.blackColor(), forState: .Normal) } else { imageView.startAnimatingGIF() - button.layer.backgroundColor = UIColor.clearColor().CGColor - button.setTitleColor(UIColor.whiteColor(), forState: .Normal) - } - } - - @IBAction func toggleGIF(sender: UISegmentedControl) { - imageView.stopAnimatingGIF() - - switch sender.selectedSegmentIndex { - case 0: imageView.animateWithImage(named: "mugen.gif") - case 1: imageView.animateWithImage(named: "almost_nailed_it.gif") - default: imageView.image = .None } } } diff --git a/Gifu.xcodeproj/project.pbxproj b/Gifu.xcodeproj/project.pbxproj index 161a960..2542d4e 100644 --- a/Gifu.xcodeproj/project.pbxproj +++ b/Gifu.xcodeproj/project.pbxproj @@ -7,47 +7,94 @@ objects = { /* Begin PBXBuildFile section */ + 0036ABB71BBD1D0B00C6CC3D /* mugen.gif in Resources */ = {isa = PBXBuildFile; fileRef = 0036ABB61BBD1D0B00C6CC3D /* mugen.gif */; settings = {ASSET_TAGS = (); }; }; + 0036ABB91BBD1D1400C6CC3D /* nailed.gif in Resources */ = {isa = PBXBuildFile; fileRef = 0036ABB81BBD1D1400C6CC3D /* nailed.gif */; settings = {ASSET_TAGS = (); }; }; + 004529921BD82209006493BF /* Runes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 004529911BD82209006493BF /* Runes.swift */; settings = {ASSET_TAGS = (); }; }; 005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EC1A6F14D6008A0ED1 /* Animator.swift */; }; 005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */; }; + 009BD1391BBC7F6500FC982B /* GifuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009BD1381BBC7F6500FC982B /* GifuTests.swift */; }; + 009BD13B1BBC7F6500FC982B /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00B8C73E1A364DA400C188E7 /* Gifu.framework */; settings = {ASSET_TAGS = (); }; }; + 009BD1441BBC93C800FC982B /* CGSizeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 009BD1431BBC93C800FC982B /* CGSizeExtension.swift */; settings = {ASSET_TAGS = (); }; }; 00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */; }; 00B8C7961A3650EE00C188E7 /* Gifu.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B8C7951A3650EE00C188E7 /* Gifu.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EA1E21131AD5D369000459BD /* Runes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA1E21121AD5D369000459BD /* Runes.framework */; }; EAF49C7F1A3A4DE000B395DF /* UIImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */; }; - EAF49C811A3A4FAA00B395DF /* Curry.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF49C801A3A4FAA00B395DF /* Curry.swift */; }; + EAF49C811A3A4FAA00B395DF /* FunctionalHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF49C801A3A4FAA00B395DF /* FunctionalHelpers.swift */; }; EAF49CB11A3B6EEB00B395DF /* AnimatedFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 009BD13C1BBC7F6500FC982B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00B8C7351A364DA400C188E7 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 00B8C73D1A364DA400C188E7; + remoteInfo = Gifu; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 0036ABB61BBD1D0B00C6CC3D /* mugen.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = mugen.gif; path = Demo/demo/Images.xcassets/mugen.dataset/mugen.gif; sourceTree = SOURCE_ROOT; }; + 0036ABB81BBD1D1400C6CC3D /* nailed.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = nailed.gif; path = Demo/demo/Images.xcassets/nailed.dataset/nailed.gif; sourceTree = SOURCE_ROOT; }; + 004529911BD82209006493BF /* Runes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Runes.swift; path = Carthage/Checkouts/Runes/Source/Runes.swift; sourceTree = SOURCE_ROOT; }; 005656EC1A6F14D6008A0ED1 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = ""; }; 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableImageView.swift; sourceTree = ""; }; + 009BD1361BBC7F6500FC982B /* GifuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GifuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 009BD1381BBC7F6500FC982B /* GifuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifuTests.swift; sourceTree = ""; }; + 009BD13A1BBC7F6500FC982B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 009BD1431BBC93C800FC982B /* CGSizeExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGSizeExtension.swift; sourceTree = ""; }; 00B8C73E1A364DA400C188E7 /* Gifu.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Gifu.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 00B8C7421A364DA400C188E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Source/Info.plist; sourceTree = ""; }; 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceHelpers.swift; sourceTree = ""; }; 00B8C7951A3650EE00C188E7 /* Gifu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Gifu.h; sourceTree = ""; }; - EA1E21121AD5D369000459BD /* Runes.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Runes.framework; path = Carthage/Build/iOS/Runes.framework; sourceTree = ""; }; EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtension.swift; sourceTree = ""; }; - EAF49C801A3A4FAA00B395DF /* Curry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Curry.swift; sourceTree = ""; }; + EAF49C801A3A4FAA00B395DF /* FunctionalHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionalHelpers.swift; sourceTree = ""; }; EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatedFrame.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 009BD1331BBC7F6500FC982B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 009BD13B1BBC7F6500FC982B /* Gifu.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 00B8C73A1A364DA400C188E7 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EA1E21131AD5D369000459BD /* Runes.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0036ABBA1BBD1D1700C6CC3D /* Images */ = { + isa = PBXGroup; + children = ( + 0036ABB61BBD1D0B00C6CC3D /* mugen.gif */, + 0036ABB81BBD1D1400C6CC3D /* nailed.gif */, + ); + name = Images; + sourceTree = ""; + }; + 009BD1371BBC7F6500FC982B /* GifuTests */ = { + isa = PBXGroup; + children = ( + 009BD1381BBC7F6500FC982B /* GifuTests.swift */, + 009BD13A1BBC7F6500FC982B /* Info.plist */, + 0036ABBA1BBD1D1700C6CC3D /* Images */, + ); + path = GifuTests; + sourceTree = ""; + }; 00B8C7341A364DA400C188E7 = { isa = PBXGroup; children = ( - EA1E21111AD5D354000459BD /* Frameworks */, 00B8C75A1A364DBE00C188E7 /* Source */, 00B8C7411A364DA400C188E7 /* Supporting Files */, + 009BD1371BBC7F6500FC982B /* GifuTests */, 00B8C73F1A364DA400C188E7 /* Products */, ); sourceTree = ""; @@ -56,6 +103,7 @@ isa = PBXGroup; children = ( 00B8C73E1A364DA400C188E7 /* Gifu.framework */, + 009BD1361BBC7F6500FC982B /* GifuTests.xctest */, ); name = Products; sourceTree = ""; @@ -75,22 +123,16 @@ 005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */, EAF49CB01A3B6EEB00B395DF /* AnimatedFrame.swift */, 005656EC1A6F14D6008A0ED1 /* Animator.swift */, + 009BD1431BBC93C800FC982B /* CGSizeExtension.swift */, + EAF49C801A3A4FAA00B395DF /* FunctionalHelpers.swift */, 00B8C7951A3650EE00C188E7 /* Gifu.h */, 00B8C75C1A364DCE00C188E7 /* ImageSourceHelpers.swift */, EAF49C7E1A3A4DE000B395DF /* UIImageExtension.swift */, - EAF49C801A3A4FAA00B395DF /* Curry.swift */, + 004529911BD82209006493BF /* Runes.swift */, ); path = Source; sourceTree = ""; }; - EA1E21111AD5D354000459BD /* Frameworks */ = { - isa = PBXGroup; - children = ( - EA1E21121AD5D369000459BD /* Runes.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -105,6 +147,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 009BD1351BBC7F6500FC982B /* GifuTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 009BD1401BBC7F6500FC982B /* Build configuration list for PBXNativeTarget "GifuTests" */; + buildPhases = ( + 009BD1321BBC7F6500FC982B /* Sources */, + 009BD1331BBC7F6500FC982B /* Frameworks */, + 009BD1341BBC7F6500FC982B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 009BD13D1BBC7F6500FC982B /* PBXTargetDependency */, + ); + name = GifuTests; + productName = GifuTests; + productReference = 009BD1361BBC7F6500FC982B /* GifuTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 00B8C73D1A364DA400C188E7 /* Gifu */ = { isa = PBXNativeTarget; buildConfigurationList = 00B8C7541A364DA500C188E7 /* Build configuration list for PBXNativeTarget "Gifu" */; @@ -129,9 +189,13 @@ 00B8C7351A364DA400C188E7 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; ORGANIZATIONNAME = "Kaishin & Co"; TargetAttributes = { + 009BD1351BBC7F6500FC982B = { + CreatedOnToolsVersion = 7.0.1; + }; 00B8C73D1A364DA400C188E7 = { CreatedOnToolsVersion = 6.1.1; }; @@ -150,11 +214,21 @@ projectRoot = ""; targets = ( 00B8C73D1A364DA400C188E7 /* Gifu */, + 009BD1351BBC7F6500FC982B /* GifuTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 009BD1341BBC7F6500FC982B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0036ABB71BBD1D0B00C6CC3D /* mugen.gif in Resources */, + 0036ABB91BBD1D1400C6CC3D /* nailed.gif in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 00B8C73C1A364DA400C188E7 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -165,22 +239,67 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 009BD1321BBC7F6500FC982B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 009BD1391BBC7F6500FC982B /* GifuTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 00B8C7391A364DA400C188E7 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 005656EF1A6F1C26008A0ED1 /* AnimatableImageView.swift in Sources */, 005656ED1A6F14D6008A0ED1 /* Animator.swift in Sources */, + 009BD1441BBC93C800FC982B /* CGSizeExtension.swift in Sources */, + 004529921BD82209006493BF /* Runes.swift in Sources */, EAF49CB11A3B6EEB00B395DF /* AnimatedFrame.swift in Sources */, 00B8C75F1A364DCE00C188E7 /* ImageSourceHelpers.swift in Sources */, - EAF49C811A3A4FAA00B395DF /* Curry.swift in Sources */, + EAF49C811A3A4FAA00B395DF /* FunctionalHelpers.swift in Sources */, EAF49C7F1A3A4DE000B395DF /* UIImageExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 009BD13D1BBC7F6500FC982B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 00B8C73D1A364DA400C188E7 /* Gifu */; + targetProxy = 009BD13C1BBC7F6500FC982B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ + 009BD13E1BBC7F6500FC982B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = GifuTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.GifuTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 009BD13F1BBC7F6500FC982B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = GifuTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.GifuTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; 00B8C7521A364DA500C188E7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -202,6 +321,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -269,18 +389,17 @@ 00B8C7551A364DA500C188E7 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -290,18 +409,17 @@ 00B8C7561A364DA500C188E7 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/iOS", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; @@ -310,6 +428,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 009BD1401BBC7F6500FC982B /* Build configuration list for PBXNativeTarget "GifuTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 009BD13E1BBC7F6500FC982B /* Debug */, + 009BD13F1BBC7F6500FC982B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 00B8C7381A364DA400C188E7 /* Build configuration list for PBXProject "Gifu" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Gifu.xcodeproj/xcshareddata/xcbaselines/009BD1351BBC7F6500FC982B.xcbaseline/17E637F3-A123-4EFF-966F-BFFEA8927264.plist b/Gifu.xcodeproj/xcshareddata/xcbaselines/009BD1351BBC7F6500FC982B.xcbaseline/17E637F3-A123-4EFF-966F-BFFEA8927264.plist new file mode 100644 index 0000000..0699d55 --- /dev/null +++ b/Gifu.xcodeproj/xcshareddata/xcbaselines/009BD1351BBC7F6500FC982B.xcbaseline/17E637F3-A123-4EFF-966F-BFFEA8927264.plist @@ -0,0 +1,22 @@ + + + + + classNames + + GifuTests + + testPrepareFramesPerformance() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.003825 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/Gifu.xcodeproj/xcshareddata/xcbaselines/009BD1351BBC7F6500FC982B.xcbaseline/Info.plist b/Gifu.xcodeproj/xcshareddata/xcbaselines/009BD1351BBC7F6500FC982B.xcbaseline/Info.plist new file mode 100644 index 0000000..16be829 --- /dev/null +++ b/Gifu.xcodeproj/xcshareddata/xcbaselines/009BD1351BBC7F6500FC982B.xcbaseline/Info.plist @@ -0,0 +1,40 @@ + + + + + runDestinationsByUUID + + 17E637F3-A123-4EFF-966F-BFFEA8927264 + + localComputer + + busSpeedInMHz + 100 + cpuCount + 1 + cpuKind + Intel Core i7 + cpuSpeedInMHz + 2700 + logicalCPUCoresPerPackage + 8 + modelCode + MacBookPro10,1 + physicalCPUCoresPerPackage + 4 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + targetDevice + + modelCode + iPhone8,1 + platformIdentifier + com.apple.platform.iphonesimulator + + + + + diff --git a/Gifu.xcodeproj/xcshareddata/xcschemes/Gifu.xcscheme b/Gifu.xcodeproj/xcshareddata/xcschemes/Gifu.xcscheme index 6665a27..e48ed88 100644 --- a/Gifu.xcodeproj/xcshareddata/xcschemes/Gifu.xcscheme +++ b/Gifu.xcodeproj/xcshareddata/xcschemes/Gifu.xcscheme @@ -1,6 +1,6 @@ @@ -37,16 +37,16 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:Gifu.xcodeproj"> + + + location = "group:Gifu.xcodeproj"> + location = "group:Demo/Demo.xcodeproj"> diff --git a/GifuTests/GifuTests.swift b/GifuTests/GifuTests.swift new file mode 100644 index 0000000..8a54ee8 --- /dev/null +++ b/GifuTests/GifuTests.swift @@ -0,0 +1,60 @@ +import XCTest +import ImageIO +@testable import Gifu + +private let imageData = testImageDataNamed("mugen.gif") +private let staticImage = UIImage(data: imageData!) + +class GifuTests: XCTestCase { + var animator: Animator? + var originalFrameCount: Int? + var preloadedFrameCount: 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() + } + + func testIsAnimatable() { + XCTAssertTrue(animator!.isAnimatable) + } + + func testFramePreload() { + XCTAssertLessThanOrEqual(preloadedFrameCount!, originalFrameCount!) + } + + func testFrameAtIndex() { + XCTAssertNotNil(animator!.frameAtIndex(preloadedFrameCount! - 1)) + } + + func testFrameDurationPrecision() { + let image = animator!.frameAtIndex(5) + XCTAssertTrue((image!.duration - 0.05) < 0.00001) + } + + func testFrameSize() { + let image = animator!.frameAtIndex(5) + XCTAssertEqual(image!.size, staticImage!.size) + } + + func testPrepareFramesPerformance() { + let tempAnimator = Animator(data: imageData!, size: CGSizeZero, contentMode: .ScaleToFill, framePreloadCount: 50) + + self.measureBlock() { + tempAnimator.prepareFrames() + } + } +} + +private func testImageDataNamed(name: String) -> NSData? { + let testBundle = NSBundle(forClass: GifuTests.self) + let imagePath = testBundle.bundleURL.URLByAppendingPathComponent(name) + return NSData(contentsOfURL: imagePath) +} diff --git a/GifuTests/Info.plist b/GifuTests/Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/GifuTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/README.md b/README.md index 2af87eb..8dd5eb5 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ -Adds performant animated GIF support to UIKit, without subclassing `UIImageView`. If you're looking for the Japanese prefecture, click [here](https://goo.gl/maps/CCeAc). - -#### Why? - -Because Apple's `+animatedImage*` is not meant to be used for animated GIFs (loads all the full-sized frames in memory), and the few third party implementations that got it right (see [Credits](#credits)) still require you to use a `UIImageView` subclass, which is not very flexible and might clash with other application-specific functionality. +Adds performant animated GIF support to UIKit. If you're looking for the Japanese prefecture, click [here](https://goo.gl/maps/CCeAc). #### How? -Gifu uses a `UIImage` subclass and `UIImageView` extension written in Swift. -It relies on `CADisplayLink` to animate the view and optimizes the frames by resizing them. +Gifu uses a `UIImageView` subclass and an animator that keeps track of frames and their durations. + +It uses `CADisplayLink` to animate the view and only keeps a limited number of +resized frames in-memory, which exponentially minimizes memory usage for large GIF files (+300 frames). + +The figure below summarizes how this works in practice. Given an image +containing 10 frames, Gifu will load the current frame (red), pre-load the next two frames in this example (orange), and nullify all the other frames to free up memory (gray): + + #### Install @@ -19,25 +22,20 @@ If your prefer Git submodules or want to support iOS 7, you want to add the file #### Usage -Start by instantiating an `AnimatedImage`, then pass it to your `UIImageView`'s `setAnimatedImage`: +Start by instantiating an `AnimatableImageView` either in code or Interface Builder, then call `animateWithImage(named:)` or `animateWithImageData(data:)` on it. ```swift -if let image = AnimatedImage.animatedImageWithName("mugen.gif") { - imageView.setAnimatedImage(image) - imageView.startAnimatingGIF() -} +let imageView = AnimatableImageView(frame: CGRect(...)) +imageView.animateWithImage(named: "mugen.gif") ``` -Note that the image view will not start animating until you call `startAnimatingGIF()` -on it. You can stop the animation anytime using `stopAnimatingGIF()`, and resume -it using `startAnimatingGIF()`. These methods will fallback to UIKit's `startAnimating()` and `stopAnimating()` -if the image view has no animatable image. +You can stop the animation anytime using `imageView.stopAnimatingGIF()`, and resume +it using `imageView.startAnimatingGIF()`. -Likewise, the `isAnimatingGIF()` method returns the current animation state of the view if it has an animatable image, -or UIKit's `isAnimating()` otherwise. +The `isAnimatingGIF()` method returns the current animation state of the view if it has an animatable image. #### Demo App - +Clone or download the repository and open `Gifu.xcworkspace` to check out the demo app. #### Compatibility @@ -46,8 +44,7 @@ or UIKit's `isAnimating()` otherwise. #### Roadmap - Documentation. -- Write some basic tests. -- Add ability to pass a frame-rate argument to `startAnimatingGIF()` +- Add ability to set the frame-rate #### Contributors diff --git a/Source/AnimatableImageView.swift b/Source/AnimatableImageView.swift index 8514ae3..b7f1e22 100644 --- a/Source/AnimatableImageView.swift +++ b/Source/AnimatableImageView.swift @@ -1,12 +1,11 @@ -import Runes import UIKit /// A subclass of `UIImageView` that can be animated using an image name string or raw data. public class AnimatableImageView: UIImageView { /// An `Animator` instance that holds the frames of a specific image in memory. - private var animator: Animator? + var animator: Animator? /// A display link that keeps calling the `updateFrame` method on every screen refresh. - private lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: Selector("updateFrame")) + lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: Selector("updateFrame")) /// The size of the frame cache. public var framePreloadCount = 50 @@ -19,15 +18,15 @@ public class AnimatableImageView: UIImageView { /// Prepares the frames using a GIF image file name, without starting the animation. /// The file name should include the `.gif` extension. /// - /// :param: imageName The name of the GIF file. The method looks for the file in the app bundle. + /// - parameter imageName: The name of the GIF file. The method looks for the file in the app bundle. public func prepareForAnimation(imageNamed imageName: String) { - let path = NSBundle.mainBundle().bundlePath.stringByAppendingPathComponent(imageName) - prepareForAnimation <^> NSData(contentsOfFile: path) + let imagePath = NSBundle.mainBundle().bundleURL.URLByAppendingPathComponent(imageName) + prepareForAnimation <^> NSData(contentsOfURL: imagePath) } /// Prepares the frames using raw GIF image data, without starting the animation. /// - /// :param: data GIF image data. + /// - parameter data: GIF image data. public func prepareForAnimation(imageData data: NSData) { image = UIImage(data: data) animator = Animator(data: data, size: frame.size, contentMode: contentMode, framePreloadCount: framePreloadCount) @@ -37,7 +36,7 @@ public class AnimatableImageView: UIImageView { /// Prepares the frames using a GIF image file name and starts animating the image view. /// - /// :param: imageName The name of the GIF file. The method looks for the file in the app bundle. + /// - parameter imageName: The name of the GIF file. The method looks for the file in the app bundle. public func animateWithImage(named imageName: String) { prepareForAnimation(imageNamed: imageName) startAnimatingGIF() @@ -45,24 +44,17 @@ public class AnimatableImageView: UIImageView { /// Prepares the frames using raw GIF image data and starts animating the image view. /// - /// :param: data GIF image data. - public func animateWithImageData(#data: NSData) { + /// - parameter data: GIF image data. + public func animateWithImageData(data: NSData) { prepareForAnimation(imageData: data) startAnimatingGIF() } - /// Updates the `UIImage` property of the image view if necessary. This method should not be called manually. - override public func displayLayer(layer: CALayer!) { + /// 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 } - /// Update the current frame with the displayLink duration - func updateFrame() { - if animator?.updateCurrentFrame(displayLink.duration) ?? false { - layer.setNeedsDisplay() - } - } - /// Starts the image view animation. public func startAnimatingGIF() { if animator?.isAnimatable ?? false { @@ -75,13 +67,20 @@ public class AnimatableImageView: UIImageView { displayLink.paused = true } + /// Update the current frame with the displayLink duration + func updateFrame() { + if animator?.updateCurrentFrame(displayLink.duration) ?? false { + layer.setNeedsDisplay() + } + } + /// Invalidate the displayLink so it releases this object. - public func cleanup() { + deinit { displayLink.invalidate() } /// Attaches the display link. - private func attachDisplayLink() { + func attachDisplayLink() { displayLink.addToRunLoop(.mainRunLoop(), forMode: NSRunLoopCommonModes) } } diff --git a/Source/AnimatedFrame.swift b/Source/AnimatedFrame.swift index f0552c8..9f76a89 100644 --- a/Source/AnimatedFrame.swift +++ b/Source/AnimatedFrame.swift @@ -2,4 +2,9 @@ struct AnimatedFrame { let image: UIImage? let duration: NSTimeInterval + + static func null() -> AnimatedFrame { + return AnimatedFrame(image: .None, duration: 0) + } } + diff --git a/Source/Animator.swift b/Source/Animator.swift index 94f7e48..4708292 100644 --- a/Source/Animator.swift +++ b/Source/Animator.swift @@ -1,29 +1,28 @@ import UIKit import ImageIO -import Runes /// Responsible for storing and updating the frames of a `AnimatableImageView` instance via delegation. class Animator { /// Maximum duration to increment the frame timer with. - private let maxTimeStep = 1.0 + let maxTimeStep = 1.0 /// An array of animated frames from a single GIF image. - private var animatedFrames = [AnimatedFrame]() + var animatedFrames = [AnimatedFrame]() /// The size to resize all frames to - private let size: CGSize + let size: CGSize /// The content mode to use when resizing - private let contentMode: UIViewContentMode + let contentMode: UIViewContentMode /// Maximum number of frames to load at once - private let maxNumberOfFrames: Int + let maxFrameCount: Int /// The total number of frames in the GIF. - private var numberOfFrames = 0 + var frameCount = 0 /// A reference to the original image source. - private var imageSource: CGImageSourceRef + var imageSource: CGImageSourceRef /// The index of the current GIF frame. - private var currentFrameIndex = 0 + var currentFrameIndex = 0 /// The index of the current GIF frame from the source. - private var currentPreloadIndex = 0 + var currentPreloadIndex = 0 /// Time elapsed since the last frame change. Used to determine when the frame should be updated. - private var timeSinceLastFrameChange: NSTimeInterval = 0.0 + var timeSinceLastFrameChange: NSTimeInterval = 0.0 /// The current image frame to show. var currentFrame: UIImage? { @@ -37,41 +36,43 @@ class Animator { /// Initializes an animator instance from raw GIF image data and an `Animatable` delegate. /// - /// :param: data The raw GIF image data. - /// :param: delegate An `Animatable` delegate. + /// - parameter data: The raw GIF image data. + /// - parameter delegate: An `Animatable` delegate. init(data: NSData, size: CGSize, contentMode: UIViewContentMode, framePreloadCount: Int) { let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse] - imageSource = CGImageSourceCreateWithData(data, options) + self.imageSource = CGImageSourceCreateWithData(data, options) ?? CGImageSourceCreateIncremental(options) self.size = size self.contentMode = contentMode - maxNumberOfFrames = framePreloadCount + self.maxFrameCount = framePreloadCount } // MARK: - Frames /// Loads the frames from an image source, resizes them, then caches them in `animatedFrames`. func prepareFrames() { - numberOfFrames = Int(CGImageSourceGetCount(imageSource)) - let framesToProcess = min(numberOfFrames, maxNumberOfFrames) + frameCount = Int(CGImageSourceGetCount(imageSource)) + let framesToProcess = min(frameCount, maxFrameCount) animatedFrames.reserveCapacity(framesToProcess) - animatedFrames = reduce(0.. AnimatedFrame { - let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index) - let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) + /// - parameter index: The index of the GIF image source to prepare + /// - returns: An AnimatedFrame object + func prepareFrame(index: Int) -> AnimatedFrame { + guard let frameImageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else { + return AnimatedFrame.null() + } + let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index: index) let image = UIImage(CGImage: frameImageRef) let scaledImage: UIImage? switch contentMode { - case .ScaleAspectFit: scaledImage = image?.resizeAspectFit(size) - case .ScaleAspectFill: scaledImage = image?.resizeAspectFill(size) - default: scaledImage = image?.resize(size) + case .ScaleAspectFit: scaledImage = image.resizeAspectFit(size) + case .ScaleAspectFill: scaledImage = image.resizeAspectFill(size) + default: scaledImage = image.resize(size) } return AnimatedFrame(image: scaledImage, duration: frameDuration) @@ -79,18 +80,18 @@ class Animator { /// Returns the frame at a particular index. /// - /// :param: index The index of the frame. - /// :returns: An optional image at a given frame. - private func frameAtIndex(index: Int) -> UIImage? { + /// - parameter index: The index of the frame. + /// - returns: An optional image at a given frame. + func frameAtIndex(index: Int) -> UIImage? { return animatedFrames[index].image } /// 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(duration: CFTimeInterval) -> Bool { timeSinceLastFrameChange += min(maxTimeStep, duration) - var frameDuration = animatedFrames[currentFrameIndex].duration + let frameDuration = animatedFrames[currentFrameIndex].duration if timeSinceLastFrameChange >= frameDuration { timeSinceLastFrameChange -= frameDuration @@ -98,9 +99,9 @@ class Animator { currentFrameIndex = ++currentFrameIndex % animatedFrames.count // Loads the next needed frame for progressive loading - if animatedFrames.count < numberOfFrames { + if animatedFrames.count < frameCount { animatedFrames[lastFrameIndex] = prepareFrame(currentPreloadIndex) - currentPreloadIndex = ++currentPreloadIndex % numberOfFrames + currentPreloadIndex = ++currentPreloadIndex % frameCount } return true } diff --git a/Source/CGSizeExtension.swift b/Source/CGSizeExtension.swift new file mode 100644 index 0000000..8f66266 --- /dev/null +++ b/Source/CGSizeExtension.swift @@ -0,0 +1,39 @@ +extension CGSize { + /// Calculates the aspect ratio of the size. + /// + /// - returns: aspectRatio The aspect ratio of the size. + var aspectRatio: CGFloat { + if height == 0 { return 1 } + return width / height + } + + /// Finds a new size constrained by a size keeping the aspect ratio. + /// + /// - parameter size: The contraining size. + /// - returns: size A new size that fits inside the contraining size with the same aspect ratio. + func sizeConstrainedBySize(size: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * size.height) + let aspectHeight = round(size.width / aspectRatio) + + if aspectWidth > size.width { + return CGSize(width: size.width, height: aspectHeight) + } else { + return CGSize(width: aspectWidth, height: size.height) + } + } + + /// Finds a new size filling the given size while keeping the aspect ratio. + /// + /// - parameter size: The contraining size. + /// - returns: size A new size that fills the contraining size keeping the same aspect ratio. + func sizeFillingSize(size: CGSize) -> CGSize { + let aspectWidth = round(aspectRatio * size.height) + let aspectHeight = round(size.width / aspectRatio) + + if aspectWidth > size.width { + return CGSize(width: aspectWidth, height: size.height) + } else { + return CGSize(width: size.width, height: aspectHeight) + } + } +} diff --git a/Source/Curry.swift b/Source/Curry.swift deleted file mode 100644 index 7bbba8b..0000000 --- a/Source/Curry.swift +++ /dev/null @@ -1,4 +0,0 @@ -/// One of my favorite indian spices. -func curry(f: (A, B) -> C) -> A -> B -> C { - return { a in { b in f(a, b) } } -} diff --git a/Source/FunctionalHelpers.swift b/Source/FunctionalHelpers.swift new file mode 100644 index 0000000..f05da1c --- /dev/null +++ b/Source/FunctionalHelpers.swift @@ -0,0 +1,53 @@ +/// One of my favorite indian spices. +func curry(f: (A, B) -> C) -> A -> B -> C { + return { a in { b in f(a, b) } } +} + +// MARK: - Optional +func <^> (@noescape f: T -> U, a: T?) -> U? { + return a.map(f) +} +func <*> (f: (T -> U)?, a: T?) -> U? { + return a.apply(f) +} + +func >>- (a: T?, @noescape f: T -> U?) -> U? { + return a.flatMap(f) +} + +func pure(a: T) -> T? { + return .Some(a) +} + +extension Optional { + func apply(f: (Wrapped -> U)?) -> U? { + return f.flatMap { self.map($0) } + } +} + +// MARK: - Array +public func <^> (f: T -> U, a: [T]) -> [U] { + return a.map(f) +} + +public func <*> (fs: [T -> U], a: [T]) -> [U] { + return a.apply(fs) +} + +public func >>- (a: [T], f: T -> [U]) -> [U] { + return a.flatMap(f) +} + +public func -<< (f: T -> [U], a: [T]) -> [U] { + return a.flatMap(f) +} + +public func pure(a: T) -> [T] { + return [a] +} + +public extension Array { + func apply(fs: [Element -> U]) -> [U] { + return fs.flatMap { self.map($0) } + } +} diff --git a/Source/ImageSourceHelpers.swift b/Source/ImageSourceHelpers.swift index 7d215c4..135e6a1 100755 --- a/Source/ImageSourceHelpers.swift +++ b/Source/ImageSourceHelpers.swift @@ -1,14 +1,13 @@ import ImageIO import MobileCoreServices -import Runes import UIKit typealias GIFProperties = [String : Double] -private let defaultDuration: Double = 0 +let defaultDuration: Double = 0 /// Retruns the duration of a frame at a specific index using an image source (an `CGImageSource` instance). /// -/// :returns: A frame duration. +/// - returns: A frame duration. func CGImageSourceGIFFrameDuration(imageSource: CGImageSource, index: Int) -> NSTimeInterval { if !imageSource.isAnimatedGIF { return 0.0 } @@ -21,8 +20,8 @@ func CGImageSourceGIFFrameDuration(imageSource: CGImageSource, index: Int) -> NS /// Ensures that a duration is never smaller than a threshold value. /// -/// :returns: A capped frame duration. -private func capDuration(duration: Double) -> Double? { +/// - returns: A capped frame duration. +func capDuration(duration: Double) -> Double? { if duration < 0 { return .None } let threshold = 0.02 - Double(FLT_EPSILON) let cappedDuration = duration < threshold ? 0.1 : duration @@ -31,8 +30,8 @@ private func capDuration(duration: Double) -> Double? { /// Returns a frame duration from a `GIFProperties` dictionary. /// -/// :returns: A frame duration. -private func durationFromGIFProperties(properties: GIFProperties) -> Double? { +/// - returns: A frame duration. +func durationFromGIFProperties(properties: GIFProperties) -> Double? { let unclampedDelayTime = properties[String(kCGImagePropertyGIFUnclampedDelayTime)] let delayTime = properties[String(kCGImagePropertyGIFDelayTime)] @@ -41,16 +40,16 @@ private func durationFromGIFProperties(properties: GIFProperties) -> Double? { /// Calculates frame duration based on both clamped and unclamped times. /// -/// :returns: A frame duration. -private func duration(unclampedDelayTime: Double)(delayTime: Double) -> Double { +/// - returns: A frame duration. +func duration(unclampedDelayTime: Double)(delayTime: Double) -> Double { let delayArray = [unclampedDelayTime, delayTime] return delayArray.filter(isPositive).first ?? defaultDuration } /// Checks if a `Double` value is positive. /// -/// :returns: A boolean value that is `true` if the tested value is positive. -private func isPositive(value: Double) -> Bool { +/// - returns: A boolean value that is `true` if the tested value is positive. +func isPositive(value: Double) -> Bool { return value >= 0 } @@ -58,21 +57,19 @@ private func isPositive(value: Double) -> Bool { extension CGImageSourceRef { /// Returns whether the image source contains an animated GIF. /// - /// :returns: A boolean value that is `true` if the image source contains animated GIF data. + /// - returns: A boolean value that is `true` if the image source contains animated GIF data. var isAnimatedGIF: Bool { - let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(self), kUTTypeGIF) + let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(self) ?? "", kUTTypeGIF) let imageCount = CGImageSourceGetCount(self) - return isTypeGIF != 0 && imageCount > 1 + return isTypeGIF != false && imageCount > 1 } /// Returns the GIF properties at a specific index. /// - /// :param: index The index of the GIF properties to retrieve. - /// :returns: A dictionary containing the GIF properties at the passed in index. + /// - parameter index: The index of the GIF properties to retrieve. + /// - returns: A dictionary containing the GIF properties at the passed in index. func GIFPropertiesAtIndex(index: Int) -> GIFProperties? { - if !isAnimatedGIF { return .None } - - let imageProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as Dictionary - return imageProperties[String(kCGImagePropertyGIFDictionary)] as? GIFProperties + let imageProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as Dictionary? + return imageProperties?[String(kCGImagePropertyGIFDictionary)] as? GIFProperties } } diff --git a/Source/Info.plist b/Source/Info.plist index b1ef434..378e663 100644 --- a/Source/Info.plist +++ b/Source/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - co.kaishin.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Source/UIImageExtension.swift b/Source/UIImageExtension.swift index e176461..bb501fa 100644 --- a/Source/UIImageExtension.swift +++ b/Source/UIImageExtension.swift @@ -3,20 +3,20 @@ extension UIImage { /// Resizes an image instance. /// - /// :param: size The new size of the image. - /// :returns: A new resized image instance. + /// - parameter size: The new size of the image. + /// - returns: A new resized image instance. func resize(size: CGSize) -> UIImage { UIGraphicsBeginImageContextWithOptions(size, false, 0.0) self.drawInRect(CGRect(origin: CGPointZero, size: size)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return newImage + return newImage ?? self } /// Resizes an image instance to fit inside a constraining size while keeping the aspect ratio. /// - /// :param: size The constraining size of the image. - /// :returns: A new resized image instance. + /// - parameter size: The constraining size of the image. + /// - returns: A new resized image instance. func resizeAspectFit(size: CGSize) -> UIImage { let newSize = self.size.sizeConstrainedBySize(size) return resize(newSize) @@ -24,8 +24,8 @@ extension UIImage { /// Resizes an image instance to fill a constraining size while keeping the aspect ratio. /// - /// :param: size The constraining size of the image. - /// :returns: A new resized image instance. + /// - parameter size: The constraining size of the image. + /// - returns: A new resized image instance. func resizeAspectFill(size: CGSize) -> UIImage { let newSize = self.size.sizeFillingSize(size) return resize(newSize) @@ -33,58 +33,18 @@ extension UIImage { /// Returns a new `UIImage` instance using raw image data and a size. /// - /// :param: data Raw image data. - /// :param: size The size to be used to resize the new image instance. - /// :returns: A new image instance from the passed in data. + /// - parameter data: Raw image data. + /// - parameter size: The size to be used to resize the new image instance. + /// - returns: A new image instance from the passed in data. class func imageWithData(data: NSData, size: CGSize) -> UIImage? { return UIImage(data: data)?.resize(size) } /// Returns an image size from raw image data. /// - /// :param: data Raw image data. - /// :returns: The size of the image contained in the data. + /// - parameter data: Raw image data. + /// - returns: The size of the image contained in the data. class func sizeForImageData(data: NSData) -> CGSize? { return UIImage(data: data)?.size } } - -private extension CGSize { - /// Calculates the aspect ratio of the size. - /// - /// :returns: aspectRatio The aspect ratio of the size. - var aspectRatio: CGFloat { - if height == 0 { return 1 } - return width / height - } - - /// Finds a new size constrained by a size keeping the aspect ratio. - /// - /// :param: size The contraining size. - /// :returns: size A new size that fits inside the contraining size with the same aspect ratio. - func sizeConstrainedBySize(size: CGSize) -> CGSize { - let aspectWidth = round(aspectRatio * size.height) - let aspectHeight = round(size.width / aspectRatio) - - if aspectWidth > size.width { - return CGSize(width: size.width, height: aspectHeight) - } else { - return CGSize(width: aspectWidth, height: size.height) - } - } - - /// Finds a new size filling the given size while keeping the aspect ratio. - /// - /// :param: size The contraining size. - /// :returns: size A new size that fills the contraining size keeping the same aspect ratio. - func sizeFillingSize(size: CGSize) -> CGSize { - let aspectWidth = round(aspectRatio * size.height) - let aspectHeight = round(size.width / aspectRatio) - - if aspectWidth > size.width { - return CGSize(width: aspectWidth, height: size.height) - } else { - return CGSize(width: size.width, height: aspectHeight) - } - } -}