├── .github_changelog_generator ├── .gitignore ├── .swift-version ├── .swiftlint.yml ├── .travis.yml ├── CHANGELOG.md ├── CountdownLabel.podspec ├── CountdownLabel.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── CountdownLabel.xcscheme │ └── CountdownLabelTests.xcscheme ├── CountdownLabel ├── CountdownLabel.h ├── CountdownLabel.swift ├── Info.plist └── LTMorphingLabel │ ├── Info.plist │ ├── LTCharacterDiffResult.swift │ ├── LTCharacterLimbo.swift │ ├── LTEasing.swift │ ├── LTEmitterView.swift │ ├── LTMorphingEffect.swift │ ├── LTMorphingLabel+Anvil.swift │ ├── LTMorphingLabel+Burn.swift │ ├── LTMorphingLabel+Evaporate.swift │ ├── LTMorphingLabel+Fall.swift │ ├── LTMorphingLabel+Pixelate.swift │ ├── LTMorphingLabel+Sparkle.swift │ ├── LTMorphingLabel.h │ ├── LTMorphingLabel.swift │ ├── LTStringDiffResult.swift │ ├── Particles │ ├── Fire.png │ ├── Fragment.png │ ├── Smoke.png │ └── Sparkle.png │ └── tvOS-Info.plist ├── CountdownLabelExample ├── CountdownLabelExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── CountdownLabelExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── README.md └── Screenshots ├── example01.gif ├── example02.gif ├── example03.gif ├── example04.gif ├── example05.gif ├── example06.gif ├── example07.gif ├── example08.gif ├── exampleBurn.gif ├── exampleEvaporate.gif ├── exampleFall.gif ├── examplePixelate.gif ├── exampleScale.gif └── exampleSparkle.gif /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | unreleased=false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/swift 2 | 3 | ### Swift ### 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 7 | 8 | .DS_Store 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | 25 | ## Other 26 | *.xccheckout 27 | *.moved-aside 28 | *.xcuserstate 29 | *.xcscmblueprint 30 | 31 | ## Obj-C/Swift specific 32 | *.hmap 33 | *.ipa 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | Carthage/Checkouts 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | 65 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - function_body_length 3 | - line_length 4 | - trailing_newline 5 | - trailing_whitespace 6 | - variable_name 7 | - type_name 8 | - todo 9 | - nesting 10 | 11 | excluded: 12 | - Docs 13 | - Frameworks 14 | - Pods 15 | 16 | variable_name_min_length: 1 17 | variable_name_max_length: 40 18 | function_body_length: 300 19 | file_length: 800 20 | type_body_length: 800 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7.2 3 | branches: 4 | only: 5 | - master 6 | env: 7 | global: 8 | - LC_CTYPE=en_US.UTF-8 9 | - LANG=en_US.UTF-8 10 | - FRAMEWORK_NAME="CountdownLabel" 11 | matrix: 12 | - DESTINATION="OS=9.2,name=iPhone 6" SCHEME="CountdownLabelTests" SDK="iphonesimulator9.2" $ACTION="test" 13 | 14 | before_install: 15 | - brew update 16 | - brew install carthage || brew outdated carthage || brew upgrade carthage 17 | - carthage version 18 | 19 | install: 20 | - gem install xcpretty 21 | - carthage bootstrap --no-use-binaries --platform iOS 22 | 23 | script: 24 | - set -o pipefail 25 | - xcodebuild -version 26 | - xcodebuild -showsdks 27 | # - xcodebuild 28 | # -project "$FRAMEWORK_NAME.xcodeproj" 29 | # -scheme "$SCHEME" 30 | # -sdk "$SDK" 31 | # -destination "$DESTINATION" 32 | # -configuration Debug 33 | # ONLY_ACTIVE_ARCH=NO 34 | # GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES 35 | # GCC_GENERATE_TEST_COVERAGE_FILES=YES 36 | # "$ACTION" 37 | # | xcpretty -c 38 | 39 | after_success: 40 | # - bash <(curl -s https://codecov.io/bash) 41 | 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [4.0.0](https://github.com/suzuki-0000/CountdownLabel/tree/4.0.0) (2018-09-18) 4 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/3.0.0...4.0.0) 5 | 6 | ### Changed 7 | 8 | - Remove Swift4.0 warning and fix one day bug. [\#29](https://github.com/suzuki-0000/CountdownLabel/pull/29) 9 | - Fixed 24H/12H time format due to unforced locale [\#28](https://github.com/suzuki-0000/CountdownLabel/pull/28) 10 | 11 | ## [3.0.0](https://github.com/suzuki-0000/CountdownLabel/tree/3.0.0) (2017-10-11) 12 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/2.0.1...3.0.0) 13 | 14 | ### Changed 15 | 16 | - fix swift3 issues [\#20](https://github.com/suzuki-0000/CountdownLabel/pull/20) 17 | 18 | ## [2.0.1](https://github.com/suzuki-0000/CountdownLabel/tree/2.0.1) (2017-03-14) 19 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/2.0.0...2.0.1) 20 | 21 | ## [2.0.0](https://github.com/suzuki-0000/CountdownLabel/tree/2.0.0) (2017-03-14) 22 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.2.0...2.0.0) 23 | 24 | ### Changed 25 | 26 | - updated for swift 3.0 [\#17](https://github.com/suzuki-0000/CountdownLabel/pull/17) 27 | 28 | ## [1.2.0](https://github.com/suzuki-0000/CountdownLabel/tree/1.2.0) (2016-09-30) 29 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.1.3...1.2.0) 30 | 31 | ## [1.1.3](https://github.com/suzuki-0000/CountdownLabel/tree/1.1.3) (2016-06-20) 32 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.1.2...1.1.3) 33 | 34 | ## [1.1.2](https://github.com/suzuki-0000/CountdownLabel/tree/1.1.2) (2016-06-03) 35 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.1.1...1.1.2) 36 | 37 | ## [1.1.1](https://github.com/suzuki-0000/CountdownLabel/tree/1.1.1) (2016-03-29) 38 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.1.0...1.1.1) 39 | 40 | ### Changed 41 | 42 | - Fixed time error of counter [\#5](https://github.com/suzuki-0000/CountdownLabel/pull/5) 43 | 44 | ## [1.1.0](https://github.com/suzuki-0000/CountdownLabel/tree/1.1.0) (2016-03-28) 45 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.0.3...1.1.0) 46 | 47 | ### Changed 48 | 49 | - Added multiple delegates & added method for cancelling countdown [\#4](https://github.com/suzuki-0000/CountdownLabel/pull/4) 50 | 51 | ## [1.0.3](https://github.com/suzuki-0000/CountdownLabel/tree/1.0.3) (2016-03-28) 52 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.0.2...1.0.3) 53 | 54 | ### Changed 55 | 56 | - fixed a cracking issue [\#3](https://github.com/suzuki-0000/CountdownLabel/pull/3) 57 | - Correct the spelling of CocoaPods in README [\#1](https://github.com/suzuki-0000/CountdownLabel/pull/1) 58 | 59 | ## [1.0.2](https://github.com/suzuki-0000/CountdownLabel/tree/1.0.2) (2016-01-20) 60 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.0.1...1.0.2) 61 | 62 | ## [1.0.1](https://github.com/suzuki-0000/CountdownLabel/tree/1.0.1) (2016-01-20) 63 | [Full Changelog](https://github.com/suzuki-0000/CountdownLabel/compare/1.0.0...1.0.1) 64 | 65 | ## [1.0.0](https://github.com/suzuki-0000/CountdownLabel/tree/1.0.0) (2016-01-20) 66 | 67 | 68 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* 69 | -------------------------------------------------------------------------------- /CountdownLabel.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CountdownLabel" 3 | s.version = '4.0.1' 4 | s.summary = 'Simple countdown UILabel with morphing animation, and some useful function.' 5 | s.homepage = "https://github.com/suzuki-0000/CountdownLabel" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "suzuki_keishi" => "keishi.1983@gmail.com" } 8 | s.source = { :git => "https://github.com/suzuki-0000/CountdownLabel.git", :tag => s.version } 9 | s.platform = :ios, "8.2" 10 | s.source_files = 'CountdownLabel/*.swift' 11 | s.source_files = 'CountdownLabel/**/*.swift' 12 | s.requires_arc = true 13 | s.frameworks = "UIKit" 14 | end 15 | -------------------------------------------------------------------------------- /CountdownLabel.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 764D99401F8782D000204ED2 /* LTMorphingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99361F877FF500204ED2 /* LTMorphingLabel.swift */; }; 11 | 764D99411F87830200204ED2 /* LTEmitterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D992D1F877FF500204ED2 /* LTEmitterView.swift */; }; 12 | 764D99421F87830200204ED2 /* LTMorphingLabel+Evaporate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99311F877FF500204ED2 /* LTMorphingLabel+Evaporate.swift */; }; 13 | 764D99431F87830200204ED2 /* LTStringDiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99371F877FF500204ED2 /* LTStringDiffResult.swift */; }; 14 | 764D99441F87830200204ED2 /* LTMorphingLabel+Sparkle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99341F877FF500204ED2 /* LTMorphingLabel+Sparkle.swift */; }; 15 | 764D99451F87830200204ED2 /* LTMorphingEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D992E1F877FF500204ED2 /* LTMorphingEffect.swift */; }; 16 | 764D99461F87830200204ED2 /* LTMorphingLabel+Fall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99321F877FF500204ED2 /* LTMorphingLabel+Fall.swift */; }; 17 | 764D99471F87830200204ED2 /* LTCharacterLimbo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D992B1F877FF500204ED2 /* LTCharacterLimbo.swift */; }; 18 | 764D99481F87830200204ED2 /* LTMorphingLabel+Anvil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D992F1F877FF500204ED2 /* LTMorphingLabel+Anvil.swift */; }; 19 | 764D99491F87830200204ED2 /* LTMorphingLabel+Burn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99301F877FF500204ED2 /* LTMorphingLabel+Burn.swift */; }; 20 | 764D994A1F87830200204ED2 /* LTMorphingLabel+Pixelate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D99331F877FF500204ED2 /* LTMorphingLabel+Pixelate.swift */; }; 21 | 764D994B1F87830200204ED2 /* LTEasing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D992C1F877FF500204ED2 /* LTEasing.swift */; }; 22 | 764D994C1F87830200204ED2 /* LTCharacterDiffResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 764D992A1F877FF500204ED2 /* LTCharacterDiffResult.swift */; }; 23 | 892106D41C3CF4140007CDEC /* CountdownLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = 892106D31C3CF4140007CDEC /* CountdownLabel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | 892107001C3CF4490007CDEC /* CountdownLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892106FF1C3CF4490007CDEC /* CountdownLabel.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 764D992A1F877FF500204ED2 /* LTCharacterDiffResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTCharacterDiffResult.swift; sourceTree = ""; }; 29 | 764D992B1F877FF500204ED2 /* LTCharacterLimbo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTCharacterLimbo.swift; sourceTree = ""; }; 30 | 764D992C1F877FF500204ED2 /* LTEasing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTEasing.swift; sourceTree = ""; }; 31 | 764D992D1F877FF500204ED2 /* LTEmitterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTEmitterView.swift; sourceTree = ""; }; 32 | 764D992E1F877FF500204ED2 /* LTMorphingEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTMorphingEffect.swift; sourceTree = ""; }; 33 | 764D992F1F877FF500204ED2 /* LTMorphingLabel+Anvil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LTMorphingLabel+Anvil.swift"; sourceTree = ""; }; 34 | 764D99301F877FF500204ED2 /* LTMorphingLabel+Burn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LTMorphingLabel+Burn.swift"; sourceTree = ""; }; 35 | 764D99311F877FF500204ED2 /* LTMorphingLabel+Evaporate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LTMorphingLabel+Evaporate.swift"; sourceTree = ""; }; 36 | 764D99321F877FF500204ED2 /* LTMorphingLabel+Fall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LTMorphingLabel+Fall.swift"; sourceTree = ""; }; 37 | 764D99331F877FF500204ED2 /* LTMorphingLabel+Pixelate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LTMorphingLabel+Pixelate.swift"; sourceTree = ""; }; 38 | 764D99341F877FF500204ED2 /* LTMorphingLabel+Sparkle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LTMorphingLabel+Sparkle.swift"; sourceTree = ""; }; 39 | 764D99351F877FF500204ED2 /* LTMorphingLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LTMorphingLabel.h; sourceTree = ""; }; 40 | 764D99361F877FF500204ED2 /* LTMorphingLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTMorphingLabel.swift; sourceTree = ""; }; 41 | 764D99371F877FF500204ED2 /* LTStringDiffResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LTStringDiffResult.swift; sourceTree = ""; }; 42 | 764D99391F877FF500204ED2 /* Fire.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Fire.png; sourceTree = ""; }; 43 | 764D993A1F877FF500204ED2 /* Fragment.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Fragment.png; sourceTree = ""; }; 44 | 764D993B1F877FF500204ED2 /* Smoke.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Smoke.png; sourceTree = ""; }; 45 | 764D993C1F877FF500204ED2 /* Sparkle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Sparkle.png; sourceTree = ""; }; 46 | 892106D01C3CF4140007CDEC /* CountdownLabel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CountdownLabel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 892106D31C3CF4140007CDEC /* CountdownLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CountdownLabel.h; sourceTree = ""; }; 48 | 892106D51C3CF4140007CDEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 892106FF1C3CF4490007CDEC /* CountdownLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountdownLabel.swift; sourceTree = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 892106CC1C3CF4140007CDEC /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | /* End PBXFrameworksBuildPhase section */ 61 | 62 | /* Begin PBXGroup section */ 63 | 764D99281F877FF500204ED2 /* LTMorphingLabel */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 764D992A1F877FF500204ED2 /* LTCharacterDiffResult.swift */, 67 | 764D992B1F877FF500204ED2 /* LTCharacterLimbo.swift */, 68 | 764D992C1F877FF500204ED2 /* LTEasing.swift */, 69 | 764D992D1F877FF500204ED2 /* LTEmitterView.swift */, 70 | 764D992E1F877FF500204ED2 /* LTMorphingEffect.swift */, 71 | 764D992F1F877FF500204ED2 /* LTMorphingLabel+Anvil.swift */, 72 | 764D99301F877FF500204ED2 /* LTMorphingLabel+Burn.swift */, 73 | 764D99311F877FF500204ED2 /* LTMorphingLabel+Evaporate.swift */, 74 | 764D99321F877FF500204ED2 /* LTMorphingLabel+Fall.swift */, 75 | 764D99331F877FF500204ED2 /* LTMorphingLabel+Pixelate.swift */, 76 | 764D99341F877FF500204ED2 /* LTMorphingLabel+Sparkle.swift */, 77 | 764D99351F877FF500204ED2 /* LTMorphingLabel.h */, 78 | 764D99371F877FF500204ED2 /* LTStringDiffResult.swift */, 79 | 764D99361F877FF500204ED2 /* LTMorphingLabel.swift */, 80 | 764D99381F877FF500204ED2 /* Particles */, 81 | ); 82 | path = LTMorphingLabel; 83 | sourceTree = ""; 84 | }; 85 | 764D99381F877FF500204ED2 /* Particles */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 764D99391F877FF500204ED2 /* Fire.png */, 89 | 764D993A1F877FF500204ED2 /* Fragment.png */, 90 | 764D993B1F877FF500204ED2 /* Smoke.png */, 91 | 764D993C1F877FF500204ED2 /* Sparkle.png */, 92 | ); 93 | path = Particles; 94 | sourceTree = ""; 95 | }; 96 | 892106C61C3CF4140007CDEC = { 97 | isa = PBXGroup; 98 | children = ( 99 | 892106D21C3CF4140007CDEC /* CountdownLabel */, 100 | 892106D11C3CF4140007CDEC /* Products */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 892106D11C3CF4140007CDEC /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 892106D01C3CF4140007CDEC /* CountdownLabel.framework */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 892106D21C3CF4140007CDEC /* CountdownLabel */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 764D99281F877FF500204ED2 /* LTMorphingLabel */, 116 | 892106D31C3CF4140007CDEC /* CountdownLabel.h */, 117 | 892106FF1C3CF4490007CDEC /* CountdownLabel.swift */, 118 | 892106D51C3CF4140007CDEC /* Info.plist */, 119 | ); 120 | path = CountdownLabel; 121 | sourceTree = ""; 122 | }; 123 | /* End PBXGroup section */ 124 | 125 | /* Begin PBXHeadersBuildPhase section */ 126 | 892106CD1C3CF4140007CDEC /* Headers */ = { 127 | isa = PBXHeadersBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 892106D41C3CF4140007CDEC /* CountdownLabel.h in Headers */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXHeadersBuildPhase section */ 135 | 136 | /* Begin PBXNativeTarget section */ 137 | 892106CF1C3CF4140007CDEC /* CountdownLabel */ = { 138 | isa = PBXNativeTarget; 139 | buildConfigurationList = 892106D81C3CF4140007CDEC /* Build configuration list for PBXNativeTarget "CountdownLabel" */; 140 | buildPhases = ( 141 | 892106CB1C3CF4140007CDEC /* Sources */, 142 | 892106CC1C3CF4140007CDEC /* Frameworks */, 143 | 892106CD1C3CF4140007CDEC /* Headers */, 144 | 892106CE1C3CF4140007CDEC /* Resources */, 145 | 8993AA3F1C4E028900C4A6D2 /* ShellScript */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = CountdownLabel; 152 | productName = CountdownLabel; 153 | productReference = 892106D01C3CF4140007CDEC /* CountdownLabel.framework */; 154 | productType = "com.apple.product-type.framework"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | 892106C71C3CF4140007CDEC /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastSwiftUpdateCheck = 0720; 163 | LastUpgradeCheck = 1020; 164 | ORGANIZATIONNAME = suzuki_keishi; 165 | TargetAttributes = { 166 | 892106CF1C3CF4140007CDEC = { 167 | CreatedOnToolsVersion = 7.2; 168 | LastSwiftMigration = 1020; 169 | }; 170 | }; 171 | }; 172 | buildConfigurationList = 892106CA1C3CF4140007CDEC /* Build configuration list for PBXProject "CountdownLabel" */; 173 | compatibilityVersion = "Xcode 3.2"; 174 | developmentRegion = en; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | Base, 179 | ); 180 | mainGroup = 892106C61C3CF4140007CDEC; 181 | productRefGroup = 892106D11C3CF4140007CDEC /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | 892106CF1C3CF4140007CDEC /* CountdownLabel */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXResourcesBuildPhase section */ 191 | 892106CE1C3CF4140007CDEC /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 8993AA3F1C4E028900C4A6D2 /* ShellScript */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | outputPaths = ( 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | shellPath = /bin/sh; 212 | shellScript = "#if which swiftlint >/dev/null; then\n# swiftlint\n#else\n#echo \"SwiftLint does not exist, download from https://github.com/realm/SwiftLint\"\n#fi"; 213 | }; 214 | /* End PBXShellScriptBuildPhase section */ 215 | 216 | /* Begin PBXSourcesBuildPhase section */ 217 | 892106CB1C3CF4140007CDEC /* Sources */ = { 218 | isa = PBXSourcesBuildPhase; 219 | buildActionMask = 2147483647; 220 | files = ( 221 | 764D99451F87830200204ED2 /* LTMorphingEffect.swift in Sources */, 222 | 764D99421F87830200204ED2 /* LTMorphingLabel+Evaporate.swift in Sources */, 223 | 764D99491F87830200204ED2 /* LTMorphingLabel+Burn.swift in Sources */, 224 | 764D994B1F87830200204ED2 /* LTEasing.swift in Sources */, 225 | 764D99401F8782D000204ED2 /* LTMorphingLabel.swift in Sources */, 226 | 764D994A1F87830200204ED2 /* LTMorphingLabel+Pixelate.swift in Sources */, 227 | 764D99461F87830200204ED2 /* LTMorphingLabel+Fall.swift in Sources */, 228 | 892107001C3CF4490007CDEC /* CountdownLabel.swift in Sources */, 229 | 764D99441F87830200204ED2 /* LTMorphingLabel+Sparkle.swift in Sources */, 230 | 764D994C1F87830200204ED2 /* LTCharacterDiffResult.swift in Sources */, 231 | 764D99411F87830200204ED2 /* LTEmitterView.swift in Sources */, 232 | 764D99431F87830200204ED2 /* LTStringDiffResult.swift in Sources */, 233 | 764D99471F87830200204ED2 /* LTCharacterLimbo.swift in Sources */, 234 | 764D99481F87830200204ED2 /* LTMorphingLabel+Anvil.swift in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin XCBuildConfiguration section */ 241 | 892106D61C3CF4140007CDEC /* Debug */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 251 | CLANG_WARN_BOOL_CONVERSION = YES; 252 | CLANG_WARN_COMMA = YES; 253 | CLANG_WARN_CONSTANT_CONVERSION = YES; 254 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 265 | CLANG_WARN_STRICT_PROTOTYPES = YES; 266 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | CURRENT_PROJECT_VERSION = 1; 272 | DEBUG_INFORMATION_FORMAT = dwarf; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | ENABLE_TESTABILITY = YES; 275 | GCC_C_LANGUAGE_STANDARD = gnu99; 276 | GCC_DYNAMIC_NO_PIC = NO; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_OPTIMIZATION_LEVEL = 0; 279 | GCC_PREPROCESSOR_DEFINITIONS = ( 280 | "DEBUG=1", 281 | "$(inherited)", 282 | ); 283 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 284 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 285 | GCC_WARN_UNDECLARED_SELECTOR = YES; 286 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 287 | GCC_WARN_UNUSED_FUNCTION = YES; 288 | GCC_WARN_UNUSED_VARIABLE = YES; 289 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 290 | MTL_ENABLE_DEBUG_INFO = YES; 291 | ONLY_ACTIVE_ARCH = YES; 292 | SDKROOT = iphoneos; 293 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 294 | TARGETED_DEVICE_FAMILY = "1,2"; 295 | VERSIONING_SYSTEM = "apple-generic"; 296 | VERSION_INFO_PREFIX = ""; 297 | }; 298 | name = Debug; 299 | }; 300 | 892106D71C3CF4140007CDEC /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ALWAYS_SEARCH_USER_PATHS = NO; 304 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 305 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 306 | CLANG_CXX_LIBRARY = "libc++"; 307 | CLANG_ENABLE_MODULES = YES; 308 | CLANG_ENABLE_OBJC_ARC = YES; 309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_COMMA = YES; 312 | CLANG_WARN_CONSTANT_CONVERSION = YES; 313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 329 | COPY_PHASE_STRIP = NO; 330 | CURRENT_PROJECT_VERSION = 1; 331 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 332 | ENABLE_NS_ASSERTIONS = NO; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | GCC_C_LANGUAGE_STANDARD = gnu99; 335 | GCC_NO_COMMON_BLOCKS = YES; 336 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 337 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 338 | GCC_WARN_UNDECLARED_SELECTOR = YES; 339 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 340 | GCC_WARN_UNUSED_FUNCTION = YES; 341 | GCC_WARN_UNUSED_VARIABLE = YES; 342 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 343 | MTL_ENABLE_DEBUG_INFO = NO; 344 | SDKROOT = iphoneos; 345 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 346 | TARGETED_DEVICE_FAMILY = "1,2"; 347 | VALIDATE_PRODUCT = YES; 348 | VERSIONING_SYSTEM = "apple-generic"; 349 | VERSION_INFO_PREFIX = ""; 350 | }; 351 | name = Release; 352 | }; 353 | 892106D91C3CF4140007CDEC /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | CLANG_ENABLE_MODULES = YES; 357 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 358 | DEFINES_MODULE = YES; 359 | DYLIB_COMPATIBILITY_VERSION = 1; 360 | DYLIB_CURRENT_VERSION = 1; 361 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 362 | FRAMEWORK_SEARCH_PATHS = ( 363 | "$(inherited)", 364 | "$(PROJECT_DIR)/Frameworks", 365 | "$(PROJECT_DIR)/Carthage/Build/iOS", 366 | ); 367 | INFOPLIST_FILE = CountdownLabel/Info.plist; 368 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 369 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 370 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 371 | PRODUCT_BUNDLE_IDENTIFIER = com.keishi.suzuki.CountdownLabel; 372 | PRODUCT_NAME = CountdownLabel; 373 | SKIP_INSTALL = YES; 374 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 375 | SWIFT_VERSION = 5.0; 376 | }; 377 | name = Debug; 378 | }; 379 | 892106DA1C3CF4140007CDEC /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | CLANG_ENABLE_MODULES = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 384 | DEFINES_MODULE = YES; 385 | DYLIB_COMPATIBILITY_VERSION = 1; 386 | DYLIB_CURRENT_VERSION = 1; 387 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 388 | FRAMEWORK_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "$(PROJECT_DIR)/Frameworks", 391 | "$(PROJECT_DIR)/Carthage/Build/iOS", 392 | ); 393 | INFOPLIST_FILE = CountdownLabel/Info.plist; 394 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 395 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 396 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 397 | PRODUCT_BUNDLE_IDENTIFIER = com.keishi.suzuki.CountdownLabel; 398 | PRODUCT_NAME = CountdownLabel; 399 | SKIP_INSTALL = YES; 400 | SWIFT_VERSION = 5.0; 401 | }; 402 | name = Release; 403 | }; 404 | /* End XCBuildConfiguration section */ 405 | 406 | /* Begin XCConfigurationList section */ 407 | 892106CA1C3CF4140007CDEC /* Build configuration list for PBXProject "CountdownLabel" */ = { 408 | isa = XCConfigurationList; 409 | buildConfigurations = ( 410 | 892106D61C3CF4140007CDEC /* Debug */, 411 | 892106D71C3CF4140007CDEC /* Release */, 412 | ); 413 | defaultConfigurationIsVisible = 0; 414 | defaultConfigurationName = Release; 415 | }; 416 | 892106D81C3CF4140007CDEC /* Build configuration list for PBXNativeTarget "CountdownLabel" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 892106D91C3CF4140007CDEC /* Debug */, 420 | 892106DA1C3CF4140007CDEC /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | /* End XCConfigurationList section */ 426 | }; 427 | rootObject = 892106C71C3CF4140007CDEC /* Project object */; 428 | } 429 | -------------------------------------------------------------------------------- /CountdownLabel.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CountdownLabel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CountdownLabel.xcodeproj/xcshareddata/xcschemes/CountdownLabel.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /CountdownLabel.xcodeproj/xcshareddata/xcschemes/CountdownLabelTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 | 42 | 43 | 49 | 50 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /CountdownLabel/CountdownLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // CountdownLabel.h 3 | // CountdownLabel 4 | // 5 | // Created by suzuki keishi on 2016/01/06. 6 | // Copyright © 2016 suzuki_keishi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CountdownLabel. 12 | FOUNDATION_EXPORT double CountdownLabelVersionNumber; 13 | 14 | //! Project version string for CountdownLabel. 15 | FOUNDATION_EXPORT const unsigned char CountdownLabelVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CountdownLabel/CountdownLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CountdownLabel.swift 3 | // CountdownLabel 4 | // 5 | // Created by suzuki keishi on 2016/01/06. 6 | // Copyright © 2016 suzuki_keishi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc public protocol CountdownLabelDelegate { 12 | @objc optional func countdownStarted() 13 | @objc optional func countdownPaused() 14 | @objc optional func countdownFinished() 15 | @objc optional func countdownCancelled() 16 | @objc optional func countingAt(timeCounted: TimeInterval, timeRemaining: TimeInterval) 17 | 18 | } 19 | extension TimeInterval { 20 | var int: Int { 21 | return Int(self) 22 | } 23 | } 24 | 25 | public class CountdownLabel: LTMorphingLabel { 26 | 27 | public typealias CountdownCompletion = () -> ()? 28 | public typealias CountdownExecution = () -> () 29 | internal let defaultFireInterval = 1.0 30 | internal let date1970 = NSDate(timeIntervalSince1970: 0) 31 | 32 | // conputed property 33 | public var dateFormatter: DateFormatter { 34 | let df = DateFormatter() 35 | df.locale = Locale(identifier: "en_US_POSIX") 36 | df.timeZone = TimeZone(identifier: "GMT") 37 | df.dateFormat = timeFormat 38 | return df 39 | } 40 | 41 | public var timeCounted: TimeInterval { 42 | let timeCounted = NSDate().timeIntervalSince(fromDate as Date) 43 | return round(timeCounted < 0 ? 0 : timeCounted) 44 | } 45 | 46 | public var timeRemaining: TimeInterval { 47 | return round(currentTime) - timeCounted 48 | } 49 | 50 | public var isPaused: Bool { 51 | return paused 52 | } 53 | 54 | public var isCounting: Bool { 55 | return counting 56 | } 57 | 58 | public var isFinished: Bool { 59 | return finished 60 | } 61 | 62 | public weak var countdownDelegate: CountdownLabelDelegate? 63 | 64 | // user settings 65 | public var animationType: CountdownEffect? { 66 | didSet { 67 | if let effect = animationType?.toLTMorphing() { 68 | morphingEffect = effect 69 | morphingEnabled = true 70 | } else { 71 | morphingEnabled = false 72 | } 73 | } 74 | } 75 | public var timeFormat = "dd:hh:mm:ss" 76 | public var thens = [TimeInterval: CountdownExecution]() 77 | public var countdownAttributedText: CountdownAttributedText! { 78 | didSet { 79 | range = (countdownAttributedText.text as NSString).range(of: countdownAttributedText.replacement) 80 | } 81 | } 82 | 83 | internal var completion: CountdownCompletion? 84 | internal var fromDate: NSDate = NSDate() 85 | internal var currentDate: NSDate = NSDate() 86 | internal var currentTime: TimeInterval = 0 87 | internal var diffDate: NSDate! 88 | internal var targetTime: TimeInterval = 0 89 | internal var pausedDate: NSDate! 90 | internal var range: NSRange! 91 | internal var timer: Timer! 92 | 93 | internal var counting: Bool = false 94 | internal var endOfTimer: Bool { 95 | return timeCounted >= currentTime 96 | } 97 | internal var finished: Bool = false { 98 | didSet { 99 | if finished { 100 | paused = false 101 | counting = false 102 | } 103 | } 104 | } 105 | internal var paused: Bool = false 106 | 107 | // MARK: - Initialize 108 | public required init?(coder aDecoder: NSCoder) { 109 | super.init(coder: aDecoder) 110 | setup() 111 | } 112 | 113 | public override required init(frame: CGRect) { 114 | super.init(frame: frame) 115 | setup() 116 | } 117 | 118 | public convenience init(frame: CGRect, minutes: TimeInterval) { 119 | self.init(frame: frame) 120 | setCountDownTime(minutes: minutes) 121 | } 122 | 123 | public convenience init(frame: CGRect, date: NSDate) { 124 | self.init(frame: frame) 125 | setCountDownDate(targetDate: date) 126 | } 127 | 128 | public convenience init(frame: CGRect, fromDate: NSDate, targetDate: NSDate) { 129 | self.init(frame: frame) 130 | setCountDownDate(fromDate: fromDate, targetDate: targetDate) 131 | } 132 | 133 | deinit { 134 | dispose() 135 | } 136 | 137 | // MARK: - Setter Methods 138 | public func setCountDownTime(minutes: TimeInterval) { 139 | setCountDownTime(fromDate: NSDate(), minutes: minutes) 140 | } 141 | 142 | public func setCountDownTime(fromDate: NSDate, minutes: TimeInterval) { 143 | self.fromDate = fromDate 144 | 145 | targetTime = minutes 146 | currentTime = minutes 147 | diffDate = date1970.addingTimeInterval(minutes) 148 | 149 | updateLabel() 150 | } 151 | 152 | public func setCountDownDate(targetDate: NSDate) { 153 | setCountDownDate(fromDate: NSDate(), targetDate: targetDate) 154 | } 155 | 156 | public func setCountDownDate(fromDate: NSDate, targetDate: NSDate) { 157 | self.fromDate = fromDate 158 | 159 | targetTime = targetDate.timeIntervalSince(fromDate as Date) 160 | currentTime = targetDate.timeIntervalSince(fromDate as Date) 161 | diffDate = date1970.addingTimeInterval(targetTime) 162 | 163 | updateLabel() 164 | } 165 | 166 | // MARK: - Update 167 | @objc func updateLabel() { 168 | // delegate 169 | countdownDelegate?.countingAt?(timeCounted: timeCounted, timeRemaining: timeRemaining) 170 | 171 | // then function execute if needed 172 | thens.forEach { k, v in 173 | if k.int == timeRemaining.int { 174 | v() 175 | thens[k] = nil 176 | } 177 | } 178 | 179 | // update text 180 | updateText() 181 | 182 | // if end of timer 183 | if endOfTimer { 184 | text = dateFormatter.string(from: date1970.addingTimeInterval(0) as Date) 185 | countdownDelegate?.countdownFinished?() 186 | dispose() 187 | completion?() 188 | } 189 | } 190 | } 191 | 192 | // MARK: - Public 193 | extension CountdownLabel { 194 | public func start(completion: ( () -> () )? = nil) { 195 | if !isPaused { 196 | // current date should be setted at the time of the counter's starting, or the time will be wrong (just a few seconds) after the first time of pausing. 197 | currentDate = NSDate() 198 | } 199 | 200 | // pause status check 201 | updatePauseStatusIfNeeded() 202 | 203 | // create timer 204 | updateTimer() 205 | 206 | // fire! 207 | timer.fire() 208 | 209 | // set completion if needed 210 | completion?() 211 | 212 | // set delegate 213 | countdownDelegate?.countdownStarted?() 214 | } 215 | 216 | public func pause(completion: (() -> ())? = nil) { 217 | if paused { 218 | return 219 | } 220 | 221 | // invalidate timer 222 | disposeTimer() 223 | 224 | // stop counting 225 | counting = false 226 | paused = true 227 | 228 | // reset 229 | pausedDate = NSDate() 230 | 231 | // set completion if needed 232 | completion?() 233 | 234 | // set delegate 235 | countdownDelegate?.countdownPaused?() 236 | } 237 | 238 | public func cancel(completion: (() -> ())? = nil) { 239 | text = dateFormatter.string(from: date1970.addingTimeInterval(0) as Date) 240 | dispose() 241 | 242 | // set completion if needed 243 | completion?() 244 | 245 | // set delegate 246 | countdownDelegate?.countdownCancelled?() 247 | } 248 | 249 | public func addTime(time: TimeInterval) { 250 | currentTime = time + currentTime 251 | diffDate = date1970.addingTimeInterval(currentTime) 252 | 253 | updateLabel() 254 | } 255 | 256 | @discardableResult 257 | public func then(targetTime: TimeInterval, completion: @escaping () -> ()) -> Self { 258 | let t = targetTime - (targetTime - targetTime) 259 | guard t > 0 else { 260 | return self 261 | } 262 | 263 | thens[t] = completion 264 | return self 265 | } 266 | } 267 | 268 | // MARK: - private 269 | extension CountdownLabel { 270 | func setup() { 271 | morphingEnabled = false 272 | } 273 | 274 | func updateText() { 275 | guard diffDate != nil else { return } 276 | 277 | let date = diffDate.addingTimeInterval(round(timeCounted * -1)) as Date 278 | // if time is before start 279 | let formattedText = timeCounted < 0 280 | ? dateFormatter.string(from: date1970.addingTimeInterval(0) as Date) 281 | : self.surplusTime(date) 282 | 283 | if let countdownAttributedText = countdownAttributedText { 284 | let attrTextInRange = NSAttributedString(string: formattedText, attributes: countdownAttributedText.attributes) 285 | let attributedString = NSMutableAttributedString(string: countdownAttributedText.text) 286 | attributedString.replaceCharacters(in: range, with: attrTextInRange) 287 | 288 | attributedText = attributedString 289 | text = attributedString.string 290 | } else { 291 | text = formattedText 292 | } 293 | setNeedsDisplay() 294 | } 295 | 296 | //fix one day bug 297 | func surplusTime(_ to1970Date: Date) -> String { 298 | let calendar = Calendar.init(identifier: .gregorian) 299 | var labelText = dateFormatter.string(from: to1970Date) 300 | let comp = calendar.dateComponents([.day, .hour, .minute, .second], from: date1970 as Date, to: to1970Date) 301 | 302 | // if day0 hour0 (24m10s) yes, day0 hour1 (12h10m) yes,d0 h1 m0 (10h0s) yes, day1 hour0 (2d10m)yes, day1 hour1 (1d1h) 303 | if let day = comp.day ,let _ = timeFormat.range(of: "dd"),let hour = comp.hour ,let _ = timeFormat.range(of: "hh"),let minute = comp.minute ,let _ = timeFormat.range(of: "mm"),let second = comp.second ,let _ = timeFormat.range(of: "ss") { 304 | if day == 0 { 305 | if hour == 0 { 306 | labelText = labelText.replacingOccurrences(of: "dd:", with: "") 307 | labelText = labelText.replacingOccurrences(of: "hh:", with: "") 308 | labelText = labelText.replacingOccurrences(of: "mm", with: String.init(format: "%02ldM", minute)) 309 | labelText = labelText.replacingOccurrences(of: "ss", with: String.init(format: "%02ldS", second)) 310 | } else { 311 | if minute == 0 { 312 | labelText = labelText.replacingOccurrences(of: "dd:", with: "") 313 | labelText = labelText.replacingOccurrences(of: "hh", with: String.init(format: "%02ldH", hour)) 314 | labelText = labelText.replacingOccurrences(of: "mm:", with: "") 315 | labelText = labelText.replacingOccurrences(of: "ss", with: String.init(format: "%02ldS", second)) 316 | } else { 317 | labelText = labelText.replacingOccurrences(of: "dd:", with: "") 318 | labelText = labelText.replacingOccurrences(of: "hh", with: String.init(format: "%02ldH", hour)) 319 | labelText = labelText.replacingOccurrences(of: "mm", with: String.init(format: "%02ldM", minute)) 320 | labelText = labelText.replacingOccurrences(of: ":ss", with: "") 321 | } 322 | } 323 | } else { 324 | if hour == 0 { 325 | labelText = labelText.replacingOccurrences(of: "dd", with: String.init(format: "%02ldD", day)) 326 | labelText = labelText.replacingOccurrences(of: "hh:", with: "") 327 | labelText = labelText.replacingOccurrences(of: "mm", with: String.init(format: "%02ldM", minute)) 328 | labelText = labelText.replacingOccurrences(of: ":ss", with: "") 329 | } else { 330 | labelText = labelText.replacingOccurrences(of: "dd", with: String.init(format: "%02ldD", day)) 331 | labelText = labelText.replacingOccurrences(of: "hh", with: String.init(format: "%02ldH", hour)) 332 | labelText = labelText.replacingOccurrences(of: ":mm", with: "") 333 | labelText = labelText.replacingOccurrences(of: ":ss", with: "") 334 | } 335 | } 336 | } 337 | 338 | return labelText 339 | } 340 | 341 | func updatePauseStatusIfNeeded() { 342 | guard paused else { 343 | return 344 | } 345 | // change date 346 | let pastedTime = pausedDate.timeIntervalSince(currentDate as Date) 347 | currentDate = NSDate().addingTimeInterval(-pastedTime) 348 | fromDate = currentDate 349 | 350 | // reset pause 351 | pausedDate = nil 352 | paused = false 353 | } 354 | 355 | func updateTimer() { 356 | disposeTimer() 357 | 358 | // create 359 | timer = Timer.scheduledTimer(timeInterval: defaultFireInterval, 360 | target: self, 361 | selector: #selector(updateLabel), 362 | userInfo: nil, 363 | repeats: true) 364 | 365 | // register to NSrunloop 366 | RunLoop.current.add(timer, forMode: RunLoop.Mode.common) 367 | counting = true 368 | } 369 | 370 | func disposeTimer() { 371 | if timer != nil { 372 | timer.invalidate() 373 | timer = nil 374 | } 375 | } 376 | 377 | func dispose() { 378 | // reset 379 | pausedDate = nil 380 | 381 | // invalidate timer 382 | disposeTimer() 383 | 384 | // stop counting 385 | finished = true 386 | } 387 | } 388 | 389 | public enum CountdownEffect { 390 | case Anvil 391 | case Burn 392 | case Evaporate 393 | case Fall 394 | case None 395 | case Pixelate 396 | case Scale 397 | case Sparkle 398 | 399 | func toLTMorphing() -> LTMorphingEffect? { 400 | switch self { 401 | case .Anvil : return .anvil 402 | case .Burn : return .burn 403 | case .Evaporate : return .evaporate 404 | case .Fall : return .fall 405 | case .None : return nil 406 | case .Pixelate : return .pixelate 407 | case .Scale : return .scale 408 | case .Sparkle : return .sparkle 409 | } 410 | } 411 | } 412 | 413 | public class CountdownAttributedText: NSObject { 414 | internal let text: String 415 | internal let replacement: String 416 | internal let attributes: [NSAttributedString.Key: Any]? 417 | 418 | public init(text: String, replacement: String, attributes: [NSAttributedString.Key: Any]? = nil) { 419 | self.text = text 420 | self.replacement = replacement 421 | self.attributes = attributes 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /CountdownLabel/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTCharacterDiffResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTCharacterDiffResult.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distrLTMorphingLabelibute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | public enum LTCharacterDiffResult: CustomDebugStringConvertible, Equatable { 31 | 32 | case same 33 | case add 34 | case delete 35 | case move(offset: Int) 36 | case moveAndAdd(offset: Int) 37 | case replace 38 | 39 | public var debugDescription: String { 40 | switch self { 41 | case .same: 42 | return "The character is unchanged." 43 | case .add: 44 | return "A new character is ADDED." 45 | case .delete: 46 | return "The character is DELETED." 47 | case .move(let offset): 48 | return "The character is MOVED to \(offset)." 49 | case .moveAndAdd(let offset): 50 | return "The character is MOVED to \(offset) and a new character is ADDED." 51 | case .replace: 52 | return "The character is REPLACED with a new character." 53 | } 54 | } 55 | 56 | } 57 | 58 | public func == (lhs: LTCharacterDiffResult, rhs: LTCharacterDiffResult) -> Bool { 59 | switch (lhs, rhs) { 60 | case (.move(let offset0), .move(let offset1)): 61 | return offset0 == offset1 62 | 63 | case (.moveAndAdd(let offset0), .moveAndAdd(let offset1)): 64 | return offset0 == offset1 65 | 66 | case (.add, .add): 67 | return true 68 | 69 | case (.delete, .delete): 70 | return true 71 | 72 | case (.replace, .replace): 73 | return true 74 | 75 | case (.same, .same): 76 | return true 77 | 78 | default: return false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTCharacterLimbo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTCharacterLimbo.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | public struct LTCharacterLimbo: CustomDebugStringConvertible { 31 | 32 | public let char: Character 33 | public var rect: CGRect 34 | public var alpha: CGFloat 35 | public var size: CGFloat 36 | public var drawingProgress: CGFloat = 0.0 37 | 38 | public var debugDescription: String { 39 | return "Character: '\(char)'" 40 | + "drawIn (\(rect.origin.x), \(rect.origin.y), " 41 | + "\(rect.size.width)x\(rect.size.height) " 42 | + "with alpha \(alpha) " 43 | + "and \(size)pt font." 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTEasing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTEasing.swift 3 | // LTMorphingLabelDemo 4 | // 5 | // Created by Lex on 7/1/14. 6 | // Copyright (c) 2015 lexrus.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js 12 | // t = currentTime 13 | // b = beginning 14 | // c = change 15 | // d = duration 16 | 17 | public struct LTEasing { 18 | 19 | public static func easeOutQuint(_ t: Float, _ b: Float, _ c: Float, _ d: Float = 1.0) -> Float { 20 | return { 21 | return c * ($0 * $0 * $0 * $0 * $0 + 1.0) + b 22 | }(t / d - 1.0) 23 | } 24 | 25 | public static func easeInQuint(_ t: Float, _ b: Float, _ c: Float, _ d: Float = 1.0) -> Float { 26 | return { 27 | let x = c * $0 28 | return x * $0 * $0 * $0 * $0 + b 29 | }(t / d) 30 | } 31 | 32 | public static func easeOutBack(_ t: Float, _ b: Float, _ c: Float, _ d: Float = 1.0) -> Float { 33 | let s: Float = 2.70158 34 | let t2: Float = t / d - 1.0 35 | return Float(c * (t2 * t2 * ((s + 1.0) * t2 + s) + 1.0)) + b 36 | } 37 | 38 | public static func easeOutBounce(_ t: Float, _ b: Float, _ c: Float, _ d: Float = 1.0) -> Float { 39 | return { 40 | if $0 < 1 / 2.75 { 41 | return c * 7.5625 * $0 * $0 + b 42 | } else if $0 < 2 / 2.75 { 43 | let t = $0 - 1.5 / 2.75 44 | return c * (7.5625 * t * t + 0.75) + b 45 | } else if $0 < 2.5 / 2.75 { 46 | let t = $0 - 2.25 / 2.75 47 | return c * (7.5625 * t * t + 0.9375) + b 48 | } else { 49 | let t = $0 - 2.625 / 2.75 50 | return c * (7.5625 * t * t + 0.984375) + b 51 | } 52 | }(t / d) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTEmitterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTEmitterView.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | private func < (lhs: T?, rhs: T?) -> Bool { 31 | switch (lhs, rhs) { 32 | case let (l?, r?): 33 | return l < r 34 | case (nil, _?): 35 | return true 36 | default: 37 | return false 38 | } 39 | } 40 | 41 | private func > (lhs: T?, rhs: T?) -> Bool { 42 | switch (lhs, rhs) { 43 | case let (l?, r?): 44 | return l > r 45 | default: 46 | return rhs < lhs 47 | } 48 | } 49 | 50 | public struct LTEmitter { 51 | 52 | let layer: CAEmitterLayer = { 53 | let layer = CAEmitterLayer() 54 | layer.emitterPosition = CGPoint(x: 10, y: 10) 55 | layer.emitterSize = CGSize(width: 10, height: 1) 56 | layer.renderMode = CAEmitterLayerRenderMode.additive 57 | layer.emitterShape = CAEmitterLayerEmitterShape.line 58 | return layer 59 | }() 60 | 61 | let cell: CAEmitterCell = { 62 | let cell = CAEmitterCell() 63 | cell.name = "sparkle" 64 | cell.birthRate = 150.0 65 | cell.velocity = 50.0 66 | cell.velocityRange = -80.0 67 | cell.lifetime = 0.16 68 | cell.lifetimeRange = 0.1 69 | cell.emissionLongitude = CGFloat(Double.pi / 2 * 2.0) 70 | cell.emissionRange = CGFloat(Double.pi / 2 * 2.0) 71 | cell.scale = 0.1 72 | cell.yAcceleration = 100 73 | cell.scaleSpeed = -0.06 74 | cell.scaleRange = 0.1 75 | return cell 76 | }() 77 | 78 | public var duration: Float = 0.6 79 | 80 | init(name: String, particleName: String, duration: Float) { 81 | cell.name = name 82 | self.duration = duration 83 | var image: UIImage? 84 | defer { 85 | cell.contents = image?.cgImage 86 | } 87 | 88 | image = UIImage(named: particleName) 89 | 90 | if image != nil { 91 | return 92 | } 93 | // Load from Framework 94 | image = UIImage( 95 | named: particleName, 96 | in: Bundle(for: LTMorphingLabel.self), 97 | compatibleWith: nil) 98 | } 99 | 100 | public func play() { 101 | if layer.emitterCells?.count > 0 { 102 | return 103 | } 104 | 105 | layer.emitterCells = [cell] 106 | let d = DispatchTime.now() + Double(Int64(duration * Float(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 107 | DispatchQueue.main.asyncAfter(deadline: d) { 108 | self.layer.birthRate = 0.0 109 | } 110 | } 111 | 112 | public func stop() { 113 | if nil != layer.superlayer { 114 | layer.removeFromSuperlayer() 115 | } 116 | } 117 | 118 | func update(_ configureClosure: LTEmitterConfigureClosure? = .none) -> LTEmitter { 119 | configureClosure?(layer, cell) 120 | return self 121 | } 122 | 123 | } 124 | 125 | public typealias LTEmitterConfigureClosure = (CAEmitterLayer, CAEmitterCell) -> Void 126 | 127 | open class LTEmitterView: UIView { 128 | 129 | open lazy var emitters: [String: LTEmitter] = { 130 | var _emitters = [String: LTEmitter]() 131 | return _emitters 132 | }() 133 | 134 | open func createEmitter( 135 | _ name: String, 136 | particleName: String, 137 | duration: Float, 138 | configureClosure: LTEmitterConfigureClosure? 139 | ) -> LTEmitter { 140 | 141 | var emitter: LTEmitter 142 | if let e = emitterByName(name) { 143 | emitter = e 144 | } else { 145 | emitter = LTEmitter( 146 | name: name, 147 | particleName: particleName, 148 | duration: duration 149 | ) 150 | 151 | configureClosure?(emitter.layer, emitter.cell) 152 | 153 | layer.addSublayer(emitter.layer) 154 | emitters.updateValue(emitter, forKey: name) 155 | } 156 | return emitter 157 | } 158 | 159 | open func emitterByName(_ name: String) -> LTEmitter? { 160 | if let e = emitters[name] { 161 | return e 162 | } 163 | return Optional.none 164 | } 165 | 166 | open func removeAllEmitters() { 167 | for (_, emitter) in emitters { 168 | emitter.layer.removeFromSuperlayer() 169 | } 170 | emitters.removeAll(keepingCapacity: false) 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingEffect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingEffect.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | @objc public enum LTMorphingEffect: Int, CustomStringConvertible { 31 | 32 | case scale = 0 33 | case evaporate 34 | case fall 35 | case pixelate 36 | case sparkle 37 | case burn 38 | case anvil 39 | 40 | public static let allValues = [ 41 | "Scale", "Evaporate", "Fall", "Pixelate", "Sparkle", "Burn", "Anvil" 42 | ] 43 | 44 | public var description: String { 45 | switch self { 46 | case .evaporate: 47 | return "Evaporate" 48 | case .fall: 49 | return "Fall" 50 | case .pixelate: 51 | return "Pixelate" 52 | case .sparkle: 53 | return "Sparkle" 54 | case .burn: 55 | return "Burn" 56 | case .anvil: 57 | return "Anvil" 58 | default: 59 | return "Scale" 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel+Anvil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel+Anvil.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension LTMorphingLabel { 31 | 32 | @objc 33 | func AnvilLoad() { 34 | 35 | startClosures["Anvil\(LTMorphingPhases.start)"] = { 36 | self.emitterView.removeAllEmitters() 37 | 38 | guard self.newRects.count > 0 else { return } 39 | 40 | let centerRect = self.newRects[Int(self.newRects.count / 2)] 41 | 42 | _ = self.emitterView.createEmitter( 43 | "leftSmoke", 44 | particleName: "Smoke", 45 | duration: 0.6 46 | ) { (layer, cell) in 47 | layer.emitterSize = CGSize(width: 1, height: 1) 48 | layer.emitterPosition = CGPoint( 49 | x: centerRect.origin.x, 50 | y: centerRect.origin.y + centerRect.size.height / 1.3) 51 | layer.renderMode = CAEmitterLayerRenderMode.additive 52 | cell.emissionLongitude = CGFloat(Double.pi / 2) 53 | cell.scale = self.font.pointSize / 90.0 54 | cell.scaleSpeed = self.font.pointSize / 130 55 | cell.birthRate = 60 56 | cell.velocity = CGFloat(80 + Int(arc4random_uniform(60))) 57 | cell.velocityRange = 100 58 | cell.yAcceleration = -40 59 | cell.xAcceleration = 70 60 | cell.emissionLongitude = CGFloat(-Double.pi / 2) 61 | cell.emissionRange = CGFloat(Double.pi / 4) / 5.0 62 | cell.lifetime = self.morphingDuration * 2.0 63 | cell.spin = 10 64 | cell.alphaSpeed = -0.5 / self.morphingDuration 65 | } 66 | 67 | _ = self.emitterView.createEmitter( 68 | "rightSmoke", 69 | particleName: "Smoke", 70 | duration: 0.6 71 | ) { (layer, cell) in 72 | layer.emitterSize = CGSize(width: 1, height: 1) 73 | layer.emitterPosition = CGPoint( 74 | x: centerRect.origin.x, 75 | y: centerRect.origin.y + centerRect.size.height / 1.3) 76 | layer.renderMode = CAEmitterLayerRenderMode.additive 77 | cell.emissionLongitude = CGFloat(Double.pi / 2) 78 | cell.scale = self.font.pointSize / 90.0 79 | cell.scaleSpeed = self.font.pointSize / 130 80 | cell.birthRate = 60 81 | cell.velocity = CGFloat(80 + Int(arc4random_uniform(60))) 82 | cell.velocityRange = 100 83 | cell.yAcceleration = -40 84 | cell.xAcceleration = -70 85 | cell.emissionLongitude = CGFloat(Double.pi / 2) 86 | cell.emissionRange = CGFloat(-Double.pi / 4) / 5.0 87 | cell.lifetime = self.morphingDuration * 2.0 88 | cell.spin = -10 89 | cell.alphaSpeed = -0.5 / self.morphingDuration 90 | } 91 | 92 | _ = self.emitterView.createEmitter( 93 | "leftFragments", 94 | particleName: "Fragment", 95 | duration: 0.6 96 | ) { (layer, cell) in 97 | layer.emitterSize = CGSize( 98 | width: self.font.pointSize, 99 | height: 1 100 | ) 101 | layer.emitterPosition = CGPoint( 102 | x: centerRect.origin.x, 103 | y: centerRect.origin.y + centerRect.size.height / 1.3 104 | ) 105 | cell.scale = self.font.pointSize / 90.0 106 | cell.scaleSpeed = self.font.pointSize / 40.0 107 | cell.color = self.textColor.cgColor 108 | cell.birthRate = 60 109 | cell.velocity = 350 110 | cell.yAcceleration = 0 111 | cell.xAcceleration = CGFloat(10 * Int(arc4random_uniform(10))) 112 | cell.emissionLongitude = CGFloat(-Double.pi / 2) 113 | cell.emissionRange = CGFloat(Double.pi / 4) / 5.0 114 | cell.alphaSpeed = -2 115 | cell.lifetime = self.morphingDuration 116 | } 117 | 118 | _ = self.emitterView.createEmitter( 119 | "rightFragments", 120 | particleName: "Fragment", 121 | duration: 0.6 122 | ) { (layer, cell) in 123 | layer.emitterSize = CGSize( 124 | width: self.font.pointSize, 125 | height: 1 126 | ) 127 | layer.emitterPosition = CGPoint( 128 | x: centerRect.origin.x, 129 | y: centerRect.origin.y + centerRect.size.height / 1.3) 130 | cell.scale = self.font.pointSize / 90.0 131 | cell.scaleSpeed = self.font.pointSize / 40.0 132 | cell.color = self.textColor.cgColor 133 | cell.birthRate = 60 134 | cell.velocity = 350 135 | cell.yAcceleration = 0 136 | cell.xAcceleration = CGFloat(-10 * Int(arc4random_uniform(10))) 137 | cell.emissionLongitude = CGFloat(Double.pi / 2) 138 | cell.emissionRange = CGFloat(-Double.pi / 4) / 5.0 139 | cell.alphaSpeed = -2 140 | cell.lifetime = self.morphingDuration 141 | } 142 | 143 | _ = self.emitterView.createEmitter( 144 | "fragments", 145 | particleName: "Fragment", 146 | duration: 0.6 147 | ) { (layer, cell) in 148 | layer.emitterSize = CGSize( 149 | width: self.font.pointSize, 150 | height: 1 151 | ) 152 | layer.emitterPosition = CGPoint( 153 | x: centerRect.origin.x, 154 | y: centerRect.origin.y + centerRect.size.height / 1.3) 155 | cell.scale = self.font.pointSize / 90.0 156 | cell.scaleSpeed = self.font.pointSize / 40.0 157 | cell.color = self.textColor.cgColor 158 | cell.birthRate = 60 159 | cell.velocity = 250 160 | cell.velocityRange = CGFloat(Int(arc4random_uniform(20)) + 30) 161 | cell.yAcceleration = 500 162 | cell.emissionLongitude = 0 163 | cell.emissionRange = CGFloat(Double.pi / 2) 164 | cell.alphaSpeed = -1 165 | cell.lifetime = self.morphingDuration 166 | } 167 | } 168 | 169 | progressClosures["Anvil\(LTMorphingPhases.progress)"] = { 170 | (index: Int, progress: Float, isNewChar: Bool) in 171 | 172 | if !isNewChar { 173 | return min(1.0, max(0.0, progress)) 174 | } 175 | 176 | let j = Float(sin(Float(index))) * 1.7 177 | return min(1.0, max(0.0001, progress + self.morphingCharacterDelay * j)) 178 | 179 | } 180 | 181 | effectClosures["Anvil\(LTMorphingPhases.disappear)"] = { 182 | char, index, progress in 183 | 184 | return LTCharacterLimbo( 185 | char: char, 186 | rect: self.previousRects[index], 187 | alpha: CGFloat(1.0 - progress), 188 | size: self.font.pointSize, 189 | drawingProgress: 0.0) 190 | } 191 | 192 | effectClosures["Anvil\(LTMorphingPhases.appear)"] = { 193 | char, index, progress in 194 | 195 | var rect = self.newRects[index] 196 | 197 | if progress < 1.0 { 198 | let easingValue = LTEasing.easeOutBounce(progress, 0.0, 1.0) 199 | rect.origin.y = CGFloat(Float(rect.origin.y) * easingValue) 200 | } 201 | 202 | if progress > self.morphingDuration * 0.5 { 203 | let end = self.morphingDuration * 0.55 204 | self.emitterView.createEmitter( 205 | "fragments", 206 | particleName: "Fragment", 207 | duration: 0.6 208 | ) { (_, _) in }.update { (layer, _) in 209 | if progress > end { 210 | layer.birthRate = 0 211 | } 212 | }.play() 213 | self.emitterView.createEmitter( 214 | "leftFragments", 215 | particleName: "Fragment", 216 | duration: 0.6 217 | ) { (_, _) in }.update { (layer, _) in 218 | if progress > end { 219 | layer.birthRate = 0 220 | } 221 | }.play() 222 | self.emitterView.createEmitter( 223 | "rightFragments", 224 | particleName: "Fragment", 225 | duration: 0.6 226 | ) { (_, _) in }.update { (layer, _) in 227 | if progress > end { 228 | layer.birthRate = 0 229 | } 230 | }.play() 231 | } 232 | 233 | if progress > self.morphingDuration * 0.63 { 234 | let end = self.morphingDuration * 0.7 235 | self.emitterView.createEmitter( 236 | "leftSmoke", 237 | particleName: "Smoke", 238 | duration: 0.6 239 | ) { (_, _) in }.update { (layer, _) in 240 | if progress > end { 241 | layer.birthRate = 0 242 | } 243 | }.play() 244 | self.emitterView.createEmitter( 245 | "rightSmoke", 246 | particleName: "Smoke", 247 | duration: 0.6 248 | ) { (_, _) in }.update { (layer, _) in 249 | if progress > end { 250 | layer.birthRate = 0 251 | } 252 | }.play() 253 | } 254 | 255 | return LTCharacterLimbo( 256 | char: char, 257 | rect: rect, 258 | alpha: CGFloat(self.morphingProgress), 259 | size: self.font.pointSize, 260 | drawingProgress: CGFloat(progress) 261 | ) 262 | } 263 | } 264 | 265 | } 266 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel+Burn.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel+Burn.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension LTMorphingLabel { 31 | 32 | fileprivate func burningImageForCharLimbo( 33 | _ charLimbo: LTCharacterLimbo, 34 | withProgress progress: CGFloat 35 | ) -> (UIImage, CGRect) { 36 | let maskedHeight = charLimbo.rect.size.height * max(0.01, progress) 37 | let maskedSize = CGSize( 38 | width: charLimbo.rect.size.width, 39 | height: maskedHeight 40 | ) 41 | UIGraphicsBeginImageContextWithOptions( 42 | maskedSize, 43 | false, 44 | UIScreen.main.scale 45 | ) 46 | let rect = CGRect( 47 | x: 0, 48 | y: 0, 49 | width: charLimbo.rect.size.width, 50 | height: maskedHeight 51 | ) 52 | String(charLimbo.char).draw(in: rect, withAttributes: [ 53 | .font: self.font ?? UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body), 54 | .foregroundColor: self.textColor ?? .black 55 | ]) 56 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 57 | UIGraphicsEndImageContext() 58 | let newRect = CGRect( 59 | x: charLimbo.rect.origin.x, 60 | y: charLimbo.rect.origin.y, 61 | width: charLimbo.rect.size.width, 62 | height: maskedHeight 63 | ) 64 | return (newImage!, newRect) 65 | } 66 | 67 | @objc 68 | func BurnLoad() { 69 | 70 | startClosures["Burn\(LTMorphingPhases.start)"] = { 71 | self.emitterView.removeAllEmitters() 72 | } 73 | 74 | progressClosures["Burn\(LTMorphingPhases.progress)"] = { 75 | index, progress, isNewChar in 76 | 77 | if !isNewChar { 78 | return min(1.0, max(0.0, progress)) 79 | } 80 | 81 | let j = Float(sin(Float(index))) * 1.5 82 | return min(1.0, max(0.0001, progress + self.morphingCharacterDelay * j)) 83 | 84 | } 85 | 86 | effectClosures["Burn\(LTMorphingPhases.disappear)"] = { 87 | char, index, progress in 88 | 89 | return LTCharacterLimbo( 90 | char: char, 91 | rect: self.previousRects[index], 92 | alpha: CGFloat(1.0 - progress), 93 | size: self.font.pointSize, 94 | drawingProgress: 0.0 95 | ) 96 | } 97 | 98 | effectClosures["Burn\(LTMorphingPhases.appear)"] = { 99 | char, index, progress in 100 | 101 | if char != " " { 102 | let rect = self.newRects[index] 103 | let emitterPosition = CGPoint( 104 | x: rect.origin.x + rect.size.width / 2.0, 105 | y: CGFloat(progress) * rect.size.height / 1.2 + rect.origin.y 106 | ) 107 | 108 | self.emitterView.createEmitter( 109 | "c\(index)", 110 | particleName: "Fire", 111 | duration: self.morphingDuration 112 | ) { (layer, cell) in 113 | layer.emitterSize = CGSize( 114 | width: rect.size.width, 115 | height: 1 116 | ) 117 | layer.renderMode = CAEmitterLayerRenderMode.oldestFirst 118 | layer.emitterMode = CAEmitterLayerEmitterMode.outline 119 | cell.emissionLongitude = CGFloat(Double.pi / 2) 120 | cell.scale = self.font.pointSize / 160.0 121 | cell.scaleSpeed = self.font.pointSize / 100.0 122 | cell.birthRate = Float(self.font.pointSize) 123 | cell.emissionLongitude = CGFloat(arc4random_uniform(30)) 124 | cell.emissionRange = CGFloat(Double.pi / 4) 125 | cell.alphaSpeed = self.morphingDuration * -3.0 126 | cell.yAcceleration = 10 127 | cell.velocity = CGFloat(10 + Int(arc4random_uniform(3))) 128 | cell.velocityRange = 10 129 | cell.spin = 0 130 | cell.spinRange = 0 131 | cell.lifetime = self.morphingDuration / 3.0 132 | }.update { (layer, _) in 133 | layer.emitterPosition = emitterPosition 134 | }.play() 135 | 136 | self.emitterView.createEmitter( 137 | "s\(index)", 138 | particleName: "Smoke", 139 | duration: self.morphingDuration 140 | ) { (layer, cell) in 141 | layer.emitterSize = CGSize( 142 | width: rect.size.width, 143 | height: 10 144 | ) 145 | layer.renderMode = CAEmitterLayerRenderMode.additive 146 | layer.emitterMode = CAEmitterLayerEmitterMode.volume 147 | cell.emissionLongitude = CGFloat(Double.pi / 2) 148 | cell.scale = self.font.pointSize / 40.0 149 | cell.scaleSpeed = self.font.pointSize / 100.0 150 | cell.birthRate = 151 | Float(self.font.pointSize) 152 | / Float(arc4random_uniform(10) + 10) 153 | cell.emissionLongitude = 0 154 | cell.emissionRange = CGFloat(Double.pi / 4) 155 | cell.alphaSpeed = self.morphingDuration * -3 156 | cell.yAcceleration = -5 157 | cell.velocity = CGFloat(20 + Int(arc4random_uniform(15))) 158 | cell.velocityRange = 20 159 | cell.spin = CGFloat(Float(arc4random_uniform(30)) / 10.0) 160 | cell.spinRange = 3 161 | cell.lifetime = self.morphingDuration 162 | }.update { (layer, _) in 163 | layer.emitterPosition = emitterPosition 164 | }.play() 165 | } 166 | 167 | return LTCharacterLimbo( 168 | char: char, 169 | rect: self.newRects[index], 170 | alpha: 1.0, 171 | size: self.font.pointSize, 172 | drawingProgress: CGFloat(progress) 173 | ) 174 | } 175 | 176 | drawingClosures["Burn\(LTMorphingPhases.draw)"] = { 177 | (charLimbo: LTCharacterLimbo) in 178 | 179 | if charLimbo.drawingProgress > 0.0 { 180 | 181 | let (charImage, rect) = self.burningImageForCharLimbo( 182 | charLimbo, 183 | withProgress: charLimbo.drawingProgress 184 | ) 185 | charImage.draw(in: rect) 186 | 187 | return true 188 | } 189 | 190 | return false 191 | } 192 | 193 | skipFramesClosures["Burn\(LTMorphingPhases.skipFrames)"] = { 194 | return 1 195 | } 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel+Evaporate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel+Evaporate.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension LTMorphingLabel { 31 | 32 | @objc 33 | func EvaporateLoad() { 34 | 35 | progressClosures["Evaporate\(LTMorphingPhases.progress)"] = { 36 | (index: Int, progress: Float, isNewChar: Bool) in 37 | let j: Int = Int(round(cos(Double(index)) * 1.2)) 38 | let delay = isNewChar ? self.morphingCharacterDelay * -1.0 : self.morphingCharacterDelay 39 | return min(1.0, max(0.0, self.morphingProgress + delay * Float(j))) 40 | } 41 | 42 | effectClosures["Evaporate\(LTMorphingPhases.disappear)"] = { 43 | char, index, progress in 44 | 45 | let newProgress = LTEasing.easeOutQuint(progress, 0.0, 1.0, 1.0) 46 | let yOffset: CGFloat = -0.8 * CGFloat(self.font.pointSize) * CGFloat(newProgress) 47 | let currentRect = self.previousRects[index].offsetBy(dx: 0, dy: yOffset) 48 | let currentAlpha = CGFloat(1.0 - newProgress) 49 | 50 | return LTCharacterLimbo( 51 | char: char, 52 | rect: currentRect, 53 | alpha: currentAlpha, 54 | size: self.font.pointSize, 55 | drawingProgress: 0.0) 56 | } 57 | 58 | effectClosures["Evaporate\(LTMorphingPhases.appear)"] = { 59 | char, index, progress in 60 | 61 | let newProgress = 1.0 - LTEasing.easeOutQuint(progress, 0.0, 1.0) 62 | let yOffset = CGFloat(self.font.pointSize) * CGFloat(newProgress) * 1.2 63 | 64 | return LTCharacterLimbo( 65 | char: char, 66 | rect: self.newRects[index].offsetBy(dx: 0, dy: yOffset), 67 | alpha: CGFloat(self.morphingProgress), 68 | size: self.font.pointSize, 69 | drawingProgress: 0.0 70 | ) 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel+Fall.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel+Fall.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension LTMorphingLabel { 31 | 32 | @objc 33 | func FallLoad() { 34 | 35 | progressClosures["Fall\(LTMorphingPhases.progress)"] = { 36 | (index: Int, progress: Float, isNewChar: Bool) in 37 | 38 | if isNewChar { 39 | return min( 40 | 1.0, 41 | max( 42 | 0.0, 43 | progress 44 | - self.morphingCharacterDelay 45 | * Float(index) 46 | / 1.7 47 | ) 48 | ) 49 | } 50 | 51 | let j: Float = Float(sin(Double(index))) * 1.7 52 | return min(1.0, max(0.0001, progress + self.morphingCharacterDelay * Float(j))) 53 | 54 | } 55 | 56 | effectClosures["Fall\(LTMorphingPhases.disappear)"] = { 57 | char, index, progress in 58 | 59 | return LTCharacterLimbo( 60 | char: char, 61 | rect: self.previousRects[index], 62 | alpha: CGFloat(1.0 - progress), 63 | size: self.font.pointSize, 64 | drawingProgress: CGFloat(progress)) 65 | } 66 | 67 | effectClosures["Fall\(LTMorphingPhases.appear)"] = { 68 | char, index, progress in 69 | 70 | let currentFontSize = CGFloat( 71 | LTEasing.easeOutQuint(progress, 0.0, Float(self.font.pointSize)) 72 | ) 73 | let yOffset = CGFloat(self.font.pointSize - currentFontSize) 74 | 75 | return LTCharacterLimbo( 76 | char: char, 77 | rect: self.newRects[index].offsetBy(dx: 0, dy: yOffset), 78 | alpha: CGFloat(self.morphingProgress), 79 | size: currentFontSize, 80 | drawingProgress: 0.0 81 | ) 82 | } 83 | 84 | drawingClosures["Fall\(LTMorphingPhases.draw)"] = { 85 | limbo in 86 | 87 | if limbo.drawingProgress > 0.0 { 88 | let context = UIGraphicsGetCurrentContext() 89 | var charRect = limbo.rect 90 | context!.saveGState() 91 | let charCenterX = charRect.origin.x + (charRect.size.width / 2.0) 92 | var charBottomY = charRect.origin.y + charRect.size.height - self.font.pointSize / 6 93 | var charColor: UIColor = self.textColor 94 | 95 | // Fall down if drawingProgress is more than 50% 96 | if limbo.drawingProgress > 0.5 { 97 | let ease = CGFloat( 98 | LTEasing.easeInQuint( 99 | Float(limbo.drawingProgress - 0.4), 100 | 0.0, 101 | 1.0, 102 | 0.5 103 | ) 104 | ) 105 | charBottomY += ease * 10.0 106 | let fadeOutAlpha = min( 107 | 1.0, 108 | max( 109 | 0.0, 110 | limbo.drawingProgress * -2.0 + 2.0 + 0.01 111 | ) 112 | ) 113 | charColor = self.textColor.withAlphaComponent(fadeOutAlpha) 114 | } 115 | 116 | charRect = CGRect( 117 | x: charRect.size.width / -2.0, 118 | y: charRect.size.height * -1.0 + self.font.pointSize / 6, 119 | width: charRect.size.width, 120 | height: charRect.size.height) 121 | context!.translateBy(x: charCenterX, y: charBottomY) 122 | 123 | let angle = Float(sin(Double(limbo.rect.origin.x)) > 0.5 ? 168 : -168) 124 | let rotation = CGFloat( 125 | LTEasing.easeOutBack( 126 | min( 127 | 1.0, 128 | Float(limbo.drawingProgress) 129 | ), 130 | 0.0, 131 | 1.0 132 | ) * angle 133 | ) 134 | context!.rotate(by: rotation * CGFloat(Double.pi) / 180.0) 135 | let s = String(limbo.char) 136 | let attributes: [NSAttributedString.Key: Any] = [ 137 | .font: self.font.withSize(limbo.size), 138 | .foregroundColor: charColor 139 | ] 140 | s.draw(in: charRect, withAttributes: attributes) 141 | context!.restoreGState() 142 | 143 | return true 144 | } 145 | 146 | return false 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel+Pixelate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel+Pixelate.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension LTMorphingLabel { 31 | 32 | @objc 33 | func PixelateLoad() { 34 | 35 | effectClosures["Pixelate\(LTMorphingPhases.disappear)"] = { 36 | char, index, progress in 37 | 38 | return LTCharacterLimbo( 39 | char: char, 40 | rect: self.previousRects[index], 41 | alpha: CGFloat(1.0 - progress), 42 | size: self.font.pointSize, 43 | drawingProgress: CGFloat(progress)) 44 | } 45 | 46 | effectClosures["Pixelate\(LTMorphingPhases.appear)"] = { 47 | char, index, progress in 48 | 49 | return LTCharacterLimbo( 50 | char: char, 51 | rect: self.newRects[index], 52 | alpha: CGFloat(progress), 53 | size: self.font.pointSize, 54 | drawingProgress: CGFloat(1.0 - progress) 55 | ) 56 | } 57 | 58 | drawingClosures["Pixelate\(LTMorphingPhases.draw)"] = { 59 | limbo in 60 | 61 | if limbo.drawingProgress > 0.0 { 62 | 63 | let charImage = self.pixelateImageForCharLimbo( 64 | limbo, 65 | withBlurRadius: limbo.drawingProgress * 6.0 66 | ) 67 | 68 | charImage.draw(in: limbo.rect) 69 | 70 | return true 71 | } 72 | 73 | return false 74 | } 75 | } 76 | 77 | fileprivate func pixelateImageForCharLimbo( 78 | _ charLimbo: LTCharacterLimbo, 79 | withBlurRadius blurRadius: CGFloat 80 | ) -> UIImage { 81 | let scale = min(UIScreen.main.scale, 1.0 / blurRadius) 82 | UIGraphicsBeginImageContextWithOptions(charLimbo.rect.size, false, scale) 83 | let fadeOutAlpha = min(1.0, max(0.0, charLimbo.drawingProgress * -2.0 + 2.0 + 0.01)) 84 | let rect = CGRect( 85 | x: 0, 86 | y: 0, 87 | width: charLimbo.rect.size.width, 88 | height: charLimbo.rect.size.height 89 | ) 90 | String(charLimbo.char).draw(in: rect, withAttributes: [ 91 | .font: self.font ?? UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body), 92 | .foregroundColor: self.textColor.withAlphaComponent(fadeOutAlpha) 93 | ]) 94 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 95 | UIGraphicsEndImageContext() 96 | return newImage! 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel+Sparkle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel+Sparkle.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | 30 | extension LTMorphingLabel { 31 | 32 | fileprivate func maskedImageForCharLimbo( 33 | _ charLimbo: LTCharacterLimbo, 34 | withProgress progress: CGFloat 35 | ) -> (UIImage, CGRect) { 36 | let maskedHeight = charLimbo.rect.size.height * max(0.01, progress) 37 | let maskedSize = CGSize( 38 | width: charLimbo.rect.size.width, 39 | height: maskedHeight 40 | ) 41 | UIGraphicsBeginImageContextWithOptions( 42 | maskedSize, 43 | false, 44 | UIScreen.main.scale 45 | ) 46 | let rect = CGRect( 47 | x: 0, 48 | y: 0, 49 | width: charLimbo.rect.size.width, 50 | height: maskedHeight 51 | ) 52 | String(charLimbo.char).draw(in: rect, withAttributes: [ 53 | .font: self.font ?? UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body), 54 | .foregroundColor: self.textColor ?? .black 55 | ]) 56 | guard let newImage = UIGraphicsGetImageFromCurrentImageContext() else { 57 | return (UIImage(), CGRect.zero) 58 | } 59 | UIGraphicsEndImageContext() 60 | let newRect = CGRect( 61 | x: charLimbo.rect.origin.x, 62 | y: charLimbo.rect.origin.y, 63 | width: charLimbo.rect.size.width, 64 | height: maskedHeight 65 | ) 66 | return (newImage, newRect) 67 | } 68 | 69 | @objc 70 | func SparkleLoad() { 71 | 72 | startClosures["Sparkle\(LTMorphingPhases.start)"] = { 73 | self.emitterView.removeAllEmitters() 74 | } 75 | 76 | progressClosures["Sparkle\(LTMorphingPhases.progress)"] = { 77 | (index: Int, progress: Float, isNewChar: Bool) in 78 | 79 | if !isNewChar { 80 | return min(1.0, max(0.0, progress)) 81 | } 82 | 83 | let j = Float(sin(Float(index))) * 1.5 84 | return min( 85 | 1.0, 86 | max( 87 | 0.0001, 88 | progress + self.morphingCharacterDelay * j 89 | ) 90 | ) 91 | 92 | } 93 | 94 | effectClosures["Sparkle\(LTMorphingPhases.disappear)"] = { 95 | char, index, progress in 96 | 97 | return LTCharacterLimbo( 98 | char: char, 99 | rect: self.previousRects[index], 100 | alpha: CGFloat(1.0 - progress), 101 | size: self.font.pointSize, 102 | drawingProgress: 0.0) 103 | } 104 | 105 | effectClosures["Sparkle\(LTMorphingPhases.appear)"] = { 106 | char, index, progress in 107 | 108 | if char != " " { 109 | let rect = self.newRects[index] 110 | let emitterPosition = CGPoint( 111 | x: rect.origin.x + rect.size.width / 2.0, 112 | y: CGFloat(progress) * rect.size.height * 0.9 + rect.origin.y 113 | ) 114 | 115 | self.emitterView.createEmitter( 116 | "c\(index)", 117 | particleName: "Sparkle", 118 | duration: self.morphingDuration 119 | ) { (layer, cell) in 120 | layer.emitterSize = CGSize( 121 | width: rect.size.width, 122 | height: 1 123 | ) 124 | layer.renderMode = CAEmitterLayerRenderMode.additive 125 | cell.emissionLongitude = CGFloat(Double.pi / 2.0) 126 | cell.scale = self.font.pointSize / 300.0 127 | cell.scaleSpeed = self.font.pointSize / 300.0 * -1.5 128 | cell.color = self.textColor.cgColor 129 | cell.birthRate = 130 | Float(self.font.pointSize) 131 | * Float(arc4random_uniform(7) + 3) 132 | }.update { (layer, _) in 133 | layer.emitterPosition = emitterPosition 134 | }.play() 135 | } 136 | 137 | return LTCharacterLimbo( 138 | char: char, 139 | rect: self.newRects[index], 140 | alpha: CGFloat(self.morphingProgress), 141 | size: self.font.pointSize, 142 | drawingProgress: CGFloat(progress) 143 | ) 144 | } 145 | 146 | drawingClosures["Sparkle\(LTMorphingPhases.draw)"] = { 147 | (charLimbo: LTCharacterLimbo) in 148 | 149 | if charLimbo.drawingProgress > 0.0 { 150 | 151 | let (charImage, rect) = self.maskedImageForCharLimbo( 152 | charLimbo, 153 | withProgress: charLimbo.drawingProgress 154 | ) 155 | charImage.draw(in: rect) 156 | 157 | return true 158 | } 159 | 160 | return false 161 | } 162 | 163 | skipFramesClosures["Sparkle\(LTMorphingPhases.skipFrames)"] = { 164 | return 1 165 | } 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel.h 3 | // LTMorphingLabel 4 | // 5 | // Created by Lex Tang on 1/8/15. 6 | // Copyright (c) 2015 lexrus.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for LTMorphingLabel. 12 | FOUNDATION_EXPORT double LTMorphingLabelVersionNumber; 13 | 14 | //! Project version string for LTMorphingLabel. 15 | FOUNDATION_EXPORT const unsigned char LTMorphingLabelVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTMorphingLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTMorphingLabel.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import UIKit 30 | import QuartzCore 31 | 32 | private func < (lhs: T?, rhs: T?) -> Bool { 33 | switch (lhs, rhs) { 34 | case let (l?, r?): 35 | return l < r 36 | case (nil, _?): 37 | return true 38 | default: 39 | return false 40 | } 41 | } 42 | 43 | private func >= (lhs: T?, rhs: T?) -> Bool { 44 | switch (lhs, rhs) { 45 | case let (l?, r?): 46 | return l >= r 47 | default: 48 | return !(lhs < rhs) 49 | } 50 | } 51 | 52 | enum LTMorphingPhases: Int { 53 | case start, appear, disappear, draw, progress, skipFrames 54 | } 55 | 56 | typealias LTMorphingStartClosure = 57 | () -> Void 58 | 59 | typealias LTMorphingEffectClosure = 60 | (Character, _ index: Int, _ progress: Float) -> LTCharacterLimbo 61 | 62 | typealias LTMorphingDrawingClosure = 63 | (LTCharacterLimbo) -> Bool 64 | 65 | typealias LTMorphingManipulateProgressClosure = 66 | (_ index: Int, _ progress: Float, _ isNewChar: Bool) -> Float 67 | 68 | typealias LTMorphingSkipFramesClosure = 69 | () -> Int 70 | 71 | @objc public protocol LTMorphingLabelDelegate { 72 | @objc optional func morphingDidStart(_ label: LTMorphingLabel) 73 | @objc optional func morphingDidComplete(_ label: LTMorphingLabel) 74 | @objc optional func morphingOnProgress(_ label: LTMorphingLabel, progress: Float) 75 | } 76 | 77 | // MARK: - LTMorphingLabel 78 | @IBDesignable open class LTMorphingLabel: UILabel { 79 | 80 | @IBInspectable open var morphingProgress: Float = 0.0 81 | @IBInspectable open var morphingDuration: Float = 0.6 82 | @IBInspectable open var morphingCharacterDelay: Float = 0.026 83 | @IBInspectable open var morphingEnabled: Bool = true 84 | 85 | @IBOutlet open weak var delegate: LTMorphingLabelDelegate? 86 | open var morphingEffect: LTMorphingEffect = .scale 87 | 88 | var startClosures = [String: LTMorphingStartClosure]() 89 | var effectClosures = [String: LTMorphingEffectClosure]() 90 | var drawingClosures = [String: LTMorphingDrawingClosure]() 91 | var progressClosures = [String: LTMorphingManipulateProgressClosure]() 92 | var skipFramesClosures = [String: LTMorphingSkipFramesClosure]() 93 | var diffResults: LTStringDiffResult? 94 | var previousText = "" 95 | 96 | var currentFrame = 0 97 | var totalFrames = 0 98 | var totalDelayFrames = 0 99 | 100 | var totalWidth: Float = 0.0 101 | var previousRects = [CGRect]() 102 | var newRects = [CGRect]() 103 | var charHeight: CGFloat = 0.0 104 | var skipFramesCount: Int = 0 105 | 106 | #if TARGET_INTERFACE_BUILDER 107 | let presentingInIB = true 108 | #else 109 | let presentingInIB = false 110 | #endif 111 | 112 | override open var font: UIFont! { 113 | get { 114 | return super.font ?? UIFont.systemFont(ofSize: 15) 115 | } 116 | set { 117 | super.font = newValue 118 | setNeedsLayout() 119 | } 120 | } 121 | 122 | override open var text: String! { 123 | get { 124 | return super.text ?? "" 125 | } 126 | set { 127 | guard text != newValue else { return } 128 | 129 | previousText = text ?? "" 130 | diffResults = previousText.diffWith(newValue) 131 | super.text = newValue ?? "" 132 | 133 | morphingProgress = 0.0 134 | currentFrame = 0 135 | totalFrames = 0 136 | 137 | setNeedsLayout() 138 | 139 | if !morphingEnabled { 140 | return 141 | } 142 | 143 | if presentingInIB { 144 | morphingDuration = 0.01 145 | morphingProgress = 0.5 146 | } else if previousText != text { 147 | displayLink.isPaused = false 148 | let closureKey = "\(morphingEffect.description)\(LTMorphingPhases.start)" 149 | if let closure = startClosures[closureKey] { 150 | return closure() 151 | } 152 | 153 | delegate?.morphingDidStart?(self) 154 | } 155 | } 156 | } 157 | 158 | open override func setNeedsLayout() { 159 | super.setNeedsLayout() 160 | previousRects = rectsOfEachCharacter(previousText, withFont: font) 161 | newRects = rectsOfEachCharacter(text ?? "", withFont: font) 162 | } 163 | 164 | override open var bounds: CGRect { 165 | get { 166 | return super.bounds 167 | } 168 | set { 169 | super.bounds = newValue 170 | setNeedsLayout() 171 | } 172 | } 173 | 174 | override open var frame: CGRect { 175 | get { 176 | return super.frame 177 | } 178 | set { 179 | super.frame = newValue 180 | setNeedsLayout() 181 | } 182 | } 183 | 184 | fileprivate lazy var displayLink: CADisplayLink = { 185 | let displayLink = CADisplayLink( 186 | target: self, 187 | selector: #selector(LTMorphingLabel.displayFrameTick) 188 | ) 189 | displayLink.add(to: .current, forMode: RunLoop.Mode.common) 190 | return displayLink 191 | }() 192 | 193 | deinit { 194 | displayLink.remove(from: .current, forMode: RunLoop.Mode.common) 195 | displayLink.invalidate() 196 | } 197 | 198 | lazy var emitterView: LTEmitterView = { 199 | let emitterView = LTEmitterView(frame: self.bounds) 200 | self.addSubview(emitterView) 201 | return emitterView 202 | }() 203 | } 204 | 205 | // MARK: - Animation extension 206 | extension LTMorphingLabel { 207 | 208 | @objc func displayFrameTick() { 209 | if displayLink.duration > 0.0 && totalFrames == 0 { 210 | var frameRate = Float(0) 211 | if #available(iOS 10.0, tvOS 10.0, *) { 212 | var frameInterval = 1 213 | if displayLink.preferredFramesPerSecond == 60 { 214 | frameInterval = 1 215 | } else if displayLink.preferredFramesPerSecond == 30 { 216 | frameInterval = 2 217 | } else { 218 | frameInterval = 1 219 | } 220 | frameRate = Float(displayLink.duration) / Float(frameInterval) 221 | } else { 222 | frameRate = Float(displayLink.duration) / Float(displayLink.frameInterval) 223 | } 224 | totalFrames = Int(ceil(morphingDuration / frameRate)) 225 | 226 | let totalDelay = Float((text!).count) * morphingCharacterDelay 227 | totalDelayFrames = Int(ceil(totalDelay / frameRate)) 228 | } 229 | 230 | currentFrame += 1 231 | 232 | if previousText != text && currentFrame < totalFrames + totalDelayFrames + 5 { 233 | morphingProgress += 1.0 / Float(totalFrames) 234 | 235 | let closureKey = "\(morphingEffect.description)\(LTMorphingPhases.skipFrames)" 236 | if let closure = skipFramesClosures[closureKey] { 237 | skipFramesCount += 1 238 | if skipFramesCount > closure() { 239 | skipFramesCount = 0 240 | setNeedsDisplay() 241 | } 242 | } else { 243 | setNeedsDisplay() 244 | } 245 | 246 | if let onProgress = delegate?.morphingOnProgress { 247 | onProgress(self, morphingProgress) 248 | } 249 | } else { 250 | displayLink.isPaused = true 251 | 252 | delegate?.morphingDidComplete?(self) 253 | } 254 | } 255 | 256 | // Could be enhanced by kerning text: 257 | // http://stackoverflow.com/questions/21443625/core-text-calculate-letter-frame-in-ios 258 | func rectsOfEachCharacter(_ textToDraw: String, withFont font: UIFont) -> [CGRect] { 259 | var charRects = [CGRect]() 260 | var leftOffset: CGFloat = 0.0 261 | 262 | charHeight = "Leg".size(withAttributes: [.font: font]).height 263 | 264 | let topOffset = (bounds.size.height - charHeight) / 2.0 265 | 266 | for char in textToDraw { 267 | let charSize = String(char).size(withAttributes: [.font: font]) 268 | charRects.append( 269 | CGRect( 270 | origin: CGPoint( 271 | x: leftOffset, 272 | y: topOffset 273 | ), 274 | size: charSize 275 | ) 276 | ) 277 | leftOffset += charSize.width 278 | } 279 | 280 | totalWidth = Float(leftOffset) 281 | 282 | var stringLeftOffSet: CGFloat = 0.0 283 | 284 | switch textAlignment { 285 | case .center: 286 | stringLeftOffSet = CGFloat((Float(bounds.size.width) - totalWidth) / 2.0) 287 | case .right: 288 | stringLeftOffSet = CGFloat(Float(bounds.size.width) - totalWidth) 289 | default: 290 | () 291 | } 292 | 293 | var offsetedCharRects = [CGRect]() 294 | 295 | for r in charRects { 296 | offsetedCharRects.append(r.offsetBy(dx: stringLeftOffSet, dy: 0.0)) 297 | } 298 | 299 | return offsetedCharRects 300 | } 301 | 302 | func limboOfOriginalCharacter( 303 | _ char: Character, 304 | index: Int, 305 | progress: Float) -> LTCharacterLimbo { 306 | 307 | var currentRect = previousRects[index] 308 | let oriX = Float(currentRect.origin.x) 309 | var newX = Float(currentRect.origin.x) 310 | let diffResult = diffResults!.0[index] 311 | var currentFontSize: CGFloat = font.pointSize 312 | var currentAlpha: CGFloat = 1.0 313 | 314 | switch diffResult { 315 | // Move the character that exists in the new text to current position 316 | case .same: 317 | newX = Float(newRects[index].origin.x) 318 | currentRect.origin.x = CGFloat( 319 | LTEasing.easeOutQuint(progress, oriX, newX - oriX) 320 | ) 321 | case .move(let offset): 322 | newX = Float(newRects[index + offset].origin.x) 323 | currentRect.origin.x = CGFloat( 324 | LTEasing.easeOutQuint(progress, oriX, newX - oriX) 325 | ) 326 | case .moveAndAdd(let offset): 327 | newX = Float(newRects[index + offset].origin.x) 328 | currentRect.origin.x = CGFloat( 329 | LTEasing.easeOutQuint(progress, oriX, newX - oriX) 330 | ) 331 | default: 332 | // Otherwise, remove it 333 | 334 | // Override morphing effect with closure in extenstions 335 | if let closure = effectClosures[ 336 | "\(morphingEffect.description)\(LTMorphingPhases.disappear)" 337 | ] { 338 | return closure(char, index, progress) 339 | } else { 340 | // And scale it by default 341 | let fontEase = CGFloat( 342 | LTEasing.easeOutQuint( 343 | progress, 0, Float(font.pointSize) 344 | ) 345 | ) 346 | // For emojis 347 | currentFontSize = max(0.0001, font.pointSize - fontEase) 348 | currentAlpha = CGFloat(1.0 - progress) 349 | currentRect = previousRects[index].offsetBy( 350 | dx: 0, 351 | dy: CGFloat(font.pointSize - currentFontSize) 352 | ) 353 | } 354 | } 355 | 356 | return LTCharacterLimbo( 357 | char: char, 358 | rect: currentRect, 359 | alpha: currentAlpha, 360 | size: currentFontSize, 361 | drawingProgress: 0.0 362 | ) 363 | } 364 | 365 | func limboOfNewCharacter( 366 | _ char: Character, 367 | index: Int, 368 | progress: Float) -> LTCharacterLimbo { 369 | 370 | let currentRect = newRects[index] 371 | var currentFontSize = CGFloat( 372 | LTEasing.easeOutQuint(progress, 0, Float(font.pointSize)) 373 | ) 374 | 375 | if let closure = effectClosures[ 376 | "\(morphingEffect.description)\(LTMorphingPhases.appear)" 377 | ] { 378 | return closure(char, index, progress) 379 | } else { 380 | currentFontSize = CGFloat( 381 | LTEasing.easeOutQuint(progress, 0.0, Float(font.pointSize)) 382 | ) 383 | // For emojis 384 | currentFontSize = max(0.0001, currentFontSize) 385 | 386 | let yOffset = CGFloat(font.pointSize - currentFontSize) 387 | 388 | return LTCharacterLimbo( 389 | char: char, 390 | rect: currentRect.offsetBy(dx: 0, dy: yOffset), 391 | alpha: CGFloat(morphingProgress), 392 | size: currentFontSize, 393 | drawingProgress: 0.0 394 | ) 395 | } 396 | } 397 | 398 | func limboOfCharacters() -> [LTCharacterLimbo] { 399 | var limbo = [LTCharacterLimbo]() 400 | 401 | // Iterate original characters 402 | for (i, character) in previousText.enumerated() { 403 | var progress: Float = 0.0 404 | 405 | if let closure = progressClosures[ 406 | "\(morphingEffect.description)\(LTMorphingPhases.progress)" 407 | ] { 408 | progress = closure(i, morphingProgress, false) 409 | } else { 410 | progress = min(1.0, max(0.0, morphingProgress + morphingCharacterDelay * Float(i))) 411 | } 412 | 413 | let limboOfCharacter = limboOfOriginalCharacter(character, index: i, progress: progress) 414 | limbo.append(limboOfCharacter) 415 | } 416 | 417 | // Add new characters 418 | for (i, character) in (text!).enumerated() { 419 | if i >= diffResults?.0.count { 420 | break 421 | } 422 | 423 | var progress: Float = 0.0 424 | 425 | if let closure = progressClosures[ 426 | "\(morphingEffect.description)\(LTMorphingPhases.progress)" 427 | ] { 428 | progress = closure(i, morphingProgress, true) 429 | } else { 430 | progress = min(1.0, max(0.0, morphingProgress - morphingCharacterDelay * Float(i))) 431 | } 432 | 433 | // Don't draw character that already exists 434 | if diffResults?.skipDrawingResults[i] == true { 435 | continue 436 | } 437 | 438 | if let diffResult = diffResults?.0[i] { 439 | switch diffResult { 440 | case .moveAndAdd, .replace, .add, .delete: 441 | let limboOfCharacter = limboOfNewCharacter( 442 | character, 443 | index: i, 444 | progress: progress 445 | ) 446 | limbo.append(limboOfCharacter) 447 | default: 448 | () 449 | } 450 | } 451 | } 452 | 453 | return limbo 454 | } 455 | 456 | } 457 | 458 | // MARK: - Drawing extension 459 | extension LTMorphingLabel { 460 | 461 | override open func didMoveToSuperview() { 462 | if let s = text { 463 | text = s 464 | } 465 | 466 | // Load all morphing effects 467 | for effectName: String in LTMorphingEffect.allValues { 468 | let effectFunc = Selector("\(effectName)Load") 469 | if responds(to: effectFunc) { 470 | perform(effectFunc) 471 | } 472 | } 473 | } 474 | 475 | override open func drawText(in rect: CGRect) { 476 | if !morphingEnabled || limboOfCharacters().count == 0 { 477 | super.drawText(in: rect) 478 | return 479 | } 480 | 481 | for charLimbo in limboOfCharacters() { 482 | let charRect = charLimbo.rect 483 | 484 | let willAvoidDefaultDrawing: Bool = { 485 | if let closure = drawingClosures[ 486 | "\(morphingEffect.description)\(LTMorphingPhases.draw)" 487 | ] { 488 | return closure($0) 489 | } 490 | return false 491 | }(charLimbo) 492 | 493 | if !willAvoidDefaultDrawing { 494 | var attrs: [NSAttributedString.Key: Any] = [ 495 | .foregroundColor: textColor.withAlphaComponent(charLimbo.alpha) 496 | ] 497 | 498 | if let font = UIFont(name: font.fontName, size: charLimbo.size) { 499 | attrs[.font] = font 500 | } 501 | let s = String(charLimbo.char) 502 | s.draw(in: charRect, withAttributes: attrs) 503 | } 504 | } 505 | } 506 | 507 | } 508 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/LTStringDiffResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LTStringDiffResult.swift 3 | // https://github.com/lexrus/LTMorphingLabel 4 | // 5 | // The MIT License (MIT) 6 | // Copyright (c) 2017 Lex Tang, http://lexrus.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a 9 | // copy of this software and associated documentation files 10 | // (the “Software”), to deal in the Software without restriction, 11 | // including without limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of the Software, 13 | // and to permit persons to whom the Software is furnished to do so, 14 | // subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included 17 | // in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | public typealias LTStringDiffResult = ([LTCharacterDiffResult], skipDrawingResults: [Bool]) 31 | 32 | public extension String { 33 | 34 | func diffWith(_ anotherString: String?) -> LTStringDiffResult { 35 | 36 | guard let anotherString = anotherString else { 37 | let diffResults: [LTCharacterDiffResult] = 38 | Array(repeating: .delete, count: self.count) 39 | let skipDrawingResults: [Bool] = Array(repeating: false, count: self.count) 40 | return (diffResults, skipDrawingResults) 41 | } 42 | 43 | let newChars = anotherString.enumerated() 44 | let lhsLength = self.count 45 | let rhsLength = anotherString.count 46 | var skipIndexes = [Int]() 47 | let leftChars = Array(self) 48 | 49 | let maxLength = max(lhsLength, rhsLength) 50 | var diffResults: [LTCharacterDiffResult] = Array(repeating: .add, count: maxLength) 51 | var skipDrawingResults: [Bool] = Array(repeating: false, count: maxLength) 52 | 53 | for i in 0.. lhsLength - 1 { 56 | continue 57 | } 58 | 59 | let leftChar = leftChars[i] 60 | 61 | // Search left character in the new string 62 | var foundCharacterInRhs = false 63 | for (j, newChar) in newChars { 64 | if skipIndexes.contains(j) || leftChar != newChar { 65 | continue 66 | } 67 | 68 | skipIndexes.append(j) 69 | foundCharacterInRhs = true 70 | if i == j { 71 | // Character not changed 72 | diffResults[i] = .same 73 | } else { 74 | // foundCharacterInRhs and move 75 | let offset = j - i 76 | 77 | if i <= rhsLength - 1 { 78 | // Move to a new index and add a new character to new original place 79 | diffResults[i] = .moveAndAdd(offset: offset) 80 | } else { 81 | diffResults[i] = .move(offset: offset) 82 | } 83 | 84 | skipDrawingResults[j] = true 85 | } 86 | break 87 | } 88 | 89 | if !foundCharacterInRhs { 90 | if i < rhsLength - 1 { 91 | diffResults[i] = .replace 92 | } else { 93 | diffResults[i] = .delete 94 | } 95 | } 96 | } 97 | 98 | return (diffResults, skipDrawingResults) 99 | 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/Particles/Fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/CountdownLabel/LTMorphingLabel/Particles/Fire.png -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/Particles/Fragment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/CountdownLabel/LTMorphingLabel/Particles/Fragment.png -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/Particles/Smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/CountdownLabel/LTMorphingLabel/Particles/Smoke.png -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/Particles/Sparkle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/CountdownLabel/LTMorphingLabel/Particles/Sparkle.png -------------------------------------------------------------------------------- /CountdownLabel/LTMorphingLabel/tvOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 89993D961C3FAD910099AC14 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89993D951C3FAD910099AC14 /* AppDelegate.swift */; }; 11 | 89993D981C3FAD910099AC14 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89993D971C3FAD910099AC14 /* ViewController.swift */; }; 12 | 89993D9B1C3FAD910099AC14 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89993D991C3FAD910099AC14 /* Main.storyboard */; }; 13 | 89993D9D1C3FAD910099AC14 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 89993D9C1C3FAD910099AC14 /* Assets.xcassets */; }; 14 | 89993DA01C3FAD910099AC14 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89993D9E1C3FAD910099AC14 /* LaunchScreen.storyboard */; }; 15 | 89CA98A21C4E412200C74EF9 /* CountdownLabel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89CA989B1C4E3C5B00C74EF9 /* CountdownLabel.framework */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXContainerItemProxy section */ 19 | 89993DA71C3FAD920099AC14 /* PBXContainerItemProxy */ = { 20 | isa = PBXContainerItemProxy; 21 | containerPortal = 89993D8A1C3FAD910099AC14 /* Project object */; 22 | proxyType = 1; 23 | remoteGlobalIDString = 89993D911C3FAD910099AC14; 24 | remoteInfo = CountdownLabelExample; 25 | }; 26 | 8999B52B1C4F300300C46F10 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 89993DB71C3FAE1C0099AC14 /* CountdownLabel.xcodeproj */; 29 | proxyType = 2; 30 | remoteGlobalIDString = 8999B51B1C4F2F7200C46F10; 31 | remoteInfo = CountdownLabelTests; 32 | }; 33 | 89CA989A1C4E3C5B00C74EF9 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 89993DB71C3FAE1C0099AC14 /* CountdownLabel.xcodeproj */; 36 | proxyType = 2; 37 | remoteGlobalIDString = 892106D01C3CF4140007CDEC; 38 | remoteInfo = CountdownLabel; 39 | }; 40 | /* End PBXContainerItemProxy section */ 41 | 42 | /* Begin PBXFileReference section */ 43 | 89993D921C3FAD910099AC14 /* CountdownLabelExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CountdownLabelExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 89993D951C3FAD910099AC14 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 89993D971C3FAD910099AC14 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 46 | 89993D9A1C3FAD910099AC14 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 89993D9C1C3FAD910099AC14 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 89993D9F1C3FAD910099AC14 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 89993DA11C3FAD910099AC14 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 89993DA61C3FAD920099AC14 /* CountdownLabelExample.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CountdownLabelExample.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 89993DB71C3FAE1C0099AC14 /* CountdownLabel.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CountdownLabel.xcodeproj; path = ../CountdownLabel.xcodeproj; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 89993D8F1C3FAD910099AC14 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 89CA98A21C4E412200C74EF9 /* CountdownLabel.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | 89993DA31C3FAD920099AC14 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | /* End PBXFrameworksBuildPhase section */ 71 | 72 | /* Begin PBXGroup section */ 73 | 89993D891C3FAD910099AC14 = { 74 | isa = PBXGroup; 75 | children = ( 76 | 89993DB71C3FAE1C0099AC14 /* CountdownLabel.xcodeproj */, 77 | 89993D941C3FAD910099AC14 /* CountdownLabelExample */, 78 | 89993D931C3FAD910099AC14 /* Products */, 79 | ); 80 | sourceTree = ""; 81 | }; 82 | 89993D931C3FAD910099AC14 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 89993D921C3FAD910099AC14 /* CountdownLabelExample.app */, 86 | 89993DA61C3FAD920099AC14 /* CountdownLabelExample.xctest */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 89993D941C3FAD910099AC14 /* CountdownLabelExample */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 89993D951C3FAD910099AC14 /* AppDelegate.swift */, 95 | 89993D971C3FAD910099AC14 /* ViewController.swift */, 96 | 89993D991C3FAD910099AC14 /* Main.storyboard */, 97 | 89993D9C1C3FAD910099AC14 /* Assets.xcassets */, 98 | 89993D9E1C3FAD910099AC14 /* LaunchScreen.storyboard */, 99 | 89993DA11C3FAD910099AC14 /* Info.plist */, 100 | ); 101 | path = CountdownLabelExample; 102 | sourceTree = ""; 103 | }; 104 | 89CA98971C4E3C5B00C74EF9 /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 89CA989B1C4E3C5B00C74EF9 /* CountdownLabel.framework */, 108 | 8999B52C1C4F300300C46F10 /* CountdownLabelTests.xctest */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | /* End PBXGroup section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | 89993D911C3FAD910099AC14 /* CountdownLabelExample */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = 89993DAF1C3FAD920099AC14 /* Build configuration list for PBXNativeTarget "CountdownLabelExample" */; 119 | buildPhases = ( 120 | 89993D8E1C3FAD910099AC14 /* Sources */, 121 | 89993D8F1C3FAD910099AC14 /* Frameworks */, 122 | 89993D901C3FAD910099AC14 /* Resources */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = CountdownLabelExample; 129 | productName = CountdownLabelExample; 130 | productReference = 89993D921C3FAD910099AC14 /* CountdownLabelExample.app */; 131 | productType = "com.apple.product-type.application"; 132 | }; 133 | 89993DA51C3FAD920099AC14 /* CountdownLabelExampleTests */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = 89993DB21C3FAD920099AC14 /* Build configuration list for PBXNativeTarget "CountdownLabelExampleTests" */; 136 | buildPhases = ( 137 | 89993DA21C3FAD920099AC14 /* Sources */, 138 | 89993DA31C3FAD920099AC14 /* Frameworks */, 139 | 89993DA41C3FAD920099AC14 /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | 89993DA81C3FAD920099AC14 /* PBXTargetDependency */, 145 | ); 146 | name = CountdownLabelExampleTests; 147 | productName = CountdownLabelExampleTests; 148 | productReference = 89993DA61C3FAD920099AC14 /* CountdownLabelExample.xctest */; 149 | productType = "com.apple.product-type.bundle.unit-test"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 89993D8A1C3FAD910099AC14 /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastSwiftUpdateCheck = 0720; 158 | LastUpgradeCheck = 0920; 159 | ORGANIZATIONNAME = suzuki_keishi; 160 | TargetAttributes = { 161 | 89993D911C3FAD910099AC14 = { 162 | CreatedOnToolsVersion = 7.2; 163 | LastSwiftMigration = 0830; 164 | }; 165 | 89993DA51C3FAD920099AC14 = { 166 | CreatedOnToolsVersion = 7.2; 167 | TestTargetID = 89993D911C3FAD910099AC14; 168 | }; 169 | }; 170 | }; 171 | buildConfigurationList = 89993D8D1C3FAD910099AC14 /* Build configuration list for PBXProject "CountdownLabelExample" */; 172 | compatibilityVersion = "Xcode 3.2"; 173 | developmentRegion = English; 174 | hasScannedForEncodings = 0; 175 | knownRegions = ( 176 | en, 177 | Base, 178 | ); 179 | mainGroup = 89993D891C3FAD910099AC14; 180 | productRefGroup = 89993D931C3FAD910099AC14 /* Products */; 181 | projectDirPath = ""; 182 | projectReferences = ( 183 | { 184 | ProductGroup = 89CA98971C4E3C5B00C74EF9 /* Products */; 185 | ProjectRef = 89993DB71C3FAE1C0099AC14 /* CountdownLabel.xcodeproj */; 186 | }, 187 | ); 188 | projectRoot = ""; 189 | targets = ( 190 | 89993D911C3FAD910099AC14 /* CountdownLabelExample */, 191 | 89993DA51C3FAD920099AC14 /* CountdownLabelExampleTests */, 192 | ); 193 | }; 194 | /* End PBXProject section */ 195 | 196 | /* Begin PBXReferenceProxy section */ 197 | 8999B52C1C4F300300C46F10 /* CountdownLabelTests.xctest */ = { 198 | isa = PBXReferenceProxy; 199 | fileType = wrapper.cfbundle; 200 | path = CountdownLabelTests.xctest; 201 | remoteRef = 8999B52B1C4F300300C46F10 /* PBXContainerItemProxy */; 202 | sourceTree = BUILT_PRODUCTS_DIR; 203 | }; 204 | 89CA989B1C4E3C5B00C74EF9 /* CountdownLabel.framework */ = { 205 | isa = PBXReferenceProxy; 206 | fileType = wrapper.framework; 207 | path = CountdownLabel.framework; 208 | remoteRef = 89CA989A1C4E3C5B00C74EF9 /* PBXContainerItemProxy */; 209 | sourceTree = BUILT_PRODUCTS_DIR; 210 | }; 211 | /* End PBXReferenceProxy section */ 212 | 213 | /* Begin PBXResourcesBuildPhase section */ 214 | 89993D901C3FAD910099AC14 /* Resources */ = { 215 | isa = PBXResourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | 89993DA01C3FAD910099AC14 /* LaunchScreen.storyboard in Resources */, 219 | 89993D9D1C3FAD910099AC14 /* Assets.xcassets in Resources */, 220 | 89993D9B1C3FAD910099AC14 /* Main.storyboard in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | 89993DA41C3FAD920099AC14 /* Resources */ = { 225 | isa = PBXResourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXResourcesBuildPhase section */ 232 | 233 | /* Begin PBXSourcesBuildPhase section */ 234 | 89993D8E1C3FAD910099AC14 /* Sources */ = { 235 | isa = PBXSourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 89993D981C3FAD910099AC14 /* ViewController.swift in Sources */, 239 | 89993D961C3FAD910099AC14 /* AppDelegate.swift in Sources */, 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | 89993DA21C3FAD920099AC14 /* Sources */ = { 244 | isa = PBXSourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | /* End PBXSourcesBuildPhase section */ 251 | 252 | /* Begin PBXTargetDependency section */ 253 | 89993DA81C3FAD920099AC14 /* PBXTargetDependency */ = { 254 | isa = PBXTargetDependency; 255 | target = 89993D911C3FAD910099AC14 /* CountdownLabelExample */; 256 | targetProxy = 89993DA71C3FAD920099AC14 /* PBXContainerItemProxy */; 257 | }; 258 | /* End PBXTargetDependency section */ 259 | 260 | /* Begin PBXVariantGroup section */ 261 | 89993D991C3FAD910099AC14 /* Main.storyboard */ = { 262 | isa = PBXVariantGroup; 263 | children = ( 264 | 89993D9A1C3FAD910099AC14 /* Base */, 265 | ); 266 | name = Main.storyboard; 267 | sourceTree = ""; 268 | }; 269 | 89993D9E1C3FAD910099AC14 /* LaunchScreen.storyboard */ = { 270 | isa = PBXVariantGroup; 271 | children = ( 272 | 89993D9F1C3FAD910099AC14 /* Base */, 273 | ); 274 | name = LaunchScreen.storyboard; 275 | sourceTree = ""; 276 | }; 277 | /* End PBXVariantGroup section */ 278 | 279 | /* Begin XCBuildConfiguration section */ 280 | 89993DAD1C3FAD920099AC14 /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 289 | CLANG_WARN_BOOL_CONVERSION = YES; 290 | CLANG_WARN_COMMA = YES; 291 | CLANG_WARN_CONSTANT_CONVERSION = YES; 292 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 299 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 306 | COPY_PHASE_STRIP = NO; 307 | DEBUG_INFORMATION_FORMAT = dwarf; 308 | ENABLE_STRICT_OBJC_MSGSEND = YES; 309 | ENABLE_TESTABILITY = YES; 310 | GCC_C_LANGUAGE_STANDARD = gnu99; 311 | GCC_DYNAMIC_NO_PIC = NO; 312 | GCC_NO_COMMON_BLOCKS = YES; 313 | GCC_OPTIMIZATION_LEVEL = 0; 314 | GCC_PREPROCESSOR_DEFINITIONS = ( 315 | "DEBUG=1", 316 | "$(inherited)", 317 | ); 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 325 | MTL_ENABLE_DEBUG_INFO = YES; 326 | ONLY_ACTIVE_ARCH = YES; 327 | SDKROOT = iphoneos; 328 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | }; 331 | name = Debug; 332 | }; 333 | 89993DAE1C3FAD920099AC14 /* Release */ = { 334 | isa = XCBuildConfiguration; 335 | buildSettings = { 336 | ALWAYS_SEARCH_USER_PATHS = NO; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 346 | CLANG_WARN_EMPTY_BODY = YES; 347 | CLANG_WARN_ENUM_CONVERSION = YES; 348 | CLANG_WARN_INFINITE_RECURSION = YES; 349 | CLANG_WARN_INT_CONVERSION = YES; 350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 354 | CLANG_WARN_STRICT_PROTOTYPES = YES; 355 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 356 | CLANG_WARN_UNREACHABLE_CODE = YES; 357 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 358 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 359 | COPY_PHASE_STRIP = NO; 360 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 361 | ENABLE_NS_ASSERTIONS = NO; 362 | ENABLE_STRICT_OBJC_MSGSEND = YES; 363 | GCC_C_LANGUAGE_STANDARD = gnu99; 364 | GCC_NO_COMMON_BLOCKS = YES; 365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 366 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 367 | GCC_WARN_UNDECLARED_SELECTOR = YES; 368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 369 | GCC_WARN_UNUSED_FUNCTION = YES; 370 | GCC_WARN_UNUSED_VARIABLE = YES; 371 | IPHONEOS_DEPLOYMENT_TARGET = 8.4; 372 | MTL_ENABLE_DEBUG_INFO = NO; 373 | SDKROOT = iphoneos; 374 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 375 | TARGETED_DEVICE_FAMILY = "1,2"; 376 | VALIDATE_PRODUCT = YES; 377 | }; 378 | name = Release; 379 | }; 380 | 89993DB01C3FAD920099AC14 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 384 | DEVELOPMENT_TEAM = ""; 385 | INFOPLIST_FILE = CountdownLabelExample/Info.plist; 386 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 387 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 388 | PRODUCT_BUNDLE_IDENTIFIER = com.keishi.suzuki.CountdownLabelExample; 389 | PRODUCT_NAME = CountdownLabelExample; 390 | SWIFT_VERSION = 4.2; 391 | }; 392 | name = Debug; 393 | }; 394 | 89993DB11C3FAD920099AC14 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 398 | DEVELOPMENT_TEAM = ""; 399 | INFOPLIST_FILE = CountdownLabelExample/Info.plist; 400 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 401 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 402 | PRODUCT_BUNDLE_IDENTIFIER = com.keishi.suzuki.CountdownLabelExample; 403 | PRODUCT_NAME = CountdownLabelExample; 404 | SWIFT_VERSION = 4.2; 405 | }; 406 | name = Release; 407 | }; 408 | 89993DB31C3FAD920099AC14 /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | buildSettings = { 411 | BUNDLE_LOADER = "$(TEST_HOST)"; 412 | INFOPLIST_FILE = CountdownLabelExampleTests/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | PRODUCT_BUNDLE_IDENTIFIER = com.keishi.suzuki.CountdownLabelExampleTests; 415 | PRODUCT_NAME = CountdownLabelExample; 416 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CountdownLabelExample.app/CountdownLabelExample"; 417 | }; 418 | name = Debug; 419 | }; 420 | 89993DB41C3FAD920099AC14 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | BUNDLE_LOADER = "$(TEST_HOST)"; 424 | INFOPLIST_FILE = CountdownLabelExampleTests/Info.plist; 425 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 426 | PRODUCT_BUNDLE_IDENTIFIER = com.keishi.suzuki.CountdownLabelExampleTests; 427 | PRODUCT_NAME = CountdownLabelExample; 428 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CountdownLabelExample.app/CountdownLabelExample"; 429 | }; 430 | name = Release; 431 | }; 432 | /* End XCBuildConfiguration section */ 433 | 434 | /* Begin XCConfigurationList section */ 435 | 89993D8D1C3FAD910099AC14 /* Build configuration list for PBXProject "CountdownLabelExample" */ = { 436 | isa = XCConfigurationList; 437 | buildConfigurations = ( 438 | 89993DAD1C3FAD920099AC14 /* Debug */, 439 | 89993DAE1C3FAD920099AC14 /* Release */, 440 | ); 441 | defaultConfigurationIsVisible = 0; 442 | defaultConfigurationName = Release; 443 | }; 444 | 89993DAF1C3FAD920099AC14 /* Build configuration list for PBXNativeTarget "CountdownLabelExample" */ = { 445 | isa = XCConfigurationList; 446 | buildConfigurations = ( 447 | 89993DB01C3FAD920099AC14 /* Debug */, 448 | 89993DB11C3FAD920099AC14 /* Release */, 449 | ); 450 | defaultConfigurationIsVisible = 0; 451 | defaultConfigurationName = Release; 452 | }; 453 | 89993DB21C3FAD920099AC14 /* Build configuration list for PBXNativeTarget "CountdownLabelExampleTests" */ = { 454 | isa = XCConfigurationList; 455 | buildConfigurations = ( 456 | 89993DB31C3FAD920099AC14 /* Debug */, 457 | 89993DB41C3FAD920099AC14 /* Release */, 458 | ); 459 | defaultConfigurationIsVisible = 0; 460 | defaultConfigurationName = Release; 461 | }; 462 | /* End XCConfigurationList section */ 463 | }; 464 | rootObject = 89993D8A1C3FAD910099AC14 /* Project object */; 465 | } 466 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CountdownLabelExample 4 | // 5 | // Created by suzuki keishi on 2016/01/08. 6 | // Copyright © 2016年 suzuki_keishi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | private func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | mplus-1p-regular 10 | mplus-1p-regular 11 | mplus-1p-regular 12 | mplus-1p-regular 13 | mplus-1p-regular 14 | mplus-1p-regular 15 | mplus-1p-regular 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 44 | 53 | 63 | 73 | 83 | 93 | 103 | 113 | 123 | 129 | 135 | 144 | 150 | 159 | 165 | 174 | 180 | 189 | 195 | 204 | 213 | 222 | 231 | 240 | 249 | 255 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIAppFonts 6 | 7 | mplus-1p-regular 8 | 9 | CFBundleDevelopmentRegion 10 | en 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /CountdownLabelExample/CountdownLabelExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CountdownLabelExample 4 | // 5 | // Created by suzuki keishi on 2016/01/06. 6 | // Copyright © 2016 suzuki_keishi. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CountdownLabel 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var countdownLabel1: CountdownLabel! 15 | @IBOutlet weak var countdownLabelAnvil: CountdownLabel! 16 | @IBOutlet weak var countdownLabelBurn: CountdownLabel! 17 | @IBOutlet weak var countdownLabelEvaporate: CountdownLabel! 18 | @IBOutlet weak var countdownLabelFall: CountdownLabel! 19 | @IBOutlet weak var countdownLabelPixelate: CountdownLabel! 20 | @IBOutlet weak var countdownLabelScale: CountdownLabel! 21 | @IBOutlet weak var countdownLabelSparkle: CountdownLabel! 22 | @IBOutlet weak var countdownLabel2: CountdownLabel! 23 | @IBOutlet weak var countdownLabel3: CountdownLabel! 24 | @IBOutlet weak var countdownLabel4: CountdownLabel! 25 | @IBOutlet weak var countdownLabel5: CountdownLabel! 26 | @IBOutlet weak var countdownLabel6: CountdownLabel! 27 | @IBOutlet weak var countdownLabel7: CountdownLabel! 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | // 1. normal 33 | countdownLabel1.setCountDownTime(minutes: 60*60) 34 | countdownLabel1.start() 35 | 36 | // option animation ( using LTMorphing inside ) 37 | countdownLabelAnvil.setCountDownTime(minutes: 60*60) 38 | countdownLabelAnvil.animationType = .Anvil 39 | countdownLabelAnvil.start() 40 | 41 | countdownLabelBurn.setCountDownTime(minutes: 60*60) 42 | countdownLabelBurn.animationType = .Burn 43 | countdownLabelBurn.start() 44 | 45 | countdownLabelEvaporate.setCountDownTime(minutes: 60*60) 46 | countdownLabelEvaporate.animationType = .Evaporate 47 | countdownLabelEvaporate.start() 48 | 49 | countdownLabelFall.setCountDownTime(minutes: 60*60) 50 | countdownLabelFall.animationType = .Fall 51 | countdownLabelFall.start() 52 | 53 | countdownLabelPixelate.setCountDownTime(minutes: 60*60) 54 | countdownLabelPixelate.animationType = .Pixelate 55 | countdownLabelPixelate.start() 56 | 57 | countdownLabelScale.setCountDownTime(minutes: 60*60) 58 | countdownLabelScale.animationType = .Scale 59 | countdownLabelScale.start() 60 | 61 | countdownLabelSparkle.setCountDownTime(minutes: 60*60) 62 | countdownLabelSparkle.animationType = .Sparkle 63 | countdownLabelSparkle.start() 64 | 65 | // 2. style 66 | countdownLabel2.setCountDownTime(minutes: 60*60) 67 | countdownLabel2.animationType = .Evaporate 68 | countdownLabel2.textColor = UIColor.orange 69 | countdownLabel2.font = UIFont(name:"Courier", size:UIFont.labelFontSize) 70 | countdownLabel2.start() 71 | 72 | // 3. get status 73 | countdownLabel3.setCountDownTime(minutes: 30) 74 | countdownLabel3.animationType = .Sparkle 75 | countdownLabel3.start() 76 | 77 | // 4. control countdown 78 | countdownLabel4.setCountDownTime(minutes: 30) 79 | countdownLabel4.animationType = .Pixelate 80 | countdownLabel4.start() 81 | 82 | // 5. control countdown 83 | countdownLabel5.setCountDownTime(minutes: 10) 84 | countdownLabel5.animationType = .Pixelate 85 | countdownLabel5.countdownDelegate = self 86 | countdownLabel5.start() { [unowned self] in 87 | self.countdownLabel5.text = "timer finished." 88 | } 89 | 90 | // 6. control countdown 91 | countdownLabel6.setCountDownTime(minutes: 30) 92 | countdownLabel5.animationType = .Scale 93 | let _ = countdownLabel6.then(targetTime: 10) { [unowned self] in 94 | self.countdownLabel6.animationType = .Pixelate 95 | self.countdownLabel6.textColor = .green 96 | } 97 | let _ = countdownLabel6.then(targetTime: 5) { [unowned self] in 98 | self.countdownLabel6.animationType = .Sparkle 99 | self.countdownLabel6.textColor = .yellow 100 | } 101 | countdownLabel6.start() { 102 | self.countdownLabel6.textColor = .white 103 | } 104 | 105 | // 7. attributed text 106 | countdownLabel7.setCountDownTime(minutes:30) 107 | countdownLabel7.animationType = .Anvil 108 | countdownLabel7.timeFormat = "ss" 109 | countdownLabel7.countdownAttributedText = CountdownAttributedText(text: "HELLO TIME IS HERE NOW", 110 | replacement: "HERE", 111 | attributes: [NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : UIColor.red]) 112 | countdownLabel7.start() { 113 | self.countdownLabel7.text = "timer finished." 114 | } 115 | } 116 | 117 | // MARK: - countdownLabel3's IBAction 118 | @IBAction func getTimerCounted(_ sender: UIButton) { 119 | alert("\(countdownLabel3.timeCounted)") 120 | } 121 | 122 | @IBAction func getTimerRemain(_ sender: UIButton) { 123 | alert("\(countdownLabel3.timeRemaining)") 124 | } 125 | 126 | // MARK: - countdownLabel4's IBAction 127 | @IBAction func controlStartStop(_ sender: UIButton) { 128 | if countdownLabel4.isPaused { 129 | countdownLabel4.start() 130 | sender.setTitle("pause", for: UIControl.State()) 131 | } else { 132 | countdownLabel4.pause() 133 | sender.setTitle("start", for: UIControl.State()) 134 | } 135 | } 136 | 137 | @IBAction func minus(_ sender: UIButton) { 138 | countdownLabel4.addTime(time: -2) 139 | } 140 | 141 | @IBAction func plus(_ sender: UIButton) { 142 | countdownLabel4.addTime(time: 2) 143 | } 144 | } 145 | 146 | extension ViewController: CountdownLabelDelegate { 147 | func countdownFinished() { 148 | debugPrint("countdownFinished at delegate.") 149 | } 150 | 151 | func countingAt(timeCounted: TimeInterval, timeRemaining: TimeInterval) { 152 | debugPrint("time counted at delegate=\(timeCounted)") 153 | debugPrint("time remaining at delegate=\(timeRemaining)") 154 | } 155 | 156 | } 157 | 158 | extension ViewController { 159 | func alert(_ title: String) { 160 | let vc = UIAlertController(title: title, message: nil, preferredStyle: .alert) 161 | vc.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 162 | present(vc, animated: true, completion: nil) 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 suzuki_keishi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CountdownLabel 2 | ======================== 3 | 4 | ![Swift](http://img.shields.io/badge/swift-4.0-brightgreen.svg) 5 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/CountdownLabel.svg?style=flat)](http://cocoadocs.org/docsets/CountdownLabel) 7 | 8 | Simple countdown UILabel with morphing animation, and some useful function. 9 | 10 | ![sample](Screenshots/example01.gif) 11 | 12 | ## features 13 | - Simple creation 14 | - Easily get status of countdown from property and delegate. 15 | - Insert some of function, and completion 16 | - Style change as usual as UILabel do 17 | - Morphing animation from [LTMorphingLabel](https://github.com/lexrus/LTMorphingLabel). 18 | - XCTest assertion 19 | 20 | ### Version vs Swift version. 21 | 22 | Below is a table that shows which version of what you should use for your Swift version. 23 | 24 | | Swift version | version | 25 | | ------------- | --------------- | 26 | | 4.2 | >= 4.0 | 27 | | 4.0, 4.1 | >= 3.0 | 28 | | 3.X | >= 2.0 | 29 | | 2.3 | 1.3 | 30 | 31 | ## Usage 32 | You need only 2 lines. 33 | 34 | ```swift 35 | // from current Date, after 30 minutes. 36 | let countdownLabel = CountdownLabel(frame: frame, minutes: 30) // you can use NSDate as well 37 | countdownLabel.start() 38 | ``` 39 | 40 | #### Morphing example 41 | Use `animationType`. 42 | Those effect comes from [LTMorphingLabel](https://github.com/lexrus/LTMorphingLabel). 43 | 44 | ```swift 45 | let countdownLabel = CountdownLabel(frame: CGRectZero, time: 60*60) 46 | countdownLabel.animationType = .Pixelate 47 | countdownLabel.start() 48 | ``` 49 | 50 | | morphing effect | example | 51 | | -------- |--------- | 52 | | .Burn | ![sample](Screenshots/exampleBurn.gif) | 53 | | .Evaporate | ![sample](Screenshots/exampleEvaporate.gif) | 54 | | .Fall | ![sample](Screenshots/exampleFall.gif) | 55 | | .Pixelate | ![sample](Screenshots/examplePixelate.gif) | 56 | | .Scale | ![sample](Screenshots/exampleScale.gif) | 57 | | .Sparkle | ![sample](Screenshots/exampleSparkle.gif) | 58 | 59 | #### Style 60 | you can directly allocate it as a UILabel property just like usual. 61 | 62 | ```swift 63 | countdownLabel.textColor = .orangeColor() 64 | countdownLabel.font = UIFont(name:"Courier", size:UIFont.labelFontSize()) 65 | countdownLabel.start() 66 | ``` 67 | 68 | ![sample](Screenshots/example02.gif) 69 | 70 | #### Get Status of timer 71 | there's some property for reading status. 72 | ```swift 73 | countdownLabel.timeCounted // timer that has been counted 74 | countdownLabel.timeRemaining // timer's remaining 75 | 76 | // example 77 | @IBAction func getTimerCounted(sender: UIButton) { 78 | debugPrint("\(countdownLabel.timeCounted)") 79 | } 80 | 81 | @IBAction func getTimerRemain(sender: UIButton) { 82 | debugPrint("\(countdownLabel.timeRemaining)") 83 | } 84 | ``` 85 | 86 | ![sample](Screenshots/example03.gif) 87 | 88 | #### Control countdown 89 | You can pause, start, change time. 90 | 91 | ```swift 92 | // check if pause or not 93 | if countdownLabel.isPaused { 94 | // timer start 95 | countdownLabel.start() 96 | } else { 97 | // timer pause 98 | countdownLabel.pause() 99 | } 100 | ``` 101 | 102 | ```swift 103 | // -2 minutes for ending 104 | @IBAction func minus(btn: UIButton) { 105 | countdownLabel.addTime(-2) 106 | } 107 | 108 | // +2 minutes for ending 109 | @IBAction func plus(btn: UIButton) { 110 | countdownLabel.addTime(2) 111 | } 112 | ``` 113 | 114 | ![sample](Screenshots/example04.gif) 115 | 116 | #### Insert Function 117 | Using `then` function or `delegate`, you can set your function anywhere you like. 118 | 119 | ```swift 120 | // then property 121 | countdownLabel.then(10) { [unowned self] in 122 | self.countdownLabel.animationType = .Pixelate 123 | self.countdownLabel.textColor = .greenColor() 124 | } 125 | countdownLabel.then(5) { [unowned self] in 126 | self.countdownLabel.animationType = .Sparkle 127 | self.countdownLabel.textColor = .yellowColor() 128 | } 129 | countdownLabel.start() { 130 | self.countdownLabel.textColor = .whiteColor() 131 | } 132 | 133 | // delegate 134 | func countingAt(timeCounted timeCounted: NSTimeInterval, timeRemaining: NSTimeInterval) { 135 | switch timeRemaining { 136 | case 10: 137 | self.countdownLabel6.animationType = .Pixelate 138 | self.countdownLabel6.textColor = .greenColor() 139 | case 5: 140 | self.countdownLabel6.animationType = .Sparkle 141 | self.countdownLabel6.textColor = .yellowColor() 142 | default: 143 | break 144 | } 145 | } 146 | func countdownFinished() { 147 | self.countdownLabel.textColor = .whiteColor() 148 | } 149 | 150 | ``` 151 | 152 | ![sample](Screenshots/example06.gif) 153 | 154 | #### Attributed Text 155 | you can set as attributedText too. note:but morphing animation will be disabled. 156 | ```swift 157 | countdownLabel.setCountDownTime(30) 158 | countdownLabel.timeFormat = "ss" 159 | countdownLabel.countdownAttributedText = CountdownAttributedText(text: "timer HERE in text", replacement: "HERE") 160 | countdownLabel.start() 161 | ``` 162 | 163 | ![sample](Screenshots/example07.gif) 164 | 165 | 166 | #### Format 167 | Don't specified over 24 hours or you'll get wrong format. 168 | CountdownLabel uses `00:00:00 (HH:mm:ss)` as default format. 169 | if you prefer using another format, Your can set your time format like below. 170 | 171 | 172 | `countdownLabel.timeFormat = @"mm:ss"` 173 | 174 | 175 | #### Scheduled 176 | you can set scheduled timer 177 | 178 | ```swift 179 | // after 10 minutes will start a countdown from 20. 180 | let fromDate = NSDate().dateByAddingTimeInterval(10) 181 | let targetDate = fromDate.dateByAddingTimeInterval(20) 182 | let countdownLabel = CountdownLabel(frame: CGRectZero, fromDate: fromDate, targetDate: targetDate) 183 | countdownLabel.start() 184 | ``` 185 | 186 | #### Check Status 187 | some public properties are useful. 188 | 189 | ```swift 190 | countdownLabel.isCounting // check timer is counting now 191 | countdownLabel.isPaused // check timer was stopped 192 | countdownLabel.isFinished // check timer has ended 193 | countdownLabel.morphingEnabled // check morphing is enabled 194 | ``` 195 | 196 | ## Requirements 197 | - iOS 9.0+ 198 | - Swift 2.3+ 199 | - ARC 200 | 201 | ##Installation 202 | 203 | ####CocoaPods 204 | available on CocoaPods. Just add the following to your project Podfile: 205 | ``` 206 | pod 'CountdownLabel' 207 | use_frameworks! 208 | ``` 209 | 210 | ####Carthage 211 | To integrate into your Xcode project using Carthage, specify it in your Cartfile: 212 | 213 | ```ogdl 214 | github "suzuki-0000/CountdownLabel" 215 | ``` 216 | 217 | ## Inspirations 218 | * [LTMorphingLabel](https://github.com/lexrus/LTMorphingLabel) is motivation for creating this. 219 | * In many reference from [MZTimerLabel](https://github.com/mineschan/MZTimerLabel). 220 | 221 | ## License 222 | available under the MIT license. See the LICENSE file for more info. 223 | 224 | 225 | -------------------------------------------------------------------------------- /Screenshots/example01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example01.gif -------------------------------------------------------------------------------- /Screenshots/example02.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example02.gif -------------------------------------------------------------------------------- /Screenshots/example03.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example03.gif -------------------------------------------------------------------------------- /Screenshots/example04.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example04.gif -------------------------------------------------------------------------------- /Screenshots/example05.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example05.gif -------------------------------------------------------------------------------- /Screenshots/example06.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example06.gif -------------------------------------------------------------------------------- /Screenshots/example07.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example07.gif -------------------------------------------------------------------------------- /Screenshots/example08.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/example08.gif -------------------------------------------------------------------------------- /Screenshots/exampleBurn.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/exampleBurn.gif -------------------------------------------------------------------------------- /Screenshots/exampleEvaporate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/exampleEvaporate.gif -------------------------------------------------------------------------------- /Screenshots/exampleFall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/exampleFall.gif -------------------------------------------------------------------------------- /Screenshots/examplePixelate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/examplePixelate.gif -------------------------------------------------------------------------------- /Screenshots/exampleScale.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/exampleScale.gif -------------------------------------------------------------------------------- /Screenshots/exampleSparkle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suzuki-0000/CountdownLabel/02a40e59048468bbc51939f091dcecabf18602f3/Screenshots/exampleSparkle.gif --------------------------------------------------------------------------------