├── 动画展示.gif ├── DEMO ├── code │ ├── 10.jpg │ ├── 2.jpg │ ├── ViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── ViewController.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── AppDelegate.m │ ├── UIImage+DWImageUtils.h │ ├── DWSlideCaptchaView.h │ ├── UIBezierPath+DWPathUtils.h │ ├── DWMacro.h │ ├── UIBezierPath+DWPathUtils.m │ ├── DWSlideCaptchaView.m │ └── UIImage+DWImageUtils.m └── code.xcodeproj │ └── project.pbxproj ├── DWSlideCaptchaView.podspec ├── LICENSE ├── .gitignore ├── README.md └── DWSlideCaptchaView ├── UIImage+DWImageUtils.h ├── DWSlideCaptchaView.h ├── UIBezierPath+DWPathUtils.h ├── DWMacro.h ├── UIBezierPath+DWPathUtils.m ├── DWSlideCaptchaView.m └── UIImage+DWImageUtils.m /动画展示.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWicky/DWSlideCaptchaView/HEAD/动画展示.gif -------------------------------------------------------------------------------- /DEMO/code/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWicky/DWSlideCaptchaView/HEAD/DEMO/code/10.jpg -------------------------------------------------------------------------------- /DEMO/code/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeWicky/DWSlideCaptchaView/HEAD/DEMO/code/2.jpg -------------------------------------------------------------------------------- /DEMO/code/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /DEMO/code/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /DEMO/code/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. 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 | -------------------------------------------------------------------------------- /DWSlideCaptchaView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "DWSlideCaptchaView" 3 | s.version = "1.0.2" 4 | s.summary = "A view to help you to do the slide captcha." 5 | s.description = "A view to help you to do the slide captcha.You can customsize it as you want." 6 | s.homepage = "https://github.com/CodeWicky/DWSlideCaptchaView" 7 | s.social_media_url = "http://www.jianshu.com/u/a56ec10f6603" 8 | s.license= { :type => "MIT", :file => "LICENSE" } 9 | s.author = { "codeWicky" => "codewicky@163.com" } 10 | s.source = { :git => "https://github.com/CodeWicky/DWSlideCaptchaView.git", :tag => s.version.to_s } 11 | s.source_files = "DWSlideCaptchaView/*.{h,m}" 12 | s.ios.deployment_target = '7.0' 13 | s.frameworks = 'UIKit' 14 | s.requires_arc = true 15 | end 16 | -------------------------------------------------------------------------------- /DEMO/code/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "UIBezierPath+DWPathUtils.h" 11 | #import "DWSlideCaptchaView.h" 12 | #import "UIImage+DWImageUtils.h" 13 | #import "DWMacro.h" 14 | 15 | @interface ViewController () 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | DWDefaultSlideCaptchaView * cap = [[DWDefaultSlideCaptchaView alloc] initWithFrame:CGRectMakeWithPointAndSize(CGPointZero, CGSizeMake(400, 300)) image:DWImage(@"2.jpg") slider:nil]; 24 | cap.indentifyCompletion = ^(BOOL success) { 25 | NSLog(@"Identify %@",(success ? @"success" : @"fail")); 26 | }; 27 | [self.view addSubview:cap]; 28 | } 29 | - (void)didReceiveMemoryWarning { 30 | [super didReceiveMemoryWarning]; 31 | // Dispose of any resources that can be recreated. 32 | } 33 | 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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 | -------------------------------------------------------------------------------- /DEMO/code/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /DEMO/code/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DEMO/code/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 | -------------------------------------------------------------------------------- /DEMO/code/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /DEMO/code/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | DWSlideCaptchaView 3 |

4 | 5 |

6 | DWSlideCaptchaView 7 |

