parent
a4446cabbb
commit
b3f560f190
|
@ -1,3 +1,3 @@
|
||||||
language: objective-c
|
language: swift
|
||||||
script: ./bin/test
|
script: ./bin/test
|
||||||
osx_image: xcode7.3
|
osx_image: xcode8
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
0009FCE71D16A4AB0038DC85 /* earth.gif in Resources */ = {isa = PBXBuildFile; fileRef = 0009FCE61D16A4AB0038DC85 /* earth.gif */; };
|
||||||
|
002A1BFC1D1624D0005ABBD0 /* mugen.gif in Resources */ = {isa = PBXBuildFile; fileRef = 002A1BFB1D1624D0005ABBD0 /* mugen.gif */; };
|
||||||
|
002FF5141D3A91340069CA56 /* Gifu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 002A1BF91D161D33005ABBD0 /* Gifu.framework */; };
|
||||||
007380231B279644008DAD5C /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 007380221B279644008DAD5C /* Default-568h@2x.png */; };
|
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 */; };
|
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 */; };
|
||||||
|
@ -15,6 +18,9 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
0009FCE61D16A4AB0038DC85 /* earth.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = earth.gif; path = GIFs/earth.gif; sourceTree = SOURCE_ROOT; };
|
||||||
|
002A1BF91D161D33005ABBD0 /* Gifu.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Gifu.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Gifu-gwslszlwadetledwfipapwloqzrw/Build/Products/Debug-iphonesimulator/Gifu.framework"; sourceTree = "<group>"; };
|
||||||
|
002A1BFB1D1624D0005ABBD0 /* mugen.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = mugen.gif; path = ../GIFs/mugen.gif; sourceTree = "<group>"; };
|
||||||
007380221B279644008DAD5C /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
|
007380221B279644008DAD5C /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; 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>"; };
|
||||||
9D98823719BC69CA00B790C6 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
9D98823719BC69CA00B790C6 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -29,17 +35,27 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
002FF5141D3A91340069CA56 /* Gifu.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
002A1BF81D161D32005ABBD0 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
002A1BF91D161D33005ABBD0 /* Gifu.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9D98822E19BC69CA00B790C6 = {
|
9D98822E19BC69CA00B790C6 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
9D98823919BC69CA00B790C6 /* Source */,
|
9D98823919BC69CA00B790C6 /* Source */,
|
||||||
9D98823819BC69CA00B790C6 /* Products */,
|
9D98823819BC69CA00B790C6 /* Products */,
|
||||||
|
002A1BF81D161D32005ABBD0 /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -67,6 +83,8 @@
|
||||||
9D98823A19BC69CA00B790C6 /* Supporting Files */ = {
|
9D98823A19BC69CA00B790C6 /* Supporting Files */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
0009FCE61D16A4AB0038DC85 /* earth.gif */,
|
||||||
|
002A1BFB1D1624D0005ABBD0 /* mugen.gif */,
|
||||||
007380221B279644008DAD5C /* Default-568h@2x.png */,
|
007380221B279644008DAD5C /* Default-568h@2x.png */,
|
||||||
9D25870519BCCB0F00A55A18 /* Info.plist */,
|
9D25870519BCCB0F00A55A18 /* Info.plist */,
|
||||||
);
|
);
|
||||||
|
@ -100,11 +118,13 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0700;
|
LastSwiftUpdateCheck = 0700;
|
||||||
LastUpgradeCheck = 0700;
|
LastUpgradeCheck = 0800;
|
||||||
ORGANIZATIONNAME = "Kaishin & Co";
|
ORGANIZATIONNAME = "Kaishin & Co";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
9D98823619BC69CA00B790C6 = {
|
9D98823619BC69CA00B790C6 = {
|
||||||
CreatedOnToolsVersion = 6.0;
|
CreatedOnToolsVersion = 6.0;
|
||||||
|
LastSwiftMigration = 0800;
|
||||||
|
ProvisioningStyle = Manual;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -131,7 +151,9 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
0009FCE71D16A4AB0038DC85 /* earth.gif in Resources */,
|
||||||
9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */,
|
9D98824419BC69CA00B790C6 /* Images.xcassets in Resources */,
|
||||||
|
002A1BFC1D1624D0005ABBD0 /* mugen.gif in Resources */,
|
||||||
007380231B279644008DAD5C /* Default-568h@2x.png in Resources */,
|
007380231B279644008DAD5C /* Default-568h@2x.png in Resources */,
|
||||||
9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */,
|
9D98825A19BC69F600B790C6 /* Main.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
|
@ -175,6 +197,7 @@
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
|
@ -217,6 +240,7 @@
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
@ -236,10 +260,11 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = demo/Info.plist;
|
INFOPLIST_FILE = demo/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu.demo;
|
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu.demo;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
|
@ -250,10 +275,12 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = demo/Info.plist;
|
INFOPLIST_FILE = demo/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu.demo;
|
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu.demo;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
TARGETED_DEVICE_FAMILY = 1;
|
TARGETED_DEVICE_FAMILY = 1;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
Before Width: | Height: | Size: 13 MiB After Width: | Height: | Size: 13 MiB |
Before Width: | Height: | Size: 933 KiB After Width: | Height: | Size: 933 KiB |
Before Width: | Height: | Size: 11 MiB After Width: | Height: | Size: 11 MiB |
|
@ -1,5 +1,15 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"size" : "20x20",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"size" : "29x29",
|
"size" : "29x29",
|
||||||
"idiom" : "iphone",
|
"idiom" : "iphone",
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"data" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "earth.gif"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"data" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "mugen.gif"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
},
|
|
||||||
"data" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "nailed.gif"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="vXZ-lx-hvc">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Gifu-->
|
<!--Gifu-->
|
||||||
|
@ -14,31 +15,31 @@
|
||||||
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
|
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
|
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Tap the image to pause/resume" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wsv-cU-WO5">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Tap the image to pause/resume" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wsv-cU-WO5">
|
||||||
<rect key="frame" x="211" y="79" width="178.5" height="14.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
<fontDescription key="fontDescription" type="system" pointSize="12"/>
|
||||||
<color key="textColor" red="1" green="0.6334223747253418" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" red="0.99144423007965088" green="0.56549066305160522" blue="0.033751130104064941" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<imageView clipsSubviews="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="600" placeholderIntrinsicHeight="300" translatesAutoresizingMaskIntoConstraints="NO" id="FSz-xF-Xds" customClass="AnimatableImageView" customModule="Gifu">
|
<imageView clipsSubviews="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="600" placeholderIntrinsicHeight="300" translatesAutoresizingMaskIntoConstraints="NO" id="FSz-xF-Xds" customClass="AnimatableImageView" customModule="Gifu">
|
||||||
<rect key="frame" x="0.0" y="143" width="600" height="300"/>
|
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" secondItem="FSz-xF-Xds" secondAttribute="height" multiplier="5:4" id="EOH-hn-KxM"/>
|
||||||
|
</constraints>
|
||||||
<connections>
|
<connections>
|
||||||
<outletCollection property="gestureRecognizers" destination="yth-9a-24x" appends="YES" id="21e-1P-Idk"/>
|
<outletCollection property="gestureRecognizers" destination="yth-9a-24x" appends="YES" id="21e-1P-Idk"/>
|
||||||
</connections>
|
</connections>
|
||||||
</imageView>
|
</imageView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Gifu" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c8Y-41-BaC">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" text="Gifu" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c8Y-41-BaC">
|
||||||
<rect key="frame" x="283" y="55" width="34" height="20.5"/>
|
|
||||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||||
<color key="textColor" red="1" green="0.6334223747253418" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" red="0.99144423007965088" green="0.56549066305160522" blue="0.033751130104064941" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="0.98160680789999999" green="0.98160680789999999" blue="0.98160680789999999" alpha="1" colorSpace="calibratedRGB"/>
|
<color key="backgroundColor" red="0.98160680789999999" green="0.98160680789999999" blue="0.98160680789999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<color key="tintColor" red="1" green="0.6334223747253418" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.99144423007965088" green="0.56549066305160522" blue="0.033751130104064941" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="FSz-xF-Xds" secondAttribute="trailing" id="8kQ-x1-WJl"/>
|
<constraint firstAttribute="trailing" secondItem="FSz-xF-Xds" secondAttribute="trailing" id="8kQ-x1-WJl"/>
|
||||||
<constraint firstItem="c8Y-41-BaC" firstAttribute="top" secondItem="jyV-Pf-zRb" secondAttribute="bottom" constant="55" id="JHy-q1-JuJ"/>
|
<constraint firstItem="c8Y-41-BaC" firstAttribute="top" secondItem="jyV-Pf-zRb" secondAttribute="bottom" constant="55" id="JHy-q1-JuJ"/>
|
||||||
|
|
|
@ -5,7 +5,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
var window: UIWindow?
|
var window: UIWindow?
|
||||||
|
|
||||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,10 @@ class ViewController: UIViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
imageView.animate(withGIFNamed: "mugen")
|
||||||
imageView.animateWithImage(named: "mugen.gif")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func toggleAnimation(sender: AnyObject) {
|
@IBAction func toggleAnimation(_ sender: AnyObject) {
|
||||||
if imageView.isAnimatingGIF {
|
if imageView.isAnimatingGIF {
|
||||||
imageView.stopAnimatingGIF()
|
imageView.stopAnimatingGIF()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -32,8 +32,8 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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; };
|
0036ABB61BBD1D0B00C6CC3D /* mugen.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = mugen.gif; path = Demo/GIFs/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; };
|
0036ABB81BBD1D1400C6CC3D /* nailed.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = nailed.gif; path = Demo/GIFs/nailed.gif; sourceTree = SOURCE_ROOT; };
|
||||||
005656EC1A6F14D6008A0ED1 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = "<group>"; };
|
005656EC1A6F14D6008A0ED1 /* Animator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Animator.swift; sourceTree = "<group>"; };
|
||||||
005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableImageView.swift; sourceTree = "<group>"; };
|
005656EE1A6F1C26008A0ED1 /* AnimatableImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimatableImageView.swift; sourceTree = "<group>"; };
|
||||||
007E08431BD95E6200883D0C /* ArrayExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
|
007E08431BD95E6200883D0C /* ArrayExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
|
||||||
|
@ -212,14 +212,17 @@
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastSwiftUpdateCheck = 0700;
|
LastSwiftUpdateCheck = 0700;
|
||||||
LastUpgradeCheck = 0700;
|
LastUpgradeCheck = 0800;
|
||||||
ORGANIZATIONNAME = "Kaishin & Co";
|
ORGANIZATIONNAME = "Kaishin & Co";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
009BD1351BBC7F6500FC982B = {
|
009BD1351BBC7F6500FC982B = {
|
||||||
CreatedOnToolsVersion = 7.0.1;
|
CreatedOnToolsVersion = 7.0.1;
|
||||||
|
LastSwiftMigration = 0800;
|
||||||
};
|
};
|
||||||
00B8C73D1A364DA400C188E7 = {
|
00B8C73D1A364DA400C188E7 = {
|
||||||
CreatedOnToolsVersion = 6.1.1;
|
CreatedOnToolsVersion = 6.1.1;
|
||||||
|
LastSwiftMigration = 0800;
|
||||||
|
ProvisioningStyle = Manual;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -321,6 +324,7 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.GifuTests;
|
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.GifuTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -335,6 +339,8 @@
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.GifuTests;
|
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.GifuTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -362,6 +368,7 @@
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
GCC_DYNAMIC_NO_PIC = NO;
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
|
@ -408,6 +415,7 @@
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
@ -436,11 +444,13 @@
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu;
|
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -456,10 +466,13 @@
|
||||||
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
|
||||||
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
INFOPLIST_FILE = "$(SRCROOT)/Source/Info.plist";
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu;
|
PRODUCT_BUNDLE_IDENTIFIER = co.kaishin.gifu;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
SWIFT_VERSION = 3.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "0700"
|
LastUpgradeVersion = "0800"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -12,7 +12,7 @@ class GifuTests: XCTestCase {
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
animator = Animator(data: imageData, size: CGSizeZero, contentMode: .ScaleToFill, framePreloadCount: preloadFrameCount)
|
animator = Animator(data: imageData, size: CGSize.zero, contentMode: .scaleToFill, framePreloadCount: preloadFrameCount)
|
||||||
originalFrameCount = Int(CGImageSourceGetCount(animator.imageSource))
|
originalFrameCount = Int(CGImageSourceGetCount(animator.imageSource))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,28 +22,28 @@ class GifuTests: XCTestCase {
|
||||||
|
|
||||||
func testCurrentFrame() {
|
func testCurrentFrame() {
|
||||||
XCTAssertEqual(animator.currentFrameIndex, 0)
|
XCTAssertEqual(animator.currentFrameIndex, 0)
|
||||||
XCTAssertEqual(animator.currentFrameDuration, NSTimeInterval.infinity)
|
XCTAssertEqual(animator.currentFrameDuration, TimeInterval.infinity)
|
||||||
XCTAssertNil(animator.currentFrameImage)
|
XCTAssertNil(animator.currentFrameImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFramePreload() {
|
func testFramePreload() {
|
||||||
let expectation = expectationWithDescription("frameDuration")
|
let expectation = self.expectation(description: "frameDuration")
|
||||||
|
|
||||||
animator.prepareFrames {
|
animator.prepareFrames {
|
||||||
let animatedFrameCount = self.animator.animatedFrames.count
|
let animatedFrameCount = self.animator.animatedFrames.count
|
||||||
XCTAssertEqual(animatedFrameCount, self.originalFrameCount)
|
XCTAssertEqual(animatedFrameCount, self.originalFrameCount)
|
||||||
XCTAssertNotNil(self.animator.frameAtIndex(preloadFrameCount - 1))
|
XCTAssertNotNil(self.animator.frame(at: preloadFrameCount - 1))
|
||||||
XCTAssertNil(self.animator.frameAtIndex(preloadFrameCount + 1)?.images)
|
XCTAssertNil(self.animator.frame(at: preloadFrameCount + 1)?.images)
|
||||||
XCTAssertEqual(self.animator.currentFrameIndex, 0)
|
XCTAssertEqual(self.animator.currentFrameIndex, 0)
|
||||||
|
|
||||||
self.animator.shouldChangeFrame(1.0) { hasNewFrame in
|
self.animator.shouldChangeFrame(with: 1.0) { hasNewFrame in
|
||||||
XCTAssertTrue(hasNewFrame)
|
XCTAssertTrue(hasNewFrame)
|
||||||
XCTAssertEqual(self.animator.currentFrameIndex, 1)
|
XCTAssertEqual(self.animator.currentFrameIndex, 1)
|
||||||
expectation.fulfill()
|
expectation.fulfill()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForExpectationsWithTimeout(1.0) { error in
|
waitForExpectations(timeout: 1.0) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
print("Error: \(error.localizedDescription)")
|
print("Error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
@ -51,19 +51,19 @@ class GifuTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFrameInfo() {
|
func testFrameInfo() {
|
||||||
let expectation = expectationWithDescription("testFrameInfoIsAccurate")
|
let expectation = self.expectation(description: "testFrameInfoIsAccurate")
|
||||||
|
|
||||||
animator.prepareFrames {
|
animator.prepareFrames {
|
||||||
let frameDuration = self.animator.frameAtIndex(5)?.duration ?? 0
|
let frameDuration = self.animator.frame(at: 5)?.duration ?? 0
|
||||||
XCTAssertTrue((frameDuration - 0.05) < 0.00001)
|
XCTAssertTrue((frameDuration - 0.05) < 0.00001)
|
||||||
|
|
||||||
let imageSize = self.animator.frameAtIndex(5)?.size ?? CGSizeZero
|
let imageSize = self.animator.frame(at: 5)?.size ?? CGSize.zero
|
||||||
XCTAssertEqual(imageSize, staticImage.size)
|
XCTAssertEqual(imageSize, staticImage.size)
|
||||||
|
|
||||||
expectation.fulfill()
|
expectation.fulfill()
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForExpectationsWithTimeout(1.0) { error in
|
waitForExpectations(timeout: 1.0) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
print("Error: \(error.localizedDescription)")
|
print("Error: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,8 @@ class GifuTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func testImageDataNamed(name: String) -> NSData {
|
private func testImageDataNamed(_ name: String) -> Data {
|
||||||
let testBundle = NSBundle(forClass: GifuTests.self)
|
let testBundle = Bundle(for: GifuTests.self)
|
||||||
let imagePath = testBundle.bundleURL.URLByAppendingPathComponent(name)
|
let imagePath = testBundle.bundleURL.appendingPathComponent(name)
|
||||||
return NSData(contentsOfURL: imagePath)!
|
return (try! Data(contentsOf: imagePath))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import UIKit
|
||||||
public class AnimatableImageView: UIImageView {
|
public class AnimatableImageView: UIImageView {
|
||||||
/// Proxy object for preventing a reference cycle between the CADisplayLink and AnimatableImageView.
|
/// Proxy object for preventing a reference cycle between the CADisplayLink and AnimatableImageView.
|
||||||
/// Source: http://merowing.info/2015/11/the-beauty-of-imperfection/
|
/// Source: http://merowing.info/2015/11/the-beauty-of-imperfection/
|
||||||
class TargetProxy {
|
fileprivate class TargetProxy {
|
||||||
private weak var target: AnimatableImageView?
|
private weak var target: AnimatableImageView?
|
||||||
|
|
||||||
init(target: AnimatableImageView) {
|
init(target: AnimatableImageView) {
|
||||||
|
@ -23,10 +23,10 @@ public class AnimatableImageView: UIImageView {
|
||||||
private var displayLinkInitialized: Bool = false
|
private var displayLinkInitialized: Bool = false
|
||||||
|
|
||||||
/// A display link that keeps calling the `updateFrame` method on every screen refresh.
|
/// A display link that keeps calling the `updateFrame` method on every screen refresh.
|
||||||
lazy var displayLink: CADisplayLink = {
|
lazy var displayLink: CADisplayLink = { [unowned self] in
|
||||||
self.displayLinkInitialized = true
|
self.displayLinkInitialized = true
|
||||||
let display = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate))
|
let display = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate))
|
||||||
display.paused = true
|
display.isPaused = true
|
||||||
return display
|
return display
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ public class AnimatableImageView: UIImageView {
|
||||||
|
|
||||||
/// 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 !displayLink.paused
|
return !displayLink.isPaused
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A computed property that returns the total number of frames in the GIF.
|
/// A computed property that returns the total number of frames in the GIF.
|
||||||
|
@ -50,18 +50,20 @@ public class AnimatableImageView: UIImageView {
|
||||||
/// The file name should include the `.gif` extension.
|
/// The file name should include the `.gif` extension.
|
||||||
///
|
///
|
||||||
/// - parameter 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) {
|
public func prepareForAnimation(withGIFNamed imageName: String) {
|
||||||
let imagePath = NSBundle.mainBundle().bundleURL.URLByAppendingPathComponent(imageName)
|
guard let extensionRemoved = imageName.components(separatedBy: ".")[safe: 0],
|
||||||
guard let data = NSData(contentsOfURL: imagePath) else { return }
|
let imagePath = Bundle.main.url(forResource: extensionRemoved, withExtension: "gif"),
|
||||||
prepareForAnimation(imageData: data)
|
let data = try? Data(contentsOf: imagePath) else { return }
|
||||||
|
|
||||||
|
prepareForAnimation(withGIFData: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares the frames using raw GIF image data, without starting the animation.
|
/// Prepares the frames using raw GIF image data, without starting the animation.
|
||||||
///
|
///
|
||||||
/// - parameter data: GIF image data.
|
/// - parameter data: GIF image data.
|
||||||
public func prepareForAnimation(imageData data: NSData) {
|
public func prepareForAnimation(withGIFData imageData: Data) {
|
||||||
image = UIImage(data: data)
|
image = UIImage(data: imageData)
|
||||||
animator = Animator(data: data, size: frame.size, contentMode: contentMode, framePreloadCount: framePreloadCount)
|
animator = Animator(data: imageData, size: frame.size, contentMode: contentMode, framePreloadCount: framePreloadCount)
|
||||||
animator?.needsPrescaling = needsPrescaling
|
animator?.needsPrescaling = needsPrescaling
|
||||||
animator?.prepareFrames()
|
animator?.prepareFrames()
|
||||||
attachDisplayLink()
|
attachDisplayLink()
|
||||||
|
@ -70,34 +72,34 @@ public class AnimatableImageView: UIImageView {
|
||||||
/// Prepares the frames using a GIF image file name and starts animating the image view.
|
/// Prepares the frames using a GIF image file name and starts animating the image view.
|
||||||
///
|
///
|
||||||
/// - parameter 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) {
|
public func animate(withGIFNamed imageName: String) {
|
||||||
prepareForAnimation(imageNamed: imageName)
|
prepareForAnimation(withGIFNamed: imageName)
|
||||||
startAnimatingGIF()
|
startAnimatingGIF()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares the frames using raw GIF image data and starts animating the image view.
|
/// Prepares the frames using raw GIF image data and starts animating the image view.
|
||||||
///
|
///
|
||||||
/// - parameter data: GIF image data.
|
/// - parameter data: GIF image data.
|
||||||
public func animateWithImageData(data: NSData) {
|
public func animate(withGIFData data: Data) {
|
||||||
prepareForAnimation(imageData: data)
|
prepareForAnimation(withGIFData: data)
|
||||||
startAnimatingGIF()
|
startAnimatingGIF()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the `image` property of the image view if necessary. This method should not be called manually.
|
/// Updates the `image` property of the image view if necessary. This method should not be called manually.
|
||||||
override public func displayLayer(layer: CALayer) {
|
override public func display(_ layer: CALayer) {
|
||||||
image = animator?.currentFrameImage ?? image
|
image = animator?.currentFrameImage ?? image
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts the image view animation.
|
/// Starts the image view animation.
|
||||||
public func startAnimatingGIF() {
|
public func startAnimatingGIF() {
|
||||||
if animator?.isAnimatable ?? false {
|
if animator?.isAnimatable ?? false {
|
||||||
displayLink.paused = false
|
displayLink.isPaused = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops the image view animation.
|
/// Stops the image view animation.
|
||||||
public func stopAnimatingGIF() {
|
public func stopAnimatingGIF() {
|
||||||
displayLink.paused = true
|
displayLink.isPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the image view values.
|
/// Reset the image view values.
|
||||||
|
@ -109,7 +111,7 @@ public class AnimatableImageView: UIImageView {
|
||||||
/// Update the current frame if needed.
|
/// Update the current frame if needed.
|
||||||
func updateFrameIfNeeded() {
|
func updateFrameIfNeeded() {
|
||||||
guard let animator = animator else { return }
|
guard let animator = animator else { return }
|
||||||
animator.shouldChangeFrame(displayLink.duration) { hasNewFrame in
|
animator.shouldChangeFrame(with: displayLink.duration) { hasNewFrame in
|
||||||
if hasNewFrame { self.layer.setNeedsDisplay() }
|
if hasNewFrame { self.layer.setNeedsDisplay() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,6 +125,6 @@ public class AnimatableImageView: UIImageView {
|
||||||
|
|
||||||
/// Attaches the display link.
|
/// Attaches the display link.
|
||||||
func attachDisplayLink() {
|
func attachDisplayLink() {
|
||||||
displayLink.addToRunLoop(.mainRunLoop(), forMode: NSRunLoopCommonModes)
|
displayLink.add(to: .main, forMode: RunLoopMode.commonModes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ struct AnimatedFrame {
|
||||||
/// The image that should be used for this frame.
|
/// The image that should be used for this frame.
|
||||||
let image: UIImage?
|
let image: UIImage?
|
||||||
/// The duration that the frame image should be displayed.
|
/// The duration that the frame image should be displayed.
|
||||||
let duration: NSTimeInterval
|
let duration: TimeInterval
|
||||||
|
|
||||||
/// A placeholder frame with no image assigned.
|
/// A placeholder frame with no image assigned.
|
||||||
/// Used to replace frames that are no longer needed in the animation.
|
/// Used to replace frames that are no longer needed in the animation.
|
||||||
|
@ -13,15 +13,15 @@ struct AnimatedFrame {
|
||||||
|
|
||||||
/// Whether the AnimatedFrame instance contains an image or not.
|
/// Whether the AnimatedFrame instance contains an image or not.
|
||||||
var isPlaceholder: Bool {
|
var isPlaceholder: Bool {
|
||||||
return image == .None
|
return image == .none
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes an optional image and returns an non-placeholder `AnimatedFrame`.
|
/// Takes an optional image and returns an non-placeholder `AnimatedFrame`.
|
||||||
///
|
///
|
||||||
/// - parameter image: An optional `UIImage` instance to be assigned to the new frame.
|
/// - parameter image: An optional `UIImage` instance to be assigned to the new frame.
|
||||||
/// - returns: A non-placeholder `AnimatedFrame` instance.
|
/// - returns: A non-placeholder `AnimatedFrame` instance.
|
||||||
func frameWithImage(image: UIImage?) -> AnimatedFrame {
|
func animatedFrame(with newImage: UIImage?) -> AnimatedFrame {
|
||||||
return AnimatedFrame(image: image, duration: duration)
|
return AnimatedFrame(image: newImage, duration: duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Animator {
|
||||||
/// The total number of frames in the GIF.
|
/// The total number of frames in the GIF.
|
||||||
var frameCount = 0
|
var frameCount = 0
|
||||||
/// A reference to the original image source.
|
/// A reference to the original image source.
|
||||||
var imageSource: CGImageSourceRef
|
var imageSource: CGImageSource
|
||||||
|
|
||||||
/// The index of the current GIF frame.
|
/// The index of the current GIF frame.
|
||||||
var currentFrameIndex = 0 {
|
var currentFrameIndex = 0 {
|
||||||
|
@ -28,27 +28,28 @@ class Animator {
|
||||||
/// The index of the previous GIF frame.
|
/// The index of the previous GIF frame.
|
||||||
var previousFrameIndex = 0 {
|
var previousFrameIndex = 0 {
|
||||||
didSet {
|
didSet {
|
||||||
dispatch_async(preloadFrameQueue) {
|
preloadFrameQueue.async {
|
||||||
self.updatePreloadedFrames()
|
self.updatePreloadedFrames()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// 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.
|
||||||
var timeSinceLastFrameChange: NSTimeInterval = 0.0
|
var timeSinceLastFrameChange: TimeInterval = 0.0
|
||||||
/// Specifies whether GIF frames should be pre-scaled.
|
/// Specifies whether GIF frames should be pre-scaled.
|
||||||
/// - seealso: `needsPrescaling` in AnimatableImageView.
|
/// - seealso: `needsPrescaling` in AnimatableImageView.
|
||||||
var needsPrescaling = true
|
var needsPrescaling = true
|
||||||
/// Dispatch queue used for preloading images.
|
/// Dispatch queue used for preloading images.
|
||||||
private lazy var preloadFrameQueue = dispatch_queue_create("co.kaishin.Gifu.preloadQueue", DISPATCH_QUEUE_SERIAL)
|
private lazy var preloadFrameQueue: DispatchQueue = {
|
||||||
|
return DispatchQueue(label: "co.kaishin.Gifu.preloadQueue")
|
||||||
|
}()
|
||||||
/// The current image frame to show.
|
/// The current image frame to show.
|
||||||
var currentFrameImage: UIImage? {
|
var currentFrameImage: UIImage? {
|
||||||
return frameAtIndex(currentFrameIndex)
|
return frame(at: currentFrameIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The current frame duration
|
/// The current frame duration
|
||||||
var currentFrameDuration: NSTimeInterval {
|
var currentFrameDuration: TimeInterval {
|
||||||
return durationAtIndex(currentFrameIndex)
|
return duration(at: currentFrameIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is this image animatable?
|
/// Is this image animatable?
|
||||||
|
@ -60,9 +61,9 @@ class Animator {
|
||||||
///
|
///
|
||||||
/// - parameter data: The raw GIF image data.
|
/// - parameter data: The raw GIF image data.
|
||||||
/// - parameter delegate: An `Animatable` delegate.
|
/// - parameter delegate: An `Animatable` delegate.
|
||||||
init(data: NSData, size: CGSize, contentMode: UIViewContentMode, framePreloadCount: Int) {
|
init(data: Data, size: CGSize, contentMode: UIViewContentMode, framePreloadCount: Int) {
|
||||||
let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse]
|
let options = [String(kCGImageSourceShouldCache): kCFBooleanFalse] as CFDictionary
|
||||||
self.imageSource = CGImageSourceCreateWithData(data, options) ?? CGImageSourceCreateIncremental(options)
|
self.imageSource = CGImageSourceCreateWithData(data as CFData, options) ?? CGImageSourceCreateIncremental(options)
|
||||||
self.size = size
|
self.size = size
|
||||||
self.contentMode = contentMode
|
self.contentMode = contentMode
|
||||||
self.preloadFrameCount = framePreloadCount
|
self.preloadFrameCount = framePreloadCount
|
||||||
|
@ -70,10 +71,10 @@ class 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`.
|
||||||
func prepareFrames(completionHandler: (Void -> Void)? = .None) {
|
func prepareFrames(_ completionHandler: ((Void) -> Void)? = .none) {
|
||||||
frameCount = Int(CGImageSourceGetCount(imageSource))
|
frameCount = Int(CGImageSourceGetCount(imageSource))
|
||||||
animatedFrames.reserveCapacity(frameCount)
|
animatedFrames.reserveCapacity(frameCount)
|
||||||
dispatch_async(preloadFrameQueue) {
|
preloadFrameQueue.async {
|
||||||
self.setupAnimatedFrames()
|
self.setupAnimatedFrames()
|
||||||
if let handler = completionHandler { handler() }
|
if let handler = completionHandler { handler() }
|
||||||
}
|
}
|
||||||
|
@ -83,7 +84,7 @@ class Animator {
|
||||||
///
|
///
|
||||||
/// - parameter index: The index of the frame.
|
/// - parameter index: The index of the frame.
|
||||||
/// - returns: An optional image at a given frame.
|
/// - returns: An optional image at a given frame.
|
||||||
func frameAtIndex(index: Int) -> UIImage? {
|
func frame(at index: Int) -> UIImage? {
|
||||||
return animatedFrames[safe: index]?.image
|
return animatedFrames[safe: index]?.image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,16 +92,16 @@ class Animator {
|
||||||
///
|
///
|
||||||
/// - parameter index: The index of the duration.
|
/// - parameter index: The index of the duration.
|
||||||
/// - returns: The duration of the given frame.
|
/// - returns: The duration of the given frame.
|
||||||
func durationAtIndex(index: Int) -> NSTimeInterval {
|
func duration(at index: Int) -> TimeInterval {
|
||||||
return animatedFrames[safe: index]?.duration ?? NSTimeInterval.infinity
|
return animatedFrames[safe: index]?.duration ?? TimeInterval.infinity
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks whether the frame should be changed and calls a handler with the results.
|
/// Checks whether the frame should be changed and calls a handler with the results.
|
||||||
///
|
///
|
||||||
/// - parameter duration: A `CFTimeInterval` value that will be used to determine whether frame should be changed.
|
/// - parameter duration: A `CFTimeInterval` value that will be used to determine whether frame should be changed.
|
||||||
/// - parameter handler: A function that takes a `Bool` and returns nothing. It will be called with the frame change result.
|
/// - parameter handler: A function that takes a `Bool` and returns nothing. It will be called with the frame change result.
|
||||||
func shouldChangeFrame(duration: CFTimeInterval, handler: Bool -> Void) {
|
func shouldChangeFrame(with duration: CFTimeInterval, handler: (Bool) -> Void) {
|
||||||
incrementTimeSinceLastFrameChangeWithDuration(duration)
|
incrementTimeSinceLastFrameChange(with: duration)
|
||||||
|
|
||||||
if currentFrameDuration > timeSinceLastFrameChange {
|
if currentFrameDuration > timeSinceLastFrameChange {
|
||||||
handler(false)
|
handler(false)
|
||||||
|
@ -122,16 +123,16 @@ private extension Animator {
|
||||||
///
|
///
|
||||||
/// - parameter index: The index of the frame to load.
|
/// - parameter index: The index of the frame to load.
|
||||||
/// - returns: An optional `UIImage` instance.
|
/// - returns: An optional `UIImage` instance.
|
||||||
func loadFrameAtIndex(index: Int) -> UIImage? {
|
func loadFrame(at index: Int) -> UIImage? {
|
||||||
guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else { return .None }
|
guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else { return .none }
|
||||||
let image = UIImage(CGImage: imageRef)
|
let image = UIImage(cgImage: imageRef)
|
||||||
let scaledImage: UIImage?
|
let scaledImage: UIImage?
|
||||||
|
|
||||||
if needsPrescaling {
|
if needsPrescaling {
|
||||||
switch self.contentMode {
|
switch self.contentMode {
|
||||||
case .ScaleAspectFit: scaledImage = image.resizeAspectFit(size)
|
case .scaleAspectFit: scaledImage = image.constrained(by: size)
|
||||||
case .ScaleAspectFill: scaledImage = image.resizeAspectFill(size)
|
case .scaleAspectFill: scaledImage = image.filling(size: size)
|
||||||
default: scaledImage = image.resize(size)
|
default: scaledImage = image.resized(to: size)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scaledImage = image
|
scaledImage = image
|
||||||
|
@ -145,17 +146,17 @@ private extension Animator {
|
||||||
if !preloadingIsNeeded { return }
|
if !preloadingIsNeeded { return }
|
||||||
animatedFrames[previousFrameIndex] = animatedFrames[previousFrameIndex].placeholderFrame
|
animatedFrames[previousFrameIndex] = animatedFrames[previousFrameIndex].placeholderFrame
|
||||||
|
|
||||||
preloadIndexesWithStartingIndex(currentFrameIndex).forEach { index in
|
preloadIndexes(withStartingIndex: currentFrameIndex).forEach { index in
|
||||||
let currentAnimatedFrame = animatedFrames[index]
|
let currentAnimatedFrame = animatedFrames[index]
|
||||||
if !currentAnimatedFrame.isPlaceholder { return }
|
if !currentAnimatedFrame.isPlaceholder { return }
|
||||||
animatedFrames[index] = currentAnimatedFrame.frameWithImage(loadFrameAtIndex(index))
|
animatedFrames[index] = currentAnimatedFrame.animatedFrame(with: loadFrame(at: index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increments the `timeSinceLastFrameChange` property with a given duration.
|
/// Increments the `timeSinceLastFrameChange` property with a given duration.
|
||||||
///
|
///
|
||||||
/// - parameter duration: An `NSTimeInterval` value to increment the `timeSinceLastFrameChange` property with.
|
/// - parameter duration: An `NSTimeInterval` value to increment the `timeSinceLastFrameChange` property with.
|
||||||
func incrementTimeSinceLastFrameChangeWithDuration(duration: NSTimeInterval) {
|
func incrementTimeSinceLastFrameChange(with duration: TimeInterval) {
|
||||||
timeSinceLastFrameChange += min(maxTimeStep, duration)
|
timeSinceLastFrameChange += min(maxTimeStep, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +167,7 @@ private extension Animator {
|
||||||
|
|
||||||
/// Increments the `currentFrameIndex` property.
|
/// Increments the `currentFrameIndex` property.
|
||||||
func incrementCurrentFrameIndex() {
|
func incrementCurrentFrameIndex() {
|
||||||
currentFrameIndex = incrementFrameIndex(currentFrameIndex)
|
currentFrameIndex = increment(index: currentFrameIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increments a given frame index, taking into account the `frameCount` and looping when necessary.
|
/// Increments a given frame index, taking into account the `frameCount` and looping when necessary.
|
||||||
|
@ -174,7 +175,7 @@ private extension Animator {
|
||||||
/// - parameter index: The `Int` value to increment.
|
/// - parameter index: The `Int` value to increment.
|
||||||
/// - parameter byValue: The `Int` value to increment with.
|
/// - parameter byValue: The `Int` value to increment with.
|
||||||
/// - returns: A new `Int` value.
|
/// - returns: A new `Int` value.
|
||||||
func incrementFrameIndex(index: Int, byValue value: Int = 1) -> Int {
|
func increment(index: Int, by value: Int = 1) -> Int {
|
||||||
return (index + value) % frameCount
|
return (index + value) % frameCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +183,9 @@ private extension Animator {
|
||||||
///
|
///
|
||||||
/// - parameter index: Starting index.
|
/// - parameter index: Starting index.
|
||||||
/// - returns: An array of indexes to preload.
|
/// - returns: An array of indexes to preload.
|
||||||
func preloadIndexesWithStartingIndex(index: Int) -> [Int] {
|
func preloadIndexes(withStartingIndex index: Int) -> [Int] {
|
||||||
let nextIndex = incrementFrameIndex(index)
|
let nextIndex = increment(index: index)
|
||||||
let lastIndex = incrementFrameIndex(index, byValue: preloadFrameCount)
|
let lastIndex = increment(index: index, by: preloadFrameCount)
|
||||||
|
|
||||||
if lastIndex >= nextIndex {
|
if lastIndex >= nextIndex {
|
||||||
return [Int](nextIndex...lastIndex)
|
return [Int](nextIndex...lastIndex)
|
||||||
|
@ -198,11 +199,11 @@ private extension Animator {
|
||||||
resetAnimatedFrames()
|
resetAnimatedFrames()
|
||||||
|
|
||||||
(0..<frameCount).forEach { index in
|
(0..<frameCount).forEach { index in
|
||||||
let frameDuration = CGImageSourceGIFFrameDuration(imageSource, index: index)
|
let frameDuration = CGImageFrameDuration(with: imageSource, atIndex: index)
|
||||||
animatedFrames += [AnimatedFrame(image: .None, duration: frameDuration)]
|
animatedFrames += [AnimatedFrame(image: .none, duration: frameDuration)]
|
||||||
|
|
||||||
if index > preloadFrameCount { return }
|
if index > preloadFrameCount { return }
|
||||||
animatedFrames[index] = animatedFrames[index].frameWithImage(loadFrameAtIndex(index))
|
animatedFrames[index] = animatedFrames[index].animatedFrame(with: loadFrame(at: index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
extension Array {
|
extension Array {
|
||||||
subscript(safe index: Int) -> Element? {
|
subscript(safe index: Int) -> Element? {
|
||||||
return indices ~= index ? self[index] : .None
|
return indices ~= index ? self[index] : .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ extension CGSize {
|
||||||
///
|
///
|
||||||
/// - parameter size: The contraining size.
|
/// - parameter size: The contraining size.
|
||||||
/// - returns: size A new size that fits inside the contraining size with the same aspect ratio.
|
/// - returns: size A new size that fits inside the contraining size with the same aspect ratio.
|
||||||
func sizeConstrainedBySize(size: CGSize) -> CGSize {
|
func constrained(by size: CGSize) -> CGSize {
|
||||||
let aspectWidth = round(aspectRatio * size.height)
|
let aspectWidth = round(aspectRatio * size.height)
|
||||||
let aspectHeight = round(size.width / aspectRatio)
|
let aspectHeight = round(size.width / aspectRatio)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ extension CGSize {
|
||||||
///
|
///
|
||||||
/// - parameter size: The contraining size.
|
/// - parameter size: The contraining size.
|
||||||
/// - returns: size A new size that fills the contraining size keeping the same aspect ratio.
|
/// - returns: size A new size that fills the contraining size keeping the same aspect ratio.
|
||||||
func sizeFillingSize(size: CGSize) -> CGSize {
|
func filling(size: CGSize) -> CGSize {
|
||||||
let aspectWidth = round(aspectRatio * size.height)
|
let aspectWidth = round(aspectRatio * size.height)
|
||||||
let aspectHeight = round(size.width / aspectRatio)
|
let aspectHeight = round(size.width / aspectRatio)
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,18 @@ import ImageIO
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
typealias GIFProperties = [String : Double]
|
typealias GIFProperties = [String: Double]
|
||||||
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).
|
/// 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 {
|
func CGImageFrameDuration(with imageSource: CGImageSource, atIndex index: Int) -> TimeInterval {
|
||||||
if !imageSource.isAnimatedGIF { return 0.0 }
|
if !imageSource.isAnimatedGIF { return 0.0 }
|
||||||
|
|
||||||
guard let properties = imageSource.GIFPropertiesAtIndex(index),
|
guard let GIFProperties = imageSource.properties(at: index),
|
||||||
let duration = durationFromGIFProperties(properties),
|
let duration = frameDuration(with: GIFProperties),
|
||||||
let cappedDuration = capDuration(duration)
|
let cappedDuration = capDuration(with: duration)
|
||||||
else { return defaultDuration }
|
else { return defaultDuration }
|
||||||
|
|
||||||
return cappedDuration
|
return cappedDuration
|
||||||
|
@ -22,8 +22,8 @@ func CGImageSourceGIFFrameDuration(imageSource: CGImageSource, index: Int) -> NS
|
||||||
/// Ensures that a duration is never smaller than a threshold value.
|
/// Ensures that a duration is never smaller than a threshold value.
|
||||||
///
|
///
|
||||||
/// - returns: A capped frame duration.
|
/// - returns: A capped frame duration.
|
||||||
func capDuration(duration: Double) -> Double? {
|
func capDuration(with duration: Double) -> Double? {
|
||||||
if duration < 0 { return .None }
|
if duration < 0 { return .none }
|
||||||
let threshold = 0.02 - Double(FLT_EPSILON)
|
let threshold = 0.02 - Double(FLT_EPSILON)
|
||||||
let cappedDuration = duration < threshold ? 0.1 : duration
|
let cappedDuration = duration < threshold ? 0.1 : duration
|
||||||
return cappedDuration
|
return cappedDuration
|
||||||
|
@ -32,36 +32,29 @@ func capDuration(duration: Double) -> Double? {
|
||||||
/// Returns a frame duration from a `GIFProperties` dictionary.
|
/// Returns a frame duration from a `GIFProperties` dictionary.
|
||||||
///
|
///
|
||||||
/// - returns: A frame duration.
|
/// - returns: A frame duration.
|
||||||
func durationFromGIFProperties(properties: GIFProperties) -> Double? {
|
func frameDuration(with properties: GIFProperties) -> Double? {
|
||||||
guard let unclampedDelayTime = properties[String(kCGImagePropertyGIFUnclampedDelayTime)],
|
guard let unclampedDelayTime = properties[String(kCGImagePropertyGIFUnclampedDelayTime)],
|
||||||
let delayTime = properties[String(kCGImagePropertyGIFDelayTime)]
|
let delayTime = properties[String(kCGImagePropertyGIFDelayTime)]
|
||||||
else { return .None }
|
else { return .none }
|
||||||
|
|
||||||
return duration(unclampedDelayTime, delayTime: delayTime)
|
return duration(withUnclampedTime: unclampedDelayTime, andClampedTime: delayTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates frame duration based on both clamped and unclamped times.
|
/// Calculates frame duration based on both clamped and unclamped times.
|
||||||
///
|
///
|
||||||
/// - returns: A frame duration.
|
/// - returns: A frame duration.
|
||||||
func duration(unclampedDelayTime: Double, delayTime: Double) -> Double {
|
func duration(withUnclampedTime unclampedDelayTime: Double, andClampedTime delayTime: Double) -> Double {
|
||||||
let delayArray = [unclampedDelayTime, delayTime]
|
let delayArray = [unclampedDelayTime, delayTime]
|
||||||
return delayArray.filter(isPositive).first ?? defaultDuration
|
return delayArray.filter({ $0 >= 0 }).first ?? defaultDuration
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a `Double` value is positive.
|
|
||||||
///
|
|
||||||
/// - returns: A boolean value that is `true` if the tested value is positive.
|
|
||||||
func isPositive(value: Double) -> Bool {
|
|
||||||
return value >= 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An extension of `CGImageSourceRef` that add GIF introspection and easier property retrieval.
|
/// An extension of `CGImageSourceRef` that add GIF introspection and easier property retrieval.
|
||||||
extension CGImageSourceRef {
|
extension CGImageSource {
|
||||||
/// Returns whether the image source contains an animated GIF.
|
/// 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 {
|
var isAnimatedGIF: Bool {
|
||||||
let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(self) ?? "", kUTTypeGIF)
|
let isTypeGIF = UTTypeConformsTo(CGImageSourceGetType(self) ?? "" as CFString, kUTTypeGIF)
|
||||||
let imageCount = CGImageSourceGetCount(self)
|
let imageCount = CGImageSourceGetCount(self)
|
||||||
return isTypeGIF != false && imageCount > 1
|
return isTypeGIF != false && imageCount > 1
|
||||||
}
|
}
|
||||||
|
@ -70,8 +63,8 @@ extension CGImageSourceRef {
|
||||||
///
|
///
|
||||||
/// - parameter index: The index of the GIF properties to retrieve.
|
/// - parameter index: The index of the GIF properties to retrieve.
|
||||||
/// - returns: A dictionary containing the GIF properties at the passed in index.
|
/// - returns: A dictionary containing the GIF properties at the passed in index.
|
||||||
func GIFPropertiesAtIndex(index: Int) -> GIFProperties? {
|
func properties(at index: Int) -> GIFProperties? {
|
||||||
let imageProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as Dictionary?
|
guard let imageProperties = CGImageSourceCopyPropertiesAtIndex(self, index, nil) as? [String: AnyObject] else { return nil }
|
||||||
return imageProperties?[String(kCGImagePropertyGIFDictionary)] as? GIFProperties
|
return imageProperties[String(kCGImagePropertyGIFDictionary)] as? GIFProperties
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>94</string>
|
<string>100</string>
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
|
@ -5,9 +5,9 @@ extension UIImage {
|
||||||
///
|
///
|
||||||
/// - parameter size: The new size of the image.
|
/// - parameter size: The new size of the image.
|
||||||
/// - returns: A new resized image instance.
|
/// - returns: A new resized image instance.
|
||||||
func resize(size: CGSize) -> UIImage {
|
func resized(to size: CGSize) -> UIImage {
|
||||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||||
self.drawInRect(CGRect(origin: CGPointZero, size: size))
|
self.draw(in: CGRect(origin: CGPoint.zero, size: size))
|
||||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
UIGraphicsEndImageContext()
|
UIGraphicsEndImageContext()
|
||||||
return newImage ?? self
|
return newImage ?? self
|
||||||
|
@ -17,18 +17,18 @@ extension UIImage {
|
||||||
///
|
///
|
||||||
/// - parameter size: The constraining size of the image.
|
/// - parameter size: The constraining size of the image.
|
||||||
/// - returns: A new resized image instance.
|
/// - returns: A new resized image instance.
|
||||||
func resizeAspectFit(size: CGSize) -> UIImage {
|
func constrained(by size: CGSize) -> UIImage {
|
||||||
let newSize = self.size.sizeConstrainedBySize(size)
|
let newSize = size.constrained(by: size)
|
||||||
return resize(newSize)
|
return resized(to: newSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resizes an image instance to fill a constraining size while keeping the aspect ratio.
|
/// Resizes an image instance to fill a constraining size while keeping the aspect ratio.
|
||||||
///
|
///
|
||||||
/// - parameter size: The constraining size of the image.
|
/// - parameter size: The constraining size of the image.
|
||||||
/// - returns: A new resized image instance.
|
/// - returns: A new resized image instance.
|
||||||
func resizeAspectFill(size: CGSize) -> UIImage {
|
func filling(size: CGSize) -> UIImage {
|
||||||
let newSize = self.size.sizeFillingSize(size)
|
let newSize = size.filling(size: size)
|
||||||
return resize(newSize)
|
return resized(to: newSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a new `UIImage` instance using raw image data and a size.
|
/// Returns a new `UIImage` instance using raw image data and a size.
|
||||||
|
@ -36,15 +36,15 @@ extension UIImage {
|
||||||
/// - parameter data: Raw image data.
|
/// - parameter data: Raw image data.
|
||||||
/// - parameter size: The size to be used to resize the new image instance.
|
/// - parameter size: The size to be used to resize the new image instance.
|
||||||
/// - returns: A new image instance from the passed in data.
|
/// - returns: A new image instance from the passed in data.
|
||||||
class func imageWithData(data: NSData, size: CGSize) -> UIImage? {
|
class func image(with data: Data, size: CGSize) -> UIImage? {
|
||||||
return UIImage(data: data)?.resize(size)
|
return UIImage(data: data)?.resized(to: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an image size from raw image data.
|
/// Returns an image size from raw image data.
|
||||||
///
|
///
|
||||||
/// - parameter data: Raw image data.
|
/// - parameter data: Raw image data.
|
||||||
/// - returns: The size of the image contained in the data.
|
/// - returns: The size of the image contained in the data.
|
||||||
class func sizeForImageData(data: NSData) -> CGSize? {
|
class func size(withImageData data: Data) -> CGSize? {
|
||||||
return UIImage(data: data)?.size
|
return UIImage(data: data)?.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
bin/test
2
bin/test
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
xcodebuild test -project Gifu.xcodeproj -scheme Gifu -sdk iphonesimulator BUILD_ACTIVE_ARCH=NO | xcpretty -t -c
|
xcodebuild test -project Gifu.xcodeproj -scheme Gifu -destination "platform=iOS Simulator,name=iPhone 6,OS=10.0" BUILD_ACTIVE_ARCH=NO | xcpretty -t -c
|
||||||
|
|
Loading…
Reference in New Issue