├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── Resources └── logo.png ├── ThinkVerb.podspec ├── ThinkVerb ├── ThinkVerb.h └── ThinkVerb.m └── ThinkVerbDemo ├── ThinkVerbDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── ruitechen.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── ThinkVerbDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── 1.imageset │ ├── Contents.json │ ├── 升级@2x.png │ └── 升级@3x.png ├── 2.imageset │ ├── Contents.json │ ├── 通过@2x.png │ └── 通过@3x.png ├── 3.imageset │ ├── Contents.json │ ├── 退回@2x.png │ └── 退回@3x.png ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── Info.plist ├── TableViewController.h ├── TableViewController.m ├── View ├── TextProgressView.h └── TextProgressView.m ├── ViewController.h ├── ViewController.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | */.DS_Store 3 | ThinkVerbDemo/ThinkVerbDemo.xcodeproj/project.xcworkspace/xcuserdata/ 4 | ThinkVerbDemo/ThinkVerbDemo.xcodeproj/xcuserdata/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 HJ-Cai 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 | 2 | 3 | [![Cocoapods](https://img.shields.io/badge/pod-1.1.0-orange.svg)](https://img.shields.io/badge/pod-1.2.4-orange.svg) 4 | [![Platform](https://img.shields.io/badge/platform-iOS-lightgrey.svg)](https://img.shields.io/badge/platform-iOS-lightgrey.svg) 5 | [![Language](https://img.shields.io/badge/language-Objective--C-blue.svg)](https://img.shields.io/badge/language-Objective--C-blue.svg) 6 | 7 | [English Introduction](https://github.com/hon-key/ThinkVerb/blob/master/README_EN.md) 8 | 9 | # ThinkVerb 10 | ThinkVerb 是一组基于 CoreAnimation 的 API,相比与直接使用 CoreAnimation,ThinkVerb 通过链式语法进行编程,并且自管理 CAAnimation,你无需自己手动创建任何 CAAnimation 并将其添加到视图上。 11 | 12 | 得益于此,ThinkVerb 可以用非常少的代码快速生成基础动画,不单单如此,你所写的代码还相当可读而易于维护。 13 | 14 | 目前 ThinkVerb 的功能几乎涵盖了所有的基础动画,你可以轻松通过多个基础动画的组合来生成一个复杂的动画。如果用原生代码,你可能需要大量代码来完成此工作,但是用 ThinkVerb,你则可以在短短几行代码里完成相同的工作量。 15 | 16 | # Usage 17 | ThinkVerb 很简单,它只有一个入口,那就是 ThinkVerb 扩展 UIView 的一个属性:TVAnimation。 18 | 19 | TVAnimation 管理所有的动画单元,我们称动画单元为 Sprite,你需要做的只有:通过 TVAnimation 创建 sprite,配置 sprite,最后 activate sprite。 20 | 这样,动画就被激活,UIView 将自动开始动画。 21 | 22 | #### 例如,如果你想永不停息地旋转你的 UIView,你只需要下面这一句代码: 23 | ``` 24 | NSString *rotation = view.TVAnimation.rotate.z.endAngle(M_PI * 2).repeat(-1).activate(); 25 | ``` 26 | 或者,如果你想为你创建的 sprite 定义你自己想要的名字,你可以这么写: 27 | ``` 28 | view.TVAnimation.rotate.z.endAngle(M_PI * 2).repeat(-1).activateAs(@"rotation"); 29 | ``` 30 | 31 | 这行代码会绕着 z 轴旋转你的 UIView,其旋转角度是从 UIView 当前的角度旋转到 M_PI * 2,假设当前角度是 0,那就是转一圈。**`repeat(-1)`** 能够让 sprite 无限重复。最后,调用 **`activate()`** 就等于激活了该动画。 32 | 33 | #### 通常情况下,如果你没有让 sprite 永远重复下去,或者没有让 sprite 在动画结束时停留,sprite 会自动被移除并释放,而如上面的例子,你需要手动移除该动画: 34 | ``` 35 | view.TVAnimation.clear(); 36 | ``` 37 | #### 上面一行代码移除 view 的所有动画,通常情况下,你调用这一行代码就够了,如果你不想对 view 的其他动画造成影响,你可以只移除相应的动画: 38 | ``` 39 | view.TVAnimation.existSprite(rotation).stop(); 40 | ``` 41 | 42 | 如果你自己定义了名字,你可以这么做: 43 | ``` 44 | view.TVAnimation.existSprite(@"rotation").stop(); 45 | ``` 46 | 47 | 这样,旋转会停止,sprite 会被移除并释放,否则,就算 view 释放掉了,sprite 也不会被释放,从而造成内存泄漏。 48 | 49 | 你可以通过 ThinkVerbDemo 看到更多的例子。 50 | 51 | ThinkVerb 做复杂动画也是相当轻松的,你甚至可以写出一把手枪来: 52 | 53 | ``` 54 | view.TVAnimation.appearance.duration(3).timing(TVTiming.extremeEaseOut).end(); 55 | view.TVAnimation.contents.drawRange(nil,[UIImage imageNamed:@"1"]).didStop(^{ 56 | view.TVAnimation.contents.drawRange([UIImage imageNamed:@"1"],[UIImage imageNamed:@"2"]).didStop(^{ 57 | view.TVAnimation.contents.drawRange([UIImage imageNamed:@"2"],[UIImage imageNamed:@"3"]).didStop(^{ 58 | view.TVAnimation.contents.drawRange([UIImage imageNamed:@"3"],[UIImage imageNamed:@"2"]).activate(); 59 | }).activate(); 60 | }).activate(); 61 | }).activate(); 62 | ``` 63 | 64 | 65 | 66 | # Installation 67 | ## Using cocoapods 68 | ``` 69 | pod 'ThinkVerb' 70 | ``` 71 | 72 | # 版本信息 73 | 当前版本:1.1.0 74 | 75 | 更新: 76 | 77 | 适配 iOS 8 78 | 79 | ## Copy files 80 | 拷贝子 ThinkVerb 文件夹下的所有源码到你的工程 81 | 82 | # Indexes 83 | 84 | * ### Basic 85 | 86 | - [**move**](#move) **`从某个点移动 view 到另一个点`** 87 | 88 | - [**scale**](#scale) **`将 view 缩放到某个倍数`** 89 | 90 | - [**rotate**](#rotate) **`围绕 x/y/z 轴旋转 view`** 91 | 92 | - [**shadow**](#shadow) **`对 shadow 的 offset/opacity/radius/color 做动画,`** 93 | 94 | - [**bounds**](#bounds) **`对 view 的 bounds 做动画,注意该动画效果取决于 anchorPoint `** 95 | 96 | - [**anchor**](#anchor) **`对 view 的 anchorPoint 做动画,单独进行不会有任何效果,需要和相关的动画组合才会有效果`** 97 | 98 | - [**translate**](#translate) **`通过偏移来移动动画,基于 Transform3D,所以你可以将它应用到 sublayer 上`** 99 | 100 | - [**fade**](#fade) **`淡入淡出`** 101 | 102 | - [**contents**](#contents) **`对 cotnents 属性做动画,如 rect属性会对位图的渲染范围做动画,范围在 [0 0 1 1] 内`** 103 | 104 | - [**backgroundColor**](#backgroundColor) **`背景变换`** 105 | 106 | - [**cornerRadius**](#cornerRadius) **`圆角动画`** 107 | 108 | - [**border**](#border) **`对 view 的边框的宽度和颜色做动画`** 109 | 110 | - [**path**](#path) **`对 view 做关键帧动画,可通过贝塞尔控制点生成曲线动画`** 111 | 112 | - [**basicCustom**](#path) **`对 view 做自定义动画,可快速对自定义layer进行动画控制`** 113 | 114 | * ### Appearance 115 | appearance sprite 可以用来对某个 view 配置默认参数,如果你想让某个 view 的所有 sprite 默认在动画结束时停留而不移除,你可以在生成 sprite 之前写: 116 | ``` 117 | view.TVAnimation.appearance.keepAlive(YES).end(); 118 | ``` 119 | 120 | 121 | 122 | ## License 123 | 124 | ThinkVerb is released under the MIT license. See [LICENSE](https://github.com/hon-key/ThinkVerb/blob/master/LICENSE) for details. 125 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ThinkVerb 4 | ThinkVerb is an Animation Interface based on CoreAnimation, it help you make CAAnimation for view's layer easily. ThinkVerb uses chain programming style to mak CAAnimation. Most of the time you just need to type one line of code to make an animation even if it is complicated. So you can do animation anywhere easily and the code is so human readable. 5 | 6 | # Usage 7 | ThinkVerb just have one entrance,that is TVAnimation of an UIView,it is a manager of animation sprite,all you need to do is make an animation sprite using a TVAnimation and then activate it. 8 | 9 | #### Take an example,if you want to rotate an UIView forever,just type: 10 | ``` 11 | NSString *rotation = view.TVAnimation.rotate.z.endAngle(M_PI * 2).repeat(-1).activate(); 12 | ``` 13 | or you can create sprite with specific name: 14 | ``` 15 | view.TVAnimation.rotate.z.endAngle(M_PI * 2).repeat(-1).activateAs(@"rotation"); 16 | ``` 17 | 18 | The code rotate your view around the z axis from current angle to endAngle, asume that the current angle is 0, your view will make a turn. **`repeat(-1)`** make this animation repeat forever. At last you just need to call **`activate()`** and the animation will automatically run. 19 | 20 | #### If you want to stop rotation, most of time you just need to type: 21 | ``` 22 | view.TVAnimation.clear(); 23 | ``` 24 | #### The action clear all animations of the view. You can also type: 25 | ``` 26 | view.TVAnimation.existSprite(rotation).stop(); 27 | ``` 28 | 29 | or using specific name: 30 | ``` 31 | view.TVAnimation.existSprite(@"rotation").stop(); 32 | ``` 33 | 34 | The action stop and release the rotation animation 35 | 36 | You can see more animation example in ThinkVerbDemo project 37 | 38 | You can combine any animation,even like a gun: 39 | 40 | ``` 41 | view.TVAnimation.appearance.duration(3).timing(TVTiming.extremeEaseOut).end(); 42 | view.TVAnimation.contents.drawRange(nil,[UIImage imageNamed:@"1"]).didStop(^{ 43 | view.TVAnimation.contents.drawRange([UIImage imageNamed:@"1"],[UIImage imageNamed:@"2"]).didStop(^{ 44 | view.TVAnimation.contents.drawRange([UIImage imageNamed:@"2"],[UIImage imageNamed:@"3"]).didStop(^{ 45 | view.TVAnimation.contents.drawRange([UIImage imageNamed:@"3"],[UIImage imageNamed:@"2"]).activate(); 46 | }).activate(); 47 | }).activate(); 48 | }).activate(); 49 | ``` 50 | 51 | 52 | 53 | # Installation 54 | ## Using cocoapods 55 | ``` 56 | pod 'ThinkVerb' 57 | ``` 58 | 59 | ## Copy files 60 | Copy all files from ThinkVerb folder to your project 61 | 62 | # Indexes 63 | 64 | * ### Basic 65 | 66 | - [**move**](#move) **`animate your view's position from one place to another place, position is related to anchorPoint`** 67 | 68 | - [**scale**](#scale) **`scale your view with times param`** 69 | 70 | - [**rotate**](#rotate) **`rotate your view around x/y/z axis`** 71 | 72 | - [**shadow**](#shadow) **`animate shadow offset/opacity/radius/color of a view,`** 73 | 74 | - [**bounds**](#bounds) **`aniamte bounds of a view's layer,bounds,the effect is related to view position `** 75 | 76 | - [**anchor**](#anchor) **`animate anchorPoint,normally you should animate anchor with other related animations`** 77 | 78 | - [**translate**](#translate) **`animte your view's position using offset, can be apply to sublayer`** 79 | 80 | - [**fade**](#fade) **`animate your view's opacity`** 81 | 82 | - [**contents**](#contents) **`animate bitmap of layer,using rect to animate rectangle of bitmap with range of [0 0 1 1],etc`** 83 | 84 | - [**backgroundColor**](#backgroundColor) **`aniamte background color of an UIView`** 85 | 86 | - [**cornerRadius**](#cornerRadius) **`animate cornerRadius of an UIView`** 87 | 88 | - [**border**](#border) **`animate border's width and color of an UIView`** 89 | 90 | - [**path**](#path) **`animate transition path of an UIView's layer`** 91 | 92 | * ### Appearance 93 | appearance sprite is used to configure default value to all sprite of an UIView, take an example,if you want all animation keep alive when finished,you may do it like this before you make any sprite: 94 | ``` 95 | view.TVAnimation.appearance.keepAlive(YES).end(); 96 | ``` 97 | 98 | 99 | 100 | ## License 101 | 102 | ThinkVerb is released under the MIT license. See [LICENSE](https://github.com/hon-key/ThinkVerb/blob/master/LICENSE) for details. 103 | -------------------------------------------------------------------------------- /Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/Resources/logo.png -------------------------------------------------------------------------------- /ThinkVerb.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint NudeIn.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "ThinkVerb" 19 | s.version = "1.1.0" 20 | s.summary = "A super cool animation interface based on CoreAnimation." 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | # s.description = <<-DESC 28 | # A attributed text component like masonry.. 29 | # DESC 30 | 31 | s.homepage = "https://github.com/hon-key/ThinkVerb" 32 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 33 | 34 | 35 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 36 | # 37 | # Licensing your code is important. See http://choosealicense.com for more info. 38 | # CocoaPods will detect a license file if there is a named LICENSE* 39 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 40 | # 41 | 42 | s.license = "MIT" 43 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 44 | 45 | 46 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 47 | # 48 | # Specify the authors of the library, with email addresses. Email addresses 49 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 50 | # accepts just a name if you'd rather not provide an email address. 51 | # 52 | # Specify a social_media_url where others can refer to, for example a twitter 53 | # profile URL. 54 | # 55 | 56 | s.author = { "HJ-Cai" => "lj_chj@126.com" } 57 | # Or just: s.author = "CAI" 58 | # s.authors = { "CAI" => "hongji.cai@netvue.com" } 59 | # s.social_media_url = "http://twitter.com/CAI" 60 | 61 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 62 | # 63 | # If this Pod runs only on iOS or OS X, then specify the platform and 64 | # the deployment target. You can optionally include the target after the platform. 65 | # 66 | 67 | # s.platform = :ios 68 | s.platform = :ios, "8.0" 69 | # When using multiple platforms 70 | # s.ios.deployment_target = "5.0" 71 | # s.osx.deployment_target = "10.7" 72 | # s.watchos.deployment_target = "2.0" 73 | # s.tvos.deployment_target = "9.0" 74 | 75 | 76 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 77 | # 78 | # Specify the location from where the source should be retrieved. 79 | # Supports git, hg, bzr, svn and HTTP. 80 | # 81 | 82 | s.source = { :git => "https://github.com/hon-key/ThinkVerb.git", :tag => s.version } 83 | 84 | 85 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 86 | # 87 | # CocoaPods is smart about how it includes source code. For source files 88 | # giving a folder will include any swift, h, m, mm, c & cpp files. 89 | # For header files it will include any header in the folder. 90 | # Not including the public_header_files will make all headers public. 91 | # 92 | 93 | s.source_files = "ThinkVerb", "ThinkVerb/**/*.{h,m}" 94 | # s.exclude_files = "Classes/Exclude" 95 | 96 | # s.public_header_files = "Classes/**/*.h" 97 | 98 | 99 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 100 | # 101 | # A list of resources included with the Pod. These are copied into the 102 | # target bundle with a build phase script. Anything else will be cleaned. 103 | # You can preserve files from being cleaned, please don't preserve 104 | # non-essential files like tests, examples and documentation. 105 | # 106 | 107 | # s.resource = "icon.png" 108 | # s.resources = "Resources/*.png" 109 | 110 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 111 | 112 | 113 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 114 | # 115 | # Link your library with frameworks, or libraries. Libraries do not include 116 | # the lib prefix of their name. 117 | # 118 | 119 | s.frameworks = "UIKit", "Foundation" 120 | # s.frameworks = "SomeFramework", "AnotherFramework" 121 | 122 | # s.library = "iconv" 123 | # s.libraries = "iconv", "xml2" 124 | 125 | 126 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 127 | # 128 | # If your library depends on compiler flags you can set them in the xcconfig hash 129 | # where they will only apply to your library. If you depend on other Podspecs 130 | # you can include multiple dependencies to ensure it works. 131 | 132 | # s.requires_arc = true 133 | 134 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 135 | # s.dependency "JSONKit", "~> 1.4" 136 | 137 | s.requires_arc = true 138 | 139 | end 140 | -------------------------------------------------------------------------------- /ThinkVerb/ThinkVerb.h: -------------------------------------------------------------------------------- 1 | // ThinkVerb.h 2 | // Copyright (c) 2019 HJ-Cai 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | #import 23 | #import 24 | 25 | @class ThinkVerb, ThinkVerbSprite, ThinkVerbSpriteAppearance; 26 | @class ThinkVerbSpriteBasic, ThinkVerbSpriteGroup; 27 | @class TVSpriteRotate, TVSpriteScale, TVSpriteMove, TVSpriteFade; 28 | @class TVSpriteShadow, TVSpriteBounds, TVSpriteAnchor, TVSpriteTranslate; 29 | @class TVSpriteContents, TVSpriteColor, TVSpriteCornerRadius, TVSpriteBorder; 30 | @class TVSpritePath,TVSpriteBasicCustom; 31 | 32 | /** 33 | Every animation prite are inherit from class ThinkVerbSprite. And the class ThinkVerbSprite implement the protocol ThinkVerbSpriteLike. So all the animation sprites are ThinkVerbSpriteLike. 34 | This protocol is used to recognize an object which is a ThinkVerbSprite. 35 | */ 36 | @protocol ThinkVerbSpriteLike 37 | @end 38 | 39 | /** 40 | The sprite which animates the CATransform3D property has implemented this protocol. 41 | ThinkVerbTransform3DType is used to convert a CATransform3D animation to sublayers animation. 42 | If you want to apply your animation sprite to every sublayer, call -toSubLayer 43 | */ 44 | @protocol ThinkVerbTransform3DType 45 | @optional 46 | - (instancetype)toSubLayer; 47 | - (NSString * (^)(void))activate; 48 | @end 49 | 50 | @interface UIView (ThinkVerb) 51 | /** 52 | Get the animation manager of a UIView. 53 | Use the animation manager to create a ThinkVerbSprite and do animation operations. 54 | This is the only entrance you should call in a UIView. 55 | */ 56 | - (ThinkVerb *)TVAnimation; 57 | @end 58 | 59 | @interface CALayer (ThinkVerb) 60 | /** 61 | Get the animation manager of a CALayer. 62 | Use the animation manager to create a ThinkVerbSprite and do animation operations. 63 | This is the only entrance you should call in a CALayer. 64 | And,is recommanded that you should not use this entrance in an UIView's layer, use UIView's TVAnimation as an entrance instead. 65 | */ 66 | - (ThinkVerb *)TVAnimation; 67 | @end 68 | 69 | /** 70 | Animation manager of a UIView 71 | */ 72 | @interface ThinkVerb : NSObject 73 | /** 74 | The view which the receiver owns and create animation to. If you use a CALayer as an entrance, this property is nil 75 | */ 76 | @property (nonatomic,weak) UIView *view; 77 | /** 78 | The layer which the receiver owns and create animation to. If you use an UIView as an entrance, this property is the layer of the UIView 79 | */ 80 | @property (nonatomic,weak) CALayer *layer; 81 | /** 82 | Sprites contain all animations which is Animating / finished. 83 | Normaly, when an animation(sprite) is done, it will be removed from manager and auto released, but particularly when you call -keepAlive of a sprite, the animation will not be removed, You must remove it yourself by calling -stop of the sprite or call -clear of the manager which owns it. 84 | */ 85 | @property (nonatomic,strong) NSMutableSet *sprites; 86 | /** 87 | return a CALayer which is presenting on the screen. According to the Mechanism of CoreAnimation, the return layer may be a presentationLayer or the layer of an animation view. This depend on whether the animation has began. 88 | */ 89 | - (CALayer *)presentationLayer; 90 | /** 91 | use this method to get the exist sprite according to the id which was returned by -activate method. 92 | you probably use this method to get a sprite and stop it to remove the sprite from a manager. 93 | */ 94 | - (ThinkVerbSprite * (^) (NSString *))existSprite; 95 | /** 96 | clears all sprites of a animation manager. all sprites will be auto released. 97 | */ 98 | - (void (^) (void))clear; 99 | /** 100 | ThinkVerbSpriteAppearance manage the global variables. use it to apply default values, and all sprites you create later use these default values. 101 | */ 102 | - (ThinkVerbSpriteAppearance *)appearance; 103 | /** 104 | Creates an animation sprite to move the view from one point to another point. (CATransform3D). 105 | */ 106 | - (TVSpriteMove *)move; 107 | /** 108 | Creates an animation sprite to scale the view (CATransform3D). 109 | */ 110 | - (TVSpriteScale *)scale; 111 | /** 112 | Creates an animation sprite to rotate the view (CATransform3D). 113 | */ 114 | - (TVSpriteRotate *)rotate; 115 | /** 116 | Creates an animation sprite to animate shadow of the view. 117 | */ 118 | - (TVSpriteShadow *)shadow; 119 | /** 120 | Creates an animation sprite to animate bounds of the view. 121 | */ 122 | - (TVSpriteBounds *)bounds; 123 | /** 124 | Creates an animation sprite to animate anchor point of the view. a 125 | Anchor point affects bounds and transform property. 126 | */ 127 | - (TVSpriteAnchor *)anchor; 128 | /** 129 | Creates an animation sprite to animate translation (CATransform3D). 130 | */ 131 | - (TVSpriteTranslate *)translate; 132 | /** 133 | Creates an animation sprite to animate opacity of the view. 134 | */ 135 | - (TVSpriteFade *)fade; 136 | /** 137 | Creates an animation sprite to animate contents(bitmap) of the view. 138 | */ 139 | - (TVSpriteContents *)contents; 140 | /** 141 | Creates an animation sprite to animate backgroundColor of the view. 142 | */ 143 | - (TVSpriteColor *)backgroundColor; 144 | /** 145 | Creates an animation sprite to animate cornerRadius of the view. 146 | */ 147 | - (TVSpriteCornerRadius *)cornerRadius; 148 | /** 149 | Creates an animation sprite to animate border width and color of the view. 150 | */ 151 | - (TVSpriteBorder *)border; 152 | /** 153 | Creates an animation sprite to animate path 154 | */ 155 | - (TVSpritePath *)path; 156 | 157 | /** 158 | Creates an animation sprite to animate custom basic animation 159 | */ 160 | - (TVSpriteBasicCustom *)basicCustom; 161 | 162 | 163 | @end 164 | 165 | /** 166 | Timing function Encapsulation. 167 | Recommend you to use Tween-o-Matic to check the effect of the value you set. 168 | The chinese version is https://github.com/YouXianMing/Tween-o-Matic-CN 169 | */ 170 | @interface TVTiming : CAMediaTimingFunction 171 | /** 172 | Create a timing function 173 | */ 174 | + (id (^) (CGFloat,CGFloat,CGFloat,CGFloat))create; 175 | /** 176 | Ease out timing function 177 | */ 178 | + (id)extremeEaseOut; 179 | /** 180 | Ease In timing function 181 | */ 182 | + (id)extremeEaseIn; 183 | /** 184 | Linear timing function 185 | */ 186 | + (id)linear; 187 | @end 188 | 189 | #pragma mark - Basic Sprite 190 | /** 191 | ThinkVerbSprite is the basic class of all sprites. 192 | */ 193 | @interface ThinkVerbSprite : NSObject 194 | /** 195 | Every sprite has an identifier, you use identifier to recognize which animation sprite you want. 196 | */ 197 | @property (nonatomic,copy) NSString *identifier; 198 | /** 199 | The manager which owns the receiver. 200 | */ 201 | @property (nonatomic,weak) ThinkVerb *thinkVerb; 202 | /** 203 | The animation. This may be a CABasicAnimation or CAAnimationGroup. 204 | */ 205 | @property (nonatomic,strong) A animation; 206 | /** 207 | sets repeat count for animation, < 0 represent forever. 208 | */ 209 | - (T (^)(NSInteger))repeat; 210 | /** 211 | sets animation duration time. 212 | */ 213 | - (T (^)(CGFloat))duration; 214 | /** 215 | sets animation delay time 216 | */ 217 | - (T (^)(CGFloat))delay; 218 | /** 219 | sets animation timing function. 220 | if you are setting this property to keyframe animation like 'path',timing should be call after a calling of keyframe action,the timing funtion is bounded to the keyframe action. 221 | notice that if you are setting the timing property of appearance sprite,than you make a keyframe animation like 'path',timing property will effect the whole animation except every keyframe. 222 | */ 223 | - (T (^)(TVTiming *))timing; 224 | /** 225 | Determines if the receiver plays in the reverse upon completion. 226 | */ 227 | - (T)reverse; 228 | /** 229 | Determines if the animation is removed from the target layer’s animations upon completion,default is NO. 230 | */ 231 | - (T (^)(BOOL))keepAlive; 232 | /** 233 | activates the animation and return the identifier of the animation. 234 | */ 235 | - (NSString * (^)(void))activate; 236 | - (void (^)(NSString *))activateAs; 237 | /** 238 | stops the animation and removes it. 239 | */ 240 | - (void (^)(void))stop; 241 | /** 242 | called when the animation stops. 243 | */ 244 | - (T (^)(void(^)(void)))didStop; 245 | 246 | - (T)with; 247 | @end 248 | 249 | @interface ThinkVerbTransform3DTypeDefaultImplementation : ThinkVerbSprite 250 | @end 251 | 252 | @interface ThinkVerbSpriteAppearance : ThinkVerbSprite 253 | - (void(^)(void))end; 254 | @end 255 | 256 | @interface ThinkVerbSpriteBasic : ThinkVerbSprite 257 | @property (nonatomic,copy) NSString *keyPath; 258 | /* When true the value specified by the animation will be "added" to 259 | * the current presentation value of the property to produce the new 260 | * presentation value. The addition function is type-dependent, e.g. 261 | * for affine transforms the two matrices are concatenated. Defaults to 262 | * NO. */ 263 | - (T (^)(BOOL))additive; 264 | /* The `cumulative' property affects how repeating animations produce 265 | * their result. If true then the current value of the animation is the 266 | * value at the end of the previous repeat cycle, plus the value of the 267 | * current repeat cycle. If false, the value is simply the value 268 | * calculated for the current repeat cycle. Defaults to NO. */ 269 | - (T (^)(BOOL))cumulative; 270 | /* If non-nil a function that is applied to interpolated values 271 | * before they are set as the new presentation value of the animation's 272 | * target property. Defaults to nil. */ 273 | - (T (^)(CAValueFunction *))valueFunction; 274 | 275 | /* 276 | Setting spring property for sprite 277 | mass: The mass of the object attached to the end of the spring. Must be greater than 0. Defaults to one 278 | stiffness: The spring stiffness coefficient. Must be greater than 0. Defaults to 100. 279 | damping: The damping coefficient. Must be greater than or equal to 0. Defaults to 10. 280 | initialVelocity: The initial velocity of the object attached to the spring. Defaults to zero, which represents an unmoving object. Negative values represent the object moving away from the spring attachment point, positive values represent the object moving towards the spring attachment point. 281 | */ 282 | - (T (^)(CGFloat))mass; 283 | - (T (^)(CGFloat))stiffness; 284 | - (T (^)(CGFloat))damping; 285 | - (T (^)(CGFloat))initialVelocity; 286 | 287 | @end 288 | 289 | @interface ThinkVerbSpriteGroup : ThinkVerbSprite 290 | /* 291 | Spring property in ThinkVerbSpriteGroup is the same as in ThinkVerbSpriteBasic, see ThinkVerbSpriteBasic for more detail 292 | */ 293 | - (T (^)(CGFloat))mass; 294 | - (T (^)(CGFloat))stiffness; 295 | - (T (^)(CGFloat))damping; 296 | - (T (^)(CGFloat))initialVelocity; 297 | @end 298 | 299 | @interface ThinkVerbSpriteKeyframe : ThinkVerbSprite 300 | @property (nonatomic,copy) NSString *keyPath; 301 | /* 302 | The "mode". Possible values are `kCAAnimationDiscrete', `kCAAnimationLinear', 303 | `kCAAnimationPaced', `kCAAnimationCubic' and `kCAAnimationCubicPaced'. 304 | Defaults to `kCAAnimationLinear'. When set to 305 | `kCAAnimationPaced' or `kCAAnimationCubicPaced' the `timing' and `endAtPercent' 306 | properties of the animation are ignored and calculated implicitly. 307 | */ 308 | - (T (^)(CAAnimationCalculationMode))mode; 309 | /** 310 | sets the end point of a keyFrame animation action,percent value should be in range of [0,100], 311 | and the percent of begin point and end point must be 0 and 100, every percent must be larger than the previous percent 312 | you no need to call this method all the time, because system will automatically caculate the average value between the two point if there are points betwwen them. 313 | */ 314 | - (T (^)(NSInteger))endAtPercent; 315 | @end 316 | 317 | #pragma mark - General Sprite 318 | /** 319 | CustomView 320 | */ 321 | @interface TVSpriteBasicCustom : ThinkVerbSpriteBasic 322 | - (TVSpriteBasicCustom * (^)(NSString *))property; 323 | - (TVSpriteBasicCustom * (^)(id))from; 324 | - (TVSpriteBasicCustom * (^)(id))to; 325 | - (TVSpriteBasicCustom * (^)(id))by; 326 | @end 327 | 328 | #pragma mark - Sprite 329 | /** 330 | Rotation sprite,based on ThinkVerbTransform3DType 331 | 'x' : sets the x axis as rotation axis 332 | 'y' : sets the y axis as rotation axis 333 | 'z' : sets the z axis as rotation axis 334 | 'angle' : sets rotation angle range 335 | 'startAngle' : sets rotation start angle 336 | 'endAngle' : sets rotation end angle 337 | */ 338 | @interface TVSpriteRotate : ThinkVerbSpriteBasic 339 | - (TVSpriteRotate *)x; 340 | - (TVSpriteRotate *)y; 341 | - (TVSpriteRotate *)z; 342 | - (TVSpriteRotate * (^)(CGFloat,CGFloat))angle; 343 | - (TVSpriteRotate * (^)(CGFloat))startAngle; 344 | - (TVSpriteRotate * (^)(CGFloat))endAngle; 345 | @end 346 | 347 | /** 348 | Scale sprite,use this sprite to scale an UIView 349 | 'x/y/z' : sets the scale factor of x/y/z axis 350 | 'xTo/yTo/zTo' : sets the scale factor of x/y/z axis to a value,with current value as a start value 351 | 'from' : sets a start value to scale factor of all axis, you should call 'to' method after calling this 352 | 'to' : sets a end value to scale factor of all axis, you no need to call 'from' mehod before calling this 353 | */ 354 | @interface TVSpriteScale : ThinkVerbSpriteGroup 355 | - (TVSpriteScale * (^)(CGFloat,CGFloat))x; 356 | - (TVSpriteScale * (^)(CGFloat,CGFloat))y; 357 | - (TVSpriteScale * (^)(CGFloat,CGFloat))z; 358 | - (TVSpriteScale * (^)(CGFloat))xTo; 359 | - (TVSpriteScale * (^)(CGFloat))yTo; 360 | - (TVSpriteScale * (^)(CGFloat))zTo; 361 | - (TVSpriteScale * (^)(CGFloat))from; 362 | - (TVSpriteScale * (^)(CGFloat))to; 363 | @end 364 | 365 | /** 366 | Translate sprite,use this sprite to make translation to an UIView,using offset value 367 | 'x/y/z' : sets the translation offset of x/y/z axis 368 | 'xBy/yBy/zBy' : sets the translation offset of x/y/z axis to a value,with current value as a start value 369 | 'from' : sets a start value to translation offset of all axis, you should call 'to' method after calling this 370 | 'to' : sets a end value to scale factor of all axis, you no need to call 'from' mehod before calling this 371 | */ 372 | @interface TVSpriteTranslate : ThinkVerbSpriteGroup 373 | - (TVSpriteTranslate * (^)(CGFloat,CGFloat,CGFloat))from; 374 | - (TVSpriteTranslate * (^)(CGFloat,CGFloat,CGFloat))to; 375 | - (TVSpriteTranslate * (^)(CGFloat,CGFloat))x; 376 | - (TVSpriteTranslate * (^)(CGFloat,CGFloat))y; 377 | - (TVSpriteTranslate * (^)(CGFloat,CGFloat))z; 378 | - (TVSpriteTranslate * (^)(CGFloat))xBy; 379 | - (TVSpriteTranslate * (^)(CGFloat))yBy; 380 | - (TVSpriteTranslate * (^)(CGFloat))zBy; 381 | @end 382 | 383 | /** 384 | Move sprite,use this sprite to make translation to an UIView,different from 'TVSpriteTranslate',it use logical coordinate to make translation,and is not a 'ThinkVerbTransform3DType' so you cannot apply it to sublayer 385 | 'x/y/z' : sets the translation offset of x/y/z axis 386 | 'xBy/yBy/zBy' : sets the translation offset of x/y/z axis to a value,with current value as a start value 387 | 'from' : sets a start value to translation offset of all axis, you should call 'to' method after calling this 388 | 'to' : sets a end value to scale factor of all axis, you no need to call 'from' mehod before calling this 389 | */ 390 | @interface TVSpriteMove : ThinkVerbSpriteGroup 391 | - (TVSpriteMove * (^)(CGFloat,CGFloat,CGFloat))offset; 392 | - (TVSpriteMove * (^)(CGFloat,CGFloat,CGFloat))from; 393 | - (TVSpriteMove * (^)(CGFloat,CGFloat,CGFloat))to; 394 | - (TVSpriteMove * (^)(CGFloat,CGFloat))x; 395 | - (TVSpriteMove * (^)(CGFloat,CGFloat))y; 396 | - (TVSpriteMove * (^)(CGFloat,CGFloat))z; 397 | - (TVSpriteMove * (^)(CGFloat))xTo; 398 | - (TVSpriteMove * (^)(CGFloat))yTo; 399 | - (TVSpriteMove * (^)(CGFloat))zTo; 400 | @end 401 | 402 | /** 403 | Shadow sprite,use this sprite to make animate shaddow to an UIView 404 | 'offset/offsetTo' : animate shadowOffset,use offset to animate in range,use offsetTo to animate from current value to target value 405 | 'opacity/opacityTo' : animate shadowOpacity 406 | 'radius/radiusTo' : animate shadowRadius 407 | 'color/colorTo' : animate shadowColor 408 | */ 409 | @interface TVSpriteShadow : ThinkVerbSpriteGroup 410 | - (TVSpriteShadow * (^)(CGFloat,CGFloat))offsetTo; 411 | - (TVSpriteShadow * (^)(CGSize,CGSize))offset; 412 | 413 | - (TVSpriteShadow * (^)(CGFloat))opacityTo; 414 | - (TVSpriteShadow * (^)(CGFloat,CGFloat))opacity; 415 | 416 | - (TVSpriteShadow * (^)(CGFloat))radiusTo; 417 | - (TVSpriteShadow * (^)(CGFloat,CGFloat))radius; 418 | 419 | - (TVSpriteShadow * (^)(UIColor *))colorTo; 420 | - (TVSpriteShadow * (^)(UIColor *,UIColor *))color; 421 | @end 422 | 423 | /** 424 | Bounds sprite,use this sprite to animate Bounds of an UIView,this will makes an effect like what you make using scale sprite 425 | 'width/widthTo' : animate width of the bounds of view to a value 426 | 'height/heightTo' : animate height of the bounds of view to a value 427 | 'from/to' : animate height and height of the bounds of view to a value 428 | */ 429 | @interface TVSpriteBounds : ThinkVerbSpriteGroup 430 | - (TVSpriteBounds * (^)(CGFloat,CGFloat))width; 431 | - (TVSpriteBounds * (^)(CGFloat,CGFloat))height; 432 | - (TVSpriteBounds * (^)(CGFloat))widthTo; 433 | - (TVSpriteBounds * (^)(CGFloat))heightTo; 434 | - (TVSpriteBounds * (^)(CGFloat,CGFloat))from; 435 | - (TVSpriteBounds * (^)(CGFloat,CGFloat))to; 436 | @end 437 | 438 | /** 439 | Anchor sprite,use this sprite to animate anchor of an UIView 440 | Normally,anchor is related to bounds,move and ThinkVerbTransform3DType sprite,more detail in CoreAnimation Programming Guide 441 | */ 442 | @interface TVSpriteAnchor : ThinkVerbSpriteGroup 443 | - (TVSpriteAnchor * (^)(CGFloat,CGFloat,CGFloat))offset; 444 | - (TVSpriteAnchor * (^)(CGFloat,CGFloat,CGFloat))from; 445 | - (TVSpriteAnchor * (^)(CGFloat,CGFloat,CGFloat))to; 446 | - (TVSpriteAnchor * (^)(CGFloat,CGFloat))x; 447 | - (TVSpriteAnchor * (^)(CGFloat,CGFloat))y; 448 | - (TVSpriteAnchor * (^)(CGFloat,CGFloat))z; 449 | - (TVSpriteAnchor * (^)(CGFloat))xTo; 450 | - (TVSpriteAnchor * (^)(CGFloat))yTo; 451 | - (TVSpriteAnchor * (^)(CGFloat))zTo; 452 | @end 453 | 454 | /** 455 | Fade sprite,use this sprite to animate opacity of an UIView 456 | */ 457 | @interface TVSpriteFade : ThinkVerbSpriteBasic 458 | - (TVSpriteFade *)fadeOut; 459 | - (TVSpriteFade *)fadeIn; 460 | - (TVSpriteFade * (^) (CGFloat))from; 461 | - (TVSpriteFade * (^) (CGFloat))to; 462 | @end 463 | 464 | /** 465 | Contents sprite,use this sprite to animate bitmap of an UIView's layer 466 | 'drawRange' : animate from a bitmap to another bitmap, nil represents no bitmap 467 | 'rect/rectRange' : animate rectangle of the contents,a rectangle in normalized image coordinates defining the 468 | subrectangle of the `contents' property that will be drawn into the 469 | layer. If pixels outside the unit rectangles are requested, the edge 470 | pixels of the contents image will be extended outwards 471 | 'scale/scaleRange' : animate contentsScale factor of an UIView's layer contents 472 | 'center/centerRange' : contentsCenter defines a area to scale,default is [0 0 1 1],meaning of scall all of the contents 473 | */ 474 | @interface TVSpriteContents : ThinkVerbSpriteGroup 475 | - (TVSpriteContents * (^)(CGRect))rect; 476 | - (TVSpriteContents * (^)(CGFloat))scale; 477 | - (TVSpriteContents * (^)(CGRect))center; 478 | 479 | - (TVSpriteContents * (^)(UIImage *,UIImage *))drawRange; 480 | - (TVSpriteContents * (^)(CGRect,CGRect))rectRange; 481 | - (TVSpriteContents * (^)(CGFloat,CGFloat))scaleRange; 482 | - (TVSpriteContents * (^)(CGRect,CGRect))centerRange; 483 | 484 | - (TVSpriteContents * (^)(CGFloat,CGFloat))minificationFilterBias; 485 | @end 486 | 487 | /** 488 | Color sprite,use this sprite to animate color of an UIView's layer. 489 | */ 490 | @interface TVSpriteColor : ThinkVerbSpriteBasic 491 | - (TVSpriteColor * (^)(UIColor *))transitTo; 492 | - (TVSpriteColor * (^)(UIColor *,UIColor *))transit; 493 | @end 494 | 495 | /** 496 | CornerRadius sprite,use this sprite to animate cornerRadius of an UIView's layer. 497 | */ 498 | @interface TVSpriteCornerRadius : ThinkVerbSpriteBasic 499 | - (TVSpriteCornerRadius * (^)(CGFloat))transitTo; 500 | - (TVSpriteCornerRadius * (^)(CGFloat,CGFloat))transit; 501 | @end 502 | 503 | /** 504 | Border sprite,use this sprite to animate border width and border color of an UIView's layer. 505 | */ 506 | @interface TVSpriteBorder : ThinkVerbSpriteGroup 507 | - (TVSpriteBorder * (^)(CGFloat,CGFloat))width; 508 | - (TVSpriteBorder * (^)(CGFloat))widthTo; 509 | - (TVSpriteBorder * (^)(UIColor *,UIColor *))color; 510 | - (TVSpriteBorder * (^)(UIColor *))colorTo; 511 | @end 512 | 513 | /** 514 | Path sprite,use this sprite to animate transition path of an UIView's layer 515 | You use this to make a custom moving path,first you may call -beginWith to set your origin point, if you don't,sprite will sets it to current presentation layer position 516 | You can call -timing to set timing function for evey animation point which is set from -lineto and -curveTo. if you don't sprite will automatically set the missing timing function property to linear timing function 517 | You can call -endAtPercent to set end time for every animation point which is set from -lineto and -curveTo,if you don't sprite will automatically caculate missing keytimes evenly 518 | You should call -cpt1 and -cpt2 to set control point for -curveTo to let system know how to do interpolation calculation 519 | */ 520 | @interface TVSpritePath : ThinkVerbSpriteKeyframe 521 | - (TVSpritePath * (^)(CGFloat,CGFloat))beginWith; 522 | - (TVSpritePath * (^)(CGFloat,CGFloat))lineTo; 523 | - (TVSpritePath * (^)(CGFloat,CGFloat))curveTo; 524 | - (TVSpritePath * (^)(CGFloat,CGFloat))cpt1; 525 | - (TVSpritePath * (^)(CGFloat,CGFloat))cpt2; 526 | @end 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | -------------------------------------------------------------------------------- /ThinkVerb/ThinkVerb.m: -------------------------------------------------------------------------------- 1 | // ThinkVerb.m 2 | // Copyright (c) 2019 HJ-Cai 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | #import "ThinkVerb.h" 23 | #import 24 | 25 | #pragma clang diagnostic push 26 | #pragma clang diagnostic ignored "-Wincompatible-pointer-types" 27 | 28 | /* 29 | Especially for ThinkVerbTransform3DType sprite,we maintain a HasTable to store which ThinkVerbTransform3DType sprite has added default methods 30 | */ 31 | static NSMutableDictionary *tv_get_registered_3d_type_sprites() { 32 | static NSMutableDictionary *sprites; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | sprites = [[NSMutableDictionary alloc] init]; 36 | }); 37 | return sprites; 38 | } 39 | 40 | static void tv_cache_animations_into_sprite(ThinkVerbSprite *sprite,id animations,SEL cmd) { 41 | objc_setAssociatedObject(sprite, cmd, animations, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 42 | } 43 | static id tv_get_animations_from_sprite(ThinkVerbSprite *sprite,SEL cmd) { 44 | id animations = objc_getAssociatedObject(sprite, cmd); 45 | if (!animations) return nil; 46 | objc_setAssociatedObject(sprite, cmd, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 47 | return animations; 48 | } 49 | 50 | #pragma clang diagnostic push 51 | #pragma clang diagnostic ignored "-Wunguarded-availability" 52 | static CASpringAnimation *tv_create_spring_animation_with_basicAnimation(CABasicAnimation *basicAnimation) { 53 | if ([basicAnimation isKindOfClass:[CASpringAnimation class]]) { 54 | return basicAnimation; 55 | } 56 | assert([basicAnimation isKindOfClass:[CABasicAnimation class]]); 57 | CASpringAnimation *springAnimation = [[CASpringAnimation alloc] init]; 58 | springAnimation.fromValue = basicAnimation.fromValue; 59 | springAnimation.toValue = basicAnimation.toValue; 60 | springAnimation.byValue = basicAnimation.byValue; 61 | springAnimation.valueFunction = basicAnimation.valueFunction; 62 | springAnimation.cumulative = basicAnimation.cumulative; 63 | springAnimation.additive = basicAnimation.additive; 64 | springAnimation.keyPath = basicAnimation.keyPath; 65 | springAnimation.removedOnCompletion = basicAnimation.removedOnCompletion; 66 | springAnimation.delegate = basicAnimation.delegate; 67 | springAnimation.timingFunction = basicAnimation.timingFunction; 68 | return springAnimation; 69 | } 70 | 71 | static NSArray *tv_create_spring_animations_with_basicAnimations(NSArray *basicAnimations) { 72 | NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:basicAnimations.count]; 73 | for (CABasicAnimation *basicAnimation in basicAnimations) { 74 | CASpringAnimation *springAnimation = tv_create_spring_animation_with_basicAnimation(basicAnimation); 75 | [mutableArray addObject:springAnimation]; 76 | } 77 | return [mutableArray copy]; 78 | } 79 | #pragma clang diagnostic pop 80 | 81 | static void tv_add_animation_for_group(CAAnimationGroup *group,CAAnimation *animation) { 82 | NSMutableArray *animations = [group.animations mutableCopy]; 83 | if (!animations) animations = [NSMutableArray new]; 84 | if (@available(ios 9.0, *)) { 85 | if (animations.count > 0 && 86 | [animations.firstObject isKindOfClass:[CASpringAnimation class]] && 87 | [animation isKindOfClass:[CABasicAnimation class]]) { 88 | CASpringAnimation *mirrorSpringAnimation = animations.firstObject; 89 | CASpringAnimation *springAnimation = tv_create_spring_animation_with_basicAnimation(animation); 90 | springAnimation.mass = mirrorSpringAnimation.mass; 91 | springAnimation.stiffness = mirrorSpringAnimation.stiffness; 92 | springAnimation.damping = mirrorSpringAnimation.damping; 93 | springAnimation.initialVelocity = mirrorSpringAnimation.initialVelocity; 94 | animation = springAnimation; 95 | } 96 | } 97 | [animations addObject:animation]; 98 | group.animations = animations; 99 | } 100 | 101 | @implementation ThinkVerb 102 | - (ThinkVerbSprite *(^)(NSString *))existSprite { 103 | return ^ ThinkVerbSprite * (NSString *identifier) { 104 | __block ThinkVerbSprite *sprite = nil; 105 | [self.sprites enumerateObjectsUsingBlock:^(ThinkVerbSprite * _Nonnull obj, BOOL * _Nonnull stop) { 106 | if ([obj.identifier isEqualToString:identifier]) { 107 | sprite = obj; 108 | *stop = YES; 109 | } 110 | }]; 111 | return sprite ?: [[ThinkVerbSprite alloc] init]; 112 | }; 113 | } 114 | - (void (^)(void))clear { 115 | return ^ void (void) { 116 | NSSet *sprites = [self.sprites copy]; 117 | [sprites enumerateObjectsUsingBlock:^(ThinkVerbSprite * _Nonnull obj, BOOL * _Nonnull stop) { 118 | obj.stop(); 119 | }]; 120 | }; 121 | } 122 | - (ThinkVerbSpriteAppearance *)appearance { 123 | ThinkVerbSpriteAppearance *appearance = objc_getAssociatedObject(self, _cmd); 124 | if (!appearance) { 125 | appearance = [[ThinkVerbSpriteAppearance alloc] init]; 126 | objc_setAssociatedObject(self, _cmd, appearance, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 127 | } 128 | return appearance; 129 | } 130 | - (CALayer *)presentationLayer { 131 | return self.layer.presentationLayer ?: self.layer; 132 | } 133 | 134 | - (TVSpriteMove *)move {return [self createSprite:[TVSpriteMove class]];} 135 | - (TVSpriteRotate *)rotate {return [self createSprite:[TVSpriteRotate class]];} 136 | - (TVSpriteScale *)scale {return [self createSprite:[TVSpriteScale class]];} 137 | - (TVSpriteShadow *)shadow {return [self createSprite:[TVSpriteShadow class]];} 138 | - (TVSpriteBounds *)bounds {return [self createSprite:[TVSpriteBounds class]];} 139 | - (TVSpriteAnchor *)anchor {return [self createSprite:[TVSpriteAnchor class]];} 140 | - (TVSpriteTranslate *)translate {return [self createSprite:[TVSpriteTranslate class]];} 141 | - (TVSpriteFade *)fade {return [self createSprite:[TVSpriteFade class]];} 142 | - (TVSpriteContents *)contents {return [self createSprite:[TVSpriteContents class]];} 143 | - (TVSpriteColor *)backgroundColor {return [self createSprite:[TVSpriteColor class]];} 144 | - (TVSpriteCornerRadius *)cornerRadius {return [self createSprite:[TVSpriteCornerRadius class]];} 145 | - (TVSpriteBorder *)border {return [self createSprite:[TVSpriteBorder class]];} 146 | - (TVSpritePath *)path {return [self createSprite:[TVSpritePath class]];} 147 | - (TVSpriteBasicCustom *)basicCustom {return [self createSprite:[TVSpriteBasicCustom class]];} 148 | - (id)createSprite:(Class)cls { 149 | ThinkVerbSprite *sprite = [[cls alloc] init]; 150 | sprite.thinkVerb = self; 151 | return sprite; 152 | } 153 | 154 | - (NSMutableSet *)sprites { 155 | if (!_sprites) { 156 | _sprites = [NSMutableSet set]; 157 | } 158 | return _sprites; 159 | } 160 | @end 161 | 162 | @implementation UIView (ThinkVerb) 163 | - (ThinkVerb *)TVAnimation { 164 | ThinkVerb *thinkverb = objc_getAssociatedObject(self, _cmd); 165 | if (!thinkverb) { 166 | thinkverb = [[ThinkVerb alloc] init]; 167 | objc_setAssociatedObject(self, _cmd, thinkverb, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 168 | } 169 | thinkverb.view = self; 170 | thinkverb.layer = self.layer; 171 | return thinkverb; 172 | } 173 | @end 174 | 175 | @implementation CALayer (ThinkVerb) 176 | - (ThinkVerb *)TVAnimation { 177 | ThinkVerb *thinkverb = objc_getAssociatedObject(self, _cmd); 178 | if (!thinkverb) { 179 | thinkverb = [[ThinkVerb alloc] init]; 180 | objc_setAssociatedObject(self, _cmd, thinkverb, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 181 | } 182 | thinkverb.layer = self; 183 | return thinkverb; 184 | } 185 | @end 186 | 187 | #pragma mark - TimingFunction 188 | @implementation TVTiming 189 | + (id (^)(CGFloat, CGFloat, CGFloat, CGFloat))create { 190 | return ^ id (CGFloat startX,CGFloat startY,CGFloat endX,CGFloat endY) { 191 | TVTiming *timing = [self functionWithControlPoints:startX :startY :endX :endY]; 192 | return timing; 193 | }; 194 | } 195 | + (id)extremeEaseOut {return [self functionWithControlPoints:0 :0 :0 :1];} 196 | + (id)extremeEaseIn {return [self functionWithControlPoints:1 :0 :1 :1];} 197 | + (id)linear {return [self functionWithControlPoints:0 :0 :0 :0];} 198 | 199 | @end 200 | 201 | #pragma mark - Sprite base 202 | @interface ThinkVerbSprite () 203 | @property (nonatomic,strong) void(^didStopAction)(void); 204 | @end 205 | @implementation ThinkVerbSprite 206 | - (instancetype)init { 207 | if (self = [super init]) { 208 | /* 209 | Especially adding default methods for ThinkVerbTransform3DType sprite 210 | */ 211 | if (class_conformsToProtocol([self class], @protocol(ThinkVerbTransform3DType)) && 212 | strcmp(class_getName([self class]), "ThinkVerbTransform3DTypeDefaultImplementation") != 0 && 213 | !tv_get_registered_3d_type_sprites()[NSStringFromClass([self class])].boolValue) { 214 | unsigned int methodCount = 0; 215 | Method *methods = class_copyMethodList([ThinkVerbTransform3DTypeDefaultImplementation class], &methodCount); 216 | for (int i = 0; i < methodCount; i++) { 217 | SEL mname = method_getName(methods[i]); 218 | IMP imp = method_getImplementation(methods[i]); 219 | const char *type = method_getTypeEncoding(methods[i]); 220 | class_addMethod([self class], mname, imp, type); 221 | } 222 | free(methods); 223 | tv_get_registered_3d_type_sprites()[NSStringFromClass([self class])] = @(YES); 224 | } 225 | self.identifier = [[NSUUID UUID] UUIDString]; 226 | } 227 | return self; 228 | } 229 | - (void)setThinkVerb:(ThinkVerb *)thinkVerb { 230 | _thinkVerb = thinkVerb; 231 | ThinkVerbSprite *appearance = self.thinkVerb.appearance; 232 | self.animation.duration = appearance.animation.duration; 233 | self.animation.repeatCount = appearance.animation.repeatCount; 234 | self.animation.beginTime = appearance.animation.beginTime; 235 | self.animation.timingFunction = appearance.animation.timingFunction; 236 | self.animation.autoreverses = appearance.animation.autoreverses; 237 | self.animation.removedOnCompletion = appearance.animation.isRemovedOnCompletion; 238 | self.animation.fillMode = appearance.animation.fillMode; 239 | } 240 | - (id (^)(NSInteger))repeat { 241 | return ^ id (NSInteger count) { 242 | self.animation.repeatCount = count < 0 ? HUGE_VALF : count; 243 | return self; 244 | }; 245 | } 246 | - (id (^)(CGFloat))duration { 247 | return ^ id (CGFloat duration) { 248 | self.animation.duration = duration; 249 | return self; 250 | }; 251 | } 252 | - (id (^)(CGFloat))delay { 253 | return ^ id (CGFloat delay) { 254 | self.animation.beginTime = CACurrentMediaTime() + delay; 255 | return self; 256 | }; 257 | } 258 | - (id (^)(TVTiming *))timing { 259 | return ^ id (TVTiming *timing) { 260 | self.animation.timingFunction = timing; 261 | return self; 262 | }; 263 | } 264 | - (id)reverse { 265 | self.animation.autoreverses = YES; 266 | return self; 267 | } 268 | - (id (^)(BOOL))keepAlive { 269 | return ^ id (BOOL value) { 270 | self.animation.removedOnCompletion = !value; 271 | self.animation.fillMode = value ? kCAFillModeForwards : kCAFillModeRemoved; 272 | return self; 273 | }; 274 | } 275 | - (NSString *(^)(void))activate { 276 | return ^ NSString * (void) { 277 | [self modifyDurationForSpringAnimation]; 278 | [self.thinkVerb.layer addAnimation:self.animation forKey:self.identifier]; 279 | [self.thinkVerb.sprites addObject:self]; 280 | return self.identifier; 281 | }; 282 | } 283 | - (void (^)(NSString *))activateAs { 284 | return ^void (NSString *identifier) { 285 | [self modifyDurationForSpringAnimation]; 286 | for (ThinkVerbSprite *sprite in self.thinkVerb.sprites) { 287 | if ([sprite.identifier isEqualToString:identifier]) { 288 | self.animation.delegate = nil; 289 | return; 290 | } 291 | } 292 | self.identifier = identifier; 293 | [self.thinkVerb.layer addAnimation:self.animation forKey:identifier]; 294 | [self.thinkVerb.sprites addObject:self]; 295 | }; 296 | } 297 | - (void (^)(void))stop { 298 | return ^ void (void) { 299 | [self.thinkVerb.layer removeAnimationForKey:self.identifier]; 300 | self.animation.delegate = nil; 301 | [self.thinkVerb.sprites removeObject:self]; 302 | }; 303 | } 304 | - (id (^)(void (^)(void)))didStop { 305 | return ^ id (void (^didStopAction)(void)) { 306 | self.didStopAction = didStopAction; 307 | return self; 308 | }; 309 | } 310 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 311 | if (self.didStopAction) self.didStopAction(); 312 | if ([self.thinkVerb.sprites containsObject:self] && self.animation.isRemovedOnCompletion) { 313 | self.animation.delegate = nil; 314 | [self.thinkVerb.sprites removeObject:self]; 315 | } 316 | self.didStopAction = nil; 317 | } 318 | - (id)with {return self;} 319 | 320 | - (void)modifyDurationForSpringAnimation { 321 | // if we use spring animation, set duration as evaluated duration 322 | if (@available(ios 9.0, *)) { 323 | if ([self.animation isKindOfClass:CASpringAnimation.class]) { 324 | self.animation.duration = ((CASpringAnimation *)self.animation).settlingDuration; 325 | }else if ([self.animation isKindOfClass:CAAnimationGroup.class]) { 326 | CAAnimationGroup *group = self.animation; 327 | for (CABasicAnimation *basicAnimation in group.animations) { 328 | if ([basicAnimation isKindOfClass:CASpringAnimation.class]) { 329 | group.duration = ((CASpringAnimation *)basicAnimation).settlingDuration; 330 | break; 331 | } 332 | } 333 | } 334 | } 335 | } 336 | @end 337 | 338 | @implementation ThinkVerbTransform3DTypeDefaultImplementation 339 | - (instancetype)toSubLayer { 340 | objc_setAssociatedObject(self, _cmd, @(YES), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 341 | return self; 342 | } 343 | - (NSString *(^)(void))activate { 344 | if (((NSNumber *)objc_getAssociatedObject(self, @selector(toSubLayer))).boolValue == YES) { 345 | if ([self.animation isKindOfClass:[CAAnimationGroup class]]) { 346 | CAAnimationGroup *group = self.animation; 347 | for (CAPropertyAnimation *animation in group.animations) { 348 | if ([animation isKindOfClass:[CAPropertyAnimation class]]) { 349 | animation.keyPath = [animation.keyPath stringByReplacingOccurrencesOfString:@"transform" withString:@"sublayerTransform"]; 350 | } 351 | } 352 | }else if ([self.animation isKindOfClass:[CAPropertyAnimation class]]) { 353 | CAPropertyAnimation *animation = self.animation; 354 | animation.keyPath = [animation.keyPath stringByReplacingOccurrencesOfString:@"transform" withString:@"sublayerTransform"]; 355 | } 356 | } 357 | return super.activate; 358 | } 359 | @end 360 | 361 | @implementation ThinkVerbSpriteAppearance 362 | - (instancetype)init { 363 | if (self = [super init]) { 364 | self.animation = [CAAnimation animation]; 365 | } 366 | return self; 367 | } 368 | - (void(^)(void))end {return ^void(void){};} 369 | @end 370 | 371 | @implementation ThinkVerbSpriteBasic 372 | - (instancetype)init { 373 | if (self = [super init]) { 374 | self.animation = [CABasicAnimation animationWithKeyPath:self.keyPath ?: @""]; 375 | self.animation.delegate = self; 376 | } 377 | return self; 378 | } 379 | - (id (^)(BOOL))additive { 380 | return ^ id (BOOL value) { 381 | ((CABasicAnimation *)self.animation).additive = value; 382 | return self; 383 | }; 384 | } 385 | - (id (^)(BOOL))cumulative { 386 | return ^ id (BOOL value) { 387 | ((CABasicAnimation *)self.animation).cumulative = value; 388 | return self; 389 | }; 390 | } 391 | - (id (^)(CAValueFunction *))valueFunction { 392 | return ^ id (CAValueFunction *valueFunction) { 393 | ((CABasicAnimation *)self.animation).valueFunction = valueFunction; 394 | return self; 395 | }; 396 | } 397 | 398 | - (id (^)(CGFloat))mass { 399 | return ^ id (CGFloat mass) { 400 | if (@available(ios 9.0, *)) { 401 | CASpringAnimation *springAnimation = tv_create_spring_animation_with_basicAnimation(self.animation); 402 | springAnimation.mass = mass; 403 | self.animation = springAnimation; 404 | } 405 | return self; 406 | }; 407 | } 408 | 409 | - (id (^)(CGFloat))stiffness { 410 | return ^ id (CGFloat stiffness) { 411 | if (@available(ios 9.0, *)) { 412 | CASpringAnimation *springAnimation = tv_create_spring_animation_with_basicAnimation(self.animation); 413 | springAnimation.stiffness = stiffness; 414 | self.animation = springAnimation; 415 | } 416 | return self; 417 | }; 418 | } 419 | 420 | - (id (^)(CGFloat))damping { 421 | return ^ id (CGFloat damping) { 422 | if (@available(ios 9.0, *)) { 423 | CASpringAnimation *springAnimation = tv_create_spring_animation_with_basicAnimation(self.animation); 424 | springAnimation.damping = damping; 425 | self.animation = springAnimation; 426 | } 427 | return self; 428 | }; 429 | } 430 | 431 | - (id (^)(CGFloat))initialVelocity { 432 | return ^ id (CGFloat initialVelocity) { 433 | if (@available(ios 9.0, *)) { 434 | CASpringAnimation *springAnimation = tv_create_spring_animation_with_basicAnimation(self.animation); 435 | springAnimation.initialVelocity = initialVelocity; 436 | self.animation = springAnimation; 437 | } 438 | return self; 439 | }; 440 | } 441 | 442 | @end 443 | 444 | @implementation ThinkVerbSpriteGroup 445 | - (instancetype)init { 446 | if (self = [super init]) { 447 | self.animation = [CAAnimationGroup animation]; 448 | self.animation.delegate = self; 449 | } 450 | return self; 451 | } 452 | 453 | - (id (^)(CGFloat))mass { 454 | return ^ id (CGFloat mass) { 455 | if (@available(ios 9.0, *)) { 456 | CAAnimationGroup *group = self.animation; 457 | group.animations = tv_create_spring_animations_with_basicAnimations(group.animations); 458 | for (CASpringAnimation *springAnimation in group.animations) { 459 | springAnimation.mass = mass; 460 | } 461 | } 462 | return self; 463 | }; 464 | } 465 | 466 | - (id (^)(CGFloat))stiffness { 467 | return ^ id (CGFloat stiffness) { 468 | if (@available(ios 9.0, *)) { 469 | CAAnimationGroup *group = self.animation; 470 | group.animations = tv_create_spring_animations_with_basicAnimations(group.animations); 471 | for (CASpringAnimation *springAnimation in group.animations) { 472 | springAnimation.stiffness = stiffness; 473 | } 474 | } 475 | return self; 476 | }; 477 | } 478 | 479 | - (id (^)(CGFloat))damping { 480 | return ^ id (CGFloat damping) { 481 | if (@available(ios 9.0, *)) { 482 | CAAnimationGroup *group = self.animation; 483 | group.animations = tv_create_spring_animations_with_basicAnimations(group.animations); 484 | for (CASpringAnimation *springAnimation in group.animations) { 485 | springAnimation.damping = damping; 486 | } 487 | } 488 | return self; 489 | }; 490 | } 491 | 492 | - (id (^)(CGFloat))initialVelocity { 493 | return ^ id (CGFloat initialVelocity) { 494 | if (@available(ios 9.0, *)) { 495 | CAAnimationGroup *group = self.animation; 496 | group.animations = tv_create_spring_animations_with_basicAnimations(group.animations); 497 | for (CASpringAnimation *springAnimation in group.animations) { 498 | springAnimation.initialVelocity = initialVelocity; 499 | } 500 | } 501 | return self; 502 | }; 503 | } 504 | 505 | @end 506 | 507 | @implementation ThinkVerbSpriteKeyframe 508 | - (instancetype)init { 509 | if (self = [super init]) { 510 | self.animation = [CAKeyframeAnimation animationWithKeyPath:self.keyPath ?: @""]; 511 | self.animation.delegate = self; 512 | } 513 | return self; 514 | } 515 | - (id (^)(CAAnimationCalculationMode))mode { 516 | return ^ id (CAAnimationCalculationMode mode) { 517 | ((CAKeyframeAnimation *)self.animation).calculationMode = mode; 518 | return self; 519 | }; 520 | } 521 | - (id (^)(TVTiming *))timing { 522 | return ^ id (TVTiming *timing) { 523 | CAKeyframeAnimation *animation = self.animation; 524 | NSAssert(animation.values.count >= 2, @"You must add at least two point into sprite"); 525 | NSAssert(animation.timingFunctions.count < animation.values.count - 1, @"You can't add timing functions more than a count of the points - 1"); 526 | NSMutableArray *timingFunctions = [animation.timingFunctions mutableCopy]; 527 | if (!timingFunctions) timingFunctions = [NSMutableArray new]; 528 | while (timingFunctions.count < animation.values.count - 2) { 529 | [timingFunctions addObject:[TVTiming functionWithName:kCAMediaTimingFunctionDefault]]; 530 | } 531 | [timingFunctions addObject:timing]; 532 | animation.timingFunctions = timingFunctions; 533 | return self; 534 | }; 535 | } 536 | - (id (^)(NSInteger))endAtPercent { 537 | return ^ id (NSInteger percent) { 538 | CAKeyframeAnimation *animation = self.animation; 539 | NSAssert(animation.values.count > 0, @"You must add at least one point into sprite"); 540 | NSAssert(percent >= 0 && percent <= 100 , @"the percent must be in range of [0-100]"); 541 | if (animation.values.count == 1) { 542 | NSAssert(percent == 0, @"the percent must be 0 at the first point"); 543 | }else { 544 | NSAssert((percent / 100.0) > animation.keyTimes.lastObject.floatValue, @"the percent must be larger than the previous percent"); 545 | } 546 | NSMutableArray *keyTimes = [animation.keyTimes mutableCopy]; 547 | if (!keyTimes) keyTimes = [NSMutableArray new]; 548 | if (keyTimes.count == 0) [keyTimes addObject:@(0.0)]; 549 | CGFloat lastKeyTime = keyTimes.lastObject.floatValue; 550 | NSInteger lackTimesCount = animation.values.count - keyTimes.count; 551 | CGFloat avrageOfLackTimes = ((percent / 100.0) - lastKeyTime) / lackTimesCount; 552 | for (int i = 1; i <= lackTimesCount; i++) { 553 | [keyTimes addObject:@(lastKeyTime + avrageOfLackTimes * i)]; 554 | } 555 | animation.keyTimes = keyTimes; 556 | return self; 557 | }; 558 | } 559 | 560 | - (NSString *(^)(void))activate { 561 | CAKeyframeAnimation *animation = self.animation; 562 | if (animation.keyTimes) { 563 | if (animation.keyTimes.count < animation.values.count) { 564 | self.endAtPercent(100); 565 | } 566 | NSAssert(animation.keyTimes.lastObject.floatValue == 1, @"the last keyTime must be 1"); 567 | } 568 | NSMutableArray *timingFunctions = [animation.timingFunctions mutableCopy] ?: [NSMutableArray new]; 569 | while (timingFunctions.count < animation.values.count - 1) { 570 | [timingFunctions addObject:[TVTiming functionWithName:kCAMediaTimingFunctionDefault]]; 571 | } 572 | animation.timingFunctions = timingFunctions; 573 | return super.activate; 574 | } 575 | 576 | - (void (^)(void))stop { 577 | [self releasePath]; 578 | return super.stop; 579 | } 580 | 581 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 582 | [super animationDidStop:anim finished:flag]; 583 | [self releasePath]; 584 | } 585 | 586 | - (void)releasePath { 587 | CAKeyframeAnimation *animation = self.animation; 588 | CGPathRelease(animation.path); 589 | animation.path = NULL; 590 | } 591 | 592 | @end 593 | 594 | #pragma mark - General Sprite 595 | @implementation TVSpriteBasicCustom 596 | - (TVSpriteBasicCustom *(^)(NSString *))property { 597 | return ^ id (NSString *property) { 598 | self.animation.keyPath = property; 599 | return self; 600 | }; 601 | } 602 | - (TVSpriteBasicCustom *(^)(id))from { 603 | return ^ id (id obj) { 604 | self.animation.fromValue = obj; 605 | return self; 606 | }; 607 | } 608 | - (TVSpriteBasicCustom *(^)(id))to { 609 | return ^ id (id obj) { 610 | self.animation.toValue = obj; 611 | return self; 612 | }; 613 | } 614 | - (TVSpriteBasicCustom *(^)(id))by { 615 | return ^ id (id obj) { 616 | self.animation.byValue = obj; 617 | return self; 618 | }; 619 | } 620 | @end 621 | #pragma mark - Sprite 622 | @implementation TVSpriteRotate 623 | - (NSString *)keyPath { 624 | return @"transform.rotation.z"; 625 | } 626 | - (TVSpriteRotate *)x { 627 | self.animation.keyPath = @"transform.rotation.x"; 628 | return self; 629 | } 630 | - (TVSpriteRotate *)y { 631 | self.animation.keyPath = @"transform.rotation.y"; 632 | return self; 633 | } 634 | - (TVSpriteRotate *)z { 635 | self.animation.keyPath = @"transform.rotation.z"; 636 | return self; 637 | } 638 | - (TVSpriteRotate *(^)(CGFloat))startAngle { 639 | return ^ TVSpriteRotate * (CGFloat value) { 640 | self.animation.fromValue = @(value); 641 | return self; 642 | }; 643 | } 644 | - (TVSpriteRotate *(^)(CGFloat))endAngle { 645 | return ^ TVSpriteRotate * (CGFloat value) { 646 | self.animation.toValue = @(value); 647 | return self; 648 | }; 649 | } 650 | - (TVSpriteRotate *(^)(CGFloat, CGFloat))angle { 651 | return ^ TVSpriteRotate * (CGFloat from,CGFloat to) { 652 | self.animation.fromValue = @(from); 653 | self.animation.toValue = @(to); 654 | return self; 655 | }; 656 | } 657 | 658 | @end 659 | 660 | @implementation TVSpriteScale 661 | - (TVSpriteScale *(^)(CGFloat, CGFloat))x { 662 | return ^ TVSpriteScale * (CGFloat from, CGFloat to) { 663 | CABasicAnimation *xAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"]; 664 | xAnim.fromValue = @(from); xAnim.toValue = @(to); 665 | tv_add_animation_for_group(self.animation, xAnim); 666 | return self; 667 | }; 668 | } 669 | - (TVSpriteScale *(^)(CGFloat, CGFloat))y { 670 | return ^ TVSpriteScale * (CGFloat from, CGFloat to) { 671 | CABasicAnimation *xAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"]; 672 | xAnim.fromValue = @(from); xAnim.toValue = @(to); 673 | tv_add_animation_for_group(self.animation, xAnim); 674 | return self; 675 | }; 676 | } 677 | - (TVSpriteScale *(^)(CGFloat, CGFloat))z { 678 | return ^ TVSpriteScale * (CGFloat from, CGFloat to) { 679 | CABasicAnimation *xAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale.z"]; 680 | xAnim.fromValue = @(from); xAnim.toValue = @(to); 681 | tv_add_animation_for_group(self.animation, xAnim); 682 | return self; 683 | }; 684 | } 685 | - (TVSpriteScale *(^)(CGFloat))xTo { 686 | return ^ TVSpriteScale * (CGFloat to) { 687 | CABasicAnimation *xAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"]; 688 | xAnim.toValue = @(to); 689 | tv_add_animation_for_group(self.animation, xAnim); 690 | return self; 691 | }; 692 | } 693 | - (TVSpriteScale *(^)(CGFloat))yTo { 694 | return ^ TVSpriteScale * (CGFloat to) { 695 | CABasicAnimation *xAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"]; 696 | xAnim.toValue = @(to); 697 | tv_add_animation_for_group(self.animation, xAnim); 698 | return self; 699 | }; 700 | } 701 | - (TVSpriteScale *(^)(CGFloat))zTo { 702 | return ^ TVSpriteScale * (CGFloat to) { 703 | CABasicAnimation *xAnim = [CABasicAnimation animationWithKeyPath:@"transform.scale.z"]; 704 | xAnim.toValue = @(to); 705 | tv_add_animation_for_group(self.animation, xAnim); 706 | return self; 707 | }; 708 | } 709 | - (TVSpriteScale *(^)(CGFloat))from { 710 | return ^ TVSpriteScale * (CGFloat from) { 711 | CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 712 | anim.fromValue = @(from); 713 | tv_cache_animations_into_sprite(self, anim, _cmd); 714 | return self; 715 | }; 716 | } 717 | - (TVSpriteScale *(^)(CGFloat))to { 718 | return ^ TVSpriteScale * (CGFloat to) { 719 | CABasicAnimation *anim = tv_get_animations_from_sprite(self, @selector(from)) ?: [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 720 | anim.toValue = @(to); 721 | tv_add_animation_for_group(self.animation, anim); 722 | return self; 723 | }; 724 | } 725 | @end 726 | 727 | @implementation TVSpriteTranslate 728 | - (TVSpriteTranslate *(^)(CGFloat, CGFloat, CGFloat))from { 729 | return ^ TVSpriteTranslate * (CGFloat x,CGFloat y,CGFloat z) { 730 | CABasicAnimation *translationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"]; 731 | CABasicAnimation *translationY = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"]; 732 | CABasicAnimation *translationZ = [CABasicAnimation animationWithKeyPath:@"transform.translation.z"]; 733 | translationX.fromValue = @(x); 734 | translationY.fromValue = @(y); 735 | translationZ.fromValue = @(z); 736 | tv_cache_animations_into_sprite(self, @[translationX,translationY,translationZ], _cmd); 737 | return self; 738 | }; 739 | } 740 | - (TVSpriteTranslate *(^)(CGFloat, CGFloat, CGFloat))to { 741 | return ^ TVSpriteTranslate * (CGFloat x,CGFloat y,CGFloat z) { 742 | NSArray *from = tv_get_animations_from_sprite(self, @selector(from)); 743 | CABasicAnimation *translationX = from[0] ?: [CABasicAnimation animationWithKeyPath:@"transform.translation.x"]; 744 | CABasicAnimation *translationY = from[1] ?: [CABasicAnimation animationWithKeyPath:@"transform.translation.y"]; 745 | CABasicAnimation *translationZ = from[2] ?: [CABasicAnimation animationWithKeyPath:@"transform.translation.z"]; 746 | translationX.toValue = @(x); 747 | translationY.toValue = @(y); 748 | translationZ.toValue = @(z); 749 | tv_add_animation_for_group(self.animation, translationX); 750 | tv_add_animation_for_group(self.animation, translationY); 751 | tv_add_animation_for_group(self.animation, translationZ); 752 | return self; 753 | }; 754 | } 755 | - (TVSpriteTranslate *(^)(CGFloat, CGFloat))x { 756 | return ^ TVSpriteTranslate * (CGFloat from,CGFloat to) { 757 | CABasicAnimation *translationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"]; 758 | translationX.fromValue = @(from); 759 | translationX.toValue = @(to); 760 | tv_add_animation_for_group(self.animation,translationX); 761 | return self; 762 | }; 763 | } 764 | - (TVSpriteTranslate *(^)(CGFloat, CGFloat))y { 765 | return ^ TVSpriteTranslate * (CGFloat from,CGFloat to) { 766 | CABasicAnimation *translationY = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"]; 767 | translationY.fromValue = @(from); 768 | translationY.toValue = @(to); 769 | tv_add_animation_for_group(self.animation,translationY); 770 | return self; 771 | }; 772 | } 773 | - (TVSpriteTranslate *(^)(CGFloat, CGFloat))z { 774 | return ^ TVSpriteTranslate * (CGFloat from,CGFloat to) { 775 | CABasicAnimation *translationZ = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"]; 776 | translationZ.fromValue = @(from); 777 | translationZ.toValue = @(to); 778 | tv_add_animation_for_group(self.animation,translationZ); 779 | return self; 780 | }; 781 | } 782 | - (TVSpriteTranslate *(^)(CGFloat))xBy { 783 | return ^ TVSpriteTranslate * (CGFloat to) { 784 | CABasicAnimation *translationX = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"]; 785 | translationX.toValue = @(to); 786 | tv_add_animation_for_group(self.animation,translationX); 787 | return self; 788 | }; 789 | } 790 | - (TVSpriteTranslate *(^)(CGFloat))yBy { 791 | return ^ TVSpriteTranslate * (CGFloat to) { 792 | CABasicAnimation *translationY = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"]; 793 | translationY.toValue = @(to); 794 | tv_add_animation_for_group(self.animation,translationY); 795 | return self; 796 | }; 797 | } 798 | - (TVSpriteTranslate *(^)(CGFloat))zBy { 799 | return ^ TVSpriteTranslate * (CGFloat to) { 800 | CABasicAnimation *translationZ = [CABasicAnimation animationWithKeyPath:@"transform.translation.z"]; 801 | translationZ.toValue = @(to); 802 | tv_add_animation_for_group(self.animation,translationZ); 803 | return self; 804 | }; 805 | } 806 | @end 807 | 808 | @implementation TVSpriteMove 809 | - (TVSpriteMove *(^)(CGFloat, CGFloat,CGFloat))offset { 810 | return ^ TVSpriteMove * (CGFloat x,CGFloat y,CGFloat z) { 811 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"position"]; 812 | CABasicAnimation *zPosition = [CABasicAnimation animationWithKeyPath:@"zPosition"]; 813 | position.toValue = @(CGPointMake(self.thinkVerb.presentationLayer.position.x + x, 814 | self.thinkVerb.presentationLayer.position.y + y)); 815 | zPosition.toValue = @(self.thinkVerb.presentationLayer.zPosition + z); 816 | tv_add_animation_for_group(self.animation,position); 817 | tv_add_animation_for_group(self.animation,zPosition); 818 | return self; 819 | }; 820 | } 821 | - (TVSpriteMove *(^)(CGFloat, CGFloat,CGFloat))from { 822 | return ^ TVSpriteMove * (CGFloat x,CGFloat y,CGFloat z) { 823 | NSArray *array = tv_get_animations_from_sprite(self, _cmd); 824 | CABasicAnimation *position = array ? array[0] : [CABasicAnimation animationWithKeyPath:@"position"]; 825 | CABasicAnimation *zPosition = array ? array[1] : [CABasicAnimation animationWithKeyPath:@"zPosition"]; 826 | position.fromValue = @(CGPointMake(x, y)); 827 | zPosition.fromValue = @(z); 828 | tv_cache_animations_into_sprite(self, @[position,zPosition], _cmd); 829 | return self; 830 | }; 831 | } 832 | - (TVSpriteMove *(^)(CGFloat, CGFloat,CGFloat))to { 833 | return ^ TVSpriteMove * (CGFloat x,CGFloat y,CGFloat z) { 834 | NSArray *array = tv_get_animations_from_sprite(self, @selector(from)); 835 | CABasicAnimation *position = array ? array[0] : [CABasicAnimation animationWithKeyPath:@"position"]; 836 | CABasicAnimation *zPosition = array ? array[1] : [CABasicAnimation animationWithKeyPath:@"zPosition"]; 837 | position.toValue = @(CGPointMake(x, y)); 838 | zPosition.toValue = @(z); 839 | tv_add_animation_for_group(self.animation, position); 840 | tv_add_animation_for_group(self.animation, zPosition); 841 | return self; 842 | }; 843 | } 844 | - (TVSpriteMove *(^)(CGFloat, CGFloat))x { 845 | return ^ TVSpriteMove * (CGFloat from,CGFloat to) { 846 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"position.x"]; 847 | position.fromValue = @(from); 848 | position.toValue = @(to); 849 | tv_add_animation_for_group(self.animation,position); 850 | return self; 851 | }; 852 | } 853 | - (TVSpriteMove *(^)(CGFloat, CGFloat))y { 854 | return ^ TVSpriteMove * (CGFloat from,CGFloat to) { 855 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"position.y"]; 856 | position.fromValue = @(from); 857 | position.toValue = @(to); 858 | tv_add_animation_for_group(self.animation,position); 859 | return self; 860 | }; 861 | } 862 | - (TVSpriteMove *(^)(CGFloat, CGFloat))z { 863 | return ^ TVSpriteMove * (CGFloat from,CGFloat to) { 864 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"zPosition"]; 865 | position.fromValue = @(from); 866 | position.toValue = @(to); 867 | tv_add_animation_for_group(self.animation,position); 868 | return self; 869 | }; 870 | } 871 | - (TVSpriteMove *(^)(CGFloat))xTo { 872 | return ^ TVSpriteMove * (CGFloat to) { 873 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"position.x"]; 874 | position.toValue = @(to); 875 | tv_add_animation_for_group(self.animation,position); 876 | return self; 877 | }; 878 | } 879 | - (TVSpriteMove *(^)(CGFloat))yTo { 880 | return ^ TVSpriteMove * (CGFloat to) { 881 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"position.y"]; 882 | position.toValue = @(to); 883 | tv_add_animation_for_group(self.animation,position); 884 | return self; 885 | }; 886 | } 887 | - (TVSpriteMove *(^)(CGFloat))zTo { 888 | return ^ TVSpriteMove * (CGFloat to) { 889 | CABasicAnimation *position = [CABasicAnimation animationWithKeyPath:@"zPosition"]; 890 | position.toValue = @(to); 891 | tv_add_animation_for_group(self.animation,position); 892 | return self; 893 | }; 894 | } 895 | @end 896 | 897 | @implementation TVSpriteShadow 898 | - (TVSpriteShadow *(^)(CGSize, CGSize))offset { 899 | return ^ TVSpriteShadow * (CGSize startOffset,CGSize endOffset) { 900 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; 901 | subAnimation.fromValue = @(startOffset); 902 | subAnimation.toValue = @(endOffset); 903 | tv_add_animation_for_group(self.animation,subAnimation); 904 | return self; 905 | }; 906 | } 907 | - (TVSpriteShadow *(^)(CGFloat, CGFloat))offsetTo { 908 | return ^ TVSpriteShadow * (CGFloat width,CGFloat height) { 909 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOffset"]; 910 | subAnimation.toValue = @(CGSizeMake(width, height)); 911 | tv_add_animation_for_group(self.animation,subAnimation); 912 | return self; 913 | }; 914 | } 915 | 916 | - (TVSpriteShadow *(^)(CGFloat, CGFloat))opacity { 917 | return ^ TVSpriteShadow * (CGFloat startOpacity,CGFloat endOpacity) { 918 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; 919 | subAnimation.fromValue = @(startOpacity); 920 | subAnimation.toValue = @(endOpacity); 921 | tv_add_animation_for_group(self.animation,subAnimation); 922 | return self; 923 | }; 924 | } 925 | - (TVSpriteShadow *(^)(CGFloat))opacityTo { 926 | return ^ TVSpriteShadow * (CGFloat opacity) { 927 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"]; 928 | subAnimation.toValue = @(opacity); 929 | tv_add_animation_for_group(self.animation,subAnimation); 930 | return self; 931 | }; 932 | } 933 | 934 | - (TVSpriteShadow *(^)(CGFloat, CGFloat))radius { 935 | return ^ TVSpriteShadow * (CGFloat startRadius,CGFloat endRadius) { 936 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowRadius"]; 937 | subAnimation.fromValue = @(startRadius); 938 | subAnimation.toValue = @(endRadius); 939 | tv_add_animation_for_group(self.animation,subAnimation); 940 | return self; 941 | }; 942 | } 943 | - (TVSpriteShadow *(^)(CGFloat))radiusTo { 944 | return ^ TVSpriteShadow * (CGFloat radius) { 945 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowRadius"]; 946 | subAnimation.toValue = @(radius); 947 | tv_add_animation_for_group(self.animation,subAnimation); 948 | return self; 949 | }; 950 | } 951 | - (TVSpriteShadow *(^)(UIColor *, UIColor *))color { 952 | return ^ TVSpriteShadow * (UIColor * startColor,UIColor *endColor) { 953 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; 954 | subAnimation.fromValue = ((__bridge id)startColor.CGColor); 955 | subAnimation.toValue = ((__bridge id)endColor.CGColor); 956 | tv_add_animation_for_group(self.animation,subAnimation); 957 | return self; 958 | }; 959 | } 960 | - (TVSpriteShadow *(^)(UIColor *))colorTo { 961 | return ^ TVSpriteShadow * (UIColor *color) { 962 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"shadowColor"]; 963 | subAnimation.toValue = ((__bridge id)color.CGColor); 964 | tv_add_animation_for_group(self.animation,subAnimation); 965 | return self; 966 | }; 967 | } 968 | @end 969 | 970 | @implementation TVSpriteBounds 971 | - (TVSpriteBounds *(^)(CGFloat, CGFloat))width { 972 | return ^ TVSpriteBounds * (CGFloat from,CGFloat to) { 973 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"]; 974 | subAnimation.fromValue = @(from); 975 | subAnimation.toValue = @(to); 976 | tv_add_animation_for_group(self.animation,subAnimation); 977 | return self; 978 | }; 979 | } 980 | - (TVSpriteBounds *(^)(CGFloat, CGFloat))height { 981 | return ^ TVSpriteBounds * (CGFloat from,CGFloat to) { 982 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"]; 983 | subAnimation.fromValue = @(from); 984 | subAnimation.toValue = @(to); 985 | tv_add_animation_for_group(self.animation,subAnimation); 986 | return self; 987 | }; 988 | } 989 | - (TVSpriteBounds *(^)(CGFloat))widthTo { 990 | return ^ TVSpriteBounds * (CGFloat to) { 991 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"]; 992 | subAnimation.toValue = @(to); 993 | tv_add_animation_for_group(self.animation,subAnimation); 994 | return self; 995 | }; 996 | } 997 | - (TVSpriteBounds *(^)(CGFloat))heightTo { 998 | return ^ TVSpriteBounds * (CGFloat to) { 999 | CABasicAnimation *subAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"]; 1000 | subAnimation.toValue = @(to); 1001 | tv_add_animation_for_group(self.animation,subAnimation); 1002 | return self; 1003 | }; 1004 | } 1005 | 1006 | - (TVSpriteBounds *(^)(CGFloat, CGFloat))from { 1007 | return ^ TVSpriteBounds * (CGFloat width,CGFloat height) { 1008 | CABasicAnimation *subAnimation = tv_get_animations_from_sprite(self, _cmd); 1009 | subAnimation = subAnimation ?: [CABasicAnimation animationWithKeyPath:@"bounds"]; 1010 | subAnimation.fromValue = @(CGRectMake(0, 0, width, height)); 1011 | tv_cache_animations_into_sprite(self, subAnimation, _cmd); 1012 | return self; 1013 | }; 1014 | } 1015 | 1016 | - (TVSpriteBounds *(^)(CGFloat, CGFloat))to { 1017 | return ^ TVSpriteBounds * (CGFloat width,CGFloat height) { 1018 | CABasicAnimation *subAnimation = tv_get_animations_from_sprite(self, @selector(from)); 1019 | subAnimation = subAnimation ?: [CABasicAnimation animationWithKeyPath:@"bounds"]; 1020 | subAnimation.toValue = @(CGRectMake(0, 0, width, height)); 1021 | tv_add_animation_for_group(self.animation,subAnimation); 1022 | return self; 1023 | }; 1024 | } 1025 | 1026 | @end 1027 | 1028 | @implementation TVSpriteAnchor 1029 | - (instancetype)init { 1030 | if (self = [super init]) { 1031 | CABasicAnimation *anchorPoint = [CABasicAnimation animationWithKeyPath:@"anchorPoint"]; 1032 | CABasicAnimation *anchorPointZ = [CABasicAnimation animationWithKeyPath:@"anchorPointZ"]; 1033 | tv_add_animation_for_group(self.animation, anchorPoint); 1034 | tv_add_animation_for_group(self.animation, anchorPointZ); 1035 | } 1036 | return self; 1037 | } 1038 | - (TVSpriteAnchor *(^)(CGFloat, CGFloat, CGFloat))offset { 1039 | return ^ TVSpriteAnchor * (CGFloat x,CGFloat y,CGFloat z) { 1040 | CABasicAnimation *anchor = self.animation.animations[0]; 1041 | CABasicAnimation *anchorZ = self.animation.animations[1]; 1042 | CALayer *layer = self.thinkVerb.layer.presentationLayer ?: self.thinkVerb.layer; 1043 | anchor.toValue = @(CGPointMake(layer.anchorPoint.x + x,layer.anchorPoint.y + y)); 1044 | anchorZ.toValue = @(layer.anchorPointZ + z); 1045 | return self; 1046 | }; 1047 | } 1048 | - (TVSpriteAnchor *(^)(CGFloat, CGFloat,CGFloat))from { 1049 | return ^ TVSpriteAnchor * (CGFloat x,CGFloat y,CGFloat z) { 1050 | CABasicAnimation *anchor = self.animation.animations[0]; 1051 | CABasicAnimation *anchorZ = self.animation.animations[1]; 1052 | anchor.fromValue = @(CGPointMake(x, y)); 1053 | anchorZ.fromValue = @(z); 1054 | return self; 1055 | }; 1056 | } 1057 | - (TVSpriteAnchor *(^)(CGFloat, CGFloat,CGFloat))to { 1058 | return ^ TVSpriteAnchor * (CGFloat x,CGFloat y,CGFloat z) { 1059 | CABasicAnimation *anchor = self.animation.animations[0]; 1060 | CABasicAnimation *anchorZ = self.animation.animations[1]; 1061 | anchor.toValue = @(CGPointMake(x, y)); 1062 | anchorZ.toValue = @(z); 1063 | return self; 1064 | }; 1065 | } 1066 | - (TVSpriteAnchor *(^)(CGFloat, CGFloat))x { 1067 | return ^ TVSpriteAnchor * (CGFloat from,CGFloat to) { 1068 | CABasicAnimation *anchor = self.animation.animations[0]; 1069 | 1070 | CGPoint fromPoint = CGPointZero; 1071 | if (anchor.fromValue) [anchor.fromValue getValue:&fromPoint]; 1072 | else fromPoint.y = self.thinkVerb.presentationLayer.anchorPoint.y; 1073 | fromPoint.x = from; 1074 | anchor.fromValue = @(fromPoint); 1075 | 1076 | CGPoint toPoint = CGPointZero; 1077 | if (anchor.toValue) [anchor.toValue getValue:&toPoint]; 1078 | else toPoint.y = self.thinkVerb.presentationLayer.anchorPoint.y; 1079 | toPoint.x = to; 1080 | anchor.toValue = @(toPoint); 1081 | 1082 | return self; 1083 | }; 1084 | } 1085 | - (TVSpriteAnchor *(^)(CGFloat, CGFloat))y { 1086 | return ^ TVSpriteAnchor * (CGFloat from,CGFloat to) { 1087 | CABasicAnimation *anchor = self.animation.animations[0]; 1088 | 1089 | CGPoint fromPoint = CGPointZero; 1090 | if (anchor.fromValue) [anchor.fromValue getValue:&fromPoint]; 1091 | else fromPoint.x = self.thinkVerb.presentationLayer.anchorPoint.x; 1092 | fromPoint.y = from; 1093 | anchor.fromValue = @(fromPoint); 1094 | 1095 | CGPoint toPoint = CGPointZero; 1096 | if (anchor.toValue) [anchor.toValue getValue:&toPoint]; 1097 | else toPoint.x = self.thinkVerb.presentationLayer.anchorPoint.x; 1098 | toPoint.y = to; 1099 | anchor.toValue = @(toPoint); 1100 | 1101 | return self; 1102 | }; 1103 | } 1104 | - (TVSpriteAnchor *(^)(CGFloat, CGFloat))z { 1105 | return ^ TVSpriteAnchor * (CGFloat from,CGFloat to) { 1106 | CABasicAnimation *anchorPointZ = self.animation.animations[1]; 1107 | anchorPointZ.fromValue = @(from); 1108 | anchorPointZ.toValue = @(to); 1109 | return self; 1110 | }; 1111 | } 1112 | - (TVSpriteAnchor *(^)(CGFloat))xTo { 1113 | return ^ TVSpriteAnchor * (CGFloat x) { 1114 | CABasicAnimation *anchor = self.animation.animations[0]; 1115 | CGPoint toPoint = CGPointZero; 1116 | if (anchor.toValue) [anchor.toValue getValue:&toPoint]; 1117 | toPoint.x = x; 1118 | if (!anchor.toValue) toPoint.y = self.thinkVerb.presentationLayer.anchorPoint.y; 1119 | anchor.toValue = @(toPoint); 1120 | return self; 1121 | }; 1122 | } 1123 | - (TVSpriteAnchor *(^)(CGFloat))yTo { 1124 | return ^ TVSpriteAnchor * (CGFloat y) { 1125 | CABasicAnimation *anchor = self.animation.animations[0]; 1126 | CGPoint toPoint = CGPointZero; 1127 | if (anchor.toValue) [anchor.toValue getValue:&toPoint]; 1128 | toPoint.y = y; 1129 | if (!anchor.toValue) toPoint.x = self.thinkVerb.presentationLayer.anchorPoint.x; 1130 | anchor.toValue = @(toPoint); 1131 | return self; 1132 | }; 1133 | } 1134 | - (TVSpriteAnchor *(^)(CGFloat))zTo { 1135 | return ^ TVSpriteAnchor * (CGFloat to) { 1136 | CABasicAnimation *anchorPointZ = self.animation.animations[1]; 1137 | anchorPointZ.toValue = @(to); 1138 | return self; 1139 | }; 1140 | } 1141 | @end 1142 | 1143 | @implementation TVSpriteFade 1144 | - (NSString *)keyPath {return @"opacity";} 1145 | - (TVSpriteFade *)fadeOut { 1146 | self.animation.toValue = @(0); 1147 | return self; 1148 | } 1149 | - (TVSpriteFade *)fadeIn { 1150 | self.animation.toValue = @(1); 1151 | return self; 1152 | } 1153 | - (TVSpriteFade *(^)(CGFloat))from { 1154 | return ^ TVSpriteFade * (CGFloat value) { 1155 | self.animation.fromValue = @(value > 1 ? 1 : value < 0 ? 0 : value); 1156 | return self; 1157 | }; 1158 | } 1159 | - (TVSpriteFade *(^)(CGFloat))to { 1160 | return ^ TVSpriteFade * (CGFloat value) { 1161 | self.animation.toValue = @(value > 1 ? 1 : value < 0 ? 0 : value); 1162 | return self; 1163 | }; 1164 | } 1165 | @end 1166 | 1167 | @implementation TVSpriteContents 1168 | - (TVSpriteContents *(^)(UIImage *, UIImage *))drawRange { 1169 | return ^ TVSpriteContents * (UIImage *from, UIImage *to) { 1170 | CABasicAnimation *drawAnim = [CABasicAnimation animationWithKeyPath:@"contents"]; 1171 | drawAnim.fromValue = from ? (__bridge id)from.CGImage : self.thinkVerb.presentationLayer.contents ?: (__bridge id)[self nilImage].CGImage; 1172 | drawAnim.toValue = to ? (__bridge id)to.CGImage : (__bridge id)[self nilImage].CGImage; 1173 | tv_add_animation_for_group(self.animation, drawAnim); 1174 | return self; 1175 | }; 1176 | } 1177 | - (UIImage *)nilImage { 1178 | UIImage *image; 1179 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0); 1180 | image = UIGraphicsGetImageFromCurrentImageContext(); 1181 | UIGraphicsEndImageContext(); 1182 | return image; 1183 | } 1184 | - (TVSpriteContents *(^)(CGRect, CGRect))rectRange { 1185 | return ^ TVSpriteContents * (CGRect from, CGRect to) { 1186 | CABasicAnimation *rectAnim = [CABasicAnimation animationWithKeyPath:@"contentsRect"]; 1187 | if (!CGRectEqualToRect(from, CGRectNull)) rectAnim.fromValue = @(from); 1188 | rectAnim.toValue = @(to); 1189 | tv_add_animation_for_group(self.animation, rectAnim); 1190 | return self; 1191 | }; 1192 | } 1193 | - (TVSpriteContents *(^)(CGRect))rect { 1194 | return ^ TVSpriteContents * (CGRect to) { 1195 | return self.rectRange(CGRectNull,to); 1196 | }; 1197 | } 1198 | - (TVSpriteContents *(^)(CGFloat, CGFloat))scaleRange { 1199 | return ^ TVSpriteContents * (CGFloat from, CGFloat to) { 1200 | CABasicAnimation *scaleAnim = [CABasicAnimation animationWithKeyPath:@"contentsScale"]; 1201 | if (from != CGFLOAT_MAX) scaleAnim.fromValue = @(from); 1202 | scaleAnim.toValue = @(to); 1203 | tv_add_animation_for_group(self.animation, scaleAnim); 1204 | return self; 1205 | }; 1206 | } 1207 | - (TVSpriteContents *(^)(CGFloat))scale { 1208 | return ^ TVSpriteContents * (CGFloat to) { 1209 | return self.scaleRange(CGFLOAT_MAX,to); 1210 | }; 1211 | } 1212 | - (TVSpriteContents *(^)(CGRect, CGRect))centerRange { 1213 | return ^ TVSpriteContents * (CGRect from, CGRect to) { 1214 | CABasicAnimation *centerAnim = [CABasicAnimation animationWithKeyPath:@"contentsCenter"]; 1215 | if (!CGRectEqualToRect(from, CGRectNull)) centerAnim.fromValue = @(from); 1216 | centerAnim.toValue = @(to); 1217 | tv_add_animation_for_group(self.animation, centerAnim); 1218 | return self; 1219 | }; 1220 | } 1221 | - (TVSpriteContents *(^)(CGRect))center { 1222 | return ^ TVSpriteContents * (CGRect to) { 1223 | return self.centerRange(CGRectNull,to); 1224 | }; 1225 | } 1226 | - (TVSpriteContents *(^)(CGFloat, CGFloat))minificationFilterBias { 1227 | return ^ TVSpriteContents * (CGFloat from, CGFloat to) { 1228 | CABasicAnimation *scaleAnim = [CABasicAnimation animationWithKeyPath:@"minificationFilterBias"]; 1229 | if (from != CGFLOAT_MAX) scaleAnim.fromValue = @(from); 1230 | scaleAnim.toValue = @(to); 1231 | tv_add_animation_for_group(self.animation, scaleAnim); 1232 | return self; 1233 | }; 1234 | } 1235 | 1236 | @end 1237 | 1238 | @implementation TVSpriteColor 1239 | - (NSString *)keyPath {return @"backgroundColor";} 1240 | - (TVSpriteColor *(^)(UIColor *, UIColor *))transit { 1241 | return ^TVSpriteColor * (UIColor *from, UIColor *to) { 1242 | if (from) self.animation.fromValue = (__bridge id)from.CGColor; 1243 | self.animation.toValue = (__bridge id)to.CGColor; 1244 | return self; 1245 | }; 1246 | } 1247 | - (TVSpriteColor *(^)(UIColor *))transitTo { 1248 | return ^TVSpriteColor * (UIColor *to) { 1249 | return self.transit(nil,to); 1250 | }; 1251 | } 1252 | 1253 | @end 1254 | 1255 | @implementation TVSpriteCornerRadius 1256 | - (NSString *)keyPath {return @"cornerRadius";} 1257 | - (TVSpriteCornerRadius *(^)(CGFloat, CGFloat))transit { 1258 | return ^TVSpriteCornerRadius * (CGFloat from, CGFloat to) { 1259 | if (from != CGFLOAT_MAX) self.animation.fromValue = @(from); 1260 | self.animation.toValue = @(to); 1261 | return self; 1262 | }; 1263 | } 1264 | - (TVSpriteCornerRadius *(^)(CGFloat))transitTo { 1265 | return ^TVSpriteCornerRadius * (CGFloat to) { 1266 | return self.transit(CGFLOAT_MAX,to); 1267 | }; 1268 | } 1269 | 1270 | @end 1271 | 1272 | @implementation TVSpriteBorder 1273 | - (TVSpriteBorder *(^)(CGFloat, CGFloat))width { 1274 | return ^TVSpriteBorder * (CGFloat from, CGFloat to) { 1275 | CABasicAnimation *widthAnim = [CABasicAnimation animationWithKeyPath:@"borderWidth"]; 1276 | if (from != CGFLOAT_MAX) widthAnim.fromValue = @(from); 1277 | widthAnim.toValue = @(to); 1278 | tv_add_animation_for_group(self.animation, widthAnim); 1279 | return self; 1280 | }; 1281 | } 1282 | - (TVSpriteBorder *(^)(CGFloat))widthTo { 1283 | return ^TVSpriteBorder * (CGFloat to) { 1284 | return self.width(CGFLOAT_MAX,to); 1285 | }; 1286 | } 1287 | - (TVSpriteBorder *(^)(UIColor *, UIColor *))color { 1288 | return ^TVSpriteBorder * (UIColor *from, UIColor *to) { 1289 | CABasicAnimation *colorAnim = [CABasicAnimation animationWithKeyPath:@"borderColor"]; 1290 | if (from) colorAnim.fromValue = (__bridge id)from.CGColor; 1291 | colorAnim.toValue = (__bridge id)to.CGColor; 1292 | tv_add_animation_for_group(self.animation, colorAnim); 1293 | return self; 1294 | }; 1295 | } 1296 | - (TVSpriteBorder *(^)(UIColor *))colorTo { 1297 | return ^TVSpriteBorder * (UIColor *to) { 1298 | return self.color(nil,to); 1299 | }; 1300 | } 1301 | 1302 | @end 1303 | 1304 | @interface TVSpritePath () 1305 | @property (nonatomic,assign) CGPoint cachedCurvePoint; 1306 | @property (nonatomic,assign) CGPoint cachedCpt1,cachedCpt2; 1307 | @end 1308 | 1309 | @implementation TVSpritePath 1310 | - (NSString *)keyPath {return @"position";} 1311 | - (instancetype)init { 1312 | if (self = [super init]) { 1313 | self.cachedCurvePoint = self.cachedCpt1 = self.cachedCpt2 = CGRectNull.origin; 1314 | self.animation.calculationMode = kCAAnimationLinear; 1315 | } 1316 | return self; 1317 | } 1318 | - (TVSpritePath *(^)(CGFloat, CGFloat))beginWith { 1319 | NSAssert(self.animation.values.count == 0, @"you cannot call beginWith again or there must be something wrong about animation.values"); 1320 | return ^ TVSpritePath * (CGFloat x,CGFloat y) { 1321 | CGMutablePathRef path = CGPathCreateMutable(); 1322 | CGPathMoveToPoint(path, NULL, x, y); 1323 | NSMutableArray *values = [NSMutableArray new]; 1324 | [values addObject:@(CGPointMake(x, y))]; 1325 | self.animation.values = values; 1326 | CGPathRelease(self.animation.path); 1327 | self.animation.path = path; 1328 | return self; 1329 | }; 1330 | } 1331 | - (TVSpritePath *(^)(CGFloat, CGFloat))lineTo { 1332 | if (self.animation.values.count == 0) { 1333 | self.beginWith(self.thinkVerb.presentationLayer.position.x,self.thinkVerb.presentationLayer.position.y); 1334 | } 1335 | [self addCachedCurvePointIfNeeded]; 1336 | return ^ TVSpritePath * (CGFloat x,CGFloat y) { 1337 | CGMutablePathRef path = self.animation.path; 1338 | CGPathAddLineToPoint(path, NULL, x, y); 1339 | [self addValueToValuesWithX:x y:y]; 1340 | return self; 1341 | }; 1342 | } 1343 | - (TVSpritePath *(^)(CGFloat, CGFloat))curveTo { 1344 | if (self.animation.values.count == 0) { 1345 | self.beginWith(self.thinkVerb.presentationLayer.position.x,self.thinkVerb.presentationLayer.position.y); 1346 | } 1347 | [self addCachedCurvePointIfNeeded]; 1348 | return ^ TVSpritePath * (CGFloat x,CGFloat y) { 1349 | self.cachedCurvePoint = CGPointMake(x, y); 1350 | [self addValueToValuesWithX:x y:y]; 1351 | return self; 1352 | }; 1353 | } 1354 | - (TVSpritePath *(^)(CGFloat, CGFloat))cpt1 { 1355 | return ^ TVSpritePath * (CGFloat x,CGFloat y) { 1356 | self.cachedCpt1 = CGPointMake(x, y); 1357 | return self; 1358 | }; 1359 | } 1360 | - (TVSpritePath *(^)(CGFloat, CGFloat))cpt2 { 1361 | return ^ TVSpritePath * (CGFloat x,CGFloat y) { 1362 | self.cachedCpt2 = CGPointMake(x, y); 1363 | return self; 1364 | }; 1365 | } 1366 | - (void)addCachedCurvePointIfNeeded { 1367 | if (!CGPointEqualToPoint(self.cachedCurvePoint, CGRectNull.origin)) { 1368 | NSAssert(!CGPointEqualToPoint(self.cachedCpt1, CGRectNull.origin) && 1369 | !CGPointEqualToPoint(self.cachedCpt2, CGRectNull.origin), @"you should set control point"); 1370 | CGMutablePathRef path = self.animation.path; 1371 | CGPathAddCurveToPoint(path, NULL, 1372 | self.cachedCpt1.x, self.cachedCpt1.y, 1373 | self.cachedCpt2.x, self.cachedCpt2.y, 1374 | self.cachedCurvePoint.x, self.cachedCurvePoint.y); 1375 | self.cachedCurvePoint = self.cachedCpt1 = self.cachedCpt2 = CGRectNull.origin; 1376 | } 1377 | } 1378 | - (void)addValueToValuesWithX:(CGFloat)x y:(CGFloat)y { 1379 | NSMutableArray *values = [self.animation.values mutableCopy]; 1380 | [values addObject:@(CGPointMake(x, y))]; 1381 | self.animation.values = values; 1382 | } 1383 | 1384 | - (NSString *(^)(void))activate { 1385 | [self addCachedCurvePointIfNeeded]; 1386 | return super.activate; 1387 | } 1388 | 1389 | @end 1390 | 1391 | #pragma clang diagnostic pop 1392 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 92602D6C222CCE18004425B1 /* TextProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 92602D6B222CCE18004425B1 /* TextProgressView.m */; }; 11 | 926F54E521EF63140066C8EE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 926F54E421EF63140066C8EE /* AppDelegate.m */; }; 12 | 926F54E821EF63140066C8EE /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 926F54E721EF63140066C8EE /* ViewController.m */; }; 13 | 926F54ED21EF63160066C8EE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 926F54EC21EF63160066C8EE /* Assets.xcassets */; }; 14 | 926F54F021EF63160066C8EE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 926F54EE21EF63160066C8EE /* LaunchScreen.storyboard */; }; 15 | 926F54F321EF63160066C8EE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 926F54F221EF63160066C8EE /* main.m */; }; 16 | 926F54FC21EF63600066C8EE /* ThinkVerb.m in Sources */ = {isa = PBXBuildFile; fileRef = 926F54FA21EF63600066C8EE /* ThinkVerb.m */; }; 17 | 969E463221F2CE660055E836 /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 969E463121F2CE660055E836 /* TableViewController.m */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 92602D6A222CCE18004425B1 /* TextProgressView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextProgressView.h; sourceTree = ""; }; 22 | 92602D6B222CCE18004425B1 /* TextProgressView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TextProgressView.m; sourceTree = ""; }; 23 | 926F54E021EF63140066C8EE /* ThinkVerbDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ThinkVerbDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 926F54E321EF63140066C8EE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 25 | 926F54E421EF63140066C8EE /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 26 | 926F54E621EF63140066C8EE /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 27 | 926F54E721EF63140066C8EE /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 28 | 926F54EC21EF63160066C8EE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | 926F54EF21EF63160066C8EE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 30 | 926F54F121EF63160066C8EE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | 926F54F221EF63160066C8EE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 32 | 926F54FA21EF63600066C8EE /* ThinkVerb.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThinkVerb.m; sourceTree = ""; }; 33 | 926F54FB21EF63600066C8EE /* ThinkVerb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThinkVerb.h; sourceTree = ""; }; 34 | 969E463021F2CE660055E836 /* TableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; }; 35 | 969E463121F2CE660055E836 /* TableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 926F54DD21EF63140066C8EE /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 92602D69222CCDDA004425B1 /* View */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 92602D6A222CCE18004425B1 /* TextProgressView.h */, 53 | 92602D6B222CCE18004425B1 /* TextProgressView.m */, 54 | ); 55 | path = View; 56 | sourceTree = ""; 57 | }; 58 | 926F54D721EF63140066C8EE = { 59 | isa = PBXGroup; 60 | children = ( 61 | 926F54F921EF63600066C8EE /* ThinkVerb */, 62 | 926F54E221EF63140066C8EE /* ThinkVerbDemo */, 63 | 926F54E121EF63140066C8EE /* Products */, 64 | ); 65 | sourceTree = ""; 66 | }; 67 | 926F54E121EF63140066C8EE /* Products */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 926F54E021EF63140066C8EE /* ThinkVerbDemo.app */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 926F54E221EF63140066C8EE /* ThinkVerbDemo */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 92602D69222CCDDA004425B1 /* View */, 79 | 926F54E321EF63140066C8EE /* AppDelegate.h */, 80 | 926F54E421EF63140066C8EE /* AppDelegate.m */, 81 | 969E463021F2CE660055E836 /* TableViewController.h */, 82 | 969E463121F2CE660055E836 /* TableViewController.m */, 83 | 926F54E621EF63140066C8EE /* ViewController.h */, 84 | 926F54E721EF63140066C8EE /* ViewController.m */, 85 | 926F54EC21EF63160066C8EE /* Assets.xcassets */, 86 | 926F54EE21EF63160066C8EE /* LaunchScreen.storyboard */, 87 | 926F54F121EF63160066C8EE /* Info.plist */, 88 | 926F54F221EF63160066C8EE /* main.m */, 89 | ); 90 | path = ThinkVerbDemo; 91 | sourceTree = ""; 92 | }; 93 | 926F54F921EF63600066C8EE /* ThinkVerb */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 926F54FB21EF63600066C8EE /* ThinkVerb.h */, 97 | 926F54FA21EF63600066C8EE /* ThinkVerb.m */, 98 | ); 99 | name = ThinkVerb; 100 | path = ../ThinkVerb; 101 | sourceTree = ""; 102 | }; 103 | /* End PBXGroup section */ 104 | 105 | /* Begin PBXNativeTarget section */ 106 | 926F54DF21EF63140066C8EE /* ThinkVerbDemo */ = { 107 | isa = PBXNativeTarget; 108 | buildConfigurationList = 926F54F621EF63160066C8EE /* Build configuration list for PBXNativeTarget "ThinkVerbDemo" */; 109 | buildPhases = ( 110 | 926F54DC21EF63140066C8EE /* Sources */, 111 | 926F54DD21EF63140066C8EE /* Frameworks */, 112 | 926F54DE21EF63140066C8EE /* Resources */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = ThinkVerbDemo; 119 | productName = ThinkVerbDemo; 120 | productReference = 926F54E021EF63140066C8EE /* ThinkVerbDemo.app */; 121 | productType = "com.apple.product-type.application"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | 926F54D821EF63140066C8EE /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastUpgradeCheck = 1010; 130 | ORGANIZATIONNAME = CAI; 131 | TargetAttributes = { 132 | 926F54DF21EF63140066C8EE = { 133 | CreatedOnToolsVersion = 10.1; 134 | }; 135 | }; 136 | }; 137 | buildConfigurationList = 926F54DB21EF63140066C8EE /* Build configuration list for PBXProject "ThinkVerbDemo" */; 138 | compatibilityVersion = "Xcode 9.3"; 139 | developmentRegion = en; 140 | hasScannedForEncodings = 0; 141 | knownRegions = ( 142 | en, 143 | Base, 144 | ); 145 | mainGroup = 926F54D721EF63140066C8EE; 146 | productRefGroup = 926F54E121EF63140066C8EE /* Products */; 147 | projectDirPath = ""; 148 | projectRoot = ""; 149 | targets = ( 150 | 926F54DF21EF63140066C8EE /* ThinkVerbDemo */, 151 | ); 152 | }; 153 | /* End PBXProject section */ 154 | 155 | /* Begin PBXResourcesBuildPhase section */ 156 | 926F54DE21EF63140066C8EE /* Resources */ = { 157 | isa = PBXResourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | 926F54F021EF63160066C8EE /* LaunchScreen.storyboard in Resources */, 161 | 926F54ED21EF63160066C8EE /* Assets.xcassets in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | 926F54DC21EF63140066C8EE /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 926F54E821EF63140066C8EE /* ViewController.m in Sources */, 173 | 926F54F321EF63160066C8EE /* main.m in Sources */, 174 | 926F54E521EF63140066C8EE /* AppDelegate.m in Sources */, 175 | 92602D6C222CCE18004425B1 /* TextProgressView.m in Sources */, 176 | 969E463221F2CE660055E836 /* TableViewController.m in Sources */, 177 | 926F54FC21EF63600066C8EE /* ThinkVerb.m in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin PBXVariantGroup section */ 184 | 926F54EE21EF63160066C8EE /* LaunchScreen.storyboard */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | 926F54EF21EF63160066C8EE /* Base */, 188 | ); 189 | name = LaunchScreen.storyboard; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXVariantGroup section */ 193 | 194 | /* Begin XCBuildConfiguration section */ 195 | 926F54F421EF63160066C8EE /* Debug */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | CLANG_ANALYZER_NONNULL = YES; 200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 202 | CLANG_CXX_LIBRARY = "libc++"; 203 | CLANG_ENABLE_MODULES = YES; 204 | CLANG_ENABLE_OBJC_ARC = YES; 205 | CLANG_ENABLE_OBJC_WEAK = YES; 206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 207 | CLANG_WARN_BOOL_CONVERSION = YES; 208 | CLANG_WARN_COMMA = YES; 209 | CLANG_WARN_CONSTANT_CONVERSION = YES; 210 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 212 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 213 | CLANG_WARN_EMPTY_BODY = YES; 214 | CLANG_WARN_ENUM_CONVERSION = YES; 215 | CLANG_WARN_INFINITE_RECURSION = YES; 216 | CLANG_WARN_INT_CONVERSION = YES; 217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 222 | CLANG_WARN_STRICT_PROTOTYPES = YES; 223 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | CODE_SIGN_IDENTITY = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = dwarf; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | ENABLE_TESTABILITY = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu11; 233 | GCC_DYNAMIC_NO_PIC = NO; 234 | GCC_NO_COMMON_BLOCKS = YES; 235 | GCC_OPTIMIZATION_LEVEL = 0; 236 | GCC_PREPROCESSOR_DEFINITIONS = ( 237 | "DEBUG=1", 238 | "$(inherited)", 239 | ); 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 242 | GCC_WARN_UNDECLARED_SELECTOR = YES; 243 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 244 | GCC_WARN_UNUSED_FUNCTION = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 247 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 248 | MTL_FAST_MATH = YES; 249 | ONLY_ACTIVE_ARCH = YES; 250 | SDKROOT = iphoneos; 251 | }; 252 | name = Debug; 253 | }; 254 | 926F54F521EF63160066C8EE /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_ENABLE_OBJC_WEAK = YES; 265 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_COMMA = YES; 268 | CLANG_WARN_CONSTANT_CONVERSION = YES; 269 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 284 | CLANG_WARN_UNREACHABLE_CODE = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | CODE_SIGN_IDENTITY = "iPhone Developer"; 287 | COPY_PHASE_STRIP = NO; 288 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 289 | ENABLE_NS_ASSERTIONS = NO; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | GCC_C_LANGUAGE_STANDARD = gnu11; 292 | GCC_NO_COMMON_BLOCKS = YES; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 300 | MTL_ENABLE_DEBUG_INFO = NO; 301 | MTL_FAST_MATH = YES; 302 | SDKROOT = iphoneos; 303 | VALIDATE_PRODUCT = YES; 304 | }; 305 | name = Release; 306 | }; 307 | 926F54F721EF63160066C8EE /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | CODE_SIGN_STYLE = Automatic; 312 | DEVELOPMENT_TEAM = 369359VRQ2; 313 | GCC_PREPROCESSOR_DEFINITIONS = ( 314 | "DEBUG=1", 315 | "$(inherited)", 316 | ); 317 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../ThinkVerb\""; 318 | INFOPLIST_FILE = ThinkVerbDemo/Info.plist; 319 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 320 | LD_RUNPATH_SEARCH_PATHS = ( 321 | "$(inherited)", 322 | "@executable_path/Frameworks", 323 | ); 324 | PRODUCT_BUNDLE_IDENTIFIER = com.TV.ThinkVerbDemo; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | TARGETED_DEVICE_FAMILY = 1; 327 | USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../ThinkVerb\""; 328 | }; 329 | name = Debug; 330 | }; 331 | 926F54F821EF63160066C8EE /* Release */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 335 | CODE_SIGN_STYLE = Automatic; 336 | DEVELOPMENT_TEAM = 369359VRQ2; 337 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../ThinkVerb\""; 338 | INFOPLIST_FILE = ThinkVerbDemo/Info.plist; 339 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 340 | LD_RUNPATH_SEARCH_PATHS = ( 341 | "$(inherited)", 342 | "@executable_path/Frameworks", 343 | ); 344 | PRODUCT_BUNDLE_IDENTIFIER = com.TV.ThinkVerbDemo; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | TARGETED_DEVICE_FAMILY = 1; 347 | USER_HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../ThinkVerb\""; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | 926F54DB21EF63140066C8EE /* Build configuration list for PBXProject "ThinkVerbDemo" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | 926F54F421EF63160066C8EE /* Debug */, 358 | 926F54F521EF63160066C8EE /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | 926F54F621EF63160066C8EE /* Build configuration list for PBXNativeTarget "ThinkVerbDemo" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | 926F54F721EF63160066C8EE /* Debug */, 367 | 926F54F821EF63160066C8EE /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | }; 374 | rootObject = 926F54D821EF63140066C8EE /* Project object */; 375 | } 376 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo.xcodeproj/xcuserdata/ruitechen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ThinkVerbDemo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/1/16. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @property (nonatomic,strong) UINavigationController *navigationController; 16 | 17 | 18 | @end 19 | 20 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/1/16. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TableViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | [self.window makeKeyAndVisible]; 21 | return YES; 22 | } 23 | 24 | - (UIWindow *)window { 25 | if (!_window) { 26 | _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 27 | _window.rootViewController = self.navigationController; 28 | } 29 | return _window; 30 | } 31 | 32 | - (UINavigationController *)navigationController { 33 | if (!_navigationController) { 34 | _navigationController = [[UINavigationController alloc] initWithRootViewController:[[TableViewController alloc] initWithStyle:UITableViewStyleGrouped]]; 35 | } 36 | return _navigationController; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "升级@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "升级@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/1.imageset/升级@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/1.imageset/升级@2x.png -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/1.imageset/升级@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/1.imageset/升级@3x.png -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "通过@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "通过@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/2.imageset/通过@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/2.imageset/通过@2x.png -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/2.imageset/通过@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/2.imageset/通过@3x.png -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "退回@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "退回@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/3.imageset/退回@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/3.imageset/退回@2x.png -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/3.imageset/退回@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hon-key/ThinkVerb/9da3838f9afa5713aff78483e3f0876e9375ea4f/ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/3.imageset/退回@3x.png -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/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 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/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 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/TableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.h 3 | // ThinkVerbDemo 4 | // 5 | // Created by 工作 on 2019/1/19. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/TableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.m 3 | // ThinkVerbDemo 4 | // 5 | // Created by 工作 on 2019/1/19. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import "TableViewController.h" 10 | #import "ViewController.h" 11 | 12 | @interface TableViewController () 13 | @property (nonatomic,assign) int sectionCount; 14 | @property (nonatomic,strong) NSMutableArray *counts; 15 | @end 16 | 17 | @implementation TableViewController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | self.tableView.rowHeight = 44; 22 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; 23 | self.title = @"Animation List"; 24 | while (animationSections[self.sectionCount].name) { 25 | self.sectionCount++; 26 | } 27 | self.counts = [NSMutableArray new]; 28 | for (int i = 0; i < self.sectionCount; i++) { 29 | int count = 0; 30 | while (animationSections[i].unit[count].key) { 31 | count++; 32 | } 33 | [self.counts addObject:@(count)]; 34 | } 35 | 36 | } 37 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 38 | return self.sectionCount; 39 | } 40 | 41 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 42 | return animationSections[section].name; 43 | } 44 | 45 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 46 | return self.counts[section].integerValue; 47 | } 48 | 49 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 50 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; 51 | cell.textLabel.text = animationSections[indexPath.section].unit[indexPath.row].key; 52 | return cell; 53 | } 54 | 55 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 56 | ViewController *controller = [[ViewController alloc] init]; 57 | controller.unit = &animationSections[indexPath.section].unit[indexPath.row]; 58 | [self.navigationController pushViewController:controller animated:YES]; 59 | } 60 | 61 | 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/View/TextProgressView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GithubTextProgressView.h 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/3/4. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TextProgressLayer : CALayer 12 | @property (nonatomic,assign) CGFloat progress; 13 | 14 | @end 15 | 16 | 17 | @interface TextProgressView : UIView 18 | @property (nonatomic,strong) UILabel *label; 19 | @end 20 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/View/TextProgressView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GithubTextProgressView.m 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/3/4. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import "TextProgressView.h" 10 | 11 | @implementation TextProgressLayer 12 | - (instancetype)initWithLayer:(TextProgressLayer *)layer { 13 | if (self = [super initWithLayer:layer]) { 14 | self.progress = layer.progress; 15 | } 16 | return self; 17 | } 18 | - (void)drawInContext:(CGContextRef)ctx { 19 | CGContextSetFillColorWithColor(ctx, [UIColor greenColor].CGColor); 20 | CGContextFillRect(ctx, CGRectMake(0, 0, self.bounds.size.width * self.progress, self.bounds.size.height)); 21 | } 22 | 23 | + (BOOL)needsDisplayForKey:(NSString *)key { 24 | if ([key isEqualToString:@"progress"]) { 25 | return YES; 26 | }else { 27 | return [super needsDisplayForKey:key]; 28 | } 29 | } 30 | 31 | - (void)setProgress:(CGFloat)progress { 32 | _progress = progress; 33 | [self setNeedsDisplay]; 34 | } 35 | 36 | @end 37 | 38 | @implementation TextProgressView 39 | + (Class)layerClass { 40 | return [TextProgressLayer class]; 41 | } 42 | - (instancetype)init { 43 | if (self = [super init]) { 44 | self.backgroundColor = [UIColor redColor]; 45 | self.layer.contentsScale = [UIScreen mainScreen].scale; 46 | [self.layer setNeedsDisplay]; 47 | [self addObserver:self forKeyPath:@"frame" options:0 context:nil]; 48 | self.layer.mask = self.label.layer; 49 | } 50 | return self; 51 | } 52 | 53 | - (void)dealloc { 54 | [self removeObserver:self forKeyPath:@"frame"]; 55 | } 56 | 57 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 58 | self.layer.mask.frame = self.bounds; 59 | } 60 | 61 | - (UILabel *)label { 62 | if (!_label) { 63 | _label = [UILabel new]; 64 | _label.font = [UIFont boldSystemFontOfSize:40]; 65 | _label.textAlignment = NSTextAlignmentCenter; 66 | _label.translatesAutoresizingMaskIntoConstraints = NO; 67 | _label.text = @"Github"; 68 | } 69 | return _label; 70 | } 71 | @end 72 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/1/16. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef struct AnimationUnit { 12 | NSString *key; 13 | NSString *selector; 14 | }AnimationUnit; 15 | 16 | typedef struct AnimationSection { 17 | NSString *name; 18 | AnimationUnit *unit; 19 | }AnimationSection; 20 | 21 | extern AnimationSection animationSections[]; 22 | extern AnimationUnit baseAnimations[]; 23 | extern AnimationUnit animationSets[]; 24 | 25 | @interface ViewController : UIViewController 26 | @property (nonatomic,unsafe_unretained) AnimationUnit *unit; 27 | @end 28 | 29 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/1/16. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ThinkVerb.h" 11 | #import "TextProgressView.h" 12 | 13 | AnimationSection animationSections[] = { 14 | {.name = @"Base Animations",.unit = baseAnimations}, 15 | {.name = @"Animation sets",.unit = animationSets}, 16 | {.name = nil,.unit = NULL}, 17 | }; 18 | 19 | AnimationUnit baseAnimations[] = { 20 | {.key = @"Move", .selector = @"move"}, 21 | {.key = @"Scale", .selector = @"scale"}, 22 | {.key = @"Rotate", .selector = @"rotate"}, 23 | {.key = @"Shadow", .selector = @"shadow"}, 24 | {.key = @"Bounds", .selector = @"bounds"}, 25 | {.key = @"Anchor", .selector = @"anchor"}, 26 | {.key = @"Translate", .selector = @"translate"}, 27 | {.key = @"Fade", .selector = @"fade"}, 28 | {.key = @"Contents->draw", .selector = @"drawRange"}, 29 | {.key = @"Contents->scale", .selector = @"contentsScale"}, 30 | {.key = @"Contents->rect", .selector = @"contentsRect"}, 31 | {.key = @"Contents->center", .selector = @"contentsCenter"}, 32 | {.key = @"BackgroundColor", .selector = @"backgroundColor"}, 33 | {.key = @"CornerRadius", .selector = @"cornerRadius"}, 34 | {.key = @"Border", .selector = @"border"}, 35 | {.key = @"Path", .selector = @"path"}, 36 | {.key = @"Spring", .selector = @"spring"}, 37 | {.key = @"Create layer spring sprite", .selector = @"createLayerSpringSprite"}, 38 | {.key = @"SubLayer",.selector = @"sublayer"}, 39 | {.key = nil, .selector = nil}, 40 | }; 41 | AnimationUnit animationSets[] = { 42 | {.key = @"Jump and Shake", .selector = @"jumpIcon"}, 43 | {.key = @"Lyrics Animation", .selector = @"lyricsAnimation"}, 44 | {.key = nil, .selector = nil}, 45 | }; 46 | 47 | @interface ViewController () 48 | @property (nonatomic,strong) UIView *box; 49 | @end 50 | 51 | @implementation ViewController 52 | - (void)viewDidLoad { 53 | [super viewDidLoad]; 54 | self.view.backgroundColor = [UIColor groupTableViewBackgroundColor]; 55 | self.title = self.unit->key; 56 | SEL prepareSEL = NSSelectorFromString([NSString stringWithFormat:@"p_%@",self.unit->selector]); 57 | if ([self respondsToSelector:prepareSEL]) { 58 | [self performSelectorOnMainThread:prepareSEL withObject:nil waitUntilDone:NO]; 59 | } 60 | } 61 | - (void)viewDidLayoutSubviews { 62 | [super viewDidLayoutSubviews]; 63 | self.box.center = self.view.center; 64 | } 65 | - (void)viewWillDisappear:(BOOL)animated { 66 | [super viewWillDisappear:animated]; 67 | self.box.TVAnimation.clear(); 68 | } 69 | 70 | - (void)triggerAnimation { 71 | [self performSelectorOnMainThread:NSSelectorFromString(self.unit->selector) withObject:nil waitUntilDone:NO]; 72 | } 73 | 74 | #pragma mark - prepare 75 | - (void)p_anchor { 76 | self.box.frame = CGRectMake(0, 0, 100, 20); 77 | } 78 | - (void)p_contentsScale { 79 | self.box.backgroundColor = [UIColor whiteColor]; 80 | self.box.layer.contents = (__bridge id)[UIImage imageNamed:@"1"].CGImage; 81 | self.box.layer.contentsScale = [UIScreen mainScreen].scale; 82 | self.box.layer.contentsGravity = kCAGravityCenter; 83 | } 84 | - (void)p_contentsRect { 85 | self.box.backgroundColor = [UIColor whiteColor]; 86 | self.box.layer.contents = (__bridge id)[UIImage imageNamed:@"1"].CGImage; 87 | } 88 | - (void)p_contentsCenter { 89 | self.box.backgroundColor = [UIColor whiteColor]; 90 | self.box.layer.contents = (__bridge id)[UIImage imageNamed:@"1"].CGImage; 91 | self.box.frame = CGRectMake(0, 0, 100, 50); 92 | } 93 | - (void)p_sublayer { 94 | UIView *anotherBox = [UIView new]; 95 | anotherBox.backgroundColor = [UIColor blueColor]; 96 | [self.view addSubview:anotherBox]; 97 | anotherBox.frame = CGRectMake(0, 0, 50, 50); 98 | anotherBox.center = CGPointMake(self.view.center.x - 100, self.view.center.y); 99 | } 100 | - (void)p_jumpIcon { 101 | self.box.backgroundColor = [UIColor groupTableViewBackgroundColor]; 102 | self.box.layer.contents = (__bridge id)[UIImage imageNamed:@"1"].CGImage; 103 | } 104 | - (void)p_lyricsAnimation { 105 | [_box removeFromSuperview]; 106 | TextProgressView *progressView = [[TextProgressView alloc] init]; 107 | progressView.label.text = @"ONE PUNCH-MAN"; 108 | _box = progressView; 109 | [self.view addSubview:self.box]; 110 | [progressView.label sizeToFit]; 111 | self.box.frame = progressView.label.frame; 112 | [self.box addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(triggerAnimation)]]; 113 | } 114 | #pragma mark - Animation 115 | - (void)move { 116 | self.box.TVAnimation.move.offset(50,100,0).activate(); 117 | } 118 | - (void)scale { 119 | self.box.TVAnimation.scale.to(2).activate(); 120 | } 121 | - (void)rotate { 122 | self.box.TVAnimation.rotate.x.with.endAngle(M_PI * 2).repeat(-1).activate(); 123 | self.box.TVAnimation.rotate.y.with.endAngle(M_PI * 2).repeat(-1).activate(); 124 | self.box.TVAnimation.rotate.z.with.endAngle(M_PI * 2).repeat(-1).activate(); 125 | } 126 | - (void)shadow { 127 | self.box.TVAnimation.shadow 128 | .offset(CGSizeMake(0, 0),CGSizeMake(-3, -3)) 129 | .opacityTo(1) 130 | .colorTo([UIColor blueColor]) 131 | .radiusTo(8.0) 132 | .activate(); 133 | } 134 | - (void)bounds { 135 | self.box.TVAnimation.bounds.widthTo(10).heightTo(10).didStop(^{ 136 | self.box.TVAnimation.bounds.widthTo(100).heightTo(100).activate(); 137 | }).activate(); 138 | } 139 | - (void)anchor { 140 | if (self.box.TVAnimation.sprites.count >= 1) { 141 | CGFloat x = (arc4random() % 10) / 10.0,y = (arc4random() % 10) / 10.0; 142 | self.box.TVAnimation.anchor.xTo(x).yTo(y).didStop(^{ 143 | self.box.layer.anchorPoint = CGPointMake(x, y); 144 | }).keepAlive(NO).timing(TVTiming.linear).duration(0.5).activate(); 145 | }else { 146 | self.box.TVAnimation.rotate.z.with.endAngle(M_PI * 2).repeat(-1).duration(1).timing(TVTiming.linear).activate(); 147 | } 148 | } 149 | - (void)translate { 150 | self.box.TVAnimation.translate.xBy(100).yBy(100).activate(); 151 | } 152 | - (void)fade { 153 | self.box.TVAnimation.fade.fadeOut.reverse.repeat(-1).duration(0.5).activate(); 154 | } 155 | - (void)drawRange { 156 | self.box.backgroundColor = [UIColor whiteColor]; 157 | self.box.TVAnimation.appearance.keepAlive(NO).end(); 158 | self.box.TVAnimation.contents.drawRange(nil,[UIImage imageNamed:@"1"]).didStop(^{ 159 | self.box.TVAnimation.contents.drawRange([UIImage imageNamed:@"1"],[UIImage imageNamed:@"2"]).didStop(^{ 160 | self.box.TVAnimation.contents.drawRange([UIImage imageNamed:@"2"],[UIImage imageNamed:@"3"]).didStop(^{ 161 | self.box.TVAnimation.contents.drawRange([UIImage imageNamed:@"3"],[UIImage imageNamed:@"2"]).activate(); 162 | }).activate(); 163 | }).activate(); 164 | }).activate(); 165 | } 166 | 167 | - (void)contentsScale { 168 | self.box.TVAnimation.contents.scale(1).activate(); 169 | } 170 | - (void)contentsRect { 171 | self.box.TVAnimation.contents.rect(CGRectMake(0.25, 0.25, 0.5, 0.5)).didStop(^{ 172 | self.box.TVAnimation.contents.rect(CGRectMake(-0.25, -0.25, 1.5, 1.5)).activate(); 173 | }).activate(); 174 | } 175 | - (void)contentsCenter { 176 | self.box.TVAnimation.contents.centerRange(CGRectMake(0, 0, 1, 1),CGRectMake(0.4, 0.4, 0.2, 0.2)).activate(); 177 | } 178 | - (void)backgroundColor { 179 | self.box.TVAnimation.backgroundColor.transitTo([UIColor blueColor]).didStop(^{ 180 | self.box.TVAnimation.backgroundColor.transitTo([UIColor yellowColor]).didStop(^{ 181 | self.box.TVAnimation.backgroundColor.transitTo([UIColor greenColor]).didStop(^{ 182 | 183 | }).activate(); 184 | }).activate(); 185 | }).activate(); 186 | } 187 | - (void)cornerRadius { 188 | self.box.TVAnimation.cornerRadius.transitTo(25).reverse.duration(0.5).repeat(-1).activate(); 189 | } 190 | - (void)border { 191 | self.box.TVAnimation.border.widthTo(10).color([UIColor yellowColor],[UIColor blueColor]).duration(0.25).reverse.repeat(-1).activate(); 192 | } 193 | - (void)path { 194 | self.box.TVAnimation.appearance.timing(TVTiming.linear).end(); 195 | CGFloat width = [UIScreen mainScreen].bounds.size.width,height = [UIScreen mainScreen].bounds.size.height; 196 | self.box.TVAnimation.path.duration(10).mode(kCAAnimationLinear) 197 | .beginWith(self.box.center.x,self.box.center.y).endAtPercent(0) 198 | .lineTo(0,0).endAtPercent(25).timing(TVTiming.linear) 199 | .curveTo(width,height).cpt1(width, 0).cpt2(width, 0).endAtPercent(50).timing(TVTiming.extremeEaseOut) 200 | .lineTo(0,height).endAtPercent(75).timing(TVTiming.extremeEaseOut) 201 | .curveTo(width,0).cpt1(0, 0).cpt2(0, 0).endAtPercent(100).timing(TVTiming.extremeEaseIn) 202 | .didStop(^{ 203 | self.box.TVAnimation.move.xTo(self.view.center.x).yTo(self.view.center.y).didStop(^{ 204 | self.box.TVAnimation.clear(); 205 | }).activate(); 206 | }).activate(); 207 | } 208 | - (void)spring { 209 | self.box.TVAnimation.scale.to(1.01).damping(5).mass(1).initialVelocity(10000).stiffness(7500).duration(2).keepAlive(NO).activate(); 210 | } 211 | - (void)createLayerSpringSprite { 212 | self.box.layer.TVAnimation.scale.to(1.01).damping(5).mass(1).initialVelocity(10000).stiffness(7500).duration(2).keepAlive(NO).activate(); 213 | } 214 | - (void)sublayer { 215 | self.view.TVAnimation.rotate.z.endAngle(M_PI * 2).repeat(-1).duration(3).timing(TVTiming.extremeEaseOut).toSubLayer.activate(); 216 | } 217 | 218 | #pragma mark - 219 | - (void)jumpIcon { 220 | self.box.TVAnimation.translate.yBy(-100).timing(TVTiming.extremeEaseOut).repeat(-1).reverse.duration(0.5).activate(); 221 | self.box.TVAnimation.translate.x(-3,3).timing(TVTiming.extremeEaseOut).repeat(-1).reverse.duration(0.05).activate(); 222 | } 223 | - (void)lyricsAnimation { 224 | TextProgressLayer *layer = (TextProgressLayer *)self.box.layer; 225 | self.box.TVAnimation.basicCustom.property(@"progress").to(@0.5) 226 | .timing(TVTiming.extremeEaseOut).duration(2).keepAlive(YES).didStop(^{ 227 | layer.progress = 0.5; 228 | self.box.TVAnimation.clear(); 229 | self.box.TVAnimation.basicCustom.property(@"progress").to(@1.0) 230 | .timing(TVTiming.extremeEaseOut).duration(6).keepAlive(YES).didStop(^{ 231 | layer.progress = 0; 232 | self.box.TVAnimation.clear(); 233 | }).activate(); 234 | }).activate(); 235 | } 236 | 237 | #pragma mark - 238 | - (UIView *)box { 239 | if (!_box) { 240 | _box = [UIView new]; 241 | _box.frame = CGRectMake(0, 0, 50, 50); 242 | _box.backgroundColor = [UIColor redColor]; 243 | [_box addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(triggerAnimation)]]; 244 | _box.TVAnimation.appearance.duration(3).timing(TVTiming.extremeEaseOut).keepAlive(YES).end(); 245 | [self.view addSubview:_box]; 246 | } 247 | return _box; 248 | } 249 | 250 | 251 | @end 252 | -------------------------------------------------------------------------------- /ThinkVerbDemo/ThinkVerbDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ThinkVerbDemo 4 | // 5 | // Created by Ruite Chen on 2019/1/16. 6 | // Copyright © 2019 CAI. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------