8 | 9 | ## Description 10 | This is a Class help you to build a slide Captcha Quickly. 11 | 12 | Use the basic class DWSlideCaptchaView you can customsize the bgImage/thumpShape/identifyPoint/successAnimation/failAnimation. 13 | 14 | By the way I provide two protocol method which may be called when the identifyAnimation willStart and didFinished.You can use these two method to customsize you captchaView better. 15 | 16 | If you want you combine the CaptchaView with a slider(usually you may use a slider to change the value of thumb,you may also use -touchBegan to move it to point) just use The DWDefaultSlideCaptchaView or customsize it as you want. 17 | 18 | When you are customsizing the captchaView you may just focus on how the set the thumb currentPoint and when should you do the identify.You can easily find it in DWDefaultSlideCaptchaView. 19 | 20 | ## 描述 21 | 这是一个可以帮你快速集成滑动验证功能的类。 22 | 23 | 使用我提供的基础类,DWSlideCaptchaView,你可以定制验证视图中背景图片/滑块形状/验证点/验证成功动画/验证失败动画。 24 | 25 | 顺带一提的是,我提供了两个协议方法,他们分别将在验证的动画即将开始和动画已经结束时被调用。你可以借助这两个代理方法更好的定制你的验证视图。 26 | 27 | 如果你想将验证视图与一个滑块组合在一起(通常是有滑块去改变验证滑块的位置,当然你也可以通过 -touchBegan 方法控制验证滑块的位置),你可以使用 DWDefaultSlideCaptchaView 或者 按照你自己的想法去定制他。 28 | 29 | 当你要定制一个验证视图是,你只需要关心如何改变验证滑块的位置以及触发验证方法的时机。你可以在 DWDefaultSlideCaptchaView 中轻松的找到相关实现,当然我只是提供了一个简单的思路。 30 | 31 | 32 | ## Usage 33 | Firstly,drag it into your project or use cocoapods. 34 | 35 | pod 'DWSlideCaptchaView' 36 | 37 | 38 | To use the DWSlideCaptchaView,you may just need to know at least three API 39 | 40 | ///To get an instance of it. 41 | -(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage; 42 | 43 | ///Move the thumb 44 | -(void)moveToPoint:(CGPoint)point animated:(BOOL)animated 45 | 46 | ///Identify and get an result In the block 47 | -(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result 48 | 49 | With these three Method you can implement the Captcha. 50 | 51 | To use it in anyway you could change same of the property to modify its perform.**But what you should pay attention to is you should use `-beginConfigration` before set value to property and `-commitConfigration` after set value to property otherwise the new property will not be set or worked**. 52 | 53 | As said above,there are two protocol method that tell you the identifyAnimation's status,use it to customsize you captchaView.And follow the thought of DWDefaultSlideCaptchaView to create your own captchaView. 54 | 55 | 56 | ## 如何使用 57 | 首先,你应该将所需文件拖入工程中,或者你也可以用Cocoapods去集成他。 58 | 59 | pod 'DWSlideCaptchaView' 60 | 61 | 当使用DWSlideCaptchaView的时候,你最少只使用三个方法就能实现滑动验证功能。 62 | 63 | ///获得实例 64 | -(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage; 65 | 66 | ///改变滑块的位置 67 | -(void)moveToPoint:(CGPoint)point animated:(BOOL)animated 68 | 69 | ///验证当前滑块位置并在Block中获取到验证结果 70 | -(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result 71 | 72 | 借助这三个方法你完全可以实现滑动验证。 73 | 74 | 当然,为了更加多元化的使用它,你可以通过修改一些属性来让他更加适合你的需求。**但是,一定要记得,在改变属性之前,调用`-beginConfigration`方法,并在改变属性之后调用`-commitConfigration`方法,否则属性将不会被赋值或者不会奏效**。 75 | 76 | 77 | ## Contact With Me 78 | 79 | You may issue me on [my Github](https://github.com/CodeWicky/DWSlideCaptchaView) or send me a email at [codeWicky@163.com]() to tell me some advices or the bug,I will be so appreciated. 80 | 81 | If you like it please give a star. 82 | 83 | ## 联系作者 84 | 你可以通过在[我的Github](https://github.com/CodeWicky/DWSlideCaptchaView)上给我留言或者给我发送电子邮件[codeWicky@163.com]()来给我提一些建议或者指出我的bug,我将不胜感激。 85 | 86 | 如果你喜欢这个小东西,记得给我一个star吧,么么哒~ 87 | 88 | -------------------------------------------------------------------------------- /DEMO/code/UIImage+DWImageUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+DWImageUtils.h 3 | // Image 4 | // 5 | // Created by Wicky on 2016/12/6. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | /** 10 | DWImageUtils 11 | 图片工具类,提供常用图片扩展方法 12 | 13 | version 1.0.0 14 | 按功能分类扩展 15 | 16 | version 1.0.1 17 | 修复部分绘制模糊问题 18 | 修复subRect中因scale原因导致图片切割错误 19 | */ 20 | 21 | #import 22 | 23 | typedef NS_ENUM(NSInteger,DWContentMode)//图片填充模式 24 | { 25 | DWContentModeScaleAspectFit,//适应模式 26 | DWContentModeScaleAspectFill,//填充模式 27 | DWContentModeScaleToFill//拉伸模式 28 | }; 29 | 30 | @interface UIImage (DWImageInstanceUtils) 31 | 32 | /** 33 | 高性能按图片名称检索本地图片 34 | 35 | @param name 图片文件名 36 | @return 图片实例 37 | */ 38 | +(UIImage *)dw_ImageNamed:(NSString *)name; 39 | 40 | /** 41 | 高性能返回无延迟立即解压的图片实例 42 | 43 | @param url 图片路径,本地网络均可 44 | @return 图片实例 45 | */ 46 | +(UIImage *)dw_ImageWithUrl:(NSURL *)url; 47 | 48 | @end 49 | 50 | @interface UIImage (DWImageBase64Utils) 51 | 52 | ///转换图片为Base64字符串 53 | -(NSString *)dw_ImageToBase64String; 54 | 55 | ///Base64转换为图片 56 | + (UIImage *)dw_ImageWithBase64String:(NSString *)base64String; 57 | 58 | @end 59 | 60 | @interface UIImage (DWImageColorUtils) 61 | 62 | ///取图片某点颜色 63 | /** 64 | point:取色点 65 | 66 | 注:以图片自身宽高作为坐标系 67 | */ 68 | -(UIColor *)dw_ColorAtPoint:(CGPoint)point; 69 | 70 | ///按给定颜色生层图片 71 | +(UIImage *)dw_ImageWithColor:(UIColor *)color; 72 | 73 | ///以灰色空间生成图片 74 | -(UIImage *)dw_ConvertToGrayImage; 75 | 76 | ///生成图片的反色图片对象 77 | -(UIImage *)dw_ConvertToReversedColor; 78 | 79 | ///以给定颜色生成图像剪影 80 | -(UIImage *)dw_ConvertToSketchWithColor:(UIColor *)color; 81 | 82 | /** 83 | 生成处理每像素颜色后的图片 84 | 85 | @param handler 处理回调 86 | pixel为每一像素指向的内存指针 87 | x为横坐标 88 | y为纵坐标 89 | @return 处理后的图片 90 | 91 | eg: 92 | 将图片有效像素点转换为红色 93 | [self dw_ConvertImageWithPixelHandler:^(UInt8 *pixel, int x, int y) { 94 | UInt8 alpha = * (pixel + 3); 95 | if (alpha) { 96 | *pixel = 255; 97 | *(pixel + 1) = 0; 98 | *(pixel + 2) = 0; 99 | } 100 | }]; 101 | 102 | */ 103 | -(UIImage *)dw_ConvertImageWithPixelHandler:(void(^)(UInt8 * pixel,int x,int y))handler; 104 | 105 | @end 106 | 107 | @interface UIImage (DWImageClipUtils) 108 | 109 | ///获取带圆角的图片 110 | /* 111 | radius:返回图片的圆角半径 112 | 圆角半径不可超过图片尺寸的1/2,否则按1/2处理 113 | 114 | width:返回图片的宽度 115 | 返回的图片为一个宽高相等的矩形区域,但图片且居中显示 116 | 117 | mode:返回图片的填充模式 118 | 适应模式:以原图片比例,能显示全部图片的最大尺寸进行填充 119 | 填充模式:以原图片比例,图片能充满容器的最小尺寸进行填充 120 | 拉伸模式:以拉伸图片能够使图片充满容器的尺寸进行填充 121 | */ 122 | -(UIImage *)dw_CornerRadius:(CGFloat)radius withWidth:(CGFloat)width contentMode:(DWContentMode)mode; 123 | 124 | ///按给定path剪裁图片 125 | /** 126 | path:路径,剪裁区域。 127 | mode:填充模式 128 | 129 | 注: 130 | 1.路径中心对应图片中心 131 | 2.路径只决定剪裁图形,不影响剪裁位置 132 | */ 133 | -(UIImage *)dw_ClipImageWithPath:(UIBezierPath *)path mode:(DWContentMode)mode; 134 | 135 | @end 136 | 137 | @interface UIImage (DWImageTransformUtils) 138 | 139 | ///获取旋转角度的图片 140 | /** 141 | 注:角度计数单位为弧度制 142 | */ 143 | -(UIImage *)dw_RotateImageWithAngle:(CGFloat)angle; 144 | 145 | ///按给定的方向旋转图片 146 | -(UIImage*)dw_RotateWithOrient:(UIImageOrientation)orient; 147 | 148 | ///垂直翻转 149 | -(UIImage *)dw_FlipVertical; 150 | 151 | ///水平翻转 152 | -(UIImage *)dw_FlipHorizontal; 153 | 154 | ///压缩图片至指定尺寸 155 | -(UIImage *)dw_RescaleImageToSize:(CGSize)size; 156 | 157 | ///压缩图片至指定像素 158 | -(UIImage *)dw_RescaleImageToPX:(CGFloat )toPX; 159 | 160 | ///纠正图片方向 161 | -(UIImage *)dw_FixOrientation; 162 | 163 | @end 164 | 165 | @interface UIImage (DWImageCanvasUtils) 166 | 167 | ///截取当前image对象rect区域内的图像 168 | -(UIImage *)dw_SubImageWithRect:(CGRect)rect; 169 | 170 | ///在指定的size里面生成一个平铺的图片 171 | -(UIImage *)dw_GetTiledImageWithSize:(CGSize)size; 172 | 173 | ///UIView转化为UIImage 174 | +(UIImage *)dw_ImageFromView:(UIView *)view; 175 | 176 | ///将两个图片生成一张图片 177 | +(UIImage*)dw_MergeImage:(UIImage*)firstImage withImage:(UIImage*)secondImage; 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/UIImage+DWImageUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+DWImageUtils.h 3 | // Image 4 | // 5 | // Created by Wicky on 2016/12/6. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | /** 10 | DWImageUtils 11 | 图片工具类,提供常用图片扩展方法 12 | 13 | version 1.0.0 14 | 按功能分类扩展 15 | 16 | version 1.0.1 17 | 修复部分绘制模糊问题 18 | 修复subRect中因scale原因导致图片切割错误 19 | */ 20 | 21 | #import 22 | 23 | typedef NS_ENUM(NSInteger,DWContentMode)//图片填充模式 24 | { 25 | DWContentModeScaleAspectFit,//适应模式 26 | DWContentModeScaleAspectFill,//填充模式 27 | DWContentModeScaleToFill//拉伸模式 28 | }; 29 | 30 | @interface UIImage (DWImageInstanceUtils) 31 | 32 | /** 33 | 高性能按图片名称检索本地图片 34 | 35 | @param name 图片文件名 36 | @return 图片实例 37 | */ 38 | +(UIImage *)dw_ImageNamed:(NSString *)name; 39 | 40 | /** 41 | 高性能返回无延迟立即解压的图片实例 42 | 43 | @param url 图片路径,本地网络均可 44 | @return 图片实例 45 | */ 46 | +(UIImage *)dw_ImageWithUrl:(NSURL *)url; 47 | 48 | @end 49 | 50 | @interface UIImage (DWImageBase64Utils) 51 | 52 | ///转换图片为Base64字符串 53 | -(NSString *)dw_ImageToBase64String; 54 | 55 | ///Base64转换为图片 56 | + (UIImage *)dw_ImageWithBase64String:(NSString *)base64String; 57 | 58 | @end 59 | 60 | @interface UIImage (DWImageColorUtils) 61 | 62 | ///取图片某点颜色 63 | /** 64 | point:取色点 65 | 66 | 注:以图片自身宽高作为坐标系 67 | */ 68 | -(UIColor *)dw_ColorAtPoint:(CGPoint)point; 69 | 70 | ///按给定颜色生层图片 71 | +(UIImage *)dw_ImageWithColor:(UIColor *)color; 72 | 73 | ///以灰色空间生成图片 74 | -(UIImage *)dw_ConvertToGrayImage; 75 | 76 | ///生成图片的反色图片对象 77 | -(UIImage *)dw_ConvertToReversedColor; 78 | 79 | ///以给定颜色生成图像剪影 80 | -(UIImage *)dw_ConvertToSketchWithColor:(UIColor *)color; 81 | 82 | /** 83 | 生成处理每像素颜色后的图片 84 | 85 | @param handler 处理回调 86 | pixel为每一像素指向的内存指针 87 | x为横坐标 88 | y为纵坐标 89 | @return 处理后的图片 90 | 91 | eg: 92 | 将图片有效像素点转换为红色 93 | [self dw_ConvertImageWithPixelHandler:^(UInt8 *pixel, int x, int y) { 94 | UInt8 alpha = * (pixel + 3); 95 | if (alpha) { 96 | *pixel = 255; 97 | *(pixel + 1) = 0; 98 | *(pixel + 2) = 0; 99 | } 100 | }]; 101 | 102 | */ 103 | -(UIImage *)dw_ConvertImageWithPixelHandler:(void(^)(UInt8 * pixel,int x,int y))handler; 104 | 105 | @end 106 | 107 | @interface UIImage (DWImageClipUtils) 108 | 109 | ///获取带圆角的图片 110 | /* 111 | radius:返回图片的圆角半径 112 | 圆角半径不可超过图片尺寸的1/2,否则按1/2处理 113 | 114 | width:返回图片的宽度 115 | 返回的图片为一个宽高相等的矩形区域,但图片且居中显示 116 | 117 | mode:返回图片的填充模式 118 | 适应模式:以原图片比例,能显示全部图片的最大尺寸进行填充 119 | 填充模式:以原图片比例,图片能充满容器的最小尺寸进行填充 120 | 拉伸模式:以拉伸图片能够使图片充满容器的尺寸进行填充 121 | */ 122 | -(UIImage *)dw_CornerRadius:(CGFloat)radius withWidth:(CGFloat)width contentMode:(DWContentMode)mode; 123 | 124 | ///按给定path剪裁图片 125 | /** 126 | path:路径,剪裁区域。 127 | mode:填充模式 128 | 129 | 注: 130 | 1.路径中心对应图片中心 131 | 2.路径只决定剪裁图形,不影响剪裁位置 132 | */ 133 | -(UIImage *)dw_ClipImageWithPath:(UIBezierPath *)path mode:(DWContentMode)mode; 134 | 135 | @end 136 | 137 | @interface UIImage (DWImageTransformUtils) 138 | 139 | ///获取旋转角度的图片 140 | /** 141 | 注:角度计数单位为弧度制 142 | */ 143 | -(UIImage *)dw_RotateImageWithAngle:(CGFloat)angle; 144 | 145 | ///按给定的方向旋转图片 146 | -(UIImage*)dw_RotateWithOrient:(UIImageOrientation)orient; 147 | 148 | ///垂直翻转 149 | -(UIImage *)dw_FlipVertical; 150 | 151 | ///水平翻转 152 | -(UIImage *)dw_FlipHorizontal; 153 | 154 | ///压缩图片至指定尺寸 155 | -(UIImage *)dw_RescaleImageToSize:(CGSize)size; 156 | 157 | ///压缩图片至指定像素 158 | -(UIImage *)dw_RescaleImageToPX:(CGFloat )toPX; 159 | 160 | ///纠正图片方向 161 | -(UIImage *)dw_FixOrientation; 162 | 163 | @end 164 | 165 | @interface UIImage (DWImageCanvasUtils) 166 | 167 | ///截取当前image对象rect区域内的图像 168 | -(UIImage *)dw_SubImageWithRect:(CGRect)rect; 169 | 170 | ///在指定的size里面生成一个平铺的图片 171 | -(UIImage *)dw_GetTiledImageWithSize:(CGSize)size; 172 | 173 | ///UIView转化为UIImage 174 | +(UIImage *)dw_ImageFromView:(UIView *)view; 175 | 176 | ///将两个图片生成一张图片 177 | +(UIImage*)dw_MergeImage:(UIImage*)firstImage withImage:(UIImage*)secondImage; 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /DEMO/code/DWSlideCaptchaView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DWSlideCaptchaView.h 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | 10 | /** 11 | DWSlideCaptchaView 12 | 滑动验证视图,提供滑动验证功能 13 | 14 | version 1.0.0 15 | 提供以点或值改变两种api 16 | 图片自定义支持 17 | 滑块自定义支持 18 | */ 19 | 20 | #import 21 | 22 | #define DWSlideCaptchaUndefineValue (-1) 23 | @class DWSlideCaptchaView; 24 | @protocol DWSlideCaptchaDelegate 25 | 26 | @optional 27 | /** 28 | 验证动画将要开始代理 29 | 30 | @param captchaView 验证视图 31 | @param success 验证结果是否成功 32 | */ 33 | -(void)dw_CaptchaView:(DWSlideCaptchaView *)captchaView animationWillStartWithSuccess:(BOOL)success; 34 | 35 | /** 36 | 验证动画完成代理 37 | 38 | @param captchaView 验证视图 39 | @param success 验证结果是否成功 40 | */ 41 | -(void)dw_CaptchaView:(DWSlideCaptchaView *)captchaView animationCompletionWithSuccess:(BOOL)success; 42 | 43 | @end 44 | 45 | @interface DWSlideCaptchaView : UIView 46 | 47 | /** 48 | 图层相关 49 | */ 50 | ///验证位置遮罩层 51 | @property (nonatomic ,strong ,readonly) CAShapeLayer * positionLayer; 52 | 53 | ///滑块层 54 | @property (nonatomic ,strong ,readonly) CAShapeLayer * thumbLayer; 55 | 56 | /** 57 | 滑块相关属性 58 | */ 59 | ///滑块形状 60 | @property (nonatomic ,strong) UIBezierPath * thumbShape; 61 | 62 | /** 63 | 以下四个属性用于指定验证点位置。 64 | 65 | 当使用useRandomValue时会随机生成验证点 66 | 67 | 当不使用随机验证点时,以targetValue及thumbCenterY联合决定验证点 68 | 当targetValue或thumbCenterY没有指定值得时候,会以随机的合适的值决定验证点 69 | 当targetValue或thumbCenterY设置成 DWSlideCaptchaUndefineValue 时,也会以随机的合适的值决定验证点 70 | 71 | tolerance为验证容差,及与实际位置相差在一定范围内均可以验证成功 72 | */ 73 | ///指定滑块验证值,有效范围 [0,1] 74 | @property (nonatomic ,assign) CGFloat targetValue; 75 | ///指定滑块纵坐标,有限制范围是可以使滑块边界始终在当前控件内的中心范围 76 | @property (nonatomic ,assign) CGFloat thumbCenterY; 77 | ///是否使用随机验证点,默认为Yes 78 | @property (nonatomic ,assign) BOOL useRandomValue; 79 | ///验证容差,验证时允许的距离误差,有效值为自然数。若不指定,默认为3 80 | @property (nonatomic ,assign) CGFloat tolerance; 81 | 82 | /** 83 | 以下分别为验证成功及失败后动画,动画为thumbLayer的动画 84 | */ 85 | ///成功动画 86 | @property (nonatomic ,strong) CAAnimation * successAnimation; 87 | 88 | ///失败动画 89 | @property (nonatomic ,strong) CAAnimation * failAnimation; 90 | 91 | ///代理 92 | @property (nonatomic ,weak) id delegate; 93 | 94 | /** 95 | 滑块验证的背景图片 96 | */ 97 | ///背景图片 98 | @property (nonatomic ,strong) UIImage * bgImage; 99 | 100 | /** 101 | 验证相关 102 | */ 103 | ///验证成功 104 | @property (nonatomic ,assign ,readonly ,getter = isSuccessed) BOOL successed; 105 | 106 | ///正在配置参数 107 | @property (nonatomic ,assign ,readonly ,getter = isConfigurating) BOOL configurating; 108 | 109 | ///是否验证过 110 | @property (nonatomic ,assign ,readonly ,getter = isIndentified) BOOL indentified; 111 | 112 | 113 | /** 114 | 初始化方法 115 | 116 | @param frame 尺寸 117 | @param bgImage 背景图片 118 | @return 实例 119 | */ 120 | -(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage; 121 | 122 | 123 | /** 124 | 改变滑块位置至指定点 125 | 126 | @param point 目标点 127 | @param animated 是否需要动画 128 | */ 129 | -(void)moveToPoint:(CGPoint)point animated:(BOOL)animated; 130 | 131 | 132 | /** 133 | 改变滑块位置到指定值 134 | 135 | @param value 指定值 136 | @param animated 是否需要动画 137 | 138 | 注:纵坐标为验证点纵坐标 139 | */ 140 | -(void)setValue:(CGFloat)value animated:(BOOL)animated; 141 | 142 | 143 | /** 144 | 验证当前是否通过验证 145 | 146 | @param animated 验证是否需要动画 147 | @param result 验证结果 148 | */ 149 | -(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result; 150 | 151 | 152 | 153 | /** 154 | 开始配置参数 155 | 156 | 注:开始所有属性,若不调用此方法,则所有属性修改并不会生效 157 | */ 158 | -(void)beginConfiguration; 159 | 160 | 161 | /** 162 | 提交配置参数 163 | 164 | 注:是否提交属性并不影响属性值是否赋值成功,但提交属性后,视图会跟随做相关调整 165 | */ 166 | -(void)commitConfiguration; 167 | 168 | 169 | /** 170 | 重新配置滑动验证 171 | 172 | 注:重新设置一些必要参数 173 | */ 174 | -(void)reset; 175 | 176 | 177 | /** 178 | 展示滑块 179 | 180 | @param animated 是否需要动画 181 | */ 182 | -(void)showThumbWithAnimated:(BOOL)animated; 183 | 184 | 185 | /** 186 | 隐藏滑块 187 | 188 | @param animated 是否需要动画 189 | */ 190 | -(void)hideThumbWithAnimated:(BOOL)animated; 191 | 192 | @end 193 | 194 | 195 | 196 | /** 197 | 默认滑动验证视图 198 | 199 | 提供默认效果的活动验证视图,主要向使用者提供封装思路 200 | */ 201 | @interface DWDefaultSlideCaptchaView : UIView 202 | 203 | ///默认滑块 204 | @property (nonatomic ,strong) UISlider * slider; 205 | 206 | ///验证视图 207 | @property (nonatomic ,strong) DWSlideCaptchaView * captchaView; 208 | 209 | ///验证回调 210 | @property (nonatomic ,copy) void (^indentifyCompletion)(BOOL success); 211 | 212 | ///实例化方法 213 | -(instancetype)initWithFrame:(CGRect)frame image:(UIImage *)image slider:(UISlider *)slider; 214 | 215 | @end 216 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/DWSlideCaptchaView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DWSlideCaptchaView.h 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | 10 | /** 11 | DWSlideCaptchaView 12 | 滑动验证视图,提供滑动验证功能 13 | 14 | version 1.0.0 15 | 提供以点或值改变两种api 16 | 图片自定义支持 17 | 滑块自定义支持 18 | */ 19 | 20 | #import 21 | 22 | #define DWSlideCaptchaUndefineValue (-1) 23 | @class DWSlideCaptchaView; 24 | @protocol DWSlideCaptchaDelegate 25 | 26 | @optional 27 | /** 28 | 验证动画将要开始代理 29 | 30 | @param captchaView 验证视图 31 | @param success 验证结果是否成功 32 | */ 33 | -(void)dw_CaptchaView:(DWSlideCaptchaView *)captchaView animationWillStartWithSuccess:(BOOL)success; 34 | 35 | /** 36 | 验证动画完成代理 37 | 38 | @param captchaView 验证视图 39 | @param success 验证结果是否成功 40 | */ 41 | -(void)dw_CaptchaView:(DWSlideCaptchaView *)captchaView animationCompletionWithSuccess:(BOOL)success; 42 | 43 | @end 44 | 45 | @interface DWSlideCaptchaView : UIView 46 | 47 | /** 48 | 图层相关 49 | */ 50 | ///验证位置遮罩层 51 | @property (nonatomic ,strong ,readonly) CAShapeLayer * positionLayer; 52 | 53 | ///滑块层 54 | @property (nonatomic ,strong ,readonly) CAShapeLayer * thumbLayer; 55 | 56 | /** 57 | 滑块相关属性 58 | */ 59 | ///滑块形状 60 | @property (nonatomic ,strong) UIBezierPath * thumbShape; 61 | 62 | /** 63 | 以下四个属性用于指定验证点位置。 64 | 65 | 当使用useRandomValue时会随机生成验证点 66 | 67 | 当不使用随机验证点时,以targetValue及thumbCenterY联合决定验证点 68 | 当targetValue或thumbCenterY没有指定值得时候,会以随机的合适的值决定验证点 69 | 当targetValue或thumbCenterY设置成 DWSlideCaptchaUndefineValue 时,也会以随机的合适的值决定验证点 70 | 71 | tolerance为验证容差,及与实际位置相差在一定范围内均可以验证成功 72 | */ 73 | ///指定滑块验证值,有效范围 [0,1] 74 | @property (nonatomic ,assign) CGFloat targetValue; 75 | ///指定滑块纵坐标,有限制范围是可以使滑块边界始终在当前控件内的中心范围 76 | @property (nonatomic ,assign) CGFloat thumbCenterY; 77 | ///是否使用随机验证点,默认为Yes 78 | @property (nonatomic ,assign) BOOL useRandomValue; 79 | ///验证容差,验证时允许的距离误差,有效值为自然数。若不指定,默认为3 80 | @property (nonatomic ,assign) CGFloat tolerance; 81 | 82 | /** 83 | 以下分别为验证成功及失败后动画,动画为thumbLayer的动画 84 | */ 85 | ///成功动画 86 | @property (nonatomic ,strong) CAAnimation * successAnimation; 87 | 88 | ///失败动画 89 | @property (nonatomic ,strong) CAAnimation * failAnimation; 90 | 91 | ///代理 92 | @property (nonatomic ,weak) id delegate; 93 | 94 | /** 95 | 滑块验证的背景图片 96 | */ 97 | ///背景图片 98 | @property (nonatomic ,strong) UIImage * bgImage; 99 | 100 | /** 101 | 验证相关 102 | */ 103 | ///验证成功 104 | @property (nonatomic ,assign ,readonly ,getter = isSuccessed) BOOL successed; 105 | 106 | ///正在配置参数 107 | @property (nonatomic ,assign ,readonly ,getter = isConfigurating) BOOL configurating; 108 | 109 | ///是否验证过 110 | @property (nonatomic ,assign ,readonly ,getter = isIndentified) BOOL indentified; 111 | 112 | 113 | /** 114 | 初始化方法 115 | 116 | @param frame 尺寸 117 | @param bgImage 背景图片 118 | @return 实例 119 | */ 120 | -(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage; 121 | 122 | 123 | /** 124 | 改变滑块位置至指定点 125 | 126 | @param point 目标点 127 | @param animated 是否需要动画 128 | */ 129 | -(void)moveToPoint:(CGPoint)point animated:(BOOL)animated; 130 | 131 | 132 | /** 133 | 改变滑块位置到指定值 134 | 135 | @param value 指定值 136 | @param animated 是否需要动画 137 | 138 | 注:纵坐标为验证点纵坐标 139 | */ 140 | -(void)setValue:(CGFloat)value animated:(BOOL)animated; 141 | 142 | 143 | /** 144 | 验证当前是否通过验证 145 | 146 | @param animated 验证是否需要动画 147 | @param result 验证结果 148 | */ 149 | -(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result; 150 | 151 | 152 | 153 | /** 154 | 开始配置参数 155 | 156 | 注:开始所有属性,若不调用此方法,则所有属性修改并不会生效 157 | */ 158 | -(void)beginConfiguration; 159 | 160 | 161 | /** 162 | 提交配置参数 163 | 164 | 注:是否提交属性并不影响属性值是否赋值成功,但提交属性后,视图会跟随做相关调整 165 | */ 166 | -(void)commitConfiguration; 167 | 168 | 169 | /** 170 | 重新配置滑动验证 171 | 172 | 注:重新设置一些必要参数 173 | */ 174 | -(void)reset; 175 | 176 | 177 | /** 178 | 展示滑块 179 | 180 | @param animated 是否需要动画 181 | */ 182 | -(void)showThumbWithAnimated:(BOOL)animated; 183 | 184 | 185 | /** 186 | 隐藏滑块 187 | 188 | @param animated 是否需要动画 189 | */ 190 | -(void)hideThumbWithAnimated:(BOOL)animated; 191 | 192 | @end 193 | 194 | 195 | 196 | /** 197 | 默认滑动验证视图 198 | 199 | 提供默认效果的活动验证视图,主要向使用者提供封装思路 200 | */ 201 | @interface DWDefaultSlideCaptchaView : UIView 202 | 203 | ///默认滑块 204 | @property (nonatomic ,strong) UISlider * slider; 205 | 206 | ///验证视图 207 | @property (nonatomic ,strong) DWSlideCaptchaView * captchaView; 208 | 209 | ///验证回调 210 | @property (nonatomic ,copy) void (^indentifyCompletion)(BOOL success); 211 | 212 | ///实例化方法 213 | -(instancetype)initWithFrame:(CGRect)frame image:(UIImage *)image slider:(UISlider *)slider; 214 | 215 | @end 216 | -------------------------------------------------------------------------------- /DEMO/code/UIBezierPath+DWPathUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+DWPathUtils.h 3 | // DWHUD 4 | // 5 | // Created by Wicky on 16/10/23. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | /* 10 | UIBezierPath+DWPathUtils 11 | 12 | UIBezierPath的扩展类 13 | 14 | version 1.0.0 15 | 添加以两点绘制圆弧api 16 | 添加以block形式生成曲线api 17 | 18 | version 1.0.1 19 | 引入大于半圆弧概念,修复无法绘制大于半圆弧的问题 20 | 21 | version 1.0.2 22 | 添加正弦曲线绘制api,修复计算角度的api错误 23 | */ 24 | 25 | #import 26 | 27 | /** 28 | 镜像轴 29 | */ 30 | typedef NS_ENUM(NSUInteger, DWPathUtilsMirrorAxis) { 31 | DWPathUtilsMirrorAxisX,///x轴镜像 32 | DWPathUtilsMirrorAxisY///y轴镜像 33 | }; 34 | 35 | @interface DWPathMaker : NSObject 36 | 37 | @property (nonatomic ,strong) UIBezierPath * path; 38 | 39 | ///移动画笔至点 40 | @property (nonatomic ,copy) DWPathMaker *(^MoveTo)(CGFloat x,CGFloat y); 41 | 42 | ///添加直线至点 43 | @property (nonatomic ,copy) DWPathMaker *(^AddLineTo)(CGFloat x,CGFloat y); 44 | 45 | ///以角度添加圆弧 46 | /* 47 | centerX 圆弧圆心x坐标 48 | centerY 圆弧圆心y坐标 49 | radius 圆弧半径 50 | startAngle 圆弧起始角度 51 | endAngle 圆弧终止角度 52 | clockwise 顺逆时针 53 | 54 | 注:角度均为弧度制 55 | */ 56 | @property (nonatomic ,copy) DWPathMaker *(^AddArcWithAngle)(CGFloat CenterX,CGFloat CenterY,CGFloat radius,CGFloat startAngle,CGFloat endAngle,BOOL clockwise); 57 | 58 | ///以起始终止坐标添加圆弧 59 | /* 60 | startX 圆弧起始点x坐标 61 | startY 圆弧起始点y坐标 62 | endX 圆弧终止点x坐标 63 | endY 圆弧终止点y坐标 64 | radius 圆弧半径 65 | clockwise 顺逆时针 66 | moreThanHalf 大于半圆弧 67 | */ 68 | @property (nonatomic ,copy) DWPathMaker *(^AddArcWithPoint)(CGFloat startX,CGFloat startY,CGFloat endX,CGFloat endY,CGFloat radius,BOOL clockwise ,BOOL moreThanHalf); 69 | 70 | ///添加一次贝塞尔曲线 71 | /* 72 | pointX 曲线终点x坐标 73 | pointY 曲线终点y坐标 74 | controlX 曲线控制点x坐标 75 | controlY 曲线控制点y坐标 76 | */ 77 | @property (nonatomic ,copy) DWPathMaker *(^AddQuadCurve)(CGFloat pointX,CGFloat pointY,CGFloat controlX,CGFloat controlY); 78 | 79 | ///添加二次贝塞尔曲线 80 | /* 81 | pointX 曲线终点x坐标 82 | pointY 曲线终点y坐标 83 | controlX1 曲线第一个控制点x坐标 84 | controlY1 曲线第一个控制点y坐标 85 | controlX2 曲线第二个控制点x坐标 86 | controlY2 曲线第二个控制点y坐标 87 | */ 88 | @property (nonatomic ,copy) DWPathMaker *(^AddCurve)(CGFloat pointX,CGFloat pointY,CGFloat controlX1,CGFloat controlY1,CGFloat controlX2,CGFloat controlY2); 89 | 90 | ///添加正弦曲线 91 | /** 92 | A 振幅A 93 | Omega 角速度w 94 | Phi 相位差 95 | K 偏移量K 96 | deltaX 曲线横向长度 97 | */ 98 | @property (nonatomic ,copy) DWPathMaker *(^AddSin)(CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K,CGFloat deltaX); 99 | 100 | ///闭合曲线 101 | @property (nonatomic ,copy) DWPathMaker *(^ClosePath)(); 102 | 103 | ///镜像曲线 104 | /** 105 | path转换为指定bounds的指定中心轴线的镜像路径 106 | */ 107 | @property (nonatomic ,copy) DWPathMaker *(^MirrorPath)(DWPathUtilsMirrorAxis axis,CGRect bounds); 108 | 109 | ///保证图形区域中心不变以内距形式缩放路径 110 | @property (nonatomic ,copy) DWPathMaker *(^ScalePathWithMargin)(CGFloat margin); 111 | 112 | ///保证图形区域中心不变以比例形式缩放路径 113 | @property (nonatomic ,copy) DWPathMaker *(^ScalePathWithScale)(CGFloat scale); 114 | 115 | ///保证图形区域中心不变以角度旋转路径 116 | @property (nonatomic ,copy) DWPathMaker *(^RotatePathWithAngle)(CGFloat angle); 117 | 118 | ///平移路径 119 | @property (nonatomic ,copy) DWPathMaker *(^TranslatePathWithOffset)(CGFloat offsetX,CGFloat offsetY); 120 | 121 | ///移动路径至原点 122 | @property (nonatomic ,copy) DWPathMaker *(^PathOriginToZero)(); 123 | 124 | @end 125 | 126 | @interface UIBezierPath (DWPathUtils) 127 | 128 | ///以block形式生成自定义的贝塞尔曲线(移动点、添加直线、圆弧、贝塞尔曲线、闭合曲线) 129 | +(instancetype)bezierPathWithPathMaker:(void(^)(DWPathMaker * maker))pathMaker; 130 | 131 | ///以起始终止坐标生成曲线 132 | +(instancetype)bezierPathWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf; 133 | 134 | ///生成正弦曲线 135 | +(instancetype)bezierPathWithSinStartPoint:(CGPoint)startP A:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX; 136 | 137 | ///以起始终止坐标添加圆弧 138 | /** 139 | startP: 圆弧起点 140 | endP: 圆弧终点 141 | radius: 圆弧半径 142 | clockwise: 顺逆时针 143 | moreThanHalf: 大于半圆弧 144 | */ 145 | -(void)addArcWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf; 146 | 147 | ///绘制正弦曲线 148 | /** 149 | A 振幅A 150 | Omega 角速度w 151 | Phi 相位差 152 | K 偏移量K 153 | deltaX 曲线横向长度 154 | */ 155 | -(void)addSinWithA:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX; 156 | 157 | ///使path以指定bounds的指定轴线做镜像 158 | -(void)dw_MirrorAxis:(DWPathUtilsMirrorAxis)axis inBounds:(CGRect)bounds; 159 | 160 | ///保证图形区域中心不变以内距形式缩放路径 161 | -(void)dw_ScalePathWithMargin:(CGFloat)margin; 162 | 163 | ///保证图形区域中心不变以比例形式缩放路径 164 | -(void)dw_ScalePathWithScale:(CGFloat)scale; 165 | 166 | ///保证图形区域中心不变旋转路径 167 | -(void)dw_RotatePathWithAngle:(CGFloat)angle; 168 | 169 | ///平移路径 170 | -(void)dw_TranslatePathWithOffsetX:(CGFloat)offsetX offsetY:(CGFloat)offsetY; 171 | 172 | ///移动path回到原点 173 | -(void)dw_PathOriginToZero; 174 | @end 175 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/UIBezierPath+DWPathUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+DWPathUtils.h 3 | // DWHUD 4 | // 5 | // Created by Wicky on 16/10/23. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | /* 10 | UIBezierPath+DWPathUtils 11 | 12 | UIBezierPath的扩展类 13 | 14 | version 1.0.0 15 | 添加以两点绘制圆弧api 16 | 添加以block形式生成曲线api 17 | 18 | version 1.0.1 19 | 引入大于半圆弧概念,修复无法绘制大于半圆弧的问题 20 | 21 | version 1.0.2 22 | 添加正弦曲线绘制api,修复计算角度的api错误 23 | */ 24 | 25 | #import 26 | 27 | /** 28 | 镜像轴 29 | */ 30 | typedef NS_ENUM(NSUInteger, DWPathUtilsMirrorAxis) { 31 | DWPathUtilsMirrorAxisX,///x轴镜像 32 | DWPathUtilsMirrorAxisY///y轴镜像 33 | }; 34 | 35 | @interface DWPathMaker : NSObject 36 | 37 | @property (nonatomic ,strong) UIBezierPath * path; 38 | 39 | ///移动画笔至点 40 | @property (nonatomic ,copy) DWPathMaker *(^MoveTo)(CGFloat x,CGFloat y); 41 | 42 | ///添加直线至点 43 | @property (nonatomic ,copy) DWPathMaker *(^AddLineTo)(CGFloat x,CGFloat y); 44 | 45 | ///以角度添加圆弧 46 | /* 47 | centerX 圆弧圆心x坐标 48 | centerY 圆弧圆心y坐标 49 | radius 圆弧半径 50 | startAngle 圆弧起始角度 51 | endAngle 圆弧终止角度 52 | clockwise 顺逆时针 53 | 54 | 注:角度均为弧度制 55 | */ 56 | @property (nonatomic ,copy) DWPathMaker *(^AddArcWithAngle)(CGFloat CenterX,CGFloat CenterY,CGFloat radius,CGFloat startAngle,CGFloat endAngle,BOOL clockwise); 57 | 58 | ///以起始终止坐标添加圆弧 59 | /* 60 | startX 圆弧起始点x坐标 61 | startY 圆弧起始点y坐标 62 | endX 圆弧终止点x坐标 63 | endY 圆弧终止点y坐标 64 | radius 圆弧半径 65 | clockwise 顺逆时针 66 | moreThanHalf 大于半圆弧 67 | */ 68 | @property (nonatomic ,copy) DWPathMaker *(^AddArcWithPoint)(CGFloat startX,CGFloat startY,CGFloat endX,CGFloat endY,CGFloat radius,BOOL clockwise ,BOOL moreThanHalf); 69 | 70 | ///添加一次贝塞尔曲线 71 | /* 72 | pointX 曲线终点x坐标 73 | pointY 曲线终点y坐标 74 | controlX 曲线控制点x坐标 75 | controlY 曲线控制点y坐标 76 | */ 77 | @property (nonatomic ,copy) DWPathMaker *(^AddQuadCurve)(CGFloat pointX,CGFloat pointY,CGFloat controlX,CGFloat controlY); 78 | 79 | ///添加二次贝塞尔曲线 80 | /* 81 | pointX 曲线终点x坐标 82 | pointY 曲线终点y坐标 83 | controlX1 曲线第一个控制点x坐标 84 | controlY1 曲线第一个控制点y坐标 85 | controlX2 曲线第二个控制点x坐标 86 | controlY2 曲线第二个控制点y坐标 87 | */ 88 | @property (nonatomic ,copy) DWPathMaker *(^AddCurve)(CGFloat pointX,CGFloat pointY,CGFloat controlX1,CGFloat controlY1,CGFloat controlX2,CGFloat controlY2); 89 | 90 | ///添加正弦曲线 91 | /** 92 | A 振幅A 93 | Omega 角速度w 94 | Phi 相位差 95 | K 偏移量K 96 | deltaX 曲线横向长度 97 | */ 98 | @property (nonatomic ,copy) DWPathMaker *(^AddSin)(CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K,CGFloat deltaX); 99 | 100 | ///闭合曲线 101 | @property (nonatomic ,copy) DWPathMaker *(^ClosePath)(); 102 | 103 | ///镜像曲线 104 | /** 105 | path转换为指定bounds的指定中心轴线的镜像路径 106 | */ 107 | @property (nonatomic ,copy) DWPathMaker *(^MirrorPath)(DWPathUtilsMirrorAxis axis,CGRect bounds); 108 | 109 | ///保证图形区域中心不变以内距形式缩放路径 110 | @property (nonatomic ,copy) DWPathMaker *(^ScalePathWithMargin)(CGFloat margin); 111 | 112 | ///保证图形区域中心不变以比例形式缩放路径 113 | @property (nonatomic ,copy) DWPathMaker *(^ScalePathWithScale)(CGFloat scale); 114 | 115 | ///保证图形区域中心不变以角度旋转路径 116 | @property (nonatomic ,copy) DWPathMaker *(^RotatePathWithAngle)(CGFloat angle); 117 | 118 | ///平移路径 119 | @property (nonatomic ,copy) DWPathMaker *(^TranslatePathWithOffset)(CGFloat offsetX,CGFloat offsetY); 120 | 121 | ///移动路径至原点 122 | @property (nonatomic ,copy) DWPathMaker *(^PathOriginToZero)(); 123 | 124 | @end 125 | 126 | @interface UIBezierPath (DWPathUtils) 127 | 128 | ///以block形式生成自定义的贝塞尔曲线(移动点、添加直线、圆弧、贝塞尔曲线、闭合曲线) 129 | +(instancetype)bezierPathWithPathMaker:(void(^)(DWPathMaker * maker))pathMaker; 130 | 131 | ///以起始终止坐标生成曲线 132 | +(instancetype)bezierPathWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf; 133 | 134 | ///生成正弦曲线 135 | +(instancetype)bezierPathWithSinStartPoint:(CGPoint)startP A:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX; 136 | 137 | ///以起始终止坐标添加圆弧 138 | /** 139 | startP: 圆弧起点 140 | endP: 圆弧终点 141 | radius: 圆弧半径 142 | clockwise: 顺逆时针 143 | moreThanHalf: 大于半圆弧 144 | */ 145 | -(void)addArcWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf; 146 | 147 | ///绘制正弦曲线 148 | /** 149 | A 振幅A 150 | Omega 角速度w 151 | Phi 相位差 152 | K 偏移量K 153 | deltaX 曲线横向长度 154 | */ 155 | -(void)addSinWithA:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX; 156 | 157 | ///使path以指定bounds的指定轴线做镜像 158 | -(void)dw_MirrorAxis:(DWPathUtilsMirrorAxis)axis inBounds:(CGRect)bounds; 159 | 160 | ///保证图形区域中心不变以内距形式缩放路径 161 | -(void)dw_ScalePathWithMargin:(CGFloat)margin; 162 | 163 | ///保证图形区域中心不变以比例形式缩放路径 164 | -(void)dw_ScalePathWithScale:(CGFloat)scale; 165 | 166 | ///保证图形区域中心不变旋转路径 167 | -(void)dw_RotatePathWithAngle:(CGFloat)angle; 168 | 169 | ///平移路径 170 | -(void)dw_TranslatePathWithOffsetX:(CGFloat)offsetX offsetY:(CGFloat)offsetY; 171 | 172 | ///移动path回到原点 173 | -(void)dw_PathOriginToZero; 174 | @end 175 | -------------------------------------------------------------------------------- /DEMO/code/DWMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // DWMacro.h 3 | // hgfd 4 | // 5 | // Created by Wicky on 2017/2/26. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #ifndef DWMacro_h 10 | #define DWMacro_h 11 | 12 | //MARK: - Quick Variable 13 | 14 | /****** 引用相关 ******/ 15 | ///弱引用,使用如:DWWeak(alert) 之后调用weakalert 16 | #define DWWeak(type) __weak typeof(type) weak##type = type; 17 | 18 | ///强引用,用法参见弱引用 19 | #define DWStrong(type) __strong typeof(type) type = weak##type; 20 | 21 | /****** IndexPath相关 ******/ 22 | ///快速生成NSIndexPath 23 | #define DWIndexPath(section,row) ([NSIndexPath indexPathForRow:row inSection:section]) 24 | 25 | ///NSIndexPath的文字化 26 | #define NSStringFromIndexPath(idxP) ([NSString stringWithFormat:@"S%ldR%ld",idxP.section,idxP.row]) 27 | 28 | /****** 错误相关 ******/ 29 | ///快速生成error对象 30 | #define DWErrorWithDescription(aCode,desc) ([NSError errorWithDomain:@"com.Wicky.DWWebImage" code:aCode userInfo:@{NSLocalizedDescriptionKey:desc}]) 31 | 32 | /****** 颜色相关 ******/ 33 | ///快速以rgba生成UIColor对象 34 | #define DWRGBAColor(r,g,b,a) ([UIColor colorWithRed:(r/255.0) green:(g/255.0) blue:(b/255.0) alpha:a]) 35 | 36 | ///快速以16进制数字生成UIColor对象,使用如:DWHEXColor(0xd8d8d8) 37 | #define DWHexColor(hex) ([UIColor colorWithRed:((float)((hex & 0xFF0000) >> 16))/255.0 green:((float)((hex & 0xFF00) >> 8))/255.0 blue:((float)(hex & 0xFF))/255.0 alpha:1.0]) 38 | 39 | /****** 字体相关 ******/ 40 | #define DWFont(size) ([UIFont systemFontOfSize:size]) 41 | 42 | /****** 字符串相关 ******/ 43 | ///快速返回字符串高度 44 | #define DWStringHeight(string,widthLimit,font) ([string boundingRectWithSize:CGSizeMake(widthLimit, MAXFLOAT) options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil].size.height) 45 | 46 | /****** 图片相关 ******/ 47 | ///快速返回图片,png需文件全名 48 | #define DWImage(name) [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:nil]] 49 | 50 | /****** 视图相关 ******/ 51 | ///快速返回视图中心 52 | #define DWViewCenter(view) (CGPointMake(view.bounds.size.width / 2,view.bounds.size.height / 2)) 53 | 54 | ///设备宽度 55 | #define DWDeviceWidth ([UIScreen mainScreen].bounds.size.width) 56 | 57 | ///设备高度 58 | #define DWDeviceHeight ([UIScreen mainScreen].bounds.size.height) 59 | 60 | ///状态栏与导航栏高度之和 61 | #define DWHeightUnderNavigationBar 64 62 | 63 | ///导航栏高度 64 | #define DWHeightOfNavigationBar 44 65 | 66 | ///选项栏高度 67 | #define DWHeightOfTabBar 49 68 | 69 | ///以point和size生成Frame 70 | #define CGRectMakeWithPointAndSize(point,size) (CGRect){point, size} 71 | 72 | /****** 沙盒相关 ******/ 73 | ///沙盒主路径 74 | #define DWHomeDir NSHomeDirectory() 75 | 76 | ///沙盒Documents路径 77 | #define DWDocumentsDir [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] 78 | 79 | ///沙盒Library路径 80 | #define DWLibraryDir [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] 81 | 82 | ///沙盒Prefenreces路径 83 | #define DWPrefenencesDir [DWLibraryDir stringByAppendingPathComponent:@"Preferences"] 84 | 85 | ///沙盒Caches路径 86 | #define DWCachesDir [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] 87 | 88 | ///沙盒Tmp路径 89 | #define DWTmpDir NSTemporaryDirectory() 90 | 91 | 92 | 93 | //MARK: - Additional Function 94 | 95 | ///安全回到主线程 96 | #define dispatch_async_main_safe(block)\ 97 | if ([NSThread currentThread].isMainThread) {\ 98 | block();\ 99 | } else {\ 100 | dispatch_async(dispatch_get_main_queue(),block);\ 101 | } 102 | 103 | ///改变Layer属性是否需要动画 104 | #define DWLayerTransactionWithAnimation(animated,animationBlock) \ 105 | [CATransaction begin];\ 106 | if (!animated) {\ 107 | [CATransaction setAnimationDuration:0];\ 108 | }\ 109 | animationBlock();\ 110 | [CATransaction commit];\ 111 | 112 | ///角度转弧度 113 | #define DWDegreesToRadian(x) (M_PI * (x) / 180.0) 114 | 115 | ///弧度转角度 116 | #define DWRadianToDegrees(radian) (radian*180.0)/(M_PI) 117 | 118 | ///版本判断 119 | #define DW_SYSTEM_VERSION [[[UIDevice currentDevice] systemVersion] floatValue] 120 | #define DW_SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) 121 | #define DW_SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 122 | #define DW_SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 123 | #define DW_SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 124 | #define DW_SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) 125 | 126 | ///设备判断 127 | #define DW_Device_IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) 128 | #define DW_Device_IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) 129 | #define DW_Device_IS_IPOD ([[[UIDevice currentDevice] model] isEqualToString:@"iPod touch"]) 130 | 131 | ///Log 132 | #ifdef DEBUG 133 | #define DWLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) 134 | #else 135 | #define DWLog(...) 136 | #endif 137 | 138 | #endif /* DWMacro_h */ 139 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/DWMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // DWMacro.h 3 | // hgfd 4 | // 5 | // Created by Wicky on 2017/2/26. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #ifndef DWMacro_h 10 | #define DWMacro_h 11 | 12 | //MARK: - Quick Variable 13 | 14 | /****** 引用相关 ******/ 15 | ///弱引用,使用如:DWWeak(alert) 之后调用weakalert 16 | #define DWWeak(type) __weak typeof(type) weak##type = type; 17 | 18 | ///强引用,用法参见弱引用 19 | #define DWStrong(type) __strong typeof(type) type = weak##type; 20 | 21 | /****** IndexPath相关 ******/ 22 | ///快速生成NSIndexPath 23 | #define DWIndexPath(section,row) ([NSIndexPath indexPathForRow:row inSection:section]) 24 | 25 | ///NSIndexPath的文字化 26 | #define NSStringFromIndexPath(idxP) ([NSString stringWithFormat:@"S%ldR%ld",idxP.section,idxP.row]) 27 | 28 | /****** 错误相关 ******/ 29 | ///快速生成error对象 30 | #define DWErrorWithDescription(aCode,desc) ([NSError errorWithDomain:@"com.Wicky.DWWebImage" code:aCode userInfo:@{NSLocalizedDescriptionKey:desc}]) 31 | 32 | /****** 颜色相关 ******/ 33 | ///快速以rgba生成UIColor对象 34 | #define DWRGBAColor(r,g,b,a) ([UIColor colorWithRed:(r/255.0) green:(g/255.0) blue:(b/255.0) alpha:a]) 35 | 36 | ///快速以16进制数字生成UIColor对象,使用如:DWHEXColor(0xd8d8d8) 37 | #define DWHexColor(hex) ([UIColor colorWithRed:((float)((hex & 0xFF0000) >> 16))/255.0 green:((float)((hex & 0xFF00) >> 8))/255.0 blue:((float)(hex & 0xFF))/255.0 alpha:1.0]) 38 | 39 | /****** 字体相关 ******/ 40 | #define DWFont(size) ([UIFont systemFontOfSize:size]) 41 | 42 | /****** 字符串相关 ******/ 43 | ///快速返回字符串高度 44 | #define DWStringHeight(string,widthLimit,font) ([string boundingRectWithSize:CGSizeMake(widthLimit, MAXFLOAT) options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:font} context:nil].size.height) 45 | 46 | /****** 图片相关 ******/ 47 | ///快速返回图片,png需文件全名 48 | #define DWImage(name) [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:name ofType:nil]] 49 | 50 | /****** 视图相关 ******/ 51 | ///快速返回视图中心 52 | #define DWViewCenter(view) (CGPointMake(view.bounds.size.width / 2,view.bounds.size.height / 2)) 53 | 54 | ///设备宽度 55 | #define DWDeviceWidth ([UIScreen mainScreen].bounds.size.width) 56 | 57 | ///设备高度 58 | #define DWDeviceHeight ([UIScreen mainScreen].bounds.size.height) 59 | 60 | ///状态栏与导航栏高度之和 61 | #define DWHeightUnderNavigationBar 64 62 | 63 | ///导航栏高度 64 | #define DWHeightOfNavigationBar 44 65 | 66 | ///选项栏高度 67 | #define DWHeightOfTabBar 49 68 | 69 | ///以point和size生成Frame 70 | #define CGRectMakeWithPointAndSize(point,size) (CGRect){point, size} 71 | 72 | /****** 沙盒相关 ******/ 73 | ///沙盒主路径 74 | #define DWHomeDir NSHomeDirectory() 75 | 76 | ///沙盒Documents路径 77 | #define DWDocumentsDir [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] 78 | 79 | ///沙盒Library路径 80 | #define DWLibraryDir [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] 81 | 82 | ///沙盒Prefenreces路径 83 | #define DWPrefenencesDir [DWLibraryDir stringByAppendingPathComponent:@"Preferences"] 84 | 85 | ///沙盒Caches路径 86 | #define DWCachesDir [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject] 87 | 88 | ///沙盒Tmp路径 89 | #define DWTmpDir NSTemporaryDirectory() 90 | 91 | 92 | 93 | //MARK: - Additional Function 94 | 95 | ///安全回到主线程 96 | #define dispatch_async_main_safe(block)\ 97 | if ([NSThread currentThread].isMainThread) {\ 98 | block();\ 99 | } else {\ 100 | dispatch_async(dispatch_get_main_queue(),block);\ 101 | } 102 | 103 | ///改变Layer属性是否需要动画 104 | #define DWLayerTransactionWithAnimation(animated,animationBlock) \ 105 | [CATransaction begin];\ 106 | if (!animated) {\ 107 | [CATransaction setAnimationDuration:0];\ 108 | }\ 109 | animationBlock();\ 110 | [CATransaction commit];\ 111 | 112 | ///角度转弧度 113 | #define DWDegreesToRadian(x) (M_PI * (x) / 180.0) 114 | 115 | ///弧度转角度 116 | #define DWRadianToDegrees(radian) (radian*180.0)/(M_PI) 117 | 118 | ///版本判断 119 | #define DW_SYSTEM_VERSION [[[UIDevice currentDevice] systemVersion] floatValue] 120 | #define DW_SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) 121 | #define DW_SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) 122 | #define DW_SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 123 | #define DW_SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 124 | #define DW_SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) 125 | 126 | ///设备判断 127 | #define DW_Device_IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) 128 | #define DW_Device_IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) 129 | #define DW_Device_IS_IPOD ([[[UIDevice currentDevice] model] isEqualToString:@"iPod touch"]) 130 | 131 | ///Log 132 | #ifdef DEBUG 133 | #define DWLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) 134 | #else 135 | #define DWLog(...) 136 | #endif 137 | 138 | #endif /* DWMacro_h */ 139 | -------------------------------------------------------------------------------- /DEMO/code/UIBezierPath+DWPathUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+DWPathUtils.m 3 | // DWHUD 4 | // 5 | // Created by Wicky on 16/10/23. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "UIBezierPath+DWPathUtils.h" 10 | 11 | @implementation DWPathMaker 12 | 13 | -(DWPathMaker *(^)(CGFloat, CGFloat))MoveTo 14 | { 15 | return ^(CGFloat x,CGFloat y){ 16 | [self.path moveToPoint:CGPointMake(x, y)]; 17 | return self; 18 | }; 19 | } 20 | 21 | -(DWPathMaker *(^)(CGFloat, CGFloat))AddLineTo 22 | { 23 | return ^(CGFloat x,CGFloat y){ 24 | [self.path addLineToPoint:CGPointMake(x, y)]; 25 | return self; 26 | }; 27 | } 28 | 29 | -(DWPathMaker *(^)(CGFloat,CGFloat ,CGFloat ,CGFloat ,CGFloat ,BOOL))AddArcWithAngle 30 | { 31 | return ^(CGFloat x,CGFloat y,CGFloat radius,CGFloat startAngle,CGFloat endAngle,BOOL clockwise){ 32 | [self.path addArcWithCenter:CGPointMake(x, y) radius:radius startAngle:startAngle endAngle:endAngle clockwise:clockwise]; 33 | return self; 34 | }; 35 | } 36 | 37 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, BOOL,BOOL))AddArcWithPoint 38 | { 39 | return ^(CGFloat startX,CGFloat startY,CGFloat endX,CGFloat endY,CGFloat radius,BOOL clockwise,BOOL moreThanHalf){ 40 | [self.path addArcWithStartPoint:CGPointMake(startX, startY) endPoint:CGPointMake(endX, endY) radius:radius clockwise:clockwise moreThanHalf:moreThanHalf]; 41 | return self; 42 | }; 43 | } 44 | 45 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat))AddQuadCurve 46 | { 47 | return ^(CGFloat pointX,CGFloat pointY,CGFloat controlX,CGFloat controlY){ 48 | [self.path addQuadCurveToPoint:CGPointMake(pointX, pointY) controlPoint:CGPointMake(controlX, controlY)]; 49 | return self; 50 | }; 51 | } 52 | 53 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat))AddCurve 54 | { 55 | return ^(CGFloat pointX,CGFloat pointY,CGFloat controlX1,CGFloat controlY1,CGFloat controlX2,CGFloat controlY2){ 56 | [self.path addCurveToPoint:CGPointMake(pointX, pointY) controlPoint1:CGPointMake(controlX1, controlY1) controlPoint2:CGPointMake(controlX2, controlY2)]; 57 | return self; 58 | }; 59 | } 60 | 61 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat))AddSin 62 | { 63 | return ^(CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K,CGFloat deltaX){ 64 | [self.path addSinWithA:A Omega:Omega Phi:Phi K:K deltaX:deltaX]; 65 | return self; 66 | }; 67 | } 68 | 69 | -(DWPathMaker *(^)())ClosePath 70 | { 71 | return ^(){ 72 | [self.path closePath]; 73 | return self; 74 | }; 75 | } 76 | 77 | -(DWPathMaker *(^)(DWPathUtilsMirrorAxis, CGRect))MirrorPath 78 | { 79 | return ^(DWPathUtilsMirrorAxis axis,CGRect bounds){ 80 | [self.path dw_MirrorAxis:axis inBounds:bounds]; 81 | return self; 82 | }; 83 | } 84 | 85 | -(DWPathMaker *(^)(CGFloat))ScalePathWithMargin 86 | { 87 | return ^(CGFloat margin){ 88 | [self.path dw_ScalePathWithMargin:margin]; 89 | return self; 90 | }; 91 | } 92 | 93 | -(DWPathMaker *(^)(CGFloat))ScalePathWithScale 94 | { 95 | return ^(CGFloat scale){ 96 | [self.path dw_ScalePathWithScale:scale]; 97 | return self; 98 | }; 99 | } 100 | 101 | -(DWPathMaker *(^)(CGFloat))RotatePathWithAngle 102 | { 103 | return ^(CGFloat angle){ 104 | [self.path dw_RotatePathWithAngle:angle]; 105 | return self; 106 | }; 107 | } 108 | 109 | -(DWPathMaker *(^)(CGFloat, CGFloat))TranslatePathWithOffset 110 | { 111 | return ^(CGFloat offsetX,CGFloat offsetY){ 112 | [self.path dw_TranslatePathWithOffsetX:offsetX offsetY:offsetY]; 113 | return self; 114 | }; 115 | } 116 | 117 | -(DWPathMaker *(^)())PathOriginToZero 118 | { 119 | return ^(){ 120 | [self.path dw_PathOriginToZero]; 121 | return self; 122 | }; 123 | } 124 | 125 | @end 126 | @implementation UIBezierPath (DWPathUtils) 127 | 128 | +(instancetype)bezierPathWithPathMaker:(void (^)(DWPathMaker *maker))pathMaker 129 | { 130 | UIBezierPath * path = [UIBezierPath bezierPath]; 131 | if (pathMaker) { 132 | DWPathMaker * maker = [[DWPathMaker alloc] init]; 133 | maker.path = path; 134 | pathMaker(maker); 135 | } 136 | return path; 137 | } 138 | 139 | +(instancetype)bezierPathWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf 140 | { 141 | UIBezierPath * path = [UIBezierPath bezierPath]; 142 | [path moveToPoint:startP]; 143 | [path addArcWithStartPoint:startP endPoint:endP radius:radius clockwise:clockwise moreThanHalf:moreThanHalf]; 144 | return path; 145 | } 146 | 147 | +(instancetype)bezierPathWithSinStartPoint:(CGPoint)startP A:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX 148 | { 149 | UIBezierPath * path = [UIBezierPath bezierPath]; 150 | [path moveToPoint:startP]; 151 | [path addSinWithA:A Omega:Omega Phi:Phi K:K deltaX:deltaX]; 152 | return path; 153 | } 154 | 155 | -(void)addArcWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf 156 | { 157 | CGPoint center = [self getCenterFromFirstPoint:startP secondPoint:endP radius:radius clockwise:clockwise moreThanhalf:moreThanHalf]; 158 | CGFloat startA = [self getAngleFromFirstPoint:center secondP:startP]; 159 | CGFloat endA = [self getAngleFromFirstPoint:center secondP:endP]; 160 | [self addArcWithCenter:center radius:radius startAngle:startA endAngle:endA clockwise:clockwise]; 161 | } 162 | 163 | -(void)addSinWithA:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX 164 | { 165 | CGPoint originPoint = self.currentPoint; 166 | 167 | CGPoint currentPoint = self.currentPoint; 168 | CGFloat currentX = 0; 169 | CGFloat step = 0.05; 170 | while (currentX <= deltaX) { 171 | currentX += step; 172 | currentPoint = CGPointMake(currentPoint.x + step, currentPoint.y - deltaSinValueWith(currentX, A, Omega, Phi, K, step)); 173 | [self addLineToPoint:currentPoint]; 174 | } 175 | 176 | if (currentX != deltaX) { 177 | step = deltaX; 178 | [self addLineToPoint:CGPointMake(originPoint.x + step, originPoint.y - deltaSinValueWith(deltaX, A, Omega, Phi, K, step))]; 179 | } 180 | } 181 | 182 | -(void)dw_MirrorAxis:(DWPathUtilsMirrorAxis)axis inBounds:(CGRect)bounds 183 | { 184 | if (axis == DWPathUtilsMirrorAxisX) { 185 | [self applyTransform:CGAffineTransformMakeScale(-1, 1)]; 186 | [self dw_TranslatePathWithOffsetX:2 * bounds.origin.x + bounds.size.width offsetY:0]; 187 | } 188 | else 189 | { 190 | [self applyTransform:CGAffineTransformMakeScale(1, -1)]; 191 | [self dw_TranslatePathWithOffsetX:0 offsetY:2 * bounds.origin.y + bounds.size.height]; 192 | } 193 | } 194 | 195 | -(void)dw_ScalePathWithMargin:(CGFloat)margin 196 | { 197 | if (margin == 0) { 198 | return; 199 | } 200 | CGFloat widthScale = 1 - margin * 2 / self.bounds.size.width; 201 | CGFloat heightScale = 1 - margin * 2 / self.bounds.size.height; 202 | CGFloat offsetX = self.bounds.origin.x * (1 - widthScale) + margin; 203 | CGFloat offsetY = self.bounds.origin.y * (1 - heightScale) + margin; 204 | [self applyTransform:CGAffineTransformMakeScale(widthScale, heightScale)]; 205 | [self dw_TranslatePathWithOffsetX:offsetX offsetY:offsetY]; 206 | } 207 | 208 | -(void)dw_ScalePathWithScale:(CGFloat)scale 209 | { 210 | if (scale == 1) { 211 | return; 212 | } 213 | CGFloat marginX = self.bounds.size.width * (1 - scale) / 2; 214 | CGFloat marginY = self.bounds.size.height * (1 - scale) / 2; 215 | [self applyTransform:CGAffineTransformMakeScale(scale, scale)]; 216 | [self dw_TranslatePathWithOffsetX:marginX * 3 offsetY:marginY * 3]; 217 | } 218 | 219 | -(void)dw_RotatePathWithAngle:(CGFloat)angle 220 | { 221 | angle = fmod(angle, M_PI * 2); 222 | if (angle == 0) { 223 | return; 224 | } 225 | CGFloat offsetX = self.bounds.origin.x + self.bounds.size.width / 2; 226 | CGFloat offsetY = self.bounds.origin.y + self.bounds.size.height / 2; 227 | [self dw_TranslatePathWithOffsetX:-offsetX offsetY:-offsetY]; 228 | [self applyTransform:CGAffineTransformMakeRotation(angle)]; 229 | [self dw_TranslatePathWithOffsetX:offsetX offsetY:offsetY]; 230 | } 231 | 232 | -(void)dw_TranslatePathWithOffsetX:(CGFloat)offsetX offsetY:(CGFloat)offsetY 233 | { 234 | if (!(offsetX * offsetY)) { 235 | return; 236 | } 237 | [self applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 238 | } 239 | 240 | -(void)dw_PathOriginToZero 241 | { 242 | [self dw_TranslatePathWithOffsetX:-self.bounds.origin.x offsetY:-self.bounds.origin.y]; 243 | } 244 | 245 | static inline CGFloat sinValueWith(CGFloat x,CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K){ 246 | return A * sinf(Omega * x + Phi) + K; 247 | } 248 | 249 | static inline CGFloat deltaSinValueWith(CGFloat x,CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K ,CGFloat step){ 250 | return sinValueWith(x, A, Omega, Phi, K) - sinValueWith(x - step, A, Omega, Phi, K); 251 | } 252 | 253 | ///根据两点、半径、顺逆时针获取圆心 254 | -(CGPoint)getCenterFromFirstPoint:(CGPoint)firstP 255 | secondPoint:(CGPoint)secondP 256 | radius:(CGFloat)radius 257 | clockwise:(BOOL)clockwise 258 | moreThanhalf:(BOOL)moreThanHalf 259 | { 260 | CGFloat centerX = 0.5 * secondP.x - 0.5 * firstP.x; 261 | CGFloat centerY = 0.5 * firstP.y - 0.5 * secondP.y; 262 | centerX = round6f(centerX); 263 | centerY = round6f(centerY); 264 | radius = round6f(radius); 265 | ///获取相似三角形相似比例 266 | CGFloat scale = sqrt((pow(radius, 2) - (pow(centerX, 2) + pow(centerY, 2))) / (pow(centerX, 2) + pow(centerY, 2))); 267 | scale = round6f(scale); 268 | if (clockwise != moreThanHalf) { 269 | return CGPointMake(centerX + centerY * scale + firstP.x, - centerY + centerX * scale + firstP.y); 270 | } 271 | else 272 | { 273 | return CGPointMake(centerX - centerY * scale + firstP.x, - centerY - centerX * scale + firstP.y); 274 | } 275 | } 276 | 277 | ///保留6位小数 278 | CGFloat round6f(CGFloat x){ 279 | return roundf(x * 1000000) / 1000000.0; 280 | } 281 | 282 | ///获取第二点相对第一点的角度 283 | -(CGFloat)getAngleFromFirstPoint:(CGPoint)firstP 284 | secondP:(CGPoint)secondP 285 | { 286 | CGFloat deltaX = secondP.x - firstP.x; 287 | CGFloat deltaY = secondP.y - firstP.y; 288 | deltaX = round6f(deltaX); 289 | deltaY = round6f(deltaY); 290 | if (deltaX > 0) { 291 | if (deltaY >= 0) { 292 | return atanf(deltaY / deltaX); 293 | } 294 | return M_PI * 2 + atanf(deltaY / deltaX); 295 | } 296 | if (deltaX == 0) { 297 | if (deltaY >= 0) { 298 | return M_PI_2; 299 | } 300 | else 301 | { 302 | return M_PI_2 * 3; 303 | } 304 | } 305 | return atanf(deltaY / deltaX) + M_PI; 306 | } 307 | 308 | @end 309 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/UIBezierPath+DWPathUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+DWPathUtils.m 3 | // DWHUD 4 | // 5 | // Created by Wicky on 16/10/23. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "UIBezierPath+DWPathUtils.h" 10 | 11 | @implementation DWPathMaker 12 | 13 | -(DWPathMaker *(^)(CGFloat, CGFloat))MoveTo 14 | { 15 | return ^(CGFloat x,CGFloat y){ 16 | [self.path moveToPoint:CGPointMake(x, y)]; 17 | return self; 18 | }; 19 | } 20 | 21 | -(DWPathMaker *(^)(CGFloat, CGFloat))AddLineTo 22 | { 23 | return ^(CGFloat x,CGFloat y){ 24 | [self.path addLineToPoint:CGPointMake(x, y)]; 25 | return self; 26 | }; 27 | } 28 | 29 | -(DWPathMaker *(^)(CGFloat,CGFloat ,CGFloat ,CGFloat ,CGFloat ,BOOL))AddArcWithAngle 30 | { 31 | return ^(CGFloat x,CGFloat y,CGFloat radius,CGFloat startAngle,CGFloat endAngle,BOOL clockwise){ 32 | [self.path addArcWithCenter:CGPointMake(x, y) radius:radius startAngle:startAngle endAngle:endAngle clockwise:clockwise]; 33 | return self; 34 | }; 35 | } 36 | 37 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, BOOL,BOOL))AddArcWithPoint 38 | { 39 | return ^(CGFloat startX,CGFloat startY,CGFloat endX,CGFloat endY,CGFloat radius,BOOL clockwise,BOOL moreThanHalf){ 40 | [self.path addArcWithStartPoint:CGPointMake(startX, startY) endPoint:CGPointMake(endX, endY) radius:radius clockwise:clockwise moreThanHalf:moreThanHalf]; 41 | return self; 42 | }; 43 | } 44 | 45 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat))AddQuadCurve 46 | { 47 | return ^(CGFloat pointX,CGFloat pointY,CGFloat controlX,CGFloat controlY){ 48 | [self.path addQuadCurveToPoint:CGPointMake(pointX, pointY) controlPoint:CGPointMake(controlX, controlY)]; 49 | return self; 50 | }; 51 | } 52 | 53 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat))AddCurve 54 | { 55 | return ^(CGFloat pointX,CGFloat pointY,CGFloat controlX1,CGFloat controlY1,CGFloat controlX2,CGFloat controlY2){ 56 | [self.path addCurveToPoint:CGPointMake(pointX, pointY) controlPoint1:CGPointMake(controlX1, controlY1) controlPoint2:CGPointMake(controlX2, controlY2)]; 57 | return self; 58 | }; 59 | } 60 | 61 | -(DWPathMaker *(^)(CGFloat, CGFloat, CGFloat, CGFloat, CGFloat))AddSin 62 | { 63 | return ^(CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K,CGFloat deltaX){ 64 | [self.path addSinWithA:A Omega:Omega Phi:Phi K:K deltaX:deltaX]; 65 | return self; 66 | }; 67 | } 68 | 69 | -(DWPathMaker *(^)())ClosePath 70 | { 71 | return ^(){ 72 | [self.path closePath]; 73 | return self; 74 | }; 75 | } 76 | 77 | -(DWPathMaker *(^)(DWPathUtilsMirrorAxis, CGRect))MirrorPath 78 | { 79 | return ^(DWPathUtilsMirrorAxis axis,CGRect bounds){ 80 | [self.path dw_MirrorAxis:axis inBounds:bounds]; 81 | return self; 82 | }; 83 | } 84 | 85 | -(DWPathMaker *(^)(CGFloat))ScalePathWithMargin 86 | { 87 | return ^(CGFloat margin){ 88 | [self.path dw_ScalePathWithMargin:margin]; 89 | return self; 90 | }; 91 | } 92 | 93 | -(DWPathMaker *(^)(CGFloat))ScalePathWithScale 94 | { 95 | return ^(CGFloat scale){ 96 | [self.path dw_ScalePathWithScale:scale]; 97 | return self; 98 | }; 99 | } 100 | 101 | -(DWPathMaker *(^)(CGFloat))RotatePathWithAngle 102 | { 103 | return ^(CGFloat angle){ 104 | [self.path dw_RotatePathWithAngle:angle]; 105 | return self; 106 | }; 107 | } 108 | 109 | -(DWPathMaker *(^)(CGFloat, CGFloat))TranslatePathWithOffset 110 | { 111 | return ^(CGFloat offsetX,CGFloat offsetY){ 112 | [self.path dw_TranslatePathWithOffsetX:offsetX offsetY:offsetY]; 113 | return self; 114 | }; 115 | } 116 | 117 | -(DWPathMaker *(^)())PathOriginToZero 118 | { 119 | return ^(){ 120 | [self.path dw_PathOriginToZero]; 121 | return self; 122 | }; 123 | } 124 | 125 | @end 126 | @implementation UIBezierPath (DWPathUtils) 127 | 128 | +(instancetype)bezierPathWithPathMaker:(void (^)(DWPathMaker *maker))pathMaker 129 | { 130 | UIBezierPath * path = [UIBezierPath bezierPath]; 131 | if (pathMaker) { 132 | DWPathMaker * maker = [[DWPathMaker alloc] init]; 133 | maker.path = path; 134 | pathMaker(maker); 135 | } 136 | return path; 137 | } 138 | 139 | +(instancetype)bezierPathWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf 140 | { 141 | UIBezierPath * path = [UIBezierPath bezierPath]; 142 | [path moveToPoint:startP]; 143 | [path addArcWithStartPoint:startP endPoint:endP radius:radius clockwise:clockwise moreThanHalf:moreThanHalf]; 144 | return path; 145 | } 146 | 147 | +(instancetype)bezierPathWithSinStartPoint:(CGPoint)startP A:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX 148 | { 149 | UIBezierPath * path = [UIBezierPath bezierPath]; 150 | [path moveToPoint:startP]; 151 | [path addSinWithA:A Omega:Omega Phi:Phi K:K deltaX:deltaX]; 152 | return path; 153 | } 154 | 155 | -(void)addArcWithStartPoint:(CGPoint)startP endPoint:(CGPoint)endP radius:(CGFloat)radius clockwise:(BOOL)clockwise moreThanHalf:(BOOL)moreThanHalf 156 | { 157 | CGPoint center = [self getCenterFromFirstPoint:startP secondPoint:endP radius:radius clockwise:clockwise moreThanhalf:moreThanHalf]; 158 | CGFloat startA = [self getAngleFromFirstPoint:center secondP:startP]; 159 | CGFloat endA = [self getAngleFromFirstPoint:center secondP:endP]; 160 | [self addArcWithCenter:center radius:radius startAngle:startA endAngle:endA clockwise:clockwise]; 161 | } 162 | 163 | -(void)addSinWithA:(CGFloat)A Omega:(CGFloat)Omega Phi:(CGFloat)Phi K:(CGFloat)K deltaX:(CGFloat)deltaX 164 | { 165 | CGPoint originPoint = self.currentPoint; 166 | 167 | CGPoint currentPoint = self.currentPoint; 168 | CGFloat currentX = 0; 169 | CGFloat step = 0.05; 170 | while (currentX <= deltaX) { 171 | currentX += step; 172 | currentPoint = CGPointMake(currentPoint.x + step, currentPoint.y - deltaSinValueWith(currentX, A, Omega, Phi, K, step)); 173 | [self addLineToPoint:currentPoint]; 174 | } 175 | 176 | if (currentX != deltaX) { 177 | step = deltaX; 178 | [self addLineToPoint:CGPointMake(originPoint.x + step, originPoint.y - deltaSinValueWith(deltaX, A, Omega, Phi, K, step))]; 179 | } 180 | } 181 | 182 | -(void)dw_MirrorAxis:(DWPathUtilsMirrorAxis)axis inBounds:(CGRect)bounds 183 | { 184 | if (axis == DWPathUtilsMirrorAxisX) { 185 | [self applyTransform:CGAffineTransformMakeScale(-1, 1)]; 186 | [self dw_TranslatePathWithOffsetX:2 * bounds.origin.x + bounds.size.width offsetY:0]; 187 | } 188 | else 189 | { 190 | [self applyTransform:CGAffineTransformMakeScale(1, -1)]; 191 | [self dw_TranslatePathWithOffsetX:0 offsetY:2 * bounds.origin.y + bounds.size.height]; 192 | } 193 | } 194 | 195 | -(void)dw_ScalePathWithMargin:(CGFloat)margin 196 | { 197 | if (margin == 0) { 198 | return; 199 | } 200 | CGFloat widthScale = 1 - margin * 2 / self.bounds.size.width; 201 | CGFloat heightScale = 1 - margin * 2 / self.bounds.size.height; 202 | CGFloat offsetX = self.bounds.origin.x * (1 - widthScale) + margin; 203 | CGFloat offsetY = self.bounds.origin.y * (1 - heightScale) + margin; 204 | [self applyTransform:CGAffineTransformMakeScale(widthScale, heightScale)]; 205 | [self dw_TranslatePathWithOffsetX:offsetX offsetY:offsetY]; 206 | } 207 | 208 | -(void)dw_ScalePathWithScale:(CGFloat)scale 209 | { 210 | if (scale == 1) { 211 | return; 212 | } 213 | CGFloat marginX = self.bounds.size.width * (1 - scale) / 2; 214 | CGFloat marginY = self.bounds.size.height * (1 - scale) / 2; 215 | [self applyTransform:CGAffineTransformMakeScale(scale, scale)]; 216 | [self dw_TranslatePathWithOffsetX:marginX * 3 offsetY:marginY * 3]; 217 | } 218 | 219 | -(void)dw_RotatePathWithAngle:(CGFloat)angle 220 | { 221 | angle = fmod(angle, M_PI * 2); 222 | if (angle == 0) { 223 | return; 224 | } 225 | CGFloat offsetX = self.bounds.origin.x + self.bounds.size.width / 2; 226 | CGFloat offsetY = self.bounds.origin.y + self.bounds.size.height / 2; 227 | [self dw_TranslatePathWithOffsetX:-offsetX offsetY:-offsetY]; 228 | [self applyTransform:CGAffineTransformMakeRotation(angle)]; 229 | [self dw_TranslatePathWithOffsetX:offsetX offsetY:offsetY]; 230 | } 231 | 232 | -(void)dw_TranslatePathWithOffsetX:(CGFloat)offsetX offsetY:(CGFloat)offsetY 233 | { 234 | if (!(offsetX * offsetY)) { 235 | return; 236 | } 237 | [self applyTransform:CGAffineTransformMakeTranslation(offsetX, offsetY)]; 238 | } 239 | 240 | -(void)dw_PathOriginToZero 241 | { 242 | [self dw_TranslatePathWithOffsetX:-self.bounds.origin.x offsetY:-self.bounds.origin.y]; 243 | } 244 | 245 | static inline CGFloat sinValueWith(CGFloat x,CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K){ 246 | return A * sinf(Omega * x + Phi) + K; 247 | } 248 | 249 | static inline CGFloat deltaSinValueWith(CGFloat x,CGFloat A,CGFloat Omega,CGFloat Phi,CGFloat K ,CGFloat step){ 250 | return sinValueWith(x, A, Omega, Phi, K) - sinValueWith(x - step, A, Omega, Phi, K); 251 | } 252 | 253 | ///根据两点、半径、顺逆时针获取圆心 254 | -(CGPoint)getCenterFromFirstPoint:(CGPoint)firstP 255 | secondPoint:(CGPoint)secondP 256 | radius:(CGFloat)radius 257 | clockwise:(BOOL)clockwise 258 | moreThanhalf:(BOOL)moreThanHalf 259 | { 260 | CGFloat centerX = 0.5 * secondP.x - 0.5 * firstP.x; 261 | CGFloat centerY = 0.5 * firstP.y - 0.5 * secondP.y; 262 | centerX = round6f(centerX); 263 | centerY = round6f(centerY); 264 | radius = round6f(radius); 265 | ///获取相似三角形相似比例 266 | CGFloat scale = sqrt((pow(radius, 2) - (pow(centerX, 2) + pow(centerY, 2))) / (pow(centerX, 2) + pow(centerY, 2))); 267 | scale = round6f(scale); 268 | if (clockwise != moreThanHalf) { 269 | return CGPointMake(centerX + centerY * scale + firstP.x, - centerY + centerX * scale + firstP.y); 270 | } 271 | else 272 | { 273 | return CGPointMake(centerX - centerY * scale + firstP.x, - centerY - centerX * scale + firstP.y); 274 | } 275 | } 276 | 277 | ///保留6位小数 278 | CGFloat round6f(CGFloat x){ 279 | return roundf(x * 1000000) / 1000000.0; 280 | } 281 | 282 | ///获取第二点相对第一点的角度 283 | -(CGFloat)getAngleFromFirstPoint:(CGPoint)firstP 284 | secondP:(CGPoint)secondP 285 | { 286 | CGFloat deltaX = secondP.x - firstP.x; 287 | CGFloat deltaY = secondP.y - firstP.y; 288 | deltaX = round6f(deltaX); 289 | deltaY = round6f(deltaY); 290 | if (deltaX > 0) { 291 | if (deltaY >= 0) { 292 | return atanf(deltaY / deltaX); 293 | } 294 | return M_PI * 2 + atanf(deltaY / deltaX); 295 | } 296 | if (deltaX == 0) { 297 | if (deltaY >= 0) { 298 | return M_PI_2; 299 | } 300 | else 301 | { 302 | return M_PI_2 * 3; 303 | } 304 | } 305 | return atanf(deltaY / deltaX) + M_PI; 306 | } 307 | 308 | @end 309 | -------------------------------------------------------------------------------- /DEMO/code/DWSlideCaptchaView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DWSlideCaptchaView.m 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "DWSlideCaptchaView.h" 10 | #import "UIImage+DWImageUtils.h" 11 | #import "UIBezierPath+DWPathUtils.h" 12 | #import "DWMacro.h" 13 | 14 | #define SafeConfiguration if (!self.configurating) return; 15 | 16 | @interface DWSlideCaptchaView () 17 | 18 | ///滑块尺寸 19 | @property (nonatomic ,assign) CGSize thumbSize; 20 | 21 | ///目标验证点 22 | @property (nonatomic ,assign) CGPoint targetPoint; 23 | 24 | ///有效验证范围 25 | @property (nonatomic ,assign) CGSize validSize; 26 | 27 | ///是否需要重新设置验证点 28 | @property (nonatomic ,assign) BOOL resetTargetPoint; 29 | 30 | ///当前点 31 | @property (nonatomic ,assign) CGPoint currentPoint; 32 | 33 | @end 34 | 35 | @implementation DWSlideCaptchaView 36 | @synthesize thumbSize = _thumbSize; 37 | @synthesize thumbShape = _thumbShape; 38 | @synthesize tolerance = _tolerance; 39 | @synthesize positionLayer = _positionLayer; 40 | @synthesize thumbLayer = _thumbLayer; 41 | -(instancetype)initWithFrame:(CGRect)frame { 42 | NSAssert((frame.size.width >= 100 && frame.size.height >= 40), @"To get a better experience,you may set the width more than 100 and height more than 50."); 43 | if (self = [super initWithFrame:frame]) { 44 | _useRandomValue = YES; 45 | _targetValue = DWSlideCaptchaUndefineValue; 46 | _thumbCenterY = DWSlideCaptchaUndefineValue; 47 | _tolerance = DWSlideCaptchaUndefineValue; 48 | _thumbSize = puzzlePath().bounds.size; 49 | } 50 | return self; 51 | } 52 | 53 | -(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage { 54 | if (self = [self initWithFrame:frame]) { 55 | [self beginConfiguration]; 56 | self.bgImage = bgImage; 57 | [self commitConfiguration]; 58 | } 59 | return self; 60 | } 61 | 62 | -(void)beginConfiguration { 63 | _configurating = YES; 64 | self.resetTargetPoint = YES; 65 | } 66 | 67 | -(void)commitConfiguration { 68 | if (!self.configurating) { 69 | return; 70 | } 71 | _configurating = NO; 72 | self.layer.contents = (id)self.bgImage.CGImage; 73 | [self handlePositionLayer]; 74 | [self handleThumbLayer]; 75 | [self hideThumbWithAnimated:NO]; 76 | } 77 | 78 | -(void)reset { 79 | _successed = NO; 80 | [self beginConfiguration]; 81 | [self commitConfiguration]; 82 | } 83 | 84 | -(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result { 85 | BOOL isSuccess = fabs(self.targetPoint.x - self.currentPoint.x) < self.tolerance; 86 | isSuccess &= fabs(self.targetPoint.y - self.currentPoint.y) < self.tolerance; 87 | _successed = isSuccess; 88 | _indentified = YES; 89 | if (isSuccess) { 90 | if (animated) { 91 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationWillStartWithSuccess:)]) {///动画开始回调 92 | [self.delegate dw_CaptchaView:self animationWillStartWithSuccess:YES]; 93 | } 94 | if (!self.successAnimation) {///未指定动画使用默认动画 95 | [self.layer addAnimation:defaultSuccessAnimaiton(self) forKey:@"successAnimation"]; 96 | [self hideThumbWithAnimated:NO]; 97 | DWLayerTransactionWithAnimation(NO, ^(){ 98 | self.positionLayer.opacity = 0; 99 | }); 100 | } else {///使用指定动画 101 | [self.thumbLayer addAnimation:self.successAnimation forKey:@"successAnimation"]; 102 | } 103 | } 104 | if (result) result(YES); 105 | } else { 106 | if (animated) { 107 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationWillStartWithSuccess:)]) {///动画开始回调 108 | [self.delegate dw_CaptchaView:self animationWillStartWithSuccess:YES]; 109 | } 110 | if (!self.failAnimation) {///未指定动画则使用默认动画 111 | [self.thumbLayer addAnimation:defaultFailAnimation(self) forKey:@"failAnimation"]; 112 | } else {///使用指定动画 113 | [self.thumbLayer addAnimation:self.failAnimation forKey:@"failAnimation"]; 114 | } 115 | } 116 | if (result) result(NO); 117 | } 118 | } 119 | 120 | -(void)moveToPoint:(CGPoint)point animated:(BOOL)animated { 121 | if (self.successed) { 122 | return; 123 | } 124 | _indentified = NO; 125 | point = fixPointWithLimit(point, self.validSize, self.thumbSize); 126 | self.currentPoint = point; 127 | if (self.thumbLayer.opacity != 1) { 128 | [self showThumbWithAnimated:YES]; 129 | } 130 | DWLayerTransactionWithAnimation(animated, ^(){ 131 | self.thumbLayer.position = transformLocation2Center(point, self.thumbSize); 132 | }); 133 | } 134 | 135 | -(void)setValue:(CGFloat)value animated:(BOOL)animated { 136 | CGFloat x = value * self.validSize.width + self.thumbSize.width / 2; 137 | CGFloat y = self.targetPoint.y + self.thumbSize.height / 2; 138 | [self moveToPoint:CGPointMake(x, y) animated:animated]; 139 | } 140 | 141 | -(void)showThumbWithAnimated:(BOOL)animated { 142 | DWLayerTransactionWithAnimation(animated,^(){ 143 | self.thumbLayer.opacity = 1; 144 | }); 145 | } 146 | 147 | -(void)hideThumbWithAnimated:(BOOL)animated { 148 | DWLayerTransactionWithAnimation(animated, ^(){ 149 | self.thumbLayer.opacity = 0; 150 | }); 151 | } 152 | 153 | #pragma mark --- tool Method --- 154 | -(void)handleThumbLayer { 155 | UIImage * thumbImage = [self.bgImage dw_SubImageWithRect:self.positionLayer.frame]; 156 | thumbImage = [thumbImage dw_ClipImageWithPath:self.thumbShape mode:(DWContentModeScaleToFill)]; 157 | self.thumbLayer.contents = (id)thumbImage.CGImage; 158 | self.thumbLayer.frame = self.positionLayer.frame; 159 | [self setValue:0 animated:NO]; 160 | if (!self.thumbLayer.superlayer) { 161 | [self.layer addSublayer:self.thumbLayer]; 162 | } 163 | } 164 | 165 | -(void)handlePositionLayer { 166 | UIBezierPath * path = [self.thumbShape copy]; 167 | self.positionLayer.fillColor = [UIColor colorWithWhite:1 alpha:0.7].CGColor; 168 | self.positionLayer.path = path.CGPath; 169 | self.positionLayer.frame = CGRectMake(self.targetPoint.x, self.targetPoint.y, (int)path.bounds.size.width, (int)path.bounds.size.height); 170 | if (!self.positionLayer.superlayer) { 171 | [self.layer addSublayer:self.positionLayer]; 172 | } 173 | } 174 | 175 | #pragma mark --- animation delegate --- 176 | -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 177 | if (self.isSuccessed) { 178 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationCompletionWithSuccess:)]) { 179 | [self.delegate dw_CaptchaView:self animationCompletionWithSuccess:YES]; 180 | } 181 | } else { 182 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationCompletionWithSuccess:)]) { 183 | [self.delegate dw_CaptchaView:self animationCompletionWithSuccess:NO]; 184 | } 185 | } 186 | } 187 | 188 | #pragma mark --- 内联方法 --- 189 | ///默认滑块形状 190 | static inline UIBezierPath * puzzlePath (){ 191 | UIBezierPath * path = [UIBezierPath bezierPathWithPathMaker:^(DWPathMaker *maker) { 192 | maker.MoveTo(0, 8). 193 | AddLineTo(12, 8).AddArcWithPoint(12, 8, 20, 8, 5, YES, YES).AddLineTo(32, 8). 194 | AddLineTo(32, 20).AddArcWithPoint(32, 20, 32, 28, 5, YES, YES).AddLineTo(32, 40). 195 | AddLineTo(20, 40).AddArcWithPoint(20, 40, 12, 40, 5, NO, YES).AddLineTo(0, 40). 196 | AddLineTo(0, 28).AddArcWithPoint(0, 28, 0, 20, 5, NO, YES).ClosePath(); 197 | }]; 198 | return path; 199 | } 200 | 201 | ///指定尺寸内的随机点 202 | static inline CGPoint randomPointInSize(CGSize size) { 203 | CGPoint point = CGPointZero; 204 | point.x = randomValueInLength((int)size.width); 205 | point.y = randomValueInLength((int)size.height); 206 | return point; 207 | } 208 | 209 | ///指定范围内的随机值 210 | static inline int randomValueInLength(int length) { 211 | return arc4random() % ((int)(length + 1)); 212 | } 213 | 214 | ///修正centerY值合适的值 215 | static inline CGFloat fixCenterYWithSize(CGSize thumbSize,CGSize validSize,CGFloat centerY) { 216 | CGFloat y = centerY - thumbSize.height / 2; 217 | return fixValueWithLimit(y, validSize.height); 218 | } 219 | 220 | ///将值修正至指定范围 221 | static inline CGFloat fixValueWithLimit(CGFloat value,CGFloat limitLength) { 222 | return value < 0 ? 0 : (value > limitLength ? limitLength : value); 223 | } 224 | 225 | ///将点修正值有效范围内 226 | static inline CGPoint fixPointWithLimit(CGPoint point,CGSize validSize,CGSize thumbSize) { 227 | CGFloat x = point.x - thumbSize.width / 2; 228 | CGFloat y = point.y - thumbSize.height / 2; 229 | return CGPointMake(fixValueWithLimit(x, validSize.width), fixValueWithLimit(y, validSize.height)); 230 | } 231 | 232 | ///将验证位置转换为layer中心点 233 | static inline CGPoint transformLocation2Center(CGPoint origin,CGSize thumbSize) { 234 | return CGPointMake(origin.x + thumbSize.width / 2, origin.y + thumbSize.height / 2); 235 | } 236 | 237 | ///Point转value 238 | static inline NSValue * valueOfPoint(CGPoint point) { 239 | return [NSValue valueWithCGPoint:point]; 240 | } 241 | 242 | ///默认成功动画 243 | static inline CAAnimation * defaultSuccessAnimaiton(id delegate) { 244 | CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 245 | animation.duration = 0.2; 246 | animation.autoreverses = YES; 247 | animation.fromValue = @1; 248 | animation.toValue = @0; 249 | animation.removedOnCompletion = YES; 250 | animation.delegate = delegate; 251 | return animation; 252 | } 253 | 254 | ///默认失败动画 255 | static inline CAAnimation * defaultFailAnimation(id delegate) { 256 | DWSlideCaptchaView * captcha = (DWSlideCaptchaView *)delegate; 257 | CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 258 | CGFloat a = 3; 259 | CGPoint Cp = captcha.thumbLayer.position; 260 | CGPoint Lp = CGPointMake(Cp.x - a, Cp.y); 261 | CGPoint Rp = CGPointMake(Cp.x + a, Cp.y); 262 | animation.values = @[valueOfPoint(Cp),valueOfPoint(Lp),valueOfPoint(Rp),valueOfPoint(Cp)]; 263 | animation.repeatCount = 2; 264 | animation.removedOnCompletion = YES; 265 | animation.duration = 0.2; 266 | animation.delegate = captcha; 267 | return animation; 268 | } 269 | 270 | #pragma mark --- setter/getter --- 271 | -(CAShapeLayer *)positionLayer { 272 | if (!_positionLayer) { 273 | _positionLayer = [CAShapeLayer layer]; 274 | } 275 | return _positionLayer; 276 | } 277 | 278 | -(CAShapeLayer *)thumbLayer { 279 | if (!_thumbLayer) { 280 | _thumbLayer = [CAShapeLayer layer]; 281 | } 282 | return _thumbLayer; 283 | } 284 | 285 | -(void)setThumbShape:(UIBezierPath *)thumbShape { 286 | SafeConfiguration 287 | CGSize size = thumbShape.bounds.size; 288 | if (!(size.width >= 40 && size.height >= 40)) { 289 | NSAssert(NO, @"To get a better experience,the width and height of thumbShape both should be more than 40."); 290 | return; 291 | } 292 | 293 | _thumbShape = thumbShape; 294 | _thumbSize = size; 295 | } 296 | 297 | -(UIBezierPath *)thumbShape { 298 | if (!_thumbShape) { 299 | return puzzlePath(); 300 | } 301 | return _thumbShape; 302 | } 303 | 304 | -(void)setTargetValue:(CGFloat)targetValue { 305 | SafeConfiguration 306 | _targetValue = fixValueWithLimit(targetValue, 1); 307 | } 308 | 309 | -(void)setThumbCenterY:(CGFloat)thumbCenterY { 310 | SafeConfiguration 311 | _thumbCenterY = thumbCenterY; 312 | } 313 | 314 | -(void)setUseRandomValue:(BOOL)useRandomValue { 315 | SafeConfiguration 316 | _useRandomValue = useRandomValue; 317 | } 318 | 319 | -(void)setTolerance:(CGFloat)tolerance { 320 | SafeConfiguration 321 | _tolerance = tolerance; 322 | } 323 | 324 | -(CGFloat)tolerance { 325 | if (_tolerance < 0) { 326 | return 3; 327 | } 328 | return _tolerance; 329 | } 330 | 331 | -(void)setSuccessAnimation:(CAAnimation *)successAnimation { 332 | SafeConfiguration 333 | _successAnimation = successAnimation; 334 | _successAnimation.delegate = self; 335 | } 336 | 337 | -(void)setFailAnimation:(CAAnimation *)failAnimation { 338 | SafeConfiguration 339 | _failAnimation = failAnimation; 340 | _failAnimation.delegate = self; 341 | } 342 | 343 | -(void)setBgImage:(UIImage *)bgImage { 344 | SafeConfiguration 345 | if (bgImage) { 346 | _bgImage = [bgImage dw_RescaleImageToSize:self.frame.size]; 347 | } else { 348 | _bgImage = nil; 349 | } 350 | } 351 | 352 | -(void)setThumbSize:(CGSize)thumbSize { 353 | SafeConfiguration 354 | if (!CGSizeEqualToSize(_thumbSize, thumbSize)) { 355 | _thumbSize = thumbSize; 356 | } 357 | } 358 | 359 | -(CGPoint)targetPoint { 360 | if (!self.resetTargetPoint) { 361 | return _targetPoint; 362 | } 363 | self.resetTargetPoint = NO; 364 | if (self.useRandomValue) { 365 | _targetPoint = randomPointInSize(self.validSize); 366 | return _targetPoint; 367 | } 368 | CGFloat x = (self.targetValue != DWSlideCaptchaUndefineValue) ? self.targetValue : randomValueInLength((int)self.validSize.width); 369 | CGFloat y = (self.thumbCenterY != DWSlideCaptchaUndefineValue) ? fixCenterYWithSize(self.thumbSize, self.validSize, self.thumbCenterY) : randomValueInLength((int)self.validSize.height); 370 | _targetPoint = CGPointMake(x, y); 371 | return _targetPoint; 372 | } 373 | 374 | -(CGSize)validSize { 375 | return CGSizeMake(self.bounds.size.width - self.thumbSize.width, self.bounds.size.height - self.thumbSize.height); 376 | } 377 | 378 | @end 379 | 380 | @implementation DWDefaultSlideCaptchaView 381 | 382 | -(instancetype)initWithFrame:(CGRect)frame image:(UIImage *)image slider:(UISlider *)slider { 383 | if (self = [super initWithFrame:frame]) { 384 | if (!slider) { 385 | slider = [[UISlider alloc] initWithFrame:CGRectMake(0, frame.size.height - 32, frame.size.width, 32)]; 386 | } 387 | _slider = slider; 388 | _captchaView = [[DWSlideCaptchaView alloc] initWithFrame:CGRectMakeWithPointAndSize(CGPointZero, CGSizeMake(frame.size.width, frame.size.height - slider.frame.size.height)) bgImage:image]; 389 | _captchaView.delegate = self; 390 | [self addSubview:_slider]; 391 | [self addSubview:_captchaView]; 392 | 393 | ///为了获取slider结束拖动使用KVO 394 | [self addObserver:self forKeyPath:@"slider.tracking" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; 395 | 396 | ///为了改变验证视图的时使用通知观察slider的数值 397 | [self.slider addTarget:self action:@selector(sliderValueChange) forControlEvents:(UIControlEventValueChanged)]; 398 | } 399 | return self; 400 | } 401 | 402 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 403 | if ([keyPath isEqualToString:@"slider.tracking"]) { 404 | if ([change[@"new"] integerValue] == 0 && [change[@"old"] integerValue] == 1) {///silder结束拖动,开始验证 405 | if (!self.captchaView.isSuccessed) { 406 | [self.captchaView indentifyWithAnimated:YES result:^(BOOL success) { 407 | if (self.indentifyCompletion) { 408 | self.indentifyCompletion(success); 409 | } 410 | }]; 411 | } 412 | } else if ([change[@"new"] integerValue] == 0 && [change[@"old"] integerValue] == 0) {///slider归位 413 | if (self.slider.value) { 414 | self.slider.value = 0; 415 | } 416 | } 417 | } 418 | } 419 | 420 | -(void)sliderValueChange { 421 | if (!self.captchaView.isIndentified) { 422 | [self.captchaView setValue:self.slider.value animated:NO]; 423 | } 424 | } 425 | 426 | -(void)dw_CaptchaView:(DWSlideCaptchaView *)captchaView animationCompletionWithSuccess:(BOOL)success { 427 | if (!success) { 428 | [self.captchaView setValue:0 animated:YES]; 429 | [self.captchaView hideThumbWithAnimated:YES]; 430 | } 431 | } 432 | 433 | -(void)dealloc { 434 | [self removeObserver:self forKeyPath:@"slider.tracking"]; 435 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 436 | } 437 | @end 438 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/DWSlideCaptchaView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DWSlideCaptchaView.m 3 | // code 4 | // 5 | // Created by Wicky on 2017/4/12. 6 | // Copyright © 2017年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "DWSlideCaptchaView.h" 10 | #import "UIImage+DWImageUtils.h" 11 | #import "UIBezierPath+DWPathUtils.h" 12 | #import "DWMacro.h" 13 | 14 | #define SafeConfiguration if (!self.configurating) return; 15 | 16 | @interface DWSlideCaptchaView () 17 | 18 | ///滑块尺寸 19 | @property (nonatomic ,assign) CGSize thumbSize; 20 | 21 | ///目标验证点 22 | @property (nonatomic ,assign) CGPoint targetPoint; 23 | 24 | ///有效验证范围 25 | @property (nonatomic ,assign) CGSize validSize; 26 | 27 | ///是否需要重新设置验证点 28 | @property (nonatomic ,assign) BOOL resetTargetPoint; 29 | 30 | ///当前点 31 | @property (nonatomic ,assign) CGPoint currentPoint; 32 | 33 | @end 34 | 35 | @implementation DWSlideCaptchaView 36 | @synthesize thumbSize = _thumbSize; 37 | @synthesize thumbShape = _thumbShape; 38 | @synthesize tolerance = _tolerance; 39 | @synthesize positionLayer = _positionLayer; 40 | @synthesize thumbLayer = _thumbLayer; 41 | -(instancetype)initWithFrame:(CGRect)frame { 42 | NSAssert((frame.size.width >= 100 && frame.size.height >= 40), @"To get a better experience,you may set the width more than 100 and height more than 50."); 43 | if (self = [super initWithFrame:frame]) { 44 | _useRandomValue = YES; 45 | _targetValue = DWSlideCaptchaUndefineValue; 46 | _thumbCenterY = DWSlideCaptchaUndefineValue; 47 | _tolerance = DWSlideCaptchaUndefineValue; 48 | _thumbSize = puzzlePath().bounds.size; 49 | } 50 | return self; 51 | } 52 | 53 | -(instancetype)initWithFrame:(CGRect)frame bgImage:(UIImage *)bgImage { 54 | if (self = [self initWithFrame:frame]) { 55 | [self beginConfiguration]; 56 | self.bgImage = bgImage; 57 | [self commitConfiguration]; 58 | } 59 | return self; 60 | } 61 | 62 | -(void)beginConfiguration { 63 | _configurating = YES; 64 | self.resetTargetPoint = YES; 65 | } 66 | 67 | -(void)commitConfiguration { 68 | if (!self.configurating) { 69 | return; 70 | } 71 | _configurating = NO; 72 | self.layer.contents = (id)self.bgImage.CGImage; 73 | [self handlePositionLayer]; 74 | [self handleThumbLayer]; 75 | [self hideThumbWithAnimated:NO]; 76 | } 77 | 78 | -(void)reset { 79 | _successed = NO; 80 | [self beginConfiguration]; 81 | [self commitConfiguration]; 82 | } 83 | 84 | -(void)indentifyWithAnimated:(BOOL)animated result:(void(^)(BOOL success))result { 85 | BOOL isSuccess = fabs(self.targetPoint.x - self.currentPoint.x) < self.tolerance; 86 | isSuccess &= fabs(self.targetPoint.y - self.currentPoint.y) < self.tolerance; 87 | _successed = isSuccess; 88 | _indentified = YES; 89 | if (isSuccess) { 90 | if (animated) { 91 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationWillStartWithSuccess:)]) {///动画开始回调 92 | [self.delegate dw_CaptchaView:self animationWillStartWithSuccess:YES]; 93 | } 94 | if (!self.successAnimation) {///未指定动画使用默认动画 95 | [self.layer addAnimation:defaultSuccessAnimaiton(self) forKey:@"successAnimation"]; 96 | [self hideThumbWithAnimated:NO]; 97 | DWLayerTransactionWithAnimation(NO, ^(){ 98 | self.positionLayer.opacity = 0; 99 | }); 100 | } else {///使用指定动画 101 | [self.thumbLayer addAnimation:self.successAnimation forKey:@"successAnimation"]; 102 | } 103 | } 104 | if (result) result(YES); 105 | } else { 106 | if (animated) { 107 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationWillStartWithSuccess:)]) {///动画开始回调 108 | [self.delegate dw_CaptchaView:self animationWillStartWithSuccess:YES]; 109 | } 110 | if (!self.failAnimation) {///未指定动画则使用默认动画 111 | [self.thumbLayer addAnimation:defaultFailAnimation(self) forKey:@"failAnimation"]; 112 | } else {///使用指定动画 113 | [self.thumbLayer addAnimation:self.failAnimation forKey:@"failAnimation"]; 114 | } 115 | } 116 | if (result) result(NO); 117 | } 118 | } 119 | 120 | -(void)moveToPoint:(CGPoint)point animated:(BOOL)animated { 121 | if (self.successed) { 122 | return; 123 | } 124 | _indentified = NO; 125 | point = fixPointWithLimit(point, self.validSize, self.thumbSize); 126 | self.currentPoint = point; 127 | if (self.thumbLayer.opacity != 1) { 128 | [self showThumbWithAnimated:YES]; 129 | } 130 | DWLayerTransactionWithAnimation(animated, ^(){ 131 | self.thumbLayer.position = transformLocation2Center(point, self.thumbSize); 132 | }); 133 | } 134 | 135 | -(void)setValue:(CGFloat)value animated:(BOOL)animated { 136 | CGFloat x = value * self.validSize.width + self.thumbSize.width / 2; 137 | CGFloat y = self.targetPoint.y + self.thumbSize.height / 2; 138 | [self moveToPoint:CGPointMake(x, y) animated:animated]; 139 | } 140 | 141 | -(void)showThumbWithAnimated:(BOOL)animated { 142 | DWLayerTransactionWithAnimation(animated,^(){ 143 | self.thumbLayer.opacity = 1; 144 | }); 145 | } 146 | 147 | -(void)hideThumbWithAnimated:(BOOL)animated { 148 | DWLayerTransactionWithAnimation(animated, ^(){ 149 | self.thumbLayer.opacity = 0; 150 | }); 151 | } 152 | 153 | #pragma mark --- tool Method --- 154 | -(void)handleThumbLayer { 155 | UIImage * thumbImage = [self.bgImage dw_SubImageWithRect:self.positionLayer.frame]; 156 | thumbImage = [thumbImage dw_ClipImageWithPath:self.thumbShape mode:(DWContentModeScaleToFill)]; 157 | self.thumbLayer.contents = (id)thumbImage.CGImage; 158 | self.thumbLayer.frame = self.positionLayer.frame; 159 | [self setValue:0 animated:NO]; 160 | if (!self.thumbLayer.superlayer) { 161 | [self.layer addSublayer:self.thumbLayer]; 162 | } 163 | } 164 | 165 | -(void)handlePositionLayer { 166 | UIBezierPath * path = [self.thumbShape copy]; 167 | self.positionLayer.fillColor = [UIColor colorWithWhite:1 alpha:0.7].CGColor; 168 | self.positionLayer.path = path.CGPath; 169 | self.positionLayer.frame = CGRectMake(self.targetPoint.x, self.targetPoint.y, (int)path.bounds.size.width, (int)path.bounds.size.height); 170 | if (!self.positionLayer.superlayer) { 171 | [self.layer addSublayer:self.positionLayer]; 172 | } 173 | } 174 | 175 | #pragma mark --- animation delegate --- 176 | -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { 177 | if (self.isSuccessed) { 178 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationCompletionWithSuccess:)]) { 179 | [self.delegate dw_CaptchaView:self animationCompletionWithSuccess:YES]; 180 | } 181 | } else { 182 | if (self.delegate && [self.delegate respondsToSelector:@selector(dw_CaptchaView:animationCompletionWithSuccess:)]) { 183 | [self.delegate dw_CaptchaView:self animationCompletionWithSuccess:NO]; 184 | } 185 | } 186 | } 187 | 188 | #pragma mark --- 内联方法 --- 189 | ///默认滑块形状 190 | static inline UIBezierPath * puzzlePath (){ 191 | UIBezierPath * path = [UIBezierPath bezierPathWithPathMaker:^(DWPathMaker *maker) { 192 | maker.MoveTo(0, 8). 193 | AddLineTo(12, 8).AddArcWithPoint(12, 8, 20, 8, 5, YES, YES).AddLineTo(32, 8). 194 | AddLineTo(32, 20).AddArcWithPoint(32, 20, 32, 28, 5, YES, YES).AddLineTo(32, 40). 195 | AddLineTo(20, 40).AddArcWithPoint(20, 40, 12, 40, 5, NO, YES).AddLineTo(0, 40). 196 | AddLineTo(0, 28).AddArcWithPoint(0, 28, 0, 20, 5, NO, YES).ClosePath(); 197 | }]; 198 | return path; 199 | } 200 | 201 | ///指定尺寸内的随机点 202 | static inline CGPoint randomPointInSize(CGSize size) { 203 | CGPoint point = CGPointZero; 204 | point.x = randomValueInLength((int)size.width); 205 | point.y = randomValueInLength((int)size.height); 206 | return point; 207 | } 208 | 209 | ///指定范围内的随机值 210 | static inline int randomValueInLength(int length) { 211 | return arc4random() % ((int)(length + 1)); 212 | } 213 | 214 | ///修正centerY值合适的值 215 | static inline CGFloat fixCenterYWithSize(CGSize thumbSize,CGSize validSize,CGFloat centerY) { 216 | CGFloat y = centerY - thumbSize.height / 2; 217 | return fixValueWithLimit(y, validSize.height); 218 | } 219 | 220 | ///将值修正至指定范围 221 | static inline CGFloat fixValueWithLimit(CGFloat value,CGFloat limitLength) { 222 | return value < 0 ? 0 : (value > limitLength ? limitLength : value); 223 | } 224 | 225 | ///将点修正值有效范围内 226 | static inline CGPoint fixPointWithLimit(CGPoint point,CGSize validSize,CGSize thumbSize) { 227 | CGFloat x = point.x - thumbSize.width / 2; 228 | CGFloat y = point.y - thumbSize.height / 2; 229 | return CGPointMake(fixValueWithLimit(x, validSize.width), fixValueWithLimit(y, validSize.height)); 230 | } 231 | 232 | ///将验证位置转换为layer中心点 233 | static inline CGPoint transformLocation2Center(CGPoint origin,CGSize thumbSize) { 234 | return CGPointMake(origin.x + thumbSize.width / 2, origin.y + thumbSize.height / 2); 235 | } 236 | 237 | ///Point转value 238 | static inline NSValue * valueOfPoint(CGPoint point) { 239 | return [NSValue valueWithCGPoint:point]; 240 | } 241 | 242 | ///默认成功动画 243 | static inline CAAnimation * defaultSuccessAnimaiton(id delegate) { 244 | CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 245 | animation.duration = 0.2; 246 | animation.autoreverses = YES; 247 | animation.fromValue = @1; 248 | animation.toValue = @0; 249 | animation.removedOnCompletion = YES; 250 | animation.delegate = delegate; 251 | return animation; 252 | } 253 | 254 | ///默认失败动画 255 | static inline CAAnimation * defaultFailAnimation(id delegate) { 256 | DWSlideCaptchaView * captcha = (DWSlideCaptchaView *)delegate; 257 | CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 258 | CGFloat a = 3; 259 | CGPoint Cp = captcha.thumbLayer.position; 260 | CGPoint Lp = CGPointMake(Cp.x - a, Cp.y); 261 | CGPoint Rp = CGPointMake(Cp.x + a, Cp.y); 262 | animation.values = @[valueOfPoint(Cp),valueOfPoint(Lp),valueOfPoint(Rp),valueOfPoint(Cp)]; 263 | animation.repeatCount = 2; 264 | animation.removedOnCompletion = YES; 265 | animation.duration = 0.2; 266 | animation.delegate = captcha; 267 | return animation; 268 | } 269 | 270 | #pragma mark --- setter/getter --- 271 | -(CAShapeLayer *)positionLayer { 272 | if (!_positionLayer) { 273 | _positionLayer = [CAShapeLayer layer]; 274 | } 275 | return _positionLayer; 276 | } 277 | 278 | -(CAShapeLayer *)thumbLayer { 279 | if (!_thumbLayer) { 280 | _thumbLayer = [CAShapeLayer layer]; 281 | } 282 | return _thumbLayer; 283 | } 284 | 285 | -(void)setThumbShape:(UIBezierPath *)thumbShape { 286 | SafeConfiguration 287 | CGSize size = thumbShape.bounds.size; 288 | if (!(size.width >= 40 && size.height >= 40)) { 289 | NSAssert(NO, @"To get a better experience,the width and height of thumbShape both should be more than 40."); 290 | return; 291 | } 292 | 293 | _thumbShape = thumbShape; 294 | _thumbSize = size; 295 | } 296 | 297 | -(UIBezierPath *)thumbShape { 298 | if (!_thumbShape) { 299 | return puzzlePath(); 300 | } 301 | return _thumbShape; 302 | } 303 | 304 | -(void)setTargetValue:(CGFloat)targetValue { 305 | SafeConfiguration 306 | _targetValue = fixValueWithLimit(targetValue, 1); 307 | } 308 | 309 | -(void)setThumbCenterY:(CGFloat)thumbCenterY { 310 | SafeConfiguration 311 | _thumbCenterY = thumbCenterY; 312 | } 313 | 314 | -(void)setUseRandomValue:(BOOL)useRandomValue { 315 | SafeConfiguration 316 | _useRandomValue = useRandomValue; 317 | } 318 | 319 | -(void)setTolerance:(CGFloat)tolerance { 320 | SafeConfiguration 321 | _tolerance = tolerance; 322 | } 323 | 324 | -(CGFloat)tolerance { 325 | if (_tolerance < 0) { 326 | return 3; 327 | } 328 | return _tolerance; 329 | } 330 | 331 | -(void)setSuccessAnimation:(CAAnimation *)successAnimation { 332 | SafeConfiguration 333 | _successAnimation = successAnimation; 334 | _successAnimation.delegate = self; 335 | } 336 | 337 | -(void)setFailAnimation:(CAAnimation *)failAnimation { 338 | SafeConfiguration 339 | _failAnimation = failAnimation; 340 | _failAnimation.delegate = self; 341 | } 342 | 343 | -(void)setBgImage:(UIImage *)bgImage { 344 | SafeConfiguration 345 | if (bgImage) { 346 | _bgImage = [bgImage dw_RescaleImageToSize:self.frame.size]; 347 | } else { 348 | _bgImage = nil; 349 | } 350 | } 351 | 352 | -(void)setThumbSize:(CGSize)thumbSize { 353 | SafeConfiguration 354 | if (!CGSizeEqualToSize(_thumbSize, thumbSize)) { 355 | _thumbSize = thumbSize; 356 | } 357 | } 358 | 359 | -(CGPoint)targetPoint { 360 | if (!self.resetTargetPoint) { 361 | return _targetPoint; 362 | } 363 | self.resetTargetPoint = NO; 364 | if (self.useRandomValue) { 365 | _targetPoint = randomPointInSize(self.validSize); 366 | return _targetPoint; 367 | } 368 | CGFloat x = (self.targetValue != DWSlideCaptchaUndefineValue) ? self.targetValue : randomValueInLength((int)self.validSize.width); 369 | CGFloat y = (self.thumbCenterY != DWSlideCaptchaUndefineValue) ? fixCenterYWithSize(self.thumbSize, self.validSize, self.thumbCenterY) : randomValueInLength((int)self.validSize.height); 370 | _targetPoint = CGPointMake(x, y); 371 | return _targetPoint; 372 | } 373 | 374 | -(CGSize)validSize { 375 | return CGSizeMake(self.bounds.size.width - self.thumbSize.width, self.bounds.size.height - self.thumbSize.height); 376 | } 377 | 378 | @end 379 | 380 | @implementation DWDefaultSlideCaptchaView 381 | 382 | -(instancetype)initWithFrame:(CGRect)frame image:(UIImage *)image slider:(UISlider *)slider { 383 | if (self = [super initWithFrame:frame]) { 384 | if (!slider) { 385 | slider = [[UISlider alloc] initWithFrame:CGRectMake(0, frame.size.height - 32, frame.size.width, 32)]; 386 | } 387 | _slider = slider; 388 | _captchaView = [[DWSlideCaptchaView alloc] initWithFrame:CGRectMakeWithPointAndSize(CGPointZero, CGSizeMake(frame.size.width, frame.size.height - slider.frame.size.height)) bgImage:image]; 389 | _captchaView.delegate = self; 390 | [self addSubview:_slider]; 391 | [self addSubview:_captchaView]; 392 | 393 | ///为了获取slider结束拖动使用KVO 394 | [self addObserver:self forKeyPath:@"slider.tracking" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; 395 | 396 | ///为了改变验证视图的时使用通知观察slider的数值 397 | [self.slider addTarget:self action:@selector(sliderValueChange) forControlEvents:(UIControlEventValueChanged)]; 398 | } 399 | return self; 400 | } 401 | 402 | -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 403 | if ([keyPath isEqualToString:@"slider.tracking"]) { 404 | if ([change[@"new"] integerValue] == 0 && [change[@"old"] integerValue] == 1) {///silder结束拖动,开始验证 405 | if (!self.captchaView.isSuccessed) { 406 | [self.captchaView indentifyWithAnimated:YES result:^(BOOL success) { 407 | if (self.indentifyCompletion) { 408 | self.indentifyCompletion(success); 409 | } 410 | }]; 411 | } 412 | } else if ([change[@"new"] integerValue] == 0 && [change[@"old"] integerValue] == 0) {///slider归位 413 | if (self.slider.value) { 414 | self.slider.value = 0; 415 | } 416 | } 417 | } 418 | } 419 | 420 | -(void)sliderValueChange { 421 | if (!self.captchaView.isIndentified) { 422 | [self.captchaView setValue:self.slider.value animated:NO]; 423 | } 424 | } 425 | 426 | -(void)dw_CaptchaView:(DWSlideCaptchaView *)captchaView animationCompletionWithSuccess:(BOOL)success { 427 | if (!success) { 428 | [self.captchaView setValue:0 animated:YES]; 429 | [self.captchaView hideThumbWithAnimated:YES]; 430 | } 431 | } 432 | 433 | -(void)dealloc { 434 | [self removeObserver:self forKeyPath:@"slider.tracking"]; 435 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 436 | } 437 | @end 438 | -------------------------------------------------------------------------------- /DEMO/code.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9B780F131EA4A90E0014746A /* UIImage+DWImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B780F121EA4A90E0014746A /* UIImage+DWImageUtils.m */; }; 11 | 9BA854B41E9E78EF00970235 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA854B31E9E78EF00970235 /* main.m */; }; 12 | 9BA854B71E9E78EF00970235 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA854B61E9E78EF00970235 /* AppDelegate.m */; }; 13 | 9BA854BA1E9E78EF00970235 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA854B91E9E78EF00970235 /* ViewController.m */; }; 14 | 9BA854BD1E9E78EF00970235 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA854BB1E9E78EF00970235 /* Main.storyboard */; }; 15 | 9BA854BF1E9E78EF00970235 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9BA854BE1E9E78EF00970235 /* Assets.xcassets */; }; 16 | 9BA854C21E9E78EF00970235 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA854C01E9E78EF00970235 /* LaunchScreen.storyboard */; }; 17 | 9BA854CB1E9E797200970235 /* DWSlideCaptchaView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA854CA1E9E797200970235 /* DWSlideCaptchaView.m */; }; 18 | 9BA854D11E9E7E4D00970235 /* UIBezierPath+DWPathUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 9BA854D01E9E7E4D00970235 /* UIBezierPath+DWPathUtils.m */; }; 19 | 9BA854D31E9F2A9A00970235 /* 2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BA854D21E9F2A9A00970235 /* 2.jpg */; }; 20 | 9BA854D51E9F795C00970235 /* 10.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 9BA854D41E9F795C00970235 /* 10.jpg */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 9B780F101EA4A90E0014746A /* DWMacro.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWMacro.h; sourceTree = ""; }; 25 | 9B780F111EA4A90E0014746A /* UIImage+DWImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+DWImageUtils.h"; sourceTree = ""; }; 26 | 9B780F121EA4A90E0014746A /* UIImage+DWImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+DWImageUtils.m"; sourceTree = ""; }; 27 | 9BA854AF1E9E78EF00970235 /* code.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = code.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 9BA854B31E9E78EF00970235 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 29 | 9BA854B51E9E78EF00970235 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 30 | 9BA854B61E9E78EF00970235 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 31 | 9BA854B81E9E78EF00970235 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 32 | 9BA854B91E9E78EF00970235 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 33 | 9BA854BC1E9E78EF00970235 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 34 | 9BA854BE1E9E78EF00970235 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | 9BA854C11E9E78EF00970235 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 36 | 9BA854C31E9E78EF00970235 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 9BA854C91E9E797200970235 /* DWSlideCaptchaView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWSlideCaptchaView.h; sourceTree = ""; }; 38 | 9BA854CA1E9E797200970235 /* DWSlideCaptchaView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DWSlideCaptchaView.m; sourceTree = ""; }; 39 | 9BA854CF1E9E7E4D00970235 /* UIBezierPath+DWPathUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+DWPathUtils.h"; sourceTree = ""; }; 40 | 9BA854D01E9E7E4D00970235 /* UIBezierPath+DWPathUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+DWPathUtils.m"; sourceTree = ""; }; 41 | 9BA854D21E9F2A9A00970235 /* 2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 2.jpg; sourceTree = ""; }; 42 | 9BA854D41E9F795C00970235 /* 10.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 10.jpg; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | 9BA854AC1E9E78EF00970235 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 9BA854A61E9E78EF00970235 = { 57 | isa = PBXGroup; 58 | children = ( 59 | 9BA854B11E9E78EF00970235 /* code */, 60 | 9BA854B01E9E78EF00970235 /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 9BA854B01E9E78EF00970235 /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 9BA854AF1E9E78EF00970235 /* code.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | 9BA854B11E9E78EF00970235 /* code */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 9BA854B51E9E78EF00970235 /* AppDelegate.h */, 76 | 9BA854B61E9E78EF00970235 /* AppDelegate.m */, 77 | 9BA854B81E9E78EF00970235 /* ViewController.h */, 78 | 9BA854B91E9E78EF00970235 /* ViewController.m */, 79 | 9BA854D41E9F795C00970235 /* 10.jpg */, 80 | 9BA854D21E9F2A9A00970235 /* 2.jpg */, 81 | 9B780F101EA4A90E0014746A /* DWMacro.h */, 82 | 9B780F111EA4A90E0014746A /* UIImage+DWImageUtils.h */, 83 | 9B780F121EA4A90E0014746A /* UIImage+DWImageUtils.m */, 84 | 9BA854CF1E9E7E4D00970235 /* UIBezierPath+DWPathUtils.h */, 85 | 9BA854D01E9E7E4D00970235 /* UIBezierPath+DWPathUtils.m */, 86 | 9BA854C91E9E797200970235 /* DWSlideCaptchaView.h */, 87 | 9BA854CA1E9E797200970235 /* DWSlideCaptchaView.m */, 88 | 9BA854BB1E9E78EF00970235 /* Main.storyboard */, 89 | 9BA854BE1E9E78EF00970235 /* Assets.xcassets */, 90 | 9BA854C01E9E78EF00970235 /* LaunchScreen.storyboard */, 91 | 9BA854C31E9E78EF00970235 /* Info.plist */, 92 | 9BA854B21E9E78EF00970235 /* Supporting Files */, 93 | ); 94 | path = code; 95 | sourceTree = ""; 96 | }; 97 | 9BA854B21E9E78EF00970235 /* Supporting Files */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 9BA854B31E9E78EF00970235 /* main.m */, 101 | ); 102 | name = "Supporting Files"; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | 9BA854AE1E9E78EF00970235 /* code */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = 9BA854C61E9E78EF00970235 /* Build configuration list for PBXNativeTarget "code" */; 111 | buildPhases = ( 112 | 9BA854AB1E9E78EF00970235 /* Sources */, 113 | 9BA854AC1E9E78EF00970235 /* Frameworks */, 114 | 9BA854AD1E9E78EF00970235 /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = code; 121 | productName = code; 122 | productReference = 9BA854AF1E9E78EF00970235 /* code.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 9BA854A71E9E78EF00970235 /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | LastUpgradeCheck = 0830; 132 | ORGANIZATIONNAME = Wicky; 133 | TargetAttributes = { 134 | 9BA854AE1E9E78EF00970235 = { 135 | CreatedOnToolsVersion = 8.3; 136 | DevelopmentTeam = Z26Y87B8NH; 137 | ProvisioningStyle = Automatic; 138 | }; 139 | }; 140 | }; 141 | buildConfigurationList = 9BA854AA1E9E78EF00970235 /* Build configuration list for PBXProject "code" */; 142 | compatibilityVersion = "Xcode 3.2"; 143 | developmentRegion = English; 144 | hasScannedForEncodings = 0; 145 | knownRegions = ( 146 | en, 147 | Base, 148 | ); 149 | mainGroup = 9BA854A61E9E78EF00970235; 150 | productRefGroup = 9BA854B01E9E78EF00970235 /* Products */; 151 | projectDirPath = ""; 152 | projectRoot = ""; 153 | targets = ( 154 | 9BA854AE1E9E78EF00970235 /* code */, 155 | ); 156 | }; 157 | /* End PBXProject section */ 158 | 159 | /* Begin PBXResourcesBuildPhase section */ 160 | 9BA854AD1E9E78EF00970235 /* Resources */ = { 161 | isa = PBXResourcesBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | 9BA854D51E9F795C00970235 /* 10.jpg in Resources */, 165 | 9BA854C21E9E78EF00970235 /* LaunchScreen.storyboard in Resources */, 166 | 9BA854BF1E9E78EF00970235 /* Assets.xcassets in Resources */, 167 | 9BA854D31E9F2A9A00970235 /* 2.jpg in Resources */, 168 | 9BA854BD1E9E78EF00970235 /* Main.storyboard in Resources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXResourcesBuildPhase section */ 173 | 174 | /* Begin PBXSourcesBuildPhase section */ 175 | 9BA854AB1E9E78EF00970235 /* Sources */ = { 176 | isa = PBXSourcesBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 9BA854D11E9E7E4D00970235 /* UIBezierPath+DWPathUtils.m in Sources */, 180 | 9BA854BA1E9E78EF00970235 /* ViewController.m in Sources */, 181 | 9B780F131EA4A90E0014746A /* UIImage+DWImageUtils.m in Sources */, 182 | 9BA854B71E9E78EF00970235 /* AppDelegate.m in Sources */, 183 | 9BA854CB1E9E797200970235 /* DWSlideCaptchaView.m in Sources */, 184 | 9BA854B41E9E78EF00970235 /* main.m in Sources */, 185 | ); 186 | runOnlyForDeploymentPostprocessing = 0; 187 | }; 188 | /* End PBXSourcesBuildPhase section */ 189 | 190 | /* Begin PBXVariantGroup section */ 191 | 9BA854BB1E9E78EF00970235 /* Main.storyboard */ = { 192 | isa = PBXVariantGroup; 193 | children = ( 194 | 9BA854BC1E9E78EF00970235 /* Base */, 195 | ); 196 | name = Main.storyboard; 197 | sourceTree = ""; 198 | }; 199 | 9BA854C01E9E78EF00970235 /* LaunchScreen.storyboard */ = { 200 | isa = PBXVariantGroup; 201 | children = ( 202 | 9BA854C11E9E78EF00970235 /* Base */, 203 | ); 204 | name = LaunchScreen.storyboard; 205 | sourceTree = ""; 206 | }; 207 | /* End PBXVariantGroup section */ 208 | 209 | /* Begin XCBuildConfiguration section */ 210 | 9BA854C41E9E78EF00970235 /* Debug */ = { 211 | isa = XCBuildConfiguration; 212 | buildSettings = { 213 | ALWAYS_SEARCH_USER_PATHS = NO; 214 | CLANG_ANALYZER_NONNULL = YES; 215 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 216 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 217 | CLANG_CXX_LIBRARY = "libc++"; 218 | CLANG_ENABLE_MODULES = YES; 219 | CLANG_ENABLE_OBJC_ARC = YES; 220 | CLANG_WARN_BOOL_CONVERSION = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 223 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INFINITE_RECURSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 230 | CLANG_WARN_UNREACHABLE_CODE = YES; 231 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 232 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 233 | COPY_PHASE_STRIP = NO; 234 | DEBUG_INFORMATION_FORMAT = dwarf; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | ENABLE_TESTABILITY = YES; 237 | GCC_C_LANGUAGE_STANDARD = gnu99; 238 | GCC_DYNAMIC_NO_PIC = NO; 239 | GCC_NO_COMMON_BLOCKS = YES; 240 | GCC_OPTIMIZATION_LEVEL = 0; 241 | GCC_PREPROCESSOR_DEFINITIONS = ( 242 | "DEBUG=1", 243 | "$(inherited)", 244 | ); 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 247 | GCC_WARN_UNDECLARED_SELECTOR = YES; 248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 249 | GCC_WARN_UNUSED_FUNCTION = YES; 250 | GCC_WARN_UNUSED_VARIABLE = YES; 251 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 252 | MTL_ENABLE_DEBUG_INFO = YES; 253 | ONLY_ACTIVE_ARCH = YES; 254 | SDKROOT = iphoneos; 255 | TARGETED_DEVICE_FAMILY = "1,2"; 256 | }; 257 | name = Debug; 258 | }; 259 | 9BA854C51E9E78EF00970235 /* Release */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | ALWAYS_SEARCH_USER_PATHS = NO; 263 | CLANG_ANALYZER_NONNULL = YES; 264 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 265 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 266 | CLANG_CXX_LIBRARY = "libc++"; 267 | CLANG_ENABLE_MODULES = YES; 268 | CLANG_ENABLE_OBJC_ARC = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_CONSTANT_CONVERSION = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 279 | CLANG_WARN_UNREACHABLE_CODE = YES; 280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 281 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 282 | COPY_PHASE_STRIP = NO; 283 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 284 | ENABLE_NS_ASSERTIONS = NO; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | GCC_C_LANGUAGE_STANDARD = gnu99; 287 | GCC_NO_COMMON_BLOCKS = YES; 288 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 289 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 290 | GCC_WARN_UNDECLARED_SELECTOR = YES; 291 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 292 | GCC_WARN_UNUSED_FUNCTION = YES; 293 | GCC_WARN_UNUSED_VARIABLE = YES; 294 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 295 | MTL_ENABLE_DEBUG_INFO = NO; 296 | SDKROOT = iphoneos; 297 | TARGETED_DEVICE_FAMILY = "1,2"; 298 | VALIDATE_PRODUCT = YES; 299 | }; 300 | name = Release; 301 | }; 302 | 9BA854C71E9E78EF00970235 /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 306 | DEVELOPMENT_TEAM = Z26Y87B8NH; 307 | INFOPLIST_FILE = code/Info.plist; 308 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 309 | PRODUCT_BUNDLE_IDENTIFIER = Wicky.code; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | }; 312 | name = Debug; 313 | }; 314 | 9BA854C81E9E78EF00970235 /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 318 | DEVELOPMENT_TEAM = Z26Y87B8NH; 319 | INFOPLIST_FILE = code/Info.plist; 320 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 321 | PRODUCT_BUNDLE_IDENTIFIER = Wicky.code; 322 | PRODUCT_NAME = "$(TARGET_NAME)"; 323 | }; 324 | name = Release; 325 | }; 326 | /* End XCBuildConfiguration section */ 327 | 328 | /* Begin XCConfigurationList section */ 329 | 9BA854AA1E9E78EF00970235 /* Build configuration list for PBXProject "code" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | 9BA854C41E9E78EF00970235 /* Debug */, 333 | 9BA854C51E9E78EF00970235 /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | 9BA854C61E9E78EF00970235 /* Build configuration list for PBXNativeTarget "code" */ = { 339 | isa = XCConfigurationList; 340 | buildConfigurations = ( 341 | 9BA854C71E9E78EF00970235 /* Debug */, 342 | 9BA854C81E9E78EF00970235 /* Release */, 343 | ); 344 | defaultConfigurationIsVisible = 0; 345 | defaultConfigurationName = Release; 346 | }; 347 | /* End XCConfigurationList section */ 348 | }; 349 | rootObject = 9BA854A71E9E78EF00970235 /* Project object */; 350 | } 351 | -------------------------------------------------------------------------------- /DEMO/code/UIImage+DWImageUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+DWImageUtils.m 3 | // Image 4 | // 5 | // Created by Wicky on 2016/12/6. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "UIImage+DWImageUtils.h" 10 | #import 11 | 12 | @implementation UIImage (DWImageInstanceUtils) 13 | 14 | +(UIImage *)dw_ImageNamed:(NSString *)name 15 | { 16 | NSString * path = [[NSBundle mainBundle] pathForResource:name ofType:nil]; 17 | if (!path) { 18 | path = [[NSBundle mainBundle] pathForResource:name ofType:@"png"]; 19 | } 20 | if (!path) { 21 | return nil; 22 | } 23 | NSURL * url = [NSURL fileURLWithPath:path]; 24 | return [self dw_ImageWithUrl:url]; 25 | } 26 | 27 | +(UIImage *)dw_ImageWithUrl:(NSURL *)url 28 | { 29 | NSDictionary*options = @{(__bridge id)kCGImageSourceShouldCache: @YES}; 30 | CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url,NULL);CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source,0,(__bridge CFDictionaryRef)options); 31 | UIImage *image = [UIImage imageWithCGImage:imageRef]; 32 | CGImageRelease(imageRef); 33 | CFRelease(source); 34 | return image; 35 | } 36 | 37 | @end 38 | 39 | @implementation UIImage (DWImageBase64Utils) 40 | 41 | -(NSString *)dw_ImageToBase64String 42 | { 43 | NSData *imageData = nil; 44 | NSString *mimeType = nil; 45 | 46 | if ([self imageHasAlpha]) { 47 | imageData = UIImagePNGRepresentation(self); 48 | mimeType = @"image/png"; 49 | } else { 50 | imageData = UIImageJPEGRepresentation(self, 1.0f); 51 | mimeType = @"image/jpeg"; 52 | } 53 | return [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, 54 | [imageData base64EncodedStringWithOptions: 0]]; 55 | } 56 | 57 | + (UIImage *)dw_ImageWithBase64String:(NSString *)base64String 58 | { 59 | NSURL *url = [NSURL URLWithString: base64String]; 60 | NSData *data = [NSData dataWithContentsOfURL: url]; 61 | UIImage *image = [UIImage imageWithData: data]; 62 | 63 | return image; 64 | } 65 | 66 | -(BOOL)imageHasAlpha 67 | { 68 | CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); 69 | return (alpha == kCGImageAlphaFirst || 70 | alpha == kCGImageAlphaLast || 71 | alpha == kCGImageAlphaPremultipliedFirst || 72 | alpha == kCGImageAlphaPremultipliedLast); 73 | } 74 | 75 | @end 76 | 77 | @implementation UIImage (DWImageColorUtils) 78 | 79 | -(UIColor *)dw_ColorAtPoint:(CGPoint)point; 80 | { 81 | if (!CGRectContainsPoint(CGRectMake(0.0f, 0.0f, self.size.width, self.size.height), point)) 82 | { 83 | return nil; 84 | } 85 | 86 | NSInteger pointX = trunc(point.x); 87 | NSInteger pointY = trunc(point.y); 88 | CGImageRef cgImage = self.CGImage; 89 | NSUInteger width = self.size.width; 90 | NSUInteger height = self.size.height; 91 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 92 | int bytesPerPixel = 4; 93 | int bytesPerRow = bytesPerPixel * 1; 94 | NSUInteger bitsPerComponent = 8; 95 | unsigned char pixelData[4] = { 0, 0, 0, 0 }; 96 | 97 | ///创建1*1画布 98 | CGContextRef context = CGBitmapContextCreate(pixelData,1,1,bitsPerComponent,bytesPerRow,colorSpace,kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); 99 | CGColorSpaceRelease(colorSpace); 100 | CGContextSetBlendMode(context, kCGBlendModeCopy); 101 | 102 | CGContextTranslateCTM(context, -pointX, pointY-(CGFloat)height); 103 | ///绘图 104 | CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage); 105 | CGContextRelease(context); 106 | 107 | ///取色 108 | CGFloat red = (CGFloat)pixelData[0] / 255.0f; 109 | CGFloat green = (CGFloat)pixelData[1] / 255.0f; 110 | CGFloat blue = (CGFloat)pixelData[2] / 255.0f; 111 | CGFloat alpha = (CGFloat)pixelData[3] / 255.0f; 112 | return [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; 113 | } 114 | 115 | +(UIImage *)dw_ImageWithColor:(UIColor *)color 116 | { 117 | CGRect rect=CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 118 | UIGraphicsBeginImageContext(rect.size); 119 | CGContextRef context = UIGraphicsGetCurrentContext(); 120 | CGContextSetFillColorWithColor(context, [color CGColor]); 121 | CGContextFillRect(context, rect); 122 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 123 | UIGraphicsEndImageContext(); 124 | return newImage; 125 | } 126 | 127 | -(UIImage *)dw_ConvertToGrayImage 128 | { 129 | size_t width = self.size.width; 130 | size_t height = self.size.height; 131 | 132 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); 133 | CGContextRef context = CGBitmapContextCreate(nil,width,height,8,0,colorSpace,kCGImageAlphaNone); 134 | CGColorSpaceRelease(colorSpace); 135 | 136 | if (context == NULL) 137 | { 138 | return nil; 139 | } 140 | 141 | CGContextDrawImage(context,CGRectMake(0, 0, width, height), self.CGImage); 142 | CGImageRef contextRef = CGBitmapContextCreateImage(context); 143 | UIImage *grayImage = [UIImage imageWithCGImage:contextRef]; 144 | CGContextRelease(context); 145 | CGImageRelease(contextRef); 146 | 147 | return grayImage; 148 | } 149 | 150 | -(UIImage *)dw_ConvertToReversedColor { 151 | return [self dw_ConvertImageWithPixelHandler:^(UInt8 *pixel, int x, int y) { 152 | UInt8 alpha = * (pixel + 3); 153 | if (alpha) { 154 | *pixel = 255 - *pixel; 155 | *(pixel + 1) = 255 - *(pixel + 1); 156 | *(pixel + 2) = 255 - *(pixel + 2); 157 | } 158 | }]; 159 | } 160 | 161 | -(UIImage *)dw_ConvertToSketchWithColor:(UIColor *)color { 162 | NSInteger numComponents = CGColorGetNumberOfComponents(color.CGColor); 163 | NSInteger red , green , blue; 164 | red = green = blue = 0; 165 | if (numComponents == 4) 166 | { 167 | const CGFloat *components = CGColorGetComponents(color.CGColor); 168 | red = components[0] * 255; 169 | green = components[1] * 255; 170 | blue = components[2] * 255; 171 | } 172 | return [self dw_ConvertImageWithPixelHandler:^(UInt8 *pixel, int x, int y) { 173 | UInt8 alpha = * (pixel + 3); 174 | if (alpha) { 175 | *pixel = red; 176 | *(pixel + 1) = green; 177 | *(pixel + 2) = blue; 178 | } 179 | }]; 180 | } 181 | 182 | -(UIImage *)dw_ConvertImageWithPixelHandler:(void (^)(UInt8 *, int, int))handler { 183 | if (!handler) { 184 | return self; 185 | } 186 | size_t width = self.size.width; 187 | size_t height = self.size.height; 188 | size_t bitsPerComponent = CGImageGetBitsPerComponent(self.CGImage); 189 | size_t bitsPerPixel = CGImageGetBitsPerPixel(self.CGImage); 190 | size_t bytesPerRow = CGImageGetBytesPerRow(self.CGImage); 191 | CGColorSpaceRef colorSpace = CGImageGetColorSpace(self.CGImage); 192 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(self.CGImage); 193 | bool shouldInterpolate = CGImageGetShouldInterpolate(self.CGImage); 194 | CGColorRenderingIntent intent = CGImageGetRenderingIntent(self.CGImage); 195 | CGDataProviderRef provider = CGImageGetDataProvider(self.CGImage); 196 | CFDataRef data = CGDataProviderCopyData(provider); 197 | UInt8 * buffer = (UInt8 *)CFDataGetBytePtr(data); 198 | for (int y = 0; y < height; y++) { 199 | for (int x = 0; x < width; x++) { 200 | UInt8 * pixel = buffer + y * bytesPerRow + x * 4; 201 | handler(pixel,x,y); 202 | } 203 | } 204 | CFDataRef reverseData = CFDataCreate(NULL, buffer, CFDataGetLength(data)); 205 | CGDataProviderRef reverseProvider = CGDataProviderCreateWithCFData(reverseData); 206 | CGImageRef reverseCGImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, reverseProvider, NULL, shouldInterpolate, intent); 207 | UIImage * reverseImage = [UIImage imageWithCGImage:reverseCGImage]; 208 | CGImageRelease(reverseCGImage); 209 | CGDataProviderRelease(reverseProvider); 210 | CFRelease(reverseData); 211 | CFRelease(data); 212 | return reverseImage; 213 | } 214 | 215 | @end 216 | 217 | @implementation UIImage (DWImageClipUtils) 218 | 219 | -(UIImage *)dw_CornerRadius:(CGFloat)radius withWidth:(CGFloat)width contentMode:(DWContentMode)mode 220 | { 221 | CGFloat originScale = self.size.width / self.size.height; 222 | CGFloat height = width / originScale; 223 | CGFloat scale = [UIScreen mainScreen].scale; 224 | CGFloat maxV = MAX(width, height); 225 | if (radius < 0) { 226 | radius = 0; 227 | } 228 | UIImage * image = nil; 229 | CGRect imageFrame; 230 | if (mode == DWContentModeScaleAspectFit) {//根据图片填充模式制定绘制frame 231 | if (originScale > 1) {//适应模式 232 | imageFrame = CGRectMake(0, (width - height) / 2, width,height); 233 | } 234 | else 235 | { 236 | imageFrame = CGRectMake((height - width) / 2, 0, width, height); 237 | } 238 | } 239 | else if (mode == DWContentModeScaleAspectFill)//填充模式 240 | { 241 | CGFloat newHeight; 242 | CGFloat newWidth; 243 | if (originScale > 1) { 244 | newHeight = width; 245 | newWidth = newHeight * originScale; 246 | imageFrame = CGRectMake( -(newWidth - newHeight) / 2, 0, newWidth, newHeight); 247 | } 248 | else 249 | { 250 | newWidth = height; 251 | newHeight = newWidth / originScale; 252 | imageFrame = CGRectMake(0, - (newHeight - newWidth) / 2, newWidth, newHeight); 253 | } 254 | } 255 | else//拉伸模式 256 | { 257 | imageFrame = CGRectMake(0, 0, maxV, maxV); 258 | } 259 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(maxV, maxV), NO, scale);//以最大长度开启图片上下文 260 | CGContextRef context = UIGraphicsGetCurrentContext(); 261 | [[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, maxV, maxV) cornerRadius:radius] addClip];//绘制一个圆形的贝塞尔曲线,并做遮罩 262 | [self drawInRect:imageFrame];//在指定的frame中绘制图片 263 | CGContextRotateCTM(context, M_PI_2); 264 | image = UIGraphicsGetImageFromCurrentImageContext();//从当前上下文中获取图片 265 | UIGraphicsEndImageContext();//关闭上下文 266 | return image; 267 | } 268 | 269 | -(UIImage *)dw_ClipImageWithPath:(UIBezierPath *)path mode:(DWContentMode)mode 270 | { 271 | CGFloat originScale = self.size.width * 1.0 / self.size.height; 272 | CGRect boxBounds = path.bounds; 273 | CGFloat width = boxBounds.size.width; 274 | CGFloat height = width / originScale; 275 | switch (mode) { 276 | case DWContentModeScaleAspectFit: 277 | { 278 | if (height > boxBounds.size.height) { 279 | height = boxBounds.size.height; 280 | width = height * originScale; 281 | } 282 | } 283 | break; 284 | case DWContentModeScaleAspectFill: 285 | { 286 | if (height < boxBounds.size.height) { 287 | height = boxBounds.size.height; 288 | width = height * originScale; 289 | } 290 | } 291 | break; 292 | default: 293 | if (height != boxBounds.size.height) { 294 | height = boxBounds.size.height; 295 | } 296 | break; 297 | } 298 | 299 | ///开启上下文 300 | UIGraphicsBeginImageContextWithOptions(boxBounds.size, NO, [UIScreen mainScreen].scale); 301 | CGContextRef bitmap = UIGraphicsGetCurrentContext(); 302 | 303 | ///归零path 304 | UIBezierPath * newPath = [path copy]; 305 | [newPath applyTransform:CGAffineTransformMakeTranslation(-path.bounds.origin.x, -path.bounds.origin.y)]; 306 | [newPath addClip]; 307 | 308 | ///移动原点至图片中心 309 | CGContextTranslateCTM(bitmap, boxBounds.size.width / 2.0, boxBounds.size.height / 2.0); 310 | CGContextScaleCTM(bitmap, 1.0, -1.0); 311 | CGContextDrawImage(bitmap, CGRectMake(-width / 2, -height / 2, width, height), self.CGImage); 312 | 313 | ///生成图片 314 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 315 | UIGraphicsEndImageContext(); 316 | 317 | return newImage; 318 | } 319 | 320 | @end 321 | 322 | @implementation UIImage (DWImageTransformUtils) 323 | 324 | -(UIImage *)dw_RotateImageWithAngle:(CGFloat)angle 325 | { 326 | UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)]; 327 | CGAffineTransform t = CGAffineTransformMakeRotation(angle); 328 | rotatedViewBox.transform = t; 329 | CGSize rotatedSize = rotatedViewBox.frame.size; 330 | 331 | ///开启上下文 332 | UIGraphicsBeginImageContextWithOptions(rotatedSize, NO, [UIScreen mainScreen].scale); 333 | CGContextRef bitmap = UIGraphicsGetCurrentContext(); 334 | 335 | ///移动原点至图片中心 336 | CGContextTranslateCTM(bitmap, rotatedSize.width/2.0, rotatedSize.height/2.0); 337 | CGContextScaleCTM(bitmap, 1.0, -1.0); 338 | CGContextRotateCTM(bitmap, -angle); 339 | CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]); 340 | 341 | ///生成图片 342 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 343 | UIGraphicsEndImageContext(); 344 | 345 | return newImage; 346 | } 347 | 348 | ///按给定的方向旋转图片 349 | -(UIImage*)dw_RotateWithOrient:(UIImageOrientation)orient 350 | { 351 | CGRect bnds = CGRectZero; 352 | CGRect rect = CGRectZero; 353 | CGAffineTransform tran = CGAffineTransformIdentity; 354 | 355 | rect.size = self.size; 356 | 357 | bnds = rect; 358 | 359 | switch (orient) 360 | { 361 | case UIImageOrientationUp: 362 | return self; 363 | 364 | case UIImageOrientationUpMirrored: 365 | tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0); 366 | tran = CGAffineTransformScale(tran, -1.0, 1.0); 367 | break; 368 | 369 | case UIImageOrientationDown: 370 | tran = CGAffineTransformMakeTranslation(rect.size.width, 371 | rect.size.height); 372 | tran = CGAffineTransformRotate(tran, M_PI); 373 | break; 374 | 375 | case UIImageOrientationDownMirrored: 376 | tran = CGAffineTransformMakeTranslation(0.0, rect.size.height); 377 | tran = CGAffineTransformScale(tran, 1.0, -1.0); 378 | break; 379 | 380 | case UIImageOrientationLeft: 381 | bnds = swapWidthAndHeight(bnds); 382 | tran = CGAffineTransformMakeTranslation(0.0, rect.size.width); 383 | tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0); 384 | break; 385 | 386 | case UIImageOrientationLeftMirrored: 387 | bnds = swapWidthAndHeight(bnds); 388 | tran = CGAffineTransformMakeTranslation(rect.size.height, 389 | rect.size.width); 390 | tran = CGAffineTransformScale(tran, -1.0, 1.0); 391 | tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0); 392 | break; 393 | 394 | case UIImageOrientationRight: 395 | bnds = swapWidthAndHeight(bnds); 396 | tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0); 397 | tran = CGAffineTransformRotate(tran, M_PI / 2.0); 398 | break; 399 | 400 | case UIImageOrientationRightMirrored: 401 | bnds = swapWidthAndHeight(bnds); 402 | tran = CGAffineTransformMakeScale(-1.0, 1.0); 403 | tran = CGAffineTransformRotate(tran, M_PI / 2.0); 404 | break; 405 | 406 | default: 407 | return self; 408 | } 409 | 410 | UIGraphicsBeginImageContextWithOptions(bnds.size, NO, [UIScreen mainScreen].scale); 411 | CGContextRef ctxt = UIGraphicsGetCurrentContext(); 412 | 413 | switch (orient) 414 | { 415 | case UIImageOrientationLeft: 416 | case UIImageOrientationLeftMirrored: 417 | case UIImageOrientationRight: 418 | case UIImageOrientationRightMirrored: 419 | CGContextScaleCTM(ctxt, -1.0, 1.0); 420 | CGContextTranslateCTM(ctxt, -rect.size.height, 0.0); 421 | break; 422 | 423 | default: 424 | CGContextScaleCTM(ctxt, 1.0, -1.0); 425 | CGContextTranslateCTM(ctxt, 0.0, -rect.size.height); 426 | break; 427 | } 428 | 429 | CGContextConcatCTM(ctxt, tran); 430 | CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, self.CGImage); 431 | 432 | UIImage* copy = UIGraphicsGetImageFromCurrentImageContext(); 433 | UIGraphicsEndImageContext(); 434 | 435 | return copy; 436 | } 437 | 438 | /** 垂直翻转 */ 439 | - (UIImage *)dw_FlipVertical 440 | { 441 | return [self dw_RotateWithOrient:UIImageOrientationDownMirrored]; 442 | } 443 | 444 | /** 水平翻转 */ 445 | - (UIImage *)dw_FlipHorizontal 446 | { 447 | return [self dw_RotateWithOrient:UIImageOrientationUpMirrored]; 448 | } 449 | 450 | #pragma mark - 压缩图片至指定尺寸 451 | - (UIImage *)dw_RescaleImageToSize:(CGSize)size 452 | { 453 | CGRect rect = (CGRect){CGPointZero, size}; 454 | 455 | UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); 456 | 457 | [self drawInRect:rect]; 458 | 459 | UIImage *resImage = UIGraphicsGetImageFromCurrentImageContext(); 460 | 461 | UIGraphicsEndImageContext(); 462 | 463 | return resImage; 464 | } 465 | 466 | #pragma mark - 压缩图片至指定像素 467 | - (UIImage *)dw_RescaleImageToPX:(CGFloat )toPX 468 | { 469 | CGSize size = self.size; 470 | 471 | if(size.width <= toPX && size.height <= toPX) 472 | { 473 | return self; 474 | } 475 | 476 | CGFloat scale = size.width / size.height; 477 | 478 | if(size.width > size.height) 479 | { 480 | size.width = toPX; 481 | size.height = size.width / scale; 482 | } 483 | else 484 | { 485 | size.height = toPX; 486 | size.width = size.height * scale; 487 | } 488 | 489 | return [self dw_RescaleImageToSize:size]; 490 | } 491 | 492 | 493 | 494 | -(UIImage *)dw_FixOrientation 495 | { 496 | if (self.imageOrientation == UIImageOrientationUp) return self; 497 | 498 | // We need to calculate the proper transformation to make the image upright. 499 | // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. 500 | CGAffineTransform transform = CGAffineTransformIdentity; 501 | 502 | switch (self.imageOrientation) 503 | { 504 | case UIImageOrientationDown: 505 | case UIImageOrientationDownMirrored: 506 | transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height); 507 | transform = CGAffineTransformRotate(transform, M_PI); 508 | break; 509 | 510 | case UIImageOrientationLeft: 511 | case UIImageOrientationLeftMirrored: 512 | transform = CGAffineTransformTranslate(transform, self.size.width, 0); 513 | transform = CGAffineTransformRotate(transform, M_PI_2); 514 | break; 515 | 516 | case UIImageOrientationRight: 517 | case UIImageOrientationRightMirrored: 518 | transform = CGAffineTransformTranslate(transform, 0, self.size.height); 519 | transform = CGAffineTransformRotate(transform, -M_PI_2); 520 | break; 521 | case UIImageOrientationUp: 522 | case UIImageOrientationUpMirrored: 523 | break; 524 | } 525 | 526 | switch (self.imageOrientation) 527 | { 528 | case UIImageOrientationUpMirrored: 529 | case UIImageOrientationDownMirrored: 530 | transform = CGAffineTransformTranslate(transform, self.size.width, 0); 531 | transform = CGAffineTransformScale(transform, -1, 1); 532 | break; 533 | 534 | case UIImageOrientationLeftMirrored: 535 | case UIImageOrientationRightMirrored: 536 | transform = CGAffineTransformTranslate(transform, self.size.height, 0); 537 | transform = CGAffineTransformScale(transform, -1, 1); 538 | break; 539 | case UIImageOrientationUp: 540 | case UIImageOrientationDown: 541 | case UIImageOrientationLeft: 542 | case UIImageOrientationRight: 543 | break; 544 | } 545 | 546 | // Now we draw the underlying CGImage into a new context, applying the transform 547 | // calculated above. 548 | CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height, 549 | CGImageGetBitsPerComponent(self.CGImage), 0, 550 | CGImageGetColorSpace(self.CGImage), 551 | CGImageGetBitmapInfo(self.CGImage)); 552 | CGContextConcatCTM(ctx, transform); 553 | 554 | switch (self.imageOrientation) 555 | { 556 | case UIImageOrientationLeft: 557 | case UIImageOrientationLeftMirrored: 558 | case UIImageOrientationRight: 559 | case UIImageOrientationRightMirrored: 560 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage); 561 | break; 562 | 563 | default: 564 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage); 565 | break; 566 | } 567 | 568 | CGImageRef cgimg = CGBitmapContextCreateImage(ctx); 569 | UIImage *img = [UIImage imageWithCGImage:cgimg]; 570 | CGContextRelease(ctx); 571 | CGImageRelease(cgimg); 572 | 573 | return img; 574 | } 575 | 576 | ///交换宽和高 577 | static inline CGRect swapWidthAndHeight(CGRect rect) 578 | { 579 | CGFloat swap = rect.size.width; 580 | rect.size.width = rect.size.height; 581 | rect.size.height = swap; 582 | return rect; 583 | } 584 | @end 585 | 586 | @implementation UIImage (DWImageCanvasUtils) 587 | 588 | #pragma mark - 截取当前image对象rect区域内的图像 589 | - (UIImage *)dw_SubImageWithRect:(CGRect)rect { 590 | ///防止处理过image的scale不为1情况rect错误 591 | CGFloat scale = self.scale; 592 | CGRect scaleRect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale); 593 | CGImageRef newImageRef = CGImageCreateWithImageInRect(self.CGImage, scaleRect); 594 | UIImage *newImage = [[UIImage imageWithCGImage:newImageRef] dw_RescaleImageToSize:rect.size]; 595 | CGImageRelease(newImageRef); 596 | return newImage; 597 | } 598 | 599 | #pragma mark - 指定大小生成一个平铺的图片 600 | - (UIImage *)dw_GetTiledImageWithSize:(CGSize)size 601 | { 602 | UIView *tempView = [[UIView alloc] init]; 603 | tempView.bounds = (CGRect){CGPointZero, size}; 604 | tempView.backgroundColor = [UIColor colorWithPatternImage:self]; 605 | return [UIImage dw_ImageFromView:tempView]; 606 | } 607 | 608 | #pragma mark - UIView转化为UIImage 609 | +(UIImage *)dw_ImageFromView:(UIView *)view 610 | { 611 | UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, [UIScreen mainScreen].scale); 612 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 613 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 614 | UIGraphicsEndImageContext(); 615 | return image; 616 | } 617 | 618 | #pragma mark - 将两个图片生成一张图片 619 | +(UIImage*)dw_MergeImage:(UIImage*)firstImage withImage:(UIImage*)secondImage 620 | { 621 | CGImageRef firstImageRef = firstImage.CGImage; 622 | CGFloat firstWidth = CGImageGetWidth(firstImageRef); 623 | CGFloat firstHeight = CGImageGetHeight(firstImageRef); 624 | CGImageRef secondImageRef = secondImage.CGImage; 625 | CGFloat secondWidth = CGImageGetWidth(secondImageRef); 626 | CGFloat secondHeight = CGImageGetHeight(secondImageRef); 627 | CGSize mergedSize = CGSizeMake(MAX(firstWidth, secondWidth), MAX(firstHeight, secondHeight)); 628 | UIGraphicsBeginImageContextWithOptions(mergedSize, NO, [UIScreen mainScreen].scale); 629 | [firstImage drawInRect:CGRectMake(0, 0, firstWidth, firstHeight)]; 630 | [secondImage drawInRect:CGRectMake(0, 0, secondWidth, secondHeight)]; 631 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 632 | UIGraphicsEndImageContext(); 633 | return image; 634 | } 635 | @end 636 | -------------------------------------------------------------------------------- /DWSlideCaptchaView/UIImage+DWImageUtils.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+DWImageUtils.m 3 | // Image 4 | // 5 | // Created by Wicky on 2016/12/6. 6 | // Copyright © 2016年 Wicky. All rights reserved. 7 | // 8 | 9 | #import "UIImage+DWImageUtils.h" 10 | #import 11 | 12 | @implementation UIImage (DWImageInstanceUtils) 13 | 14 | +(UIImage *)dw_ImageNamed:(NSString *)name 15 | { 16 | NSString * path = [[NSBundle mainBundle] pathForResource:name ofType:nil]; 17 | if (!path) { 18 | path = [[NSBundle mainBundle] pathForResource:name ofType:@"png"]; 19 | } 20 | if (!path) { 21 | return nil; 22 | } 23 | NSURL * url = [NSURL fileURLWithPath:path]; 24 | return [self dw_ImageWithUrl:url]; 25 | } 26 | 27 | +(UIImage *)dw_ImageWithUrl:(NSURL *)url 28 | { 29 | NSDictionary*options = @{(__bridge id)kCGImageSourceShouldCache: @YES}; 30 | CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url,NULL);CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source,0,(__bridge CFDictionaryRef)options); 31 | UIImage *image = [UIImage imageWithCGImage:imageRef]; 32 | CGImageRelease(imageRef); 33 | CFRelease(source); 34 | return image; 35 | } 36 | 37 | @end 38 | 39 | @implementation UIImage (DWImageBase64Utils) 40 | 41 | -(NSString *)dw_ImageToBase64String 42 | { 43 | NSData *imageData = nil; 44 | NSString *mimeType = nil; 45 | 46 | if ([self imageHasAlpha]) { 47 | imageData = UIImagePNGRepresentation(self); 48 | mimeType = @"image/png"; 49 | } else { 50 | imageData = UIImageJPEGRepresentation(self, 1.0f); 51 | mimeType = @"image/jpeg"; 52 | } 53 | return [NSString stringWithFormat:@"data:%@;base64,%@", mimeType, 54 | [imageData base64EncodedStringWithOptions: 0]]; 55 | } 56 | 57 | + (UIImage *)dw_ImageWithBase64String:(NSString *)base64String 58 | { 59 | NSURL *url = [NSURL URLWithString: base64String]; 60 | NSData *data = [NSData dataWithContentsOfURL: url]; 61 | UIImage *image = [UIImage imageWithData: data]; 62 | 63 | return image; 64 | } 65 | 66 | -(BOOL)imageHasAlpha 67 | { 68 | CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage); 69 | return (alpha == kCGImageAlphaFirst || 70 | alpha == kCGImageAlphaLast || 71 | alpha == kCGImageAlphaPremultipliedFirst || 72 | alpha == kCGImageAlphaPremultipliedLast); 73 | } 74 | 75 | @end 76 | 77 | @implementation UIImage (DWImageColorUtils) 78 | 79 | -(UIColor *)dw_ColorAtPoint:(CGPoint)point; 80 | { 81 | if (!CGRectContainsPoint(CGRectMake(0.0f, 0.0f, self.size.width, self.size.height), point)) 82 | { 83 | return nil; 84 | } 85 | 86 | NSInteger pointX = trunc(point.x); 87 | NSInteger pointY = trunc(point.y); 88 | CGImageRef cgImage = self.CGImage; 89 | NSUInteger width = self.size.width; 90 | NSUInteger height = self.size.height; 91 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 92 | int bytesPerPixel = 4; 93 | int bytesPerRow = bytesPerPixel * 1; 94 | NSUInteger bitsPerComponent = 8; 95 | unsigned char pixelData[4] = { 0, 0, 0, 0 }; 96 | 97 | ///创建1*1画布 98 | CGContextRef context = CGBitmapContextCreate(pixelData,1,1,bitsPerComponent,bytesPerRow,colorSpace,kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); 99 | CGColorSpaceRelease(colorSpace); 100 | CGContextSetBlendMode(context, kCGBlendModeCopy); 101 | 102 | CGContextTranslateCTM(context, -pointX, pointY-(CGFloat)height); 103 | ///绘图 104 | CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage); 105 | CGContextRelease(context); 106 | 107 | ///取色 108 | CGFloat red = (CGFloat)pixelData[0] / 255.0f; 109 | CGFloat green = (CGFloat)pixelData[1] / 255.0f; 110 | CGFloat blue = (CGFloat)pixelData[2] / 255.0f; 111 | CGFloat alpha = (CGFloat)pixelData[3] / 255.0f; 112 | return [UIColor colorWithRed:red green:green blue:blue alpha:alpha]; 113 | } 114 | 115 | +(UIImage *)dw_ImageWithColor:(UIColor *)color 116 | { 117 | CGRect rect=CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); 118 | UIGraphicsBeginImageContext(rect.size); 119 | CGContextRef context = UIGraphicsGetCurrentContext(); 120 | CGContextSetFillColorWithColor(context, [color CGColor]); 121 | CGContextFillRect(context, rect); 122 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 123 | UIGraphicsEndImageContext(); 124 | return newImage; 125 | } 126 | 127 | -(UIImage *)dw_ConvertToGrayImage 128 | { 129 | size_t width = self.size.width; 130 | size_t height = self.size.height; 131 | 132 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray(); 133 | CGContextRef context = CGBitmapContextCreate(nil,width,height,8,0,colorSpace,kCGImageAlphaNone); 134 | CGColorSpaceRelease(colorSpace); 135 | 136 | if (context == NULL) 137 | { 138 | return nil; 139 | } 140 | 141 | CGContextDrawImage(context,CGRectMake(0, 0, width, height), self.CGImage); 142 | CGImageRef contextRef = CGBitmapContextCreateImage(context); 143 | UIImage *grayImage = [UIImage imageWithCGImage:contextRef]; 144 | CGContextRelease(context); 145 | CGImageRelease(contextRef); 146 | 147 | return grayImage; 148 | } 149 | 150 | -(UIImage *)dw_ConvertToReversedColor { 151 | return [self dw_ConvertImageWithPixelHandler:^(UInt8 *pixel, int x, int y) { 152 | UInt8 alpha = * (pixel + 3); 153 | if (alpha) { 154 | *pixel = 255 - *pixel; 155 | *(pixel + 1) = 255 - *(pixel + 1); 156 | *(pixel + 2) = 255 - *(pixel + 2); 157 | } 158 | }]; 159 | } 160 | 161 | -(UIImage *)dw_ConvertToSketchWithColor:(UIColor *)color { 162 | NSInteger numComponents = CGColorGetNumberOfComponents(color.CGColor); 163 | NSInteger red , green , blue; 164 | red = green = blue = 0; 165 | if (numComponents == 4) 166 | { 167 | const CGFloat *components = CGColorGetComponents(color.CGColor); 168 | red = components[0] * 255; 169 | green = components[1] * 255; 170 | blue = components[2] * 255; 171 | } 172 | return [self dw_ConvertImageWithPixelHandler:^(UInt8 *pixel, int x, int y) { 173 | UInt8 alpha = * (pixel + 3); 174 | if (alpha) { 175 | *pixel = red; 176 | *(pixel + 1) = green; 177 | *(pixel + 2) = blue; 178 | } 179 | }]; 180 | } 181 | 182 | -(UIImage *)dw_ConvertImageWithPixelHandler:(void (^)(UInt8 *, int, int))handler { 183 | if (!handler) { 184 | return self; 185 | } 186 | size_t width = self.size.width; 187 | size_t height = self.size.height; 188 | size_t bitsPerComponent = CGImageGetBitsPerComponent(self.CGImage); 189 | size_t bitsPerPixel = CGImageGetBitsPerPixel(self.CGImage); 190 | size_t bytesPerRow = CGImageGetBytesPerRow(self.CGImage); 191 | CGColorSpaceRef colorSpace = CGImageGetColorSpace(self.CGImage); 192 | CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(self.CGImage); 193 | bool shouldInterpolate = CGImageGetShouldInterpolate(self.CGImage); 194 | CGColorRenderingIntent intent = CGImageGetRenderingIntent(self.CGImage); 195 | CGDataProviderRef provider = CGImageGetDataProvider(self.CGImage); 196 | CFDataRef data = CGDataProviderCopyData(provider); 197 | UInt8 * buffer = (UInt8 *)CFDataGetBytePtr(data); 198 | for (int y = 0; y < height; y++) { 199 | for (int x = 0; x < width; x++) { 200 | UInt8 * pixel = buffer + y * bytesPerRow + x * 4; 201 | handler(pixel,x,y); 202 | } 203 | } 204 | CFDataRef reverseData = CFDataCreate(NULL, buffer, CFDataGetLength(data)); 205 | CGDataProviderRef reverseProvider = CGDataProviderCreateWithCFData(reverseData); 206 | CGImageRef reverseCGImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapInfo, reverseProvider, NULL, shouldInterpolate, intent); 207 | UIImage * reverseImage = [UIImage imageWithCGImage:reverseCGImage]; 208 | CGImageRelease(reverseCGImage); 209 | CGDataProviderRelease(reverseProvider); 210 | CFRelease(reverseData); 211 | CFRelease(data); 212 | return reverseImage; 213 | } 214 | 215 | @end 216 | 217 | @implementation UIImage (DWImageClipUtils) 218 | 219 | -(UIImage *)dw_CornerRadius:(CGFloat)radius withWidth:(CGFloat)width contentMode:(DWContentMode)mode 220 | { 221 | CGFloat originScale = self.size.width / self.size.height; 222 | CGFloat height = width / originScale; 223 | CGFloat scale = [UIScreen mainScreen].scale; 224 | CGFloat maxV = MAX(width, height); 225 | if (radius < 0) { 226 | radius = 0; 227 | } 228 | UIImage * image = nil; 229 | CGRect imageFrame; 230 | if (mode == DWContentModeScaleAspectFit) {//根据图片填充模式制定绘制frame 231 | if (originScale > 1) {//适应模式 232 | imageFrame = CGRectMake(0, (width - height) / 2, width,height); 233 | } 234 | else 235 | { 236 | imageFrame = CGRectMake((height - width) / 2, 0, width, height); 237 | } 238 | } 239 | else if (mode == DWContentModeScaleAspectFill)//填充模式 240 | { 241 | CGFloat newHeight; 242 | CGFloat newWidth; 243 | if (originScale > 1) { 244 | newHeight = width; 245 | newWidth = newHeight * originScale; 246 | imageFrame = CGRectMake( -(newWidth - newHeight) / 2, 0, newWidth, newHeight); 247 | } 248 | else 249 | { 250 | newWidth = height; 251 | newHeight = newWidth / originScale; 252 | imageFrame = CGRectMake(0, - (newHeight - newWidth) / 2, newWidth, newHeight); 253 | } 254 | } 255 | else//拉伸模式 256 | { 257 | imageFrame = CGRectMake(0, 0, maxV, maxV); 258 | } 259 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(maxV, maxV), NO, scale);//以最大长度开启图片上下文 260 | CGContextRef context = UIGraphicsGetCurrentContext(); 261 | [[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, maxV, maxV) cornerRadius:radius] addClip];//绘制一个圆形的贝塞尔曲线,并做遮罩 262 | [self drawInRect:imageFrame];//在指定的frame中绘制图片 263 | CGContextRotateCTM(context, M_PI_2); 264 | image = UIGraphicsGetImageFromCurrentImageContext();//从当前上下文中获取图片 265 | UIGraphicsEndImageContext();//关闭上下文 266 | return image; 267 | } 268 | 269 | -(UIImage *)dw_ClipImageWithPath:(UIBezierPath *)path mode:(DWContentMode)mode 270 | { 271 | CGFloat originScale = self.size.width * 1.0 / self.size.height; 272 | CGRect boxBounds = path.bounds; 273 | CGFloat width = boxBounds.size.width; 274 | CGFloat height = width / originScale; 275 | switch (mode) { 276 | case DWContentModeScaleAspectFit: 277 | { 278 | if (height > boxBounds.size.height) { 279 | height = boxBounds.size.height; 280 | width = height * originScale; 281 | } 282 | } 283 | break; 284 | case DWContentModeScaleAspectFill: 285 | { 286 | if (height < boxBounds.size.height) { 287 | height = boxBounds.size.height; 288 | width = height * originScale; 289 | } 290 | } 291 | break; 292 | default: 293 | if (height != boxBounds.size.height) { 294 | height = boxBounds.size.height; 295 | } 296 | break; 297 | } 298 | 299 | ///开启上下文 300 | UIGraphicsBeginImageContextWithOptions(boxBounds.size, NO, [UIScreen mainScreen].scale); 301 | CGContextRef bitmap = UIGraphicsGetCurrentContext(); 302 | 303 | ///归零path 304 | UIBezierPath * newPath = [path copy]; 305 | [newPath applyTransform:CGAffineTransformMakeTranslation(-path.bounds.origin.x, -path.bounds.origin.y)]; 306 | [newPath addClip]; 307 | 308 | ///移动原点至图片中心 309 | CGContextTranslateCTM(bitmap, boxBounds.size.width / 2.0, boxBounds.size.height / 2.0); 310 | CGContextScaleCTM(bitmap, 1.0, -1.0); 311 | CGContextDrawImage(bitmap, CGRectMake(-width / 2, -height / 2, width, height), self.CGImage); 312 | 313 | ///生成图片 314 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 315 | UIGraphicsEndImageContext(); 316 | 317 | return newImage; 318 | } 319 | 320 | @end 321 | 322 | @implementation UIImage (DWImageTransformUtils) 323 | 324 | -(UIImage *)dw_RotateImageWithAngle:(CGFloat)angle 325 | { 326 | UIView *rotatedViewBox = [[UIView alloc] initWithFrame:CGRectMake(0,0,self.size.width, self.size.height)]; 327 | CGAffineTransform t = CGAffineTransformMakeRotation(angle); 328 | rotatedViewBox.transform = t; 329 | CGSize rotatedSize = rotatedViewBox.frame.size; 330 | 331 | ///开启上下文 332 | UIGraphicsBeginImageContextWithOptions(rotatedSize, NO, [UIScreen mainScreen].scale); 333 | CGContextRef bitmap = UIGraphicsGetCurrentContext(); 334 | 335 | ///移动原点至图片中心 336 | CGContextTranslateCTM(bitmap, rotatedSize.width/2.0, rotatedSize.height/2.0); 337 | CGContextScaleCTM(bitmap, 1.0, -1.0); 338 | CGContextRotateCTM(bitmap, -angle); 339 | CGContextDrawImage(bitmap, CGRectMake(-self.size.width / 2, -self.size.height / 2, self.size.width, self.size.height), [self CGImage]); 340 | 341 | ///生成图片 342 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 343 | UIGraphicsEndImageContext(); 344 | 345 | return newImage; 346 | } 347 | 348 | ///按给定的方向旋转图片 349 | -(UIImage*)dw_RotateWithOrient:(UIImageOrientation)orient 350 | { 351 | CGRect bnds = CGRectZero; 352 | CGRect rect = CGRectZero; 353 | CGAffineTransform tran = CGAffineTransformIdentity; 354 | 355 | rect.size = self.size; 356 | 357 | bnds = rect; 358 | 359 | switch (orient) 360 | { 361 | case UIImageOrientationUp: 362 | return self; 363 | 364 | case UIImageOrientationUpMirrored: 365 | tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0); 366 | tran = CGAffineTransformScale(tran, -1.0, 1.0); 367 | break; 368 | 369 | case UIImageOrientationDown: 370 | tran = CGAffineTransformMakeTranslation(rect.size.width, 371 | rect.size.height); 372 | tran = CGAffineTransformRotate(tran, M_PI); 373 | break; 374 | 375 | case UIImageOrientationDownMirrored: 376 | tran = CGAffineTransformMakeTranslation(0.0, rect.size.height); 377 | tran = CGAffineTransformScale(tran, 1.0, -1.0); 378 | break; 379 | 380 | case UIImageOrientationLeft: 381 | bnds = swapWidthAndHeight(bnds); 382 | tran = CGAffineTransformMakeTranslation(0.0, rect.size.width); 383 | tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0); 384 | break; 385 | 386 | case UIImageOrientationLeftMirrored: 387 | bnds = swapWidthAndHeight(bnds); 388 | tran = CGAffineTransformMakeTranslation(rect.size.height, 389 | rect.size.width); 390 | tran = CGAffineTransformScale(tran, -1.0, 1.0); 391 | tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0); 392 | break; 393 | 394 | case UIImageOrientationRight: 395 | bnds = swapWidthAndHeight(bnds); 396 | tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0); 397 | tran = CGAffineTransformRotate(tran, M_PI / 2.0); 398 | break; 399 | 400 | case UIImageOrientationRightMirrored: 401 | bnds = swapWidthAndHeight(bnds); 402 | tran = CGAffineTransformMakeScale(-1.0, 1.0); 403 | tran = CGAffineTransformRotate(tran, M_PI / 2.0); 404 | break; 405 | 406 | default: 407 | return self; 408 | } 409 | 410 | UIGraphicsBeginImageContextWithOptions(bnds.size, NO, [UIScreen mainScreen].scale); 411 | CGContextRef ctxt = UIGraphicsGetCurrentContext(); 412 | 413 | switch (orient) 414 | { 415 | case UIImageOrientationLeft: 416 | case UIImageOrientationLeftMirrored: 417 | case UIImageOrientationRight: 418 | case UIImageOrientationRightMirrored: 419 | CGContextScaleCTM(ctxt, -1.0, 1.0); 420 | CGContextTranslateCTM(ctxt, -rect.size.height, 0.0); 421 | break; 422 | 423 | default: 424 | CGContextScaleCTM(ctxt, 1.0, -1.0); 425 | CGContextTranslateCTM(ctxt, 0.0, -rect.size.height); 426 | break; 427 | } 428 | 429 | CGContextConcatCTM(ctxt, tran); 430 | CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, self.CGImage); 431 | 432 | UIImage* copy = UIGraphicsGetImageFromCurrentImageContext(); 433 | UIGraphicsEndImageContext(); 434 | 435 | return copy; 436 | } 437 | 438 | /** 垂直翻转 */ 439 | - (UIImage *)dw_FlipVertical 440 | { 441 | return [self dw_RotateWithOrient:UIImageOrientationDownMirrored]; 442 | } 443 | 444 | /** 水平翻转 */ 445 | - (UIImage *)dw_FlipHorizontal 446 | { 447 | return [self dw_RotateWithOrient:UIImageOrientationUpMirrored]; 448 | } 449 | 450 | #pragma mark - 压缩图片至指定尺寸 451 | - (UIImage *)dw_RescaleImageToSize:(CGSize)size 452 | { 453 | CGRect rect = (CGRect){CGPointZero, size}; 454 | 455 | UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale); 456 | 457 | [self drawInRect:rect]; 458 | 459 | UIImage *resImage = UIGraphicsGetImageFromCurrentImageContext(); 460 | 461 | UIGraphicsEndImageContext(); 462 | 463 | return resImage; 464 | } 465 | 466 | #pragma mark - 压缩图片至指定像素 467 | - (UIImage *)dw_RescaleImageToPX:(CGFloat )toPX 468 | { 469 | CGSize size = self.size; 470 | 471 | if(size.width <= toPX && size.height <= toPX) 472 | { 473 | return self; 474 | } 475 | 476 | CGFloat scale = size.width / size.height; 477 | 478 | if(size.width > size.height) 479 | { 480 | size.width = toPX; 481 | size.height = size.width / scale; 482 | } 483 | else 484 | { 485 | size.height = toPX; 486 | size.width = size.height * scale; 487 | } 488 | 489 | return [self dw_RescaleImageToSize:size]; 490 | } 491 | 492 | 493 | 494 | -(UIImage *)dw_FixOrientation 495 | { 496 | if (self.imageOrientation == UIImageOrientationUp) return self; 497 | 498 | // We need to calculate the proper transformation to make the image upright. 499 | // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored. 500 | CGAffineTransform transform = CGAffineTransformIdentity; 501 | 502 | switch (self.imageOrientation) 503 | { 504 | case UIImageOrientationDown: 505 | case UIImageOrientationDownMirrored: 506 | transform = CGAffineTransformTranslate(transform, self.size.width, self.size.height); 507 | transform = CGAffineTransformRotate(transform, M_PI); 508 | break; 509 | 510 | case UIImageOrientationLeft: 511 | case UIImageOrientationLeftMirrored: 512 | transform = CGAffineTransformTranslate(transform, self.size.width, 0); 513 | transform = CGAffineTransformRotate(transform, M_PI_2); 514 | break; 515 | 516 | case UIImageOrientationRight: 517 | case UIImageOrientationRightMirrored: 518 | transform = CGAffineTransformTranslate(transform, 0, self.size.height); 519 | transform = CGAffineTransformRotate(transform, -M_PI_2); 520 | break; 521 | case UIImageOrientationUp: 522 | case UIImageOrientationUpMirrored: 523 | break; 524 | } 525 | 526 | switch (self.imageOrientation) 527 | { 528 | case UIImageOrientationUpMirrored: 529 | case UIImageOrientationDownMirrored: 530 | transform = CGAffineTransformTranslate(transform, self.size.width, 0); 531 | transform = CGAffineTransformScale(transform, -1, 1); 532 | break; 533 | 534 | case UIImageOrientationLeftMirrored: 535 | case UIImageOrientationRightMirrored: 536 | transform = CGAffineTransformTranslate(transform, self.size.height, 0); 537 | transform = CGAffineTransformScale(transform, -1, 1); 538 | break; 539 | case UIImageOrientationUp: 540 | case UIImageOrientationDown: 541 | case UIImageOrientationLeft: 542 | case UIImageOrientationRight: 543 | break; 544 | } 545 | 546 | // Now we draw the underlying CGImage into a new context, applying the transform 547 | // calculated above. 548 | CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height, 549 | CGImageGetBitsPerComponent(self.CGImage), 0, 550 | CGImageGetColorSpace(self.CGImage), 551 | CGImageGetBitmapInfo(self.CGImage)); 552 | CGContextConcatCTM(ctx, transform); 553 | 554 | switch (self.imageOrientation) 555 | { 556 | case UIImageOrientationLeft: 557 | case UIImageOrientationLeftMirrored: 558 | case UIImageOrientationRight: 559 | case UIImageOrientationRightMirrored: 560 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage); 561 | break; 562 | 563 | default: 564 | CGContextDrawImage(ctx, CGRectMake(0,0,self.size.width,self.size.height), self.CGImage); 565 | break; 566 | } 567 | 568 | CGImageRef cgimg = CGBitmapContextCreateImage(ctx); 569 | UIImage *img = [UIImage imageWithCGImage:cgimg]; 570 | CGContextRelease(ctx); 571 | CGImageRelease(cgimg); 572 | 573 | return img; 574 | } 575 | 576 | ///交换宽和高 577 | static inline CGRect swapWidthAndHeight(CGRect rect) 578 | { 579 | CGFloat swap = rect.size.width; 580 | rect.size.width = rect.size.height; 581 | rect.size.height = swap; 582 | return rect; 583 | } 584 | @end 585 | 586 | @implementation UIImage (DWImageCanvasUtils) 587 | 588 | #pragma mark - 截取当前image对象rect区域内的图像 589 | - (UIImage *)dw_SubImageWithRect:(CGRect)rect { 590 | ///防止处理过image的scale不为1情况rect错误 591 | CGFloat scale = self.scale; 592 | CGRect scaleRect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale); 593 | CGImageRef newImageRef = CGImageCreateWithImageInRect(self.CGImage, scaleRect); 594 | UIImage *newImage = [[UIImage imageWithCGImage:newImageRef] dw_RescaleImageToSize:rect.size]; 595 | CGImageRelease(newImageRef); 596 | return newImage; 597 | } 598 | 599 | #pragma mark - 指定大小生成一个平铺的图片 600 | - (UIImage *)dw_GetTiledImageWithSize:(CGSize)size 601 | { 602 | UIView *tempView = [[UIView alloc] init]; 603 | tempView.bounds = (CGRect){CGPointZero, size}; 604 | tempView.backgroundColor = [UIColor colorWithPatternImage:self]; 605 | return [UIImage dw_ImageFromView:tempView]; 606 | } 607 | 608 | #pragma mark - UIView转化为UIImage 609 | +(UIImage *)dw_ImageFromView:(UIView *)view 610 | { 611 | UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, [UIScreen mainScreen].scale); 612 | [view.layer renderInContext:UIGraphicsGetCurrentContext()]; 613 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 614 | UIGraphicsEndImageContext(); 615 | return image; 616 | } 617 | 618 | #pragma mark - 将两个图片生成一张图片 619 | +(UIImage*)dw_MergeImage:(UIImage*)firstImage withImage:(UIImage*)secondImage 620 | { 621 | CGImageRef firstImageRef = firstImage.CGImage; 622 | CGFloat firstWidth = CGImageGetWidth(firstImageRef); 623 | CGFloat firstHeight = CGImageGetHeight(firstImageRef); 624 | CGImageRef secondImageRef = secondImage.CGImage; 625 | CGFloat secondWidth = CGImageGetWidth(secondImageRef); 626 | CGFloat secondHeight = CGImageGetHeight(secondImageRef); 627 | CGSize mergedSize = CGSizeMake(MAX(firstWidth, secondWidth), MAX(firstHeight, secondHeight)); 628 | UIGraphicsBeginImageContextWithOptions(mergedSize, NO, [UIScreen mainScreen].scale); 629 | [firstImage drawInRect:CGRectMake(0, 0, firstWidth, firstHeight)]; 630 | [secondImage drawInRect:CGRectMake(0, 0, secondWidth, secondHeight)]; 631 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 632 | UIGraphicsEndImageContext(); 633 | return image; 634 | } 635 | @end 636 | --------------------------------------------------------------------------------