├── LICENSE ├── README.md ├── SFImageMaker.podspec ├── SFImageMaker ├── SFImageMaker.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── Memerys.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── SFImageMaker.xcscheme │ └── xcuserdata │ │ ├── Memerys.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ ├── jiang.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── mac.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── SFImageMaker │ ├── Info.plist │ ├── SFBlockImageMaker.h │ ├── SFBlockImageMaker.m │ ├── SFBlurImageMaker.h │ ├── SFBlurImageMaker.m │ ├── SFBorderImageMaker.h │ ├── SFBorderImageMaker.m │ ├── SFCSBView.h │ ├── SFCSBView.m │ ├── SFColorImageMaker.h │ ├── SFColorImageMaker.m │ ├── SFCornerImageMaker.h │ ├── SFCornerImageMaker.m │ ├── SFGradientImageMaker.h │ ├── SFGradientImageMaker.m │ ├── SFImageFlow.h │ ├── SFImageFlow.m │ ├── SFImageMaker-Protocol.h │ ├── SFImageMaker.h │ ├── SFShadowImageMaker.h │ ├── SFShadowImageMaker.m │ ├── UIColor+SFIdentifier.h │ ├── UIColor+SFIdentifier.m │ ├── UIImageEffects │ │ ├── UIImageEffects.h │ │ └── UIImageEffects.m │ ├── UIKit+SFImageFlow.h │ └── UIKit+SFImageFlow.m └── SFImageMakerTests │ ├── Info.plist │ ├── SFBorderImageMakerTests.swift │ ├── SFImageMakerTests-Bridging-Header.h │ ├── SFImageMakerTests.swift │ ├── SFShadowImageMakerTests.swift │ └── SFWebImageMakerHelperTest.swift ├── Space.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ ├── Memerys.xcuserdatad │ └── UserInterfaceState.xcuserstate │ ├── apple.xcuserdatad │ ├── UserInterfaceState.xcuserstate │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ ├── jiang.xcuserdatad │ └── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── mac.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── UICornerShadowView.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ ├── Memerys.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ ├── apple.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── mac.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── UICornerShadowView.xcscheme └── xcuserdata │ ├── Memerys.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── jiang.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── mac.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── UICornerShadowView ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── README.md ├── SFImageFlowController.h ├── SFImageFlowController.m ├── SceneDelegate.swift ├── UICornerShadowView-Bridging-Header.h ├── UICornerShadowView.swift ├── UIImage+Extentions.h ├── UIImage+Extentions.m ├── ViewController.swift ├── ViewController1.swift └── ViewController2.swift ├── UICornerShadowViewTests ├── Info.plist └── UICornerShadowViewTests.swift └── UICornerShadowViewUITests ├── Info.plist └── UICornerShadowViewUITests.swift /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Silver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI Status](https://img.shields.io/travis/389185764@qq.com/SDWebImage-SFImageMaker.svg?style=flat)](https://travis-ci.org/389185764@qq.com/SDWebImage-SFImageMaker) 2 | [![Version](https://img.shields.io/cocoapods/v/SFImageMaker.svg?style=flat)](https://cocoapods.org/pods/SFImageMaker) 3 | [![License](https://img.shields.io/cocoapods/l/SFImageMaker.svg?style=flat)](https://cocoapods.org/pods/SFImageMaker) 4 | [![Platform](https://img.shields.io/cocoapods/p/SFImageMaker.svg?style=flat)](https://cocoapods.org/pods/SFImageMaker) 5 | 6 | [掘金](https://juejin.im/post/5e1e08c36fb9a030080c9427) 7 | 8 | # SFCSBView 9 | iOS UIView Subclass: Use CoreGraphics to control the direction of Rect Corner、Shadow 、Border 10 | ## Example 11 | ```objective-c 12 | // UIAppearance 13 | [SFCSBView appearance].cornerRadius = 20; 14 | [SFCSBView appearance].rectCornner = UIRectCornerAllCorners; 15 | [SFCSBView appearance].shadowPosition = UIShadowPostionAll; 16 | [SFCSBView appearance].shadowRadius = 20; 17 | [SFCSBView appearance].borderColor = [UIColor systemBlueColor]; 18 | [SFCSBView appearance].borderWidth = 5; 19 | [SFCSBView appearance].borderPosition = UIBorderPostionAll; 20 | 21 | 22 | SFCSBView * shadowView = [SFCSBView new]; 23 | shadowView.cornerRadius = 20; 24 | shadowView.rectCornner = UIRectCornerAllCorners; 25 | shadowView.shadowPosition = UIShadowPostionAll; 26 | shadowView.shadowColor = [[UIColor blackColor]colorWithAlphaComponent:0.6]; 27 | shadowView.shadowRadius = 20; 28 | shadowView.borderColor = UIColor.systemBlueColor; 29 | shadowView.borderWidth = 5; 30 | shadowView.borderPosition = UIBorderPostionAll; 31 | ``` 32 | 33 | ## Features 34 | 1. Surpport xib / storyboard 35 | 36 | image 37 | 38 | 39 | 2. Using them freedomly 40 | 41 | image 42 | 43 | 3. Create and process image 44 | 45 | for objc: 46 | ```objc 47 | // Generate UIImage by color or gradient colors, then start UIImage process flow 48 | UIColor.lightGrayColor.sf_flow.corner(10, UIRectCornerAllCorners).border(0.5, UIColor.blackColor).image; 49 | @[UIColor.redColor,UIColor.purpleColor].sf_gradientFlow(YES,self.gradientButton.frame.size).corner(10, UIRectCornerTopLeft|UIRectCornerBottomRight).border(1, UIColor.blackColor).image 50 | 51 | // UIImage Process Flow 52 | [UIImage new].sf_flow.circle.resize(CGSizeMake(40, 40)).blur(SFBlurEffectLight).corner(10, UIRectEdgeAll).border(1, UIColor.redColor) 53 | ``` 54 | 55 | for swift: 56 | ```swift 57 | let _ = UIColor.red 58 | .sf_flow(with: CGSize.init(width: 40, height: 40)) 59 | .blur(with: SFBlurEffectLight) 60 | .border(withWidth: 1, color: .gray) 61 | .shadow(with: UIColor.black.withAlphaComponent(0.5), offset: .init(width: 10, height: 10), blurRadius: 10) 62 | .resize(with: .init(width: 20, height: 20)) 63 | .circle 64 | .image(); 65 | ``` 66 | 67 | 68 | ## CocoaPods 69 | ``` 70 | pod 'SFImageMaker' 71 | ``` 72 | 73 | ## Preview 74 | 75 | **Compare with CALayer** 76 | 77 | ![compare with CALayer](https://silverfruity.github.io/assets/img/UICornerShadowView_1.a69ce9a7.jpeg) 78 | 79 | **Random Cells** 80 | 81 | ![Review](https://silverfruity.github.io/assets/img/UICornerShadowView_2.51bb194d.jpeg) 82 | 83 | 84 | 85 | 86 | 87 | ## Performance 88 | 89 | 1000 shadow images only need 29MB and 1.01s 90 | 91 | In this condition, maximum cpu occupancy rates is 45% and 60FPS when the count of cell is 1000 and quick slide,use iPhone 7, iOS13.3.1. 92 | 93 | ![](https://silverfruity.github.io/assets/img/UICornerShadowView_2.51bb194d.jpeg) 94 | 95 | ![](https://silverfruity.github.io/assets/img/UICornerShadowView_3.a15b93dd.jpeg) 96 | 97 | In extreme cases,no reuse, maximum cpu occupancy rates is 140% and 60FPS when the count of cell is 1000 and quick slide. 98 | 99 | ![Review](https://silverfruity.github.io/assets/img/UICornerShadowView_4.3697f58a.jpeg) 100 | 101 | ![](https://silverfruity.github.io/assets/img/UICornerShadowView_5.c12a9d56.jpeg) 102 | -------------------------------------------------------------------------------- /SFImageMaker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | Pod::Spec.new do |spec| 3 | 4 | spec.name = "SFImageMaker" 5 | spec.version = "1.1.4" 6 | spec.summary = "Use CoreGraphics to control the direction of Rect Corner、Shadow 、Border" 7 | spec.homepage = "https://github.com/SilverFruity/UICornerShadowView" 8 | spec.license = { :type => "MIT", :file => "LICENSE" } 9 | spec.author = { "Jiang" => "15328044115@163.com" } 10 | spec.source = { :git => "https://github.com/SilverFruity/UICornerShadowView.git", :tag => "#{spec.version}" } 11 | spec.source_files = "SFImageMaker/SFImageMaker/*.{h,m}","SFImageMaker/SFImageMaker/**/*.{h,m}" 12 | spec.public_header_files = "SFImageMaker/SFImageMaker/*.h" 13 | spec.ios.deployment_target = '8.0' 14 | # spec.preserve_paths = "FilesToSave", "MoreFilesToSave" 15 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 16 | # 17 | # Link your library with frameworks, or libraries. Libraries do not include 18 | # the lib prefix of their name. 19 | # 20 | 21 | # spec.framework = "SomeFramework" 22 | # spec.frameworks = "SomeFramework", "AnotherFramework" 23 | 24 | # spec.library = "iconv" 25 | # spec.libraries = "iconv", "xml2" 26 | 27 | 28 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 29 | # 30 | # If your library depends on compiler flags you can set them in the xcconfig hash 31 | # where they will only apply to your library. If you depend on other Podspecs 32 | # you can include multiple dependencies to ensure it works. 33 | 34 | # spec.requires_arc = true 35 | 36 | # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 37 | # spec.dependency "JSONKit", "~> 1.4" 38 | 39 | end 40 | 41 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/project.xcworkspace/xcuserdata/Memerys.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/SFImageMaker/SFImageMaker.xcodeproj/project.xcworkspace/xcuserdata/Memerys.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/xcshareddata/xcschemes/SFImageMaker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/xcuserdata/Memerys.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SFImageMaker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/xcuserdata/jiang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SFImageMaker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 1B2CFBDF23FEF486008DBFC1 16 | 17 | primary 18 | 19 | 20 | 1B2CFBE823FEF486008DBFC1 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SFImageMaker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFBlockImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFBlockImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/29. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | @interface SFBlockImageMaker : NSObject 14 | @property(nonatomic, strong)NSMutableArray > *dependencies; 15 | 16 | + (instancetype)imageMakerWithProcessHandler:(UIImage * (^)(UIImage *image))processHandler 17 | isEnableHandler:(BOOL (^)(void))isEnableHandler 18 | identifierHandler:(NSString *(^)(void))identifierHandler; 19 | 20 | + (instancetype)centerRectWithAspectRatio:(CGFloat)aspectRatio; 21 | + (instancetype)centerSquare; 22 | + (instancetype)edgeInsets:(UIEdgeInsets)insets fillColor:(nullable UIColor *)color; 23 | + (instancetype)resizeWithSize:(CGSize)targetSize; 24 | + (instancetype)resizeWithMaxValue:(CGFloat)maxValue; 25 | + (instancetype)resizableCenterMode; 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFBlockImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFBlockImageMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/29. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFBlockImageMaker.h" 10 | #import "SFCornerImageMaker.h" 11 | 12 | @interface SFBlockImageMaker() 13 | @property (nonatomic,copy)UIImage *(^processHandler)(UIImage *image); 14 | @property (nonatomic,copy)BOOL (^isEnableHandler)(void); 15 | @property (nonatomic,copy)NSString *(^identifierHandler)(void); 16 | @end 17 | @implementation SFBlockImageMaker 18 | + (instancetype)imageMakerWithProcessHandler:(UIImage * (^)(UIImage *image))processHandler 19 | isEnableHandler:(BOOL (^)(void))isEnableHandler 20 | identifierHandler:(NSString *(^)(void))identifierHandler{ 21 | SFBlockImageMaker *maker = [SFBlockImageMaker new]; 22 | maker.processHandler = processHandler; 23 | maker.isEnableHandler = isEnableHandler; 24 | maker.identifierHandler = identifierHandler; 25 | return maker; 26 | } 27 | - (instancetype)init 28 | { 29 | self = [super init]; 30 | self.dependencies = [NSMutableArray array]; 31 | return self; 32 | } 33 | - (BOOL)isEnable{ 34 | if (self.isEnableHandler) { 35 | return self.isEnableHandler(); 36 | } 37 | return NO; 38 | } 39 | - (nonnull NSString *)identifier { 40 | if (self.identifierHandler && self.isEnable){ 41 | return self.identifierHandler(); 42 | } 43 | return @""; 44 | } 45 | 46 | - (nonnull UIImage *)process:(nullable UIImage *)target { 47 | if (self.processHandler && self.isEnable) { 48 | return self.processHandler(target); 49 | } 50 | return target; 51 | } 52 | 53 | + (instancetype)centerRectWithAspectRatio:(CGFloat)aspectRatio{ 54 | return [SFBlockImageMaker imageMakerWithProcessHandler:^UIImage * _Nonnull(UIImage * _Nonnull image) { 55 | CGFloat width = floor(image.size.width); 56 | CGFloat height = floor(image.size.height); 57 | CGFloat imageAspectRatio = width / height; 58 | if(imageAspectRatio == aspectRatio) return image; 59 | CGFloat x = 0, y = 0; 60 | if (imageAspectRatio < aspectRatio) { 61 | y = floor((height - width/aspectRatio) * 0.5); 62 | height = width / aspectRatio; 63 | }else{ 64 | x = floor((width - height*aspectRatio) * 0.5); 65 | width = height * aspectRatio; 66 | } 67 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 0); 68 | [image drawAtPoint:CGPointMake(-x, -y)]; 69 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 70 | UIGraphicsEndImageContext(); 71 | return newImage; 72 | } isEnableHandler:^BOOL{ 73 | return aspectRatio != 0; 74 | } identifierHandler:^NSString * _Nonnull{ 75 | return [NSString stringWithFormat:@"centerRect_%f",aspectRatio]; 76 | }]; 77 | } 78 | + (instancetype)centerSquare{ 79 | return [self centerRectWithAspectRatio:1.0]; 80 | } 81 | + (instancetype)edgeInsets:(UIEdgeInsets)insets fillColor:(UIColor *)color{ 82 | color = color?:[UIColor clearColor]; 83 | return [SFBlockImageMaker imageMakerWithProcessHandler:^UIImage * _Nonnull(UIImage * _Nonnull image) { 84 | CGRect imageRect = CGRectMake(insets.left, insets.top, image.size.width, image.size.height); 85 | CGSize newSize = CGSizeZero; 86 | newSize.height = imageRect.size.height + (insets.top + insets.bottom); 87 | newSize.width = imageRect.size.width + (insets.left + insets.right); 88 | UIGraphicsBeginImageContextWithOptions(newSize, NO, 0); 89 | CGContextRef context = UIGraphicsGetCurrentContext(); 90 | [color setFill]; 91 | CGContextFillRect(context, CGRectMake(0, 0, newSize.width, newSize.height)); 92 | [image drawInRect:imageRect]; 93 | UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); 94 | UIGraphicsEndImageContext(); 95 | return result; 96 | } isEnableHandler:^BOOL{ 97 | return YES; 98 | } identifierHandler:^NSString * _Nonnull{ 99 | return [NSString stringWithFormat:@"edgeInsets_%@_%@",[NSValue valueWithUIEdgeInsets:insets],color.sf_identifier]; 100 | }]; 101 | } 102 | + (instancetype)resizeWithSize:(CGSize)targetSize{ 103 | return [SFBlockImageMaker imageMakerWithProcessHandler:^UIImage * _Nonnull(UIImage * _Nonnull image) { 104 | UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0); 105 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 106 | CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor); 107 | CGRect rect = CGRectMake(0, 0, targetSize.width, targetSize.height); 108 | CGContextFillRect(ctx, rect); 109 | [image drawInRect:rect]; 110 | UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); 111 | UIGraphicsEndImageContext(); 112 | return result; 113 | } isEnableHandler:^BOOL{ 114 | return !CGSizeEqualToSize(targetSize, CGSizeZero); 115 | } identifierHandler:^NSString * _Nonnull{ 116 | return [NSString stringWithFormat:@"resize_%@",[NSValue valueWithCGSize:targetSize]]; 117 | }]; 118 | } 119 | + (instancetype)resizeWithMaxValue:(CGFloat)maxValue{ 120 | return [SFBlockImageMaker imageMakerWithProcessHandler:^UIImage * _Nonnull(UIImage * _Nonnull image) { 121 | CGFloat currentMax = MAX(image.size.width, image.size.height); 122 | if (maxValue > currentMax) return image; 123 | CGFloat ratio = image.size.width / image.size.height; 124 | CGSize size = ratio > 1 ? CGSizeMake(maxValue, maxValue/ratio) : CGSizeMake(maxValue*ratio, maxValue); 125 | return [[SFBlockImageMaker resizeWithSize:size] process:image]; 126 | } isEnableHandler:^BOOL{ 127 | return maxValue != 0; 128 | } identifierHandler:^NSString * _Nonnull{ 129 | return [NSString stringWithFormat:@"resizeMax_%@",@(maxValue)]; 130 | }]; 131 | } 132 | + (instancetype)resizableCenterMode{ 133 | return [SFBlockImageMaker imageMakerWithProcessHandler:^UIImage * _Nonnull(UIImage * _Nonnull image) { 134 | return [image resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height / 2, image.size.width / 2, image.size.height / 2, image.size.width / 2)]; 135 | } isEnableHandler:^BOOL{ 136 | return YES; 137 | } identifierHandler:^NSString * _Nonnull{ 138 | return @"resizableCenterMode"; 139 | }]; 140 | } 141 | @end 142 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFBlurImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFBlurImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/29. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | typedef enum : NSUInteger { 13 | SFBlurEffectLight, 14 | SFBlurEffectExtraLight, 15 | SFBlurEffectDark, 16 | } SFBlurEffect; 17 | @interface SFBlurImageMaker : NSObject 18 | @property (nonatomic, assign)CGFloat blurRadius; 19 | @property (nonatomic, strong)UIColor * tintColor; 20 | @property (nonatomic, assign)CGFloat saturationDeltaFactor; 21 | @property (nonatomic, strong, nullable)UIImage *maskImage; 22 | @property(nonatomic, strong)NSMutableArray > *dependencies; 23 | 24 | + (instancetype)lightEffect; 25 | + (instancetype)extraLightEffect; 26 | + (instancetype)darkEffect; 27 | + (instancetype)tintEffectWithColor:(UIColor *)tintColor; 28 | 29 | @end 30 | 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFBlurImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFBlurImageMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/29. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFBlurImageMaker.h" 10 | #import "UIImageEffects.h" 11 | @implementation SFBlurImageMaker 12 | - (instancetype)init 13 | { 14 | self = [super init]; 15 | self.dependencies = [NSMutableArray array]; 16 | return self; 17 | } 18 | - (BOOL)isEnable{ 19 | return self.blurRadius != 0 && self.tintColor != nil; 20 | } 21 | - (nonnull NSString *)identifier { 22 | return self.isEnable ? [NSString stringWithFormat:@"blureImage%.1f%@%.1f",self.blurRadius,self.tintColor.sf_identifier,self.saturationDeltaFactor] : @""; 23 | } 24 | + (instancetype)lightEffect{ 25 | SFBlurImageMaker *maker = [SFBlurImageMaker new]; 26 | maker.blurRadius = 60; 27 | maker.tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; 28 | maker.saturationDeltaFactor = 1.8; 29 | return maker; 30 | } 31 | + (instancetype)extraLightEffect{ 32 | SFBlurImageMaker *maker = [SFBlurImageMaker new]; 33 | maker.blurRadius = 40; 34 | maker.tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; 35 | maker.saturationDeltaFactor = 1.8; 36 | return maker; 37 | } 38 | + (instancetype)darkEffect{ 39 | SFBlurImageMaker *maker = [SFBlurImageMaker new]; 40 | maker.blurRadius = 40; 41 | maker.tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; 42 | maker.saturationDeltaFactor = 1.8; 43 | return maker; 44 | } 45 | + (instancetype)tintEffectWithColor:(UIColor *)tintColor{ 46 | SFBlurImageMaker *maker = [SFBlurImageMaker new]; 47 | const CGFloat EffectColorAlpha = 0.6; 48 | UIColor *effectColor = tintColor; 49 | size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor); 50 | if (componentCount == 2) { 51 | CGFloat b; 52 | if ([tintColor getWhite:&b alpha:NULL]) { 53 | effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha]; 54 | } 55 | } 56 | else { 57 | CGFloat r, g, b; 58 | if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) { 59 | effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha]; 60 | } 61 | } 62 | maker.blurRadius = 20; 63 | maker.saturationDeltaFactor = -1.0; 64 | maker.tintColor = effectColor; 65 | return maker; 66 | } 67 | 68 | - (nonnull UIImage *)process:(nullable UIImage *)target { 69 | if (self.isEnable) { 70 | return [UIImageEffects imageByApplyingBlurToImage:target withRadius:self.blurRadius tintColor:self.tintColor saturationDeltaFactor:self.saturationDeltaFactor maskImage:self.maskImage]; 71 | } 72 | return target; 73 | } 74 | - (void)saveContext { 75 | _tintColor = [UIColor colorWithCGColor:_tintColor.CGColor]; 76 | } 77 | @end 78 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFBorderImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFBorderImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @class SFCornerImageMaker; 15 | typedef NS_OPTIONS(NSUInteger, UIBorderPostion) { 16 | UIBorderPostionTop = 1 << 0, 17 | UIBorderPostionRight = 1 << 1, 18 | UIBorderPostionBottom = 1 << 2, 19 | UIBorderPostionLeft = 1 << 3, 20 | UIBorderPostionAll = ~0UL 21 | }; 22 | 23 | @interface SFBorderImageMaker: NSObject 24 | @property (nonatomic, assign)CGFloat width; 25 | @property (nonatomic, strong)UIColor *color; 26 | @property (nonatomic, strong)UIColor *fillColor; 27 | @property (nonatomic, assign)UIBorderPostion position; 28 | @property (nonatomic, getter=isEnable)BOOL enable; 29 | @property (nonatomic, strong, nullable)SFCornerImageMaker *cornerMaker; 30 | @property (nonatomic, strong)NSMutableArray > *dependencies; 31 | - (CGRect)strokeRectWithSize:(CGSize)size; 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFBorderImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFBorderImageMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFBorderImageMaker.h" 10 | #import "SFCornerImageMaker.h" 11 | 12 | 13 | @implementation SFBorderImageMaker 14 | - (instancetype)init 15 | { 16 | self = [super init]; 17 | self.fillColor = [UIColor clearColor]; 18 | self.dependencies = [NSMutableArray array]; 19 | return self; 20 | } 21 | - (BOOL)isEnable{ 22 | return self.color != nil && self.color != [UIColor clearColor] && self.width != 0; 23 | } 24 | - (UIEdgeInsets)strokeEdgeInsets{ 25 | CGFloat left=0, right = 0, top = 0, bottom = 0; 26 | left = self.position&UIBorderPostionLeft? self.width/2 : -self.width/2; 27 | right = self.position&UIBorderPostionRight? self.width/2 : -self.width/2; 28 | top = self.position&UIBorderPostionTop? self.width/2 : -self.width/2; 29 | bottom = self.position&UIBorderPostionBottom? self.width/2 : -self.width/2; 30 | return UIEdgeInsetsMake(top, left, bottom, right); 31 | } 32 | - (CGPoint)strokeOrigin{ 33 | UIEdgeInsets insets = [self strokeEdgeInsets]; 34 | return CGPointMake(insets.left, insets.top); 35 | } 36 | - (CGSize)strokeSizeWithSize:(CGSize)size{ 37 | UIEdgeInsets insets = [self strokeEdgeInsets]; 38 | return CGSizeMake(size.width - insets.left - insets.right,size.height - insets.top - insets.bottom); 39 | } 40 | - (CGRect)strokeRectWithSize:(CGSize)size{ 41 | CGPoint origin = [self strokeOrigin]; 42 | CGSize rectSize = [self strokeSizeWithSize:size]; 43 | return CGRectMake(origin.x, origin.y, rectSize.width, rectSize.height); 44 | } 45 | - (nonnull UIImage *)process:(nullable UIImage *)target { 46 | if (!self.isEnable) return target; 47 | if (!target) return [UIImage new]; 48 | CGRect rect = CGRectMake(0, 0, target.size.width, target.size.height); 49 | UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0); 50 | CGContextRef context = UIGraphicsGetCurrentContext(); 51 | [self.color setStroke]; 52 | [self.fillColor setFill]; 53 | CGContextFillRect(context, rect); 54 | [target drawInRect:rect]; 55 | SFCornerImageMaker *cornerMaker = self.cornerMaker; 56 | if (cornerMaker){ 57 | UIBezierPath *clipPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:cornerMaker.position cornerRadii:CGSizeMake(cornerMaker.radius, cornerMaker.radius)]; 58 | [clipPath addClip]; 59 | } 60 | 61 | CGRect strokeRect = CGContextGetClipBoundingBox(context); 62 | strokeRect = [self strokeRectWithSize:target.size]; 63 | UIBezierPath *linePath; 64 | if (!cornerMaker || self.width > cornerMaker.radius || cornerMaker.radius == 0){ // 当 width 40 radius 20的情况下 65 | linePath = [UIBezierPath bezierPathWithRect:strokeRect]; 66 | }else{ 67 | linePath = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:cornerMaker.position cornerRadii:CGSizeMake(cornerMaker.radius, cornerMaker.radius)]; 68 | } 69 | linePath.lineWidth = self.width; 70 | if (cornerMaker.radius > 0){ 71 | linePath.lineCapStyle = kCGLineCapRound; 72 | linePath.lineJoinStyle = kCGLineJoinRound; 73 | } 74 | [linePath stroke]; 75 | 76 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 77 | UIGraphicsEndImageContext(); 78 | return image; 79 | } 80 | 81 | - (nonnull NSString *)identifier { 82 | return self.isEnable ? [NSString stringWithFormat:@"border_%@_%@_%@",self.color.sf_identifier,@(self.width),@(self.position)] : @""; 83 | } 84 | - (void)saveContext { 85 | _fillColor = [UIColor colorWithCGColor:_fillColor.CGColor]; 86 | _color = [UIColor colorWithCGColor:_color.CGColor]; 87 | } 88 | @end 89 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFCSBView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFCSBView.h 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/2/28. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SFCSBView : UIView 14 | @property (nonatomic,assign)IBInspectable CGFloat cornerRadius UI_APPEARANCE_SELECTOR; 15 | @property (nonatomic,strong)IBInspectable UIColor *shadowColor UI_APPEARANCE_SELECTOR; 16 | @property (nonatomic,assign)IBInspectable CGSize shadowOffset UI_APPEARANCE_SELECTOR; 17 | @property (nonatomic,assign)IBInspectable CGFloat shadowRadius UI_APPEARANCE_SELECTOR; 18 | @property (nonatomic,assign)IBInspectable CGFloat borderWidth UI_APPEARANCE_SELECTOR; 19 | @property (nonatomic,strong)IBInspectable UIColor *borderColor UI_APPEARANCE_SELECTOR; 20 | @property (nonatomic,assign)IBInspectable UIRectCorner rectCornner UI_APPEARANCE_SELECTOR; 21 | @property (nonatomic,assign)IBInspectable UIShadowPostion shadowPosition UI_APPEARANCE_SELECTOR; 22 | @property (nonatomic,assign)IBInspectable UIBorderPostion borderPosition UI_APPEARANCE_SELECTOR; 23 | @property (nonatomic,nullable,copy)void(^handleMakers)(NSArray > * makers); 24 | @property (nonatomic,strong)Class defautlGeneratorClass; 25 | @property (nonatomic,readonly)id imageGenerator; 26 | @property (nonatomic,readonly)SFShadowImageMaker *shadowProcessor; 27 | @property (nonatomic,readonly)SFCornerImageMaker *cornerProcessor; 28 | @property (nonatomic,readonly)SFBorderImageMaker *borderProcessor; 29 | // 用于解决阴影衔接问题,通常不会用到,默认值为0 30 | // 需要解决时,可以将其设置为 -0.5 或者 -0.25 31 | @property (nonatomic,assign)CGFloat shadowFixValue; 32 | - (void)reloadBackGourndImage; 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFCSBView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFCSBView.m 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/2/28. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFCSBView.h" 10 | 11 | @interface SFCSBViewImageCache: NSObject 12 | @property (nonatomic, strong)NSCache *cache; 13 | @end 14 | 15 | @implementation SFCSBViewImageCache 16 | + (instancetype)shared{ 17 | static dispatch_once_t onceToken; 18 | static SFCSBViewImageCache *_instance = nil; 19 | dispatch_once(&onceToken, ^{ 20 | _instance = [SFCSBViewImageCache new]; 21 | }); 22 | return _instance; 23 | } 24 | - (instancetype)init 25 | { 26 | self = [super init]; 27 | self.cache = [[NSCache alloc] init]; 28 | self.cache.totalCostLimit = 10 * 1024 * 1024; 29 | return self; 30 | } 31 | - (nullable UIImage *)objectForKey:(NSString *)key{ 32 | return [self.cache objectForKey:key]; 33 | } 34 | - (void)setObject:(nullable UIImage *)obj forKey:(NSString *)key{ 35 | if (!obj) { 36 | return; 37 | } 38 | [self.cache setObject:obj forKey:key]; 39 | } 40 | @end 41 | 42 | @interface SFCSBView() 43 | @property (nonatomic, strong)UIColor * initailBackGroundColor; 44 | @property (nonatomic, copy)NSString * lastBackGroundImageIdentifer; 45 | @property (nonatomic, strong)UIImageView * backGroundImageView; 46 | @property (nonatomic, strong)SFImageFlow * flow; 47 | @end 48 | @implementation SFCSBView 49 | - (SFShadowImageMaker *)shadowProcessor{ 50 | SFShadowImageMaker *maker = [[SFShadowImageMaker alloc] init]; 51 | maker.shadowColor = self.shadowColor; 52 | maker.shadowOffset = self.shadowOffset; 53 | maker.shadowBlurRadius = self.shadowRadius; 54 | maker.position = self.shadowPosition; 55 | return maker; 56 | } 57 | - (SFCornerImageMaker *)cornerProcessor{ 58 | SFCornerImageMaker *maker = [[SFCornerImageMaker alloc] init]; 59 | maker.radius = self.cornerRadius; 60 | maker.position = self.rectCornner; 61 | return maker; 62 | } 63 | - (SFBorderImageMaker *)borderProcessor{ 64 | SFBorderImageMaker *maker = [[SFBorderImageMaker alloc] init]; 65 | maker.width = self.borderWidth; 66 | maker.color = self.borderColor; 67 | maker.position = self.borderPosition; 68 | maker.cornerMaker = self.cornerProcessor; 69 | return maker; 70 | } 71 | 72 | - (Class)defautlGeneratorClass{ 73 | if (!_defautlGeneratorClass) { 74 | _defautlGeneratorClass = [SFColorImageMaker class]; 75 | } 76 | return _defautlGeneratorClass; 77 | } 78 | - (id )imageGenerator{ 79 | id generator = [self.defautlGeneratorClass alloc]; 80 | // 只有纯色背景色才能以中点为拉伸点 81 | if ([generator isKindOfClass:[SFColorImageMaker class]]){ 82 | // Radius ShadowRadius BorderWidth 取最大值 83 | CGFloat maxValue = self.cornerProcessor.radius > (self.borderProcessor.width + 1) && self.cornerProcessor.isEnable ? self.cornerProcessor.radius : self.borderProcessor.width + 1; 84 | maxValue = self.shadowProcessor.shadowBlurRadius > maxValue ? self.shadowProcessor.shadowBlurRadius : maxValue; 85 | CGSize size = CGSizeMake(maxValue * 2, maxValue * 2); 86 | return [generator initWithSize:size]; 87 | }else{ 88 | return [generator initWithSize:self.bounds.size]; 89 | } 90 | 91 | } 92 | - (UIImageView *)backGroundImageView{ 93 | if (!_backGroundImageView){ 94 | _backGroundImageView = [UIImageView new]; 95 | _backGroundImageView.clipsToBounds = NO; 96 | [self addSubview:_backGroundImageView]; 97 | } 98 | return _backGroundImageView; 99 | } 100 | - (instancetype)initWithCoder:(NSCoder *)coder 101 | { 102 | self = [super initWithCoder:coder]; 103 | if (self) { 104 | [self setDefaultValues]; 105 | } 106 | return self; 107 | } 108 | - (instancetype)initWithFrame:(CGRect)frame 109 | { 110 | self = [super initWithFrame:frame]; 111 | if (self) { 112 | [self setDefaultValues]; 113 | } 114 | return self; 115 | } 116 | - (void)setDefaultValues{ 117 | self.shadowFixValue = 0; 118 | self.rectCornner = UIRectCornerAllCorners; 119 | self.cornerRadius = 8; 120 | self.shadowColor = [UIColor.blueColor colorWithAlphaComponent:0.08]; 121 | self.shadowOffset = CGSizeZero; 122 | self.shadowRadius = 4; 123 | self.borderWidth = 0; 124 | self.borderColor = UIColor.clearColor; 125 | self.shadowPosition = UIShadowPostionAll; 126 | self.borderPosition = UIBorderPostionAll; 127 | self.clipsToBounds = NO; 128 | } 129 | - (void)layoutSubviews{ 130 | [super layoutSubviews]; 131 | [self reloadBackGourndImage]; 132 | } 133 | - (void)reloadBackGourndImage{ 134 | [self sendSubviewToBack:self.backGroundImageView]; 135 | if (self.backgroundColor != nil) { 136 | self.initailBackGroundColor = self.backgroundColor; 137 | } 138 | //保证不会在子线程中调用self获取值 139 | //TODO: 增加网络图片的处理 140 | SFShadowImageMaker *shadowMaker = self.shadowProcessor; 141 | SFCornerImageMaker *cornerMaker = self.cornerProcessor; 142 | SFBorderImageMaker *borderMaker = self.borderProcessor; 143 | id imageGenerator = self.imageGenerator; 144 | if ([imageGenerator isKindOfClass:[SFColorImageMaker class]]) { 145 | SFColorImageMaker *colorMaker = imageGenerator; 146 | colorMaker.color = self.initailBackGroundColor; 147 | } 148 | if (self.handleMakers) 149 | self.handleMakers(@[imageGenerator,cornerMaker,borderMaker,shadowMaker]); 150 | 151 | self.flow = [SFImageFlow flowWithGenerator:imageGenerator]; 152 | self.flow.processors = [@[cornerMaker,borderMaker,shadowMaker] mutableCopy]; 153 | 154 | NSString *identifier = self.flow.identifier; 155 | 156 | CGRect backImageViewFrame = self.bounds; 157 | if (shadowMaker.isEnable){ 158 | //FIXME: 外边框 border和shadow同时存在时,宽高的计算,一大一小。 159 | //FIXME: 外边框 border和shadow只有一者存在时,宽高的计算。 160 | backImageViewFrame = [shadowMaker viewRectForSize:self.bounds.size]; 161 | // 解决tableView显示时,cell上下阴影衔接时会有一空缺的问题。 162 | // 2021.12.17 默认为0,解决子视图对齐时,白边的问题 163 | CGFloat insertValue = _shadowFixValue; 164 | // CGRect(2,2,2,2) -> CGRect(1,1,4,4) 165 | backImageViewFrame = CGRectInset(backImageViewFrame, insertValue, insertValue); 166 | } 167 | // 每修改一次subview的frame,view会调用layoutSubviews方法。 168 | // 目的:在高度重用UICornerShadowView的情况,并且每次都更新的情况下,减少frame更新。 169 | // 如果上一次的identifer相同说明是重用图片 170 | // 如果当前frame和需要的frame相同,也不用更新frame 171 | if (![identifier isEqualToString:self.lastBackGroundImageIdentifer] || !CGRectEqualToRect(self.backGroundImageView.frame, backImageViewFrame)) { 172 | self.backGroundImageView.frame = backImageViewFrame; 173 | } 174 | UIImage *cacheImage = [[SFCSBViewImageCache shared] objectForKey:identifier]; 175 | if (cacheImage) { 176 | self.backGroundImageView.image = cacheImage; 177 | self.backgroundColor = nil; 178 | self.lastBackGroundImageIdentifer = identifier; 179 | return; 180 | } 181 | /* 182 | 新增 saveContext 183 | 解决当 App 直接退到后台时,会直接调用 layoutSubViews 两次,并且两次 backgroudColor 的 RGB 的值都不相同, 184 | 而我们此处是异步调用的执行图片的绘制,会出现 identifier 对应的图片颜色被改变的问题。 185 | 比如:此处我们生成 identfier 时是使用的白色RGB值,但当我们在其他线程绘制的时候,color 值却在其他线程被改变为黑色,导致我们绘制颜色的 RGB 值却是黑色, 186 | 从而导致使用白色图片的 identifier 获取到的图片却是黑色的问题。 187 | 为避免这种情况发生,我们需要在异步执行前,先将相关颜色RGB值进行一次复制,避免出现以上问题。 188 | */ 189 | [self.flow saveContext]; 190 | __weak typeof(self) weakSelf = self; 191 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 192 | UIImage *image = [self.flow image]; 193 | if (shadowMaker.isEnable) { 194 | UIEdgeInsets inset = shadowMaker.convasEdgeInsets; 195 | CGFloat x = (image.size.width - inset.left - inset.right) / 2; 196 | CGFloat y = (image.size.height - inset.top - inset.bottom) / 2; 197 | image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(y + inset.top, x + inset.left, y + inset.bottom, x + inset.right)]; 198 | }else{ 199 | image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(image.size.height / 2, image.size.width / 2, image.size.height / 2, image.size.width / 2)]; 200 | } 201 | [SFCSBViewImageCache.shared setObject:image forKey:identifier]; 202 | dispatch_async(dispatch_get_main_queue(), ^{ 203 | if (!weakSelf) { 204 | return; 205 | } 206 | weakSelf.lastBackGroundImageIdentifer = identifier; 207 | weakSelf.backGroundImageView.image = image; 208 | }); 209 | }); 210 | self.backgroundColor = nil; 211 | } 212 | @end 213 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFColorImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFColorImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SFColorImageMaker : NSObject 14 | @property (nonatomic, assign)CGSize size; 15 | @property (nonatomic, strong, nullable)UIColor *color; 16 | @property (nonatomic, getter=isEnable)BOOL enable; 17 | @property (nonatomic, strong)NSMutableArray > *dependencies; 18 | + (CGSize)defaultSize; 19 | + (instancetype)imageMakerWithColor:(UIColor *)color; 20 | + (instancetype)imageMakerWithColor:(UIColor *)color size:(CGSize)size; 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFColorImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFColorImageMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFColorImageMaker.h" 10 | 11 | @implementation SFColorImageMaker 12 | + (CGSize)defaultSize{ 13 | return CGSizeMake(1, 1); 14 | } 15 | - (instancetype)init{ 16 | self = [[SFColorImageMaker alloc] initWithSize:[[self class] defaultSize]]; 17 | return self; 18 | } 19 | - (nonnull instancetype)initWithSize:(CGSize)size{ 20 | self = [super init]; 21 | self.size = size; 22 | self.dependencies = [NSMutableArray array]; 23 | return self; 24 | } 25 | + (instancetype)imageMakerWithColor:(UIColor *)color{ 26 | SFColorImageMaker *colorImage = [self imageMakerWithColor:color size:CGSizeMake(1, 1)]; 27 | return colorImage; 28 | } 29 | + (instancetype)imageMakerWithColor:(UIColor *)color size:(CGSize)size{ 30 | SFColorImageMaker *colorImage = [[SFColorImageMaker alloc] initWithSize:size]; 31 | colorImage.color = color; 32 | return colorImage; 33 | } 34 | - (BOOL)isEnable{ 35 | return self.color != [UIColor clearColor] && !CGSizeEqualToSize(self.size, CGSizeZero); 36 | } 37 | - (nonnull UIImage *)generate { 38 | if (!self.isEnable) return [UIImage new]; 39 | CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); 40 | UIGraphicsBeginImageContextWithOptions(self.size, NO, 0); 41 | CGContextRef context = UIGraphicsGetCurrentContext(); 42 | CGContextSetFillColorWithColor(context, self.color.CGColor); 43 | CGContextSetAlpha(context, CGColorGetAlpha(self.color.CGColor)); 44 | CGContextFillRect(context, rect); 45 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 46 | UIGraphicsEndImageContext(); 47 | return image; 48 | } 49 | - (nonnull NSString *)identifier { 50 | return self.isEnable ? [NSString stringWithFormat:@"%@%@",self.color.sf_identifier,[NSValue valueWithCGSize:self.size]] : @""; 51 | } 52 | - (void)saveContext { 53 | _color = [UIColor colorWithCGColor:_color.CGColor]; 54 | } 55 | @end 56 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFCornerImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFRectCornerMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SFCornerImageMaker : NSObject 14 | @property (nonatomic, assign)CGFloat radius; 15 | @property (nonatomic, strong)UIColor *fillColor; 16 | @property (nonatomic, assign)UIRectCorner position; 17 | @property (nonatomic, getter=isEnable)BOOL enable; 18 | @property (nonatomic, strong)NSMutableArray > *dependencies; 19 | @end 20 | 21 | @interface SFCircleImageMaker: SFCornerImageMaker 22 | @end 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFCornerImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFRectCornerMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFCornerImageMaker.h" 10 | #import "SFBlockImageMaker.h" 11 | @implementation SFCornerImageMaker 12 | - (instancetype)init 13 | { 14 | self = [super init]; 15 | self.fillColor = [UIColor clearColor]; 16 | self.dependencies = [NSMutableArray array]; 17 | return self; 18 | } 19 | - (BOOL)isEnable{ 20 | return self.position != 0 && self.radius != 0; 21 | } 22 | - (UIImage *)process:(UIImage *)target{ 23 | if (!self.isEnable) return target; 24 | UIGraphicsBeginImageContextWithOptions(target.size, NO, 0); 25 | CGContextRef context = UIGraphicsGetCurrentContext(); 26 | CGRect rect = CGRectMake(0, 0, target.size.width, target.size.height); 27 | [self.fillColor setFill]; 28 | CGContextFillRect(context, rect); 29 | //利用贝塞尔曲线裁剪矩形 30 | if (self.radius != 0) { 31 | UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:self.position cornerRadii:CGSizeMake(self.radius, self.radius)]; 32 | [path addClip]; 33 | } 34 | //绘制图像 35 | [target drawInRect:rect]; 36 | //获取图像 37 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 38 | UIGraphicsEndImageContext(); 39 | 40 | return image; 41 | } 42 | - (nonnull NSString *)identifier { 43 | return self.isEnable ? [NSString stringWithFormat:@"rectCorner_%@_%@",@(self.radius),@(self.position)]:@""; 44 | } 45 | - (void)saveContext { 46 | _fillColor = [UIColor colorWithCGColor:_fillColor.CGColor]; 47 | } 48 | @end 49 | 50 | @implementation SFCircleImageMaker 51 | - (BOOL)isEnable{ 52 | return YES; 53 | } 54 | - (UIImage *)process:(UIImage *)target{ 55 | if (ceil(target.size.width) != ceil(target.size.height)) { 56 | target = [[SFBlockImageMaker centerSquare] process:target]; 57 | } 58 | self.position = UIRectCornerAllCorners; 59 | self.radius = target.size.width * 0.5; 60 | return [super process:target]; 61 | } 62 | - (NSString *)identifier{ 63 | return @"circle"; 64 | } 65 | @end 66 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFGradientImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFGradientImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SFGradientImageMaker : NSObject 14 | @property (nonatomic, assign)BOOL isHorizontal; 15 | @property (nonatomic, assign)CGSize size; 16 | @property (nonatomic, copy)NSArray *locations; 17 | @property (nonatomic, copy)NSArray *colors; 18 | @property (nonatomic, strong)NSMutableArray > *dependencies; 19 | 20 | + (instancetype)isHorizontal:(BOOL)isHorizontal startColor:(UIColor *)startColor endColor:(UIColor *)endColor; 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFGradientImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFGradientImageMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFGradientImageMaker.h" 10 | #import 11 | 12 | @implementation SFGradientImageMaker 13 | - (instancetype)init{ 14 | self = [[SFGradientImageMaker alloc] initWithSize:CGSizeMake(1, 1)]; 15 | return self; 16 | } 17 | - (nonnull instancetype)initWithSize:(CGSize)size{ 18 | self = [super init]; 19 | self.size = size; 20 | self.dependencies = [NSMutableArray array]; 21 | return self; 22 | } 23 | + (instancetype)isHorizontal:(BOOL)isHorizontal startColor:(UIColor *)startColor endColor:(UIColor *)endColor{ 24 | SFGradientImageMaker *gradient = [[self class] new]; 25 | gradient.isHorizontal = isHorizontal; 26 | gradient.colors = @[startColor, endColor]; 27 | gradient.locations = @[@(0),@(1)]; 28 | gradient.size = isHorizontal ? CGSizeMake(UIScreen.mainScreen.bounds.size.width, 1) : CGSizeMake(1, UIScreen.mainScreen.bounds.size.height); 29 | return gradient; 30 | } 31 | - (BOOL)isEnable{ 32 | return self.colors.count > 0 && self.locations.count > 0 && !CGSizeEqualToSize(self.size, CGSizeZero); 33 | } 34 | - (nonnull UIImage *)generate { 35 | UIGraphicsBeginImageContext(self.size); 36 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 37 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 38 | 39 | CGFloat cLocations[2]; 40 | cLocations[0] = self.locations.firstObject.doubleValue; 41 | cLocations[1] = self.locations.lastObject.doubleValue; 42 | 43 | NSMutableArray *colors = [NSMutableArray array]; 44 | for (UIColor *color in self.colors) { 45 | [colors addObject:(__bridge id)color.CGColor]; 46 | } 47 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, cLocations); 48 | 49 | CGPathRef path = CGPathCreateWithRect(CGRectMake(0, 0, self.size.width, self.size.height), &CGAffineTransformIdentity); 50 | CGRect pathRect = CGPathGetBoundingBox(path); 51 | 52 | CGPoint startPoint = CGPointMake(CGRectGetMaxX(pathRect) * cLocations[0], CGRectGetMidY(pathRect)); 53 | CGPoint endPoint = CGPointMake(CGRectGetMaxX(pathRect) * cLocations[1], CGRectGetMidY(pathRect)); 54 | if (!self.isHorizontal) { 55 | startPoint = CGPointMake(CGRectGetMidX(pathRect), CGRectGetMaxY(pathRect) * cLocations[0]); 56 | endPoint = CGPointMake(CGRectGetMidX(pathRect), CGRectGetMaxY(pathRect) * cLocations[1]); 57 | } 58 | CGContextSaveGState(ctx); 59 | CGContextAddPath(ctx, path); 60 | CGContextClip(ctx); 61 | CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0); 62 | CGContextRestoreGState(ctx); 63 | CGGradientRelease(gradient); 64 | CGColorSpaceRelease(colorSpace); 65 | CGPathRelease(path); 66 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 67 | UIGraphicsEndImageContext(); 68 | return image; 69 | } 70 | - (nonnull NSString *)identifier { 71 | return self.isEnable ? [NSString stringWithFormat:@"gradient_%@_%@_%@",[NSValue valueWithCGSize:self.size],[self.colors componentsJoinedByString:@","],[self.locations componentsJoinedByString:@","]] : @""; 72 | } 73 | - (void)saveContext { 74 | NSMutableArray *container = [@[] mutableCopy]; 75 | for (UIColor *color in container) { 76 | [container addObject:[UIColor colorWithCGColor:color.CGColor]]; 77 | } 78 | _colors = [container copy]; 79 | } 80 | @end 81 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFImageFlow.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageManager.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/25. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SFImageMaker-Protocol.h" 11 | #import "SFBlurImageMaker.h" 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface SFImageFlow : NSObject 15 | @property (nonatomic, nullable, strong)UIImage *targetImage; 16 | @property (nonatomic, nullable, strong)id generator; 17 | @property (nonatomic, nullable, strong)NSMutableArray *processors; 18 | @property (nonatomic, nullable, strong)NSMutableArray *finals; 19 | + (instancetype)flow; 20 | + (instancetype)flowWithImage:(UIImage *)targetImage; 21 | + (instancetype)flowWithGenerator:(id )generator; 22 | - (void)appendProcessor:(id)processor; 23 | - (void)saveContext; 24 | - (UIImage *)image; 25 | - (NSString *)identifier; 26 | @end 27 | NS_ASSUME_NONNULL_END 28 | 29 | #import 30 | NS_ASSUME_NONNULL_BEGIN 31 | @interface SFImageFlow (Processor) 32 | @property (nonatomic, readonly)SFImageFlow* (^corner)(CGFloat radius, UIRectCorner rectCorner); 33 | @property (nonatomic, readonly)SFImageFlow* (^border)(CGFloat width, UIColor *color); 34 | @property (nonatomic, readonly)SFImageFlow* (^borderWithPosition)(UIBorderPostion position, CGFloat width, UIColor *color); 35 | @property (nonatomic, readonly)SFImageFlow* (^shadow)(UIColor *color, CGSize offset, CGFloat blurRadius); 36 | @property (nonatomic, readonly)SFImageFlow* (^blur)(SFBlurEffect effect);; 37 | @property (nonatomic, readonly)SFImageFlow* (^centerRect)(CGFloat aspectRatio); 38 | @property (nonatomic, readonly)SFImageFlow* (^edgeInsets)(UIEdgeInsets insets, UIColor *fillColor); 39 | @property (nonatomic, readonly)SFImageFlow* (^resize)(CGSize size); 40 | @property (nonatomic, readonly)SFImageFlow* (^resizeWithMax)(CGFloat max); 41 | @end 42 | 43 | 44 | @interface SFImageFlow (SwiftProcessor) 45 | - (SFImageFlow *)cornerWithRadius:(CGFloat)radius rectCorner: (UIRectCorner) rectCorner; 46 | - (SFImageFlow *)borderWithWidth:(CGFloat)width color:(UIColor *)color; 47 | - (SFImageFlow *)borderWithPosition:(UIBorderPostion)position width:(CGFloat)width color:(UIColor *)color; 48 | - (SFImageFlow *)shadowWithColor:(UIColor *)color offset:(CGSize)offset blurRadius:(CGFloat) blurRadius; 49 | - (SFImageFlow *)blurWithEffect:(SFBlurEffect)effect; 50 | - (SFImageFlow *)centerRectWithAspectRatio:(CGFloat)aspectRatio; 51 | - (SFImageFlow *)edgeInsetsWithInsets:(UIEdgeInsets)insets fillColor:(UIColor *)fillColor; 52 | - (SFImageFlow *)resizeWithSize:(CGSize)size; 53 | - (SFImageFlow *)resizeWithMax:(CGFloat)max; 54 | @property (nonatomic, readonly)SFImageFlow* circle; 55 | @property (nonatomic, readonly)SFImageFlow* centerSquare; 56 | @end 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFImageFlow.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageManager.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/25. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFImageFlow.h" 10 | static id visitProcessor(id processor, 11 | id result, 12 | id (^handler)(id, id)) { 13 | if (handler) { 14 | result = handler(result, processor); 15 | } 16 | id cur = processor; 17 | for (id subProcessor in cur.dependencies) { 18 | result = visitProcessor(subProcessor, result, handler); 19 | } 20 | return result; 21 | } 22 | 23 | static id visitProcessors(NSArray *processors, 24 | id result, 25 | id (^handler)(id result, id curProcessor)) { 26 | for (id processor in processors) { 27 | result = visitProcessor(processor, result, handler); 28 | } 29 | return result; 30 | } 31 | 32 | NSString *sf_identifierWithProcessors(NSArray *processors) { 33 | return visitProcessors(processors, [@"" mutableCopy], ^id(id result, id curProcessor) { 34 | return [NSString stringWithFormat:@"%@%@", result, curProcessor.identifier]; 35 | }); 36 | } 37 | 38 | @implementation SFImageFlow 39 | + (instancetype)flow{ 40 | return [SFImageFlow new]; 41 | } 42 | + (instancetype)flowWithImage:(UIImage *)targetImage{ 43 | SFImageFlow *proc = [SFImageFlow new]; 44 | proc.targetImage = targetImage; 45 | return proc; 46 | } 47 | + (instancetype)flowWithGenerator:(id )generator{ 48 | SFImageFlow *proc = [SFImageFlow new]; 49 | proc.generator = generator; 50 | return proc; 51 | } 52 | - (instancetype)init 53 | { 54 | self = [super init]; 55 | self.processors = [NSMutableArray array]; 56 | self.finals = [NSMutableArray array]; 57 | return self; 58 | } 59 | - (void)appendProcessor:(id)processor{ 60 | [self.processors addObject:processor]; 61 | } 62 | - (UIImage *)image{ 63 | UIImage *result = nil; 64 | if (self.generator) { 65 | result = [self startWithGenerator:self.generator processors:self.processors]; 66 | }else{ 67 | result = [self startWithImage:self.targetImage processors:self.processors]; 68 | } 69 | result = [self startWithImage:result processors:self.finals]; 70 | return result; 71 | } 72 | 73 | - (NSString *)identifier { 74 | NSString *result = nil; 75 | if (self.generator) { 76 | NSMutableArray *procssors = [@[ self.generator ] mutableCopy]; 77 | [procssors addObjectsFromArray:self.processors]; 78 | result = sf_identifierWithProcessors(procssors); 79 | } else { 80 | result = sf_identifierWithProcessors(self.processors); 81 | } 82 | return [result stringByAppendingString:sf_identifierWithProcessors(self.finals)]; 83 | } 84 | 85 | - (UIImage *)startWithGenerator:(id)generator processors:(NSArray> *)processors{ 86 | UIImage *image = [generator generate]; 87 | image = [self startWithImage:image processors:(id)generator.dependencies]; 88 | return [self startWithImage:image processors:processors]; 89 | } 90 | 91 | - (UIImage *)startWithImage:(UIImage *)image processors:(NSArray> *)processors { 92 | return visitProcessors(processors, image, ^id(id result, id curProcessor) { 93 | return [curProcessor process:result]; 94 | }); 95 | } 96 | 97 | - (void)saveContext { 98 | NSMutableArray *processors = [@[ self.generator ] mutableCopy]; 99 | [processors addObjectsFromArray:self.processors]; 100 | visitProcessors(processors, nil, ^id(id result, id curProcessor) { 101 | if ([curProcessor respondsToSelector:@selector(saveContext)]) { 102 | [curProcessor saveContext]; 103 | } 104 | return nil; 105 | }); 106 | } 107 | @end 108 | 109 | #import "SFImageMaker.h" 110 | 111 | @implementation SFImageFlow (Processor) 112 | - (SFImageFlow * _Nonnull (^)(CGFloat, UIRectCorner))corner{ 113 | return ^SFImageFlow* (CGFloat radius, UIRectCorner rectCorner){ 114 | return [self cornerWithRadius:radius rectCorner:rectCorner]; 115 | }; 116 | } 117 | - (SFImageFlow * _Nonnull (^)(CGFloat, UIColor * _Nonnull))border{ 118 | return ^SFImageFlow* (CGFloat width, UIColor *color){ 119 | return [self borderWithWidth:width color:color]; 120 | }; 121 | } 122 | - (SFImageFlow * _Nonnull (^)(UIBorderPostion, CGFloat, UIColor * _Nonnull))borderWithPosition{ 123 | return ^SFImageFlow* (UIBorderPostion position, CGFloat width, UIColor *color){ 124 | return [self borderWithPosition:position width:width color:color]; 125 | }; 126 | } 127 | 128 | - (SFImageFlow * _Nonnull (^)(UIColor * _Nonnull, CGSize, CGFloat))shadow{ 129 | return ^SFImageFlow* (UIColor *color, CGSize offset, CGFloat blurRadius){ 130 | return [self shadowWithColor:color offset:offset blurRadius:blurRadius]; 131 | }; 132 | } 133 | - (SFImageFlow * _Nonnull (^)(SFBlurEffect))blur{ 134 | return ^SFImageFlow* (SFBlurEffect effect){ 135 | return [self blurWithEffect:effect]; 136 | }; 137 | } 138 | - (SFImageFlow * _Nonnull (^)(CGFloat))centerRect{ 139 | return ^SFImageFlow* (CGFloat aspectRatio){ 140 | return [self centerRectWithAspectRatio:aspectRatio]; 141 | }; 142 | } 143 | - (SFImageFlow * _Nonnull (^)(UIEdgeInsets, UIColor * _Nonnull))edgeInsets{ 144 | return ^SFImageFlow* (UIEdgeInsets insets, UIColor *fillColor){ 145 | return [self edgeInsetsWithInsets:insets fillColor:fillColor]; 146 | }; 147 | } 148 | - (SFImageFlow * _Nonnull (^)(CGSize))resize{ 149 | return ^SFImageFlow* (CGSize size){ 150 | return [self resizeWithSize:size]; 151 | }; 152 | } 153 | - (SFImageFlow * _Nonnull (^)(CGFloat))resizeWithMax{ 154 | return ^SFImageFlow* (CGFloat max){ 155 | return [self resizeWithMax:max]; 156 | }; 157 | } 158 | @end 159 | 160 | 161 | 162 | @implementation SFImageFlow (SwiftProcessor) 163 | - (SFImageFlow *)cornerWithRadius:(CGFloat)radius rectCorner: (UIRectCorner) rectCorner{ 164 | SFCornerImageMaker *maker = [SFCornerImageMaker new]; 165 | maker.radius = radius; 166 | maker.position = rectCorner; 167 | if ([self.generator conformsToProtocol:@protocol(SFImageGenerator)]) { 168 | SFColorImageMaker *generator = (SFColorImageMaker *)self.generator; 169 | CGSize size = generator.size; 170 | if (size.height < radius * 2) size.height = radius * 2; 171 | if (size.width < radius * 2) size.width = radius * 2; 172 | if (!CGSizeEqualToSize(generator.size, size)) { 173 | generator.size = size; 174 | [self.finals addObject:[SFBlockImageMaker resizableCenterMode]]; 175 | } 176 | } 177 | [self appendProcessor:maker]; 178 | return self; 179 | } 180 | - (SFImageFlow *)borderWithWidth:(CGFloat)width color:(UIColor *)color{ 181 | return [self borderWithPosition:UIBorderPostionAll width:width color:color]; 182 | } 183 | - (SFImageFlow *)borderWithPosition:(UIBorderPostion)position width:(CGFloat)width color:(UIColor *)color{ 184 | SFBorderImageMaker *maker = [SFBorderImageMaker new]; 185 | maker.width = width; 186 | maker.position = position; 187 | maker.color = color; 188 | for (id processor in self.processors) { 189 | if ([processor isKindOfClass:[SFCornerImageMaker class]]) { 190 | maker.cornerMaker = processor; 191 | break; 192 | } 193 | } 194 | [self appendProcessor:maker]; 195 | return self; 196 | } 197 | - (SFImageFlow *)shadowWithColor:(UIColor *)color offset:(CGSize)offset blurRadius:(CGFloat) blurRadius{ 198 | SFShadowImageMaker *maker = [SFShadowImageMaker new]; 199 | maker.shadowColor = color; 200 | maker.shadowOffset = offset; 201 | maker.shadowBlurRadius = blurRadius; 202 | maker.position = UIShadowPostionAll; 203 | [self appendProcessor:maker]; 204 | return self; 205 | } 206 | - (SFImageFlow *)blurWithEffect:(SFBlurEffect)effect{ 207 | switch (effect) { 208 | case SFBlurEffectLight: 209 | [self appendProcessor:[SFBlurImageMaker lightEffect]]; 210 | break; 211 | case SFBlurEffectDark: 212 | [self appendProcessor:[SFBlurImageMaker darkEffect]]; 213 | break; 214 | case SFBlurEffectExtraLight: 215 | [self appendProcessor:[SFBlurImageMaker extraLightEffect]]; 216 | break; 217 | } 218 | return self; 219 | } 220 | - (SFImageFlow *)centerRectWithAspectRatio:(CGFloat)aspectRatio{ 221 | [self appendProcessor:[SFBlockImageMaker centerRectWithAspectRatio:aspectRatio]]; 222 | return self; 223 | } 224 | - (SFImageFlow *)edgeInsetsWithInsets:(UIEdgeInsets)insets fillColor:(UIColor *)fillColor{ 225 | [self appendProcessor:[SFBlockImageMaker edgeInsets:insets fillColor:fillColor]]; 226 | return self; 227 | } 228 | - (SFImageFlow *)resizeWithSize:(CGSize)size{ 229 | [self appendProcessor:[SFBlockImageMaker resizeWithSize:size]]; 230 | return self; 231 | } 232 | - (SFImageFlow *)resizeWithMax:(CGFloat)max{ 233 | [self appendProcessor:[SFBlockImageMaker resizeWithMaxValue:max]]; 234 | return self; 235 | } 236 | - (SFImageFlow *)circle{ 237 | [self appendProcessor:[SFCircleImageMaker new]]; 238 | return self; 239 | } 240 | - (SFImageFlow *)centerSquare{ 241 | [self appendProcessor:[SFBlockImageMaker centerSquare]]; 242 | return self; 243 | } 244 | @end 245 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFImageMaker-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageMaker-Protocol.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | @protocol SFImageIdentifier 13 | @property (nonatomic, readonly, getter=isEnable)BOOL enable; 14 | - (NSString *)identifier; 15 | @end 16 | 17 | @protocol SFImageProcessor; 18 | @protocol SFImageDependencies 19 | @property(nonatomic, strong)NSMutableArray > *dependencies; 20 | @end 21 | 22 | @protocol SFImageSaveContext 23 | @optional 24 | - (void)saveContext; 25 | @end 26 | 27 | @protocol SFImageProcessor 28 | @required 29 | - (UIImage *)process:(nullable UIImage *)target; 30 | @end 31 | 32 | @protocol SFImageGenerator 33 | @required 34 | @property (nonatomic,assign)CGSize size; 35 | - (instancetype)initWithSize:(CGSize)size; 36 | - (UIImage *)generate; 37 | @end 38 | 39 | 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SFImageMaker. 12 | FOUNDATION_EXPORT double SFImageMakerVersionNumber; 13 | 14 | //! Project version string for SFImageMaker. 15 | FOUNDATION_EXPORT const unsigned char SFImageMakerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFShadowImageMaker.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFShadowImageMaker.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_OPTIONS(NSUInteger, UIShadowPostion) { 14 | UIShadowPostionTop = 1 << 0, 15 | UIShadowPostionRight = 1 << 1, 16 | UIShadowPostionBottom = 1 << 2, 17 | UIShadowPostionLeft = 1 << 3, 18 | UIShadowPostionAll = ~0UL 19 | }; 20 | 21 | @interface SFShadowImageMaker : NSObject 22 | @property (nonatomic, assign)CGSize shadowOffset; 23 | @property (nonatomic, assign)CGFloat shadowBlurRadius; 24 | @property (nonatomic, strong)UIColor *shadowColor; 25 | @property (nonatomic, strong)UIColor *fillColor; 26 | @property (nonatomic, assign)UIShadowPostion position; 27 | @property (nonatomic, getter=isEnable)BOOL enable; 28 | @property(nonatomic, strong)NSMutableArray > *dependencies; 29 | 30 | - (CGRect)viewRectForSize:(CGSize)size; 31 | - (UIEdgeInsets)convasEdgeInsets; 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/SFShadowImageMaker.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFShadowImageMaker.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFShadowImageMaker.h" 10 | 11 | @implementation SFShadowImageMaker 12 | - (instancetype)init 13 | { 14 | self = [super init]; 15 | self.fillColor = [UIColor clearColor]; 16 | self.dependencies = [NSMutableArray array]; 17 | return self; 18 | } 19 | - (BOOL)isEnable{ 20 | return self.shadowColor != nil && self.shadowColor != [UIColor clearColor] && self.shadowBlurRadius != 0; 21 | } 22 | - (CGRect)viewRectForSize:(CGSize)size{ 23 | UIEdgeInsets insets = [self convasEdgeInsets]; 24 | CGSize convasSize = [self convasSizeWithSize:size]; 25 | return CGRectMake(-insets.left, -insets.top, convasSize.width, convasSize.height); 26 | } 27 | - (CGSize)convasSizeWithSize:(CGSize)size{ 28 | UIEdgeInsets insets = [self convasEdgeInsets]; 29 | return CGSizeMake(size.width + insets.left + insets.right, size.height + insets.top + insets.bottom); 30 | } 31 | - (UIEdgeInsets)convasEdgeInsets{ 32 | CGFloat left=0, right = 0, top = 0, bottom = 0; 33 | if (self.position&UIShadowPostionRight&&self.shadowOffset.width>0) { 34 | right += fabs(self.shadowOffset.width); 35 | }else if (self.position&UIShadowPostionLeft&&self.shadowOffset.width<0) { 36 | left += fabs(self.shadowOffset.width); 37 | } 38 | if (self.position&UIShadowPostionBottom&&self.shadowOffset.height>0) { 39 | bottom += fabs(self.shadowOffset.height); 40 | }else if (self.position&UIShadowPostionTop&&self.shadowOffset.height<0) { 41 | top += fabs(self.shadowOffset.height); 42 | } 43 | top += self.position&UIShadowPostionTop?self.shadowBlurRadius:0; 44 | bottom += self.position&UIShadowPostionBottom?self.shadowBlurRadius:0; 45 | left += self.position&UIShadowPostionLeft?self.shadowBlurRadius:0; 46 | right += self.position&UIShadowPostionRight?self.shadowBlurRadius:0; 47 | return UIEdgeInsetsMake(top, left, bottom, right); 48 | } 49 | - (UIImage *)process:(UIImage *)target{ 50 | if (!self.isEnable) return target; 51 | UIEdgeInsets insets = [self convasEdgeInsets]; 52 | CGSize canvasSize = [self convasSizeWithSize:target.size]; 53 | CGRect canvasRect = CGRectMake(0, 0, canvasSize.width, canvasSize.height); 54 | UIGraphicsBeginImageContextWithOptions(canvasRect.size, NO, 0); 55 | CGContextRef context = UIGraphicsGetCurrentContext(); 56 | [self.fillColor setFill]; 57 | CGContextFillRect(context,canvasRect); 58 | CGContextSetShadowWithColor(context, self.shadowOffset, self.shadowBlurRadius, self.shadowColor.CGColor); 59 | [target drawInRect:CGRectMake(insets.left, insets.top, target.size.width, target.size.height)]; 60 | //获取图像 61 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 62 | UIGraphicsEndImageContext(); 63 | return image; 64 | } 65 | - (nonnull NSString *)identifier { 66 | return self.isEnable ? [NSString stringWithFormat:@"shadow_%@_%@_%@_%@",[NSValue valueWithCGSize:self.shadowOffset],@(self.shadowBlurRadius),self.shadowColor.sf_identifier,@(self.position)] : @""; 67 | } 68 | @end 69 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/UIColor+SFIdentifier.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+SFIdentifier.h 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2021/1/20. 6 | // Copyright © 2021 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIColor (SFIdentifier) 14 | @property (nonatomic, readonly)NSString *sf_identifier; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/UIColor+SFIdentifier.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+SFIdentifier.m 3 | // SFImageMaker 4 | // 5 | // Created by Jiang on 2021/1/20. 6 | // Copyright © 2021 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "UIColor+SFIdentifier.h" 10 | 11 | @implementation UIColor (SFIdentifier) 12 | - (NSString *)sf_identifier{ 13 | CGFloat red, green, blue, alpha; 14 | [self getRed:&red green:&green blue:&blue alpha:&alpha]; 15 | return [NSString stringWithFormat:@"color_%.2f%.2f%.2f%.2f",red,green,blue,alpha]; 16 | } 17 | @end 18 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/UIImageEffects/UIImageEffects.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImageEffects.h 3 | Abstract: This class contains methods to apply blur and tint effects to an image. 4 | This is the code you’ll want to look out to find out how to use vImage to 5 | efficiently calculate a blur. 6 | Version: 1.1 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 9 | Inc. ("Apple") in consideration of your agreement to the following 10 | terms, and your use, installation, modification or redistribution of 11 | this Apple software constitutes acceptance of these terms. If you do 12 | not agree with these terms, please do not use, install, modify or 13 | redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. may 24 | be used to endorse or promote products derived from the Apple Software 25 | without specific prior written permission from Apple. Except as 26 | expressly stated in this notice, no other rights or licenses, express or 27 | implied, are granted by Apple herein, including but not limited to any 28 | patent rights that may be infringed by your derivative works or by other 29 | works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 47 | 48 | */ 49 | 50 | #import 51 | 52 | @interface UIImageEffects : NSObject 53 | 54 | + (UIImage*)imageByApplyingLightEffectToImage:(UIImage*)inputImage; 55 | + (UIImage*)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage; 56 | + (UIImage*)imageByApplyingDarkEffectToImage:(UIImage*)inputImage; 57 | + (UIImage*)imageByApplyingTintEffectWithColor:(UIColor *)tintColor toImage:(UIImage*)inputImage; 58 | 59 | //| ---------------------------------------------------------------------------- 60 | //! Applies a blur, tint color, and saturation adjustment to @a inputImage, 61 | //! optionally within the area specified by @a maskImage. 62 | //! 63 | //! @param inputImage 64 | //! The source image. A modified copy of this image will be returned. 65 | //! @param blurRadius 66 | //! The radius of the blur in points. 67 | //! @param tintColor 68 | //! An optional UIColor object that is uniformly blended with the 69 | //! result of the blur and saturation operations. The alpha channel 70 | //! of this color determines how strong the tint is. 71 | //! @param saturationDeltaFactor 72 | //! A value of 1.0 produces no change in the resulting image. Values 73 | //! less than 1.0 will desaturation the resulting image while values 74 | //! greater than 1.0 will have the opposite effect. 75 | //! @param maskImage 76 | //! If specified, @a inputImage is only modified in the area(s) defined 77 | //! by this mask. This must be an image mask or it must meet the 78 | //! requirements of the mask parameter of CGContextClipToMask. 79 | + (UIImage *)imageByApplyingBlurToImage:(UIImage*)inputImage withRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage; 80 | 81 | @end 82 | 83 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/UIImageEffects/UIImageEffects.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: UIImageEffects.m 3 | Abstract: This class contains methods to apply blur and tint effects to an image. 4 | This is the code you’ll want to look out to find out how to use vImage to 5 | efficiently calculate a blur. 6 | Version: 1.1 7 | 8 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 9 | Inc. ("Apple") in consideration of your agreement to the following 10 | terms, and your use, installation, modification or redistribution of 11 | this Apple software constitutes acceptance of these terms. If you do 12 | not agree with these terms, please do not use, install, modify or 13 | redistribute this Apple software. 14 | 15 | In consideration of your agreement to abide by the following terms, and 16 | subject to these terms, Apple grants you a personal, non-exclusive 17 | license, under Apple's copyrights in this original Apple software (the 18 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 19 | Software, with or without modifications, in source and/or binary forms; 20 | provided that if you redistribute the Apple Software in its entirety and 21 | without modifications, you must retain this notice and the following 22 | text and disclaimers in all such redistributions of the Apple Software. 23 | Neither the name, trademarks, service marks or logos of Apple Inc. may 24 | be used to endorse or promote products derived from the Apple Software 25 | without specific prior written permission from Apple. Except as 26 | expressly stated in this notice, no other rights or licenses, express or 27 | implied, are granted by Apple herein, including but not limited to any 28 | patent rights that may be infringed by your derivative works or by other 29 | works in which the Apple Software may be incorporated. 30 | 31 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 32 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 33 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 34 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 35 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 36 | 37 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 38 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 39 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 40 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 41 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 42 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 43 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 44 | POSSIBILITY OF SUCH DAMAGE. 45 | 46 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 47 | 48 | */ 49 | 50 | #import "UIImageEffects.h" 51 | #import 52 | 53 | @implementation UIImageEffects 54 | 55 | #pragma mark - 56 | #pragma mark - Effects 57 | 58 | //| ---------------------------------------------------------------------------- 59 | + (UIImage *)imageByApplyingLightEffectToImage:(UIImage*)inputImage 60 | { 61 | UIColor *tintColor = [UIColor colorWithWhite:1.0 alpha:0.3]; 62 | return [self imageByApplyingBlurToImage:inputImage withRadius:60 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 63 | } 64 | 65 | 66 | //| ---------------------------------------------------------------------------- 67 | + (UIImage *)imageByApplyingExtraLightEffectToImage:(UIImage*)inputImage 68 | { 69 | UIColor *tintColor = [UIColor colorWithWhite:0.97 alpha:0.82]; 70 | return [self imageByApplyingBlurToImage:inputImage withRadius:40 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 71 | } 72 | 73 | 74 | //| ---------------------------------------------------------------------------- 75 | + (UIImage *)imageByApplyingDarkEffectToImage:(UIImage*)inputImage 76 | { 77 | UIColor *tintColor = [UIColor colorWithWhite:0.11 alpha:0.73]; 78 | return [self imageByApplyingBlurToImage:inputImage withRadius:40 tintColor:tintColor saturationDeltaFactor:1.8 maskImage:nil]; 79 | } 80 | 81 | 82 | //| ---------------------------------------------------------------------------- 83 | + (UIImage *)imageByApplyingTintEffectWithColor:(UIColor *)tintColor toImage:(UIImage*)inputImage 84 | { 85 | const CGFloat EffectColorAlpha = 0.6; 86 | UIColor *effectColor = tintColor; 87 | size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor); 88 | if (componentCount == 2) { 89 | CGFloat b; 90 | if ([tintColor getWhite:&b alpha:NULL]) { 91 | effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha]; 92 | } 93 | } 94 | else { 95 | CGFloat r, g, b; 96 | if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) { 97 | effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha]; 98 | } 99 | } 100 | return [self imageByApplyingBlurToImage:inputImage withRadius:20 tintColor:effectColor saturationDeltaFactor:-1.0 maskImage:nil]; 101 | } 102 | 103 | #pragma mark - 104 | #pragma mark - Implementation 105 | 106 | //| ---------------------------------------------------------------------------- 107 | + (UIImage*)imageByApplyingBlurToImage:(UIImage*)inputImage withRadius:(CGFloat)blurRadius tintColor:(UIColor *)tintColor saturationDeltaFactor:(CGFloat)saturationDeltaFactor maskImage:(UIImage *)maskImage 108 | { 109 | #define ENABLE_BLUR 1 110 | #define ENABLE_SATURATION_ADJUSTMENT 1 111 | #define ENABLE_TINT 1 112 | 113 | // Check pre-conditions. 114 | if (inputImage.size.width < 1 || inputImage.size.height < 1) 115 | { 116 | NSLog(@"*** error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", inputImage.size.width, inputImage.size.height, inputImage); 117 | return nil; 118 | } 119 | if (!inputImage.CGImage) 120 | { 121 | NSLog(@"*** error: inputImage must be backed by a CGImage: %@", inputImage); 122 | return nil; 123 | } 124 | if (maskImage && !maskImage.CGImage) 125 | { 126 | NSLog(@"*** error: effectMaskImage must be backed by a CGImage: %@", maskImage); 127 | return nil; 128 | } 129 | 130 | BOOL hasBlur = blurRadius > __FLT_EPSILON__; 131 | BOOL hasSaturationChange = fabs(saturationDeltaFactor - 1.) > __FLT_EPSILON__; 132 | 133 | CGImageRef inputCGImage = inputImage.CGImage; 134 | CGFloat inputImageScale = inputImage.scale; 135 | CGBitmapInfo inputImageBitmapInfo = CGImageGetBitmapInfo(inputCGImage); 136 | CGImageAlphaInfo inputImageAlphaInfo = (inputImageBitmapInfo & kCGBitmapAlphaInfoMask); 137 | 138 | CGSize outputImageSizeInPoints = inputImage.size; 139 | CGRect outputImageRectInPoints = { CGPointZero, outputImageSizeInPoints }; 140 | 141 | // Set up output context. 142 | BOOL useOpaqueContext; 143 | if (inputImageAlphaInfo == kCGImageAlphaNone || inputImageAlphaInfo == kCGImageAlphaNoneSkipLast || inputImageAlphaInfo == kCGImageAlphaNoneSkipFirst) 144 | useOpaqueContext = YES; 145 | else 146 | useOpaqueContext = NO; 147 | UIGraphicsBeginImageContextWithOptions(outputImageRectInPoints.size, useOpaqueContext, inputImageScale); 148 | CGContextRef outputContext = UIGraphicsGetCurrentContext(); 149 | CGContextScaleCTM(outputContext, 1.0, -1.0); 150 | CGContextTranslateCTM(outputContext, 0, -outputImageRectInPoints.size.height); 151 | 152 | if (hasBlur || hasSaturationChange) 153 | { 154 | vImage_Buffer effectInBuffer; 155 | vImage_Buffer scratchBuffer1; 156 | 157 | vImage_Buffer *inputBuffer; 158 | vImage_Buffer *outputBuffer; 159 | 160 | vImage_CGImageFormat format = { 161 | .bitsPerComponent = 8, 162 | .bitsPerPixel = 32, 163 | .colorSpace = NULL, 164 | // (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) 165 | // requests a BGRA buffer. 166 | .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, 167 | .version = 0, 168 | .decode = NULL, 169 | .renderingIntent = kCGRenderingIntentDefault 170 | }; 171 | 172 | vImage_Error e = vImageBuffer_InitWithCGImage(&effectInBuffer, &format, NULL, inputImage.CGImage, kvImagePrintDiagnosticsToConsole); 173 | if (e != kvImageNoError) 174 | { 175 | NSLog(@"*** error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", e, inputImage); 176 | UIGraphicsEndImageContext(); 177 | return nil; 178 | } 179 | 180 | vImageBuffer_Init(&scratchBuffer1, effectInBuffer.height, effectInBuffer.width, format.bitsPerPixel, kvImageNoFlags); 181 | inputBuffer = &effectInBuffer; 182 | outputBuffer = &scratchBuffer1; 183 | 184 | #if ENABLE_BLUR 185 | if (hasBlur) 186 | { 187 | // A description of how to compute the box kernel width from the Gaussian 188 | // radius (aka standard deviation) appears in the SVG spec: 189 | // http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement 190 | // 191 | // For larger values of 's' (s >= 2.0), an approximation can be used: Three 192 | // successive box-blurs build a piece-wise quadratic convolution kernel, which 193 | // approximates the Gaussian kernel to within roughly 3%. 194 | // 195 | // let d = floor(s * 3*sqrt(2*pi)/4 + 0.5) 196 | // 197 | // ... if d is odd, use three box-blurs of size 'd', centered on the output pixel. 198 | // 199 | CGFloat inputRadius = blurRadius * inputImageScale; 200 | if (inputRadius - 2. < __FLT_EPSILON__) 201 | inputRadius = 2.; 202 | uint32_t radius = floor((inputRadius * 3. * sqrt(2 * M_PI) / 4 + 0.5) / 2); 203 | 204 | radius |= 1; // force radius to be odd so that the three box-blur methodology works. 205 | 206 | NSInteger tempBufferSize = vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend); 207 | void *tempBuffer = malloc(tempBufferSize); 208 | 209 | vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend); 210 | vImageBoxConvolve_ARGB8888(outputBuffer, inputBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend); 211 | vImageBoxConvolve_ARGB8888(inputBuffer, outputBuffer, tempBuffer, 0, 0, radius, radius, NULL, kvImageEdgeExtend); 212 | 213 | free(tempBuffer); 214 | 215 | vImage_Buffer *temp = inputBuffer; 216 | inputBuffer = outputBuffer; 217 | outputBuffer = temp; 218 | } 219 | #endif 220 | 221 | #if ENABLE_SATURATION_ADJUSTMENT 222 | if (hasSaturationChange) 223 | { 224 | CGFloat s = saturationDeltaFactor; 225 | // These values appear in the W3C Filter Effects spec: 226 | // https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/index.html#grayscaleEquivalent 227 | // 228 | CGFloat floatingPointSaturationMatrix[] = { 229 | 0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0, 230 | 0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0, 231 | 0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0, 232 | 0, 0, 0, 1, 233 | }; 234 | const int32_t divisor = 256; 235 | NSUInteger matrixSize = sizeof(floatingPointSaturationMatrix)/sizeof(floatingPointSaturationMatrix[0]); 236 | int16_t saturationMatrix[matrixSize]; 237 | for (NSUInteger i = 0; i < matrixSize; ++i) { 238 | saturationMatrix[i] = (int16_t)roundf(floatingPointSaturationMatrix[i] * divisor); 239 | } 240 | vImageMatrixMultiply_ARGB8888(inputBuffer, outputBuffer, saturationMatrix, divisor, NULL, NULL, kvImageNoFlags); 241 | 242 | vImage_Buffer *temp = inputBuffer; 243 | inputBuffer = outputBuffer; 244 | outputBuffer = temp; 245 | } 246 | #endif 247 | 248 | CGImageRef effectCGImage; 249 | if ( (effectCGImage = vImageCreateCGImageFromBuffer(inputBuffer, &format, &cleanupBuffer, NULL, kvImageNoAllocate, NULL)) == NULL ) { 250 | effectCGImage = vImageCreateCGImageFromBuffer(inputBuffer, &format, NULL, NULL, kvImageNoFlags, NULL); 251 | free(inputBuffer->data); 252 | } 253 | if (maskImage) { 254 | // Only need to draw the base image if the effect image will be masked. 255 | CGContextDrawImage(outputContext, outputImageRectInPoints, inputCGImage); 256 | } 257 | 258 | // draw effect image 259 | CGContextSaveGState(outputContext); 260 | if (maskImage) 261 | CGContextClipToMask(outputContext, outputImageRectInPoints, maskImage.CGImage); 262 | CGContextDrawImage(outputContext, outputImageRectInPoints, effectCGImage); 263 | CGContextRestoreGState(outputContext); 264 | 265 | // Cleanup 266 | CGImageRelease(effectCGImage); 267 | free(outputBuffer->data); 268 | } 269 | else 270 | { 271 | // draw base image 272 | CGContextDrawImage(outputContext, outputImageRectInPoints, inputCGImage); 273 | } 274 | 275 | #if ENABLE_TINT 276 | // Add in color tint. 277 | if (tintColor) 278 | { 279 | CGContextSaveGState(outputContext); 280 | CGContextSetFillColorWithColor(outputContext, tintColor.CGColor); 281 | CGContextFillRect(outputContext, outputImageRectInPoints); 282 | CGContextRestoreGState(outputContext); 283 | } 284 | #endif 285 | 286 | // Output image is ready. 287 | UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext(); 288 | UIGraphicsEndImageContext(); 289 | 290 | return outputImage; 291 | #undef ENABLE_BLUR 292 | #undef ENABLE_SATURATION_ADJUSTMENT 293 | #undef ENABLE_TINT 294 | } 295 | 296 | 297 | //| ---------------------------------------------------------------------------- 298 | // Helper function to handle deferred cleanup of a buffer. 299 | // 300 | void cleanupBuffer(void *userData, void *buf_data) 301 | { free(buf_data); } 302 | 303 | @end 304 | 305 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/UIKit+SFImageFlow.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit+SFImageFlow.h 3 | // SFImageMaker 4 | // 5 | // Created by APPLE on 2021/5/21. 6 | // Copyright © 2021 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @class SFImageFlow; 13 | @interface UIColor (SFImageFlow) 14 | @property (nonatomic, readonly)SFImageFlow *sf_flow; 15 | @property (nonatomic, readonly)SFImageFlow *(^sf_flowWithSize)(CGSize size); 16 | - (SFImageFlow *)sf_flowWithSize:(CGSize)size; 17 | @end 18 | 19 | @class SFImageFlow; 20 | @interface UIImage (SFImageFlow) 21 | @property (nonatomic, readonly)SFImageFlow *sf_flow; 22 | @end 23 | 24 | @class SFImageFlow; 25 | @interface NSArray (SFImageFlow) 26 | @property (nonatomic, readonly)SFImageFlow *(^sf_gradientFlow)(BOOL isHorizontal, CGSize size); 27 | - (SFImageFlow *)sf_gradientFlow:(BOOL)isHorizontal size:(CGSize)size; 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMaker/UIKit+SFImageFlow.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIKit+SFImageFlow.m 3 | // SFImageMaker 4 | // 5 | // Created by APPLE on 2021/5/21. 6 | // Copyright © 2021 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "UIKit+SFImageFlow.h" 10 | #import "SFImageMaker.h" 11 | @implementation UIColor (SFImageFlow) 12 | - (SFImageFlow *)sf_flow{ 13 | SFColorImageMaker *maker = [SFColorImageMaker imageMakerWithColor:self]; 14 | return [SFImageFlow flowWithGenerator:maker];; 15 | } 16 | - (SFImageFlow * _Nonnull (^)(CGSize))sf_flowWithSize{ 17 | return ^SFImageFlow* (CGSize size){ 18 | return [self sf_flowWithSize:size]; 19 | }; 20 | } 21 | - (SFImageFlow *)sf_flowWithSize:(CGSize)size{ 22 | SFColorImageMaker *maker = [SFColorImageMaker imageMakerWithColor:self size:size]; 23 | return [SFImageFlow flowWithGenerator:maker];; 24 | } 25 | @end 26 | 27 | 28 | @implementation UIImage (SFImageFlow) 29 | - (SFImageFlow *)sf_flow{ 30 | return [SFImageFlow flowWithImage:self];; 31 | } 32 | @end 33 | 34 | 35 | @implementation NSArray (SFImageFlow) 36 | - (SFImageFlow * _Nonnull (^)(BOOL, CGSize))sf_gradientFlow{ 37 | return ^SFImageFlow* (BOOL isHorizontal, CGSize size){ 38 | return [self sf_gradientFlow:isHorizontal size:size]; 39 | }; 40 | } 41 | - (SFImageFlow *)sf_gradientFlow:(BOOL)isHorizontal size:(CGSize)size{ 42 | NSAssert(self.count == 2, @""); 43 | SFGradientImageMaker *maker = [SFGradientImageMaker isHorizontal:isHorizontal startColor:self[0] endColor:self[1]]; 44 | maker.size = size; 45 | return [SFImageFlow flowWithGenerator:maker]; 46 | } 47 | @end 48 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMakerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMakerTests/SFBorderImageMakerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFBorderImageMakerTests.swift 3 | // SFImageMakerTests 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SFBorderImageMakerTests: XCTestCase { 12 | let border = SFBorderImageMaker.init() 13 | override func setUp() { 14 | border.width = 20 15 | border.color = UIColor.black 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testAllBorder(){ 23 | border.position = [.left,.right,.top,.bottom] 24 | let rect = border.strokeRect(with: CGSize.init(width: 200, height: 200)) 25 | XCTAssertTrue(rect == CGRect(x: 10, y: 10, width: 180, height: 180),rect.debugDescription) 26 | } 27 | func testHideLeftBorder(){ 28 | border.position = [.right,.top,.bottom] 29 | let rect = border.strokeRect(with: CGSize.init(width: 200, height: 200)) 30 | XCTAssertTrue(rect == CGRect(x: -10, y: 10, width: 200, height: 180),rect.debugDescription) 31 | } 32 | func testHideRightBorder(){ 33 | border.position = [.left,.top,.bottom] 34 | let rect = border.strokeRect(with: CGSize.init(width: 200, height: 200)) 35 | XCTAssertTrue(rect == CGRect(x: 10, y: 10, width: 200, height: 180),rect.debugDescription) 36 | } 37 | 38 | func testHideTopBorder(){ 39 | border.position = [.left,.right,.bottom] 40 | let rect = border.strokeRect(with: CGSize.init(width: 200, height: 200)) 41 | XCTAssertTrue(rect == CGRect(x: 10, y: -10, width: 180, height: 200),rect.debugDescription) 42 | } 43 | func testHideBottomBorder(){ 44 | border.position = [.left,.right,.top] 45 | let rect = border.strokeRect(with: CGSize.init(width: 200, height: 200)) 46 | XCTAssertTrue(rect == CGRect(x: 10, y: 10, width: 180, height: 200),rect.debugDescription) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMakerTests/SFImageMakerTests-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMakerTests/SFImageMakerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageMakerTests.swift 3 | // SFImageMakerTests 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import SFImageMaker 11 | 12 | class SFImageMakerTests: XCTestCase { 13 | let shadow = SFShadowImageMaker.init() 14 | let border = SFBorderImageMaker.init() 15 | let rectCorner = SFCornerImageMaker.init() 16 | override func setUp() { 17 | rectCorner.radius = 20 18 | rectCorner.position = .allCorners 19 | shadow.position = .all 20 | shadow.shadowColor = UIColor.black 21 | shadow.shadowBlurRadius = 20 22 | border.color = UIColor.black 23 | border.width = 5 24 | border.cornerMaker = rectCorner 25 | } 26 | override func tearDown() { 27 | 28 | } 29 | func testDependenciesIdentifier(){ 30 | let noDependencies = SFColorImageMaker.init(color: UIColor.white) 31 | let colorMaker = SFColorImageMaker.init(color: UIColor.white) 32 | colorMaker.dependencies.addObjects(from: [rectCorner,border,shadow]) 33 | var result = noDependencies.identifier() + rectCorner.identifier() + border.identifier() + shadow.identifier() 34 | XCTAssert(SFImageMakerManager.shared().identifier(with: colorMaker, processors: nil) == result, result) 35 | 36 | colorMaker.dependencies = NSMutableArray.init() 37 | colorMaker.dependencies.add(SFBlurImageMaker.lightEffect()) 38 | let processors:[SFImageProcessor] = [rectCorner,border,shadow] 39 | result = noDependencies.identifier() + SFBlurImageMaker.lightEffect().identifier() + processors.reduce("", {$0 + $1.identifier()}) 40 | XCTAssert(SFImageMakerManager.shared().identifier(with: colorMaker, processors: processors) == result, result) 41 | } 42 | func testPerformanceExample() { 43 | self.measure { 44 | var cost: Int = 0 45 | for _ in (0...1000){ 46 | rectCorner.radius = CGFloat(Int.random(in: 0..<10)) 47 | let cornerPositions: [UIRectCorner] = [.topLeft,.topRight,.bottomLeft,.bottomRight] 48 | var cornerResult = [UIRectCorner]() 49 | var cornerCount = Int.random(in: 0..<4) 50 | while cornerCount >= 0 { 51 | cornerResult.append(cornerPositions[Int.random(in: 0..<4)]) 52 | cornerCount -= 1 53 | } 54 | rectCorner.position = UIRectCorner.init(cornerResult) 55 | 56 | let shadowPositions: [UIShadowPostion] = [.left,.top,.right,.bottom] 57 | var shadowResult = [UIShadowPostion]() 58 | var shadowResultCount = Int.random(in: 0..<4) 59 | while shadowResultCount >= 0 { 60 | shadowResult.append(shadowPositions[Int.random(in: 0..<4)]) 61 | shadowResultCount -= 1 62 | } 63 | shadow.position = UIShadowPostion.init(shadowResult) 64 | shadow.shadowColor = UIColor.init(red: CGFloat(Int.random(in: 0...10)) / 10, green: CGFloat(Int.random(in: 0...10)) / 10, blue: CGFloat(Int.random(in: 0...10)) / 10, alpha: 1) 65 | shadow.shadowBlurRadius = CGFloat(Int.random(in: 8..<20)) 66 | border.color = UIColor.init(red: CGFloat(Int.random(in: 0...10)) / 10, green: CGFloat(Int.random(in: 0...10)) / 10, blue: CGFloat(Int.random(in: 0...10)) / 10, alpha: 1) 67 | border.width = CGFloat(Int.random(in: 0..<20)) 68 | 69 | let borderPositions: [UIBorderPostion] = [.left,.top,.right,.bottom] 70 | var borderResult = [UIBorderPostion]() 71 | var borderCount = Int.random(in: 0..<4) 72 | while borderCount >= 0 { 73 | borderResult.append(borderPositions[Int.random(in: 0..<4)]) 74 | borderCount -= 1 75 | } 76 | border.position = UIBorderPostion.init(borderResult) 77 | border.cornerMaker = rectCorner 78 | var maxValue = rectCorner.radius > (border.width + 1) && rectCorner.isEnable ? rectCorner.radius : border.width + 1 79 | maxValue = shadow.shadowBlurRadius > maxValue ? shadow.shadowBlurRadius : maxValue 80 | let size = CGSize.init(width: maxValue * 2, height: maxValue * 2) 81 | // let size = CGSize.init(width: 400, height: 100) 82 | let image = SFImageMakerManager.shared().start(with: SFColorImageMaker(color: UIColor.white, size: size), processors: [rectCorner,border,shadow]) 83 | if let cgimg = image.cgImage{ 84 | cost += cgimg.height * cgimg.width * (cgimg.bitsPerPixel / cgimg.bitsPerComponent) 85 | } 86 | } 87 | // width:400 height:100 1000次 耗时 7.5s 缓存大小715MB 88 | // 仅使用圆角大小、阴影、边框的大小计算宽高时 1000次 耗时 1.01s 缓存大小29MB 89 | print("\(cost / (1024 * 1024))MB") 90 | cost = 0 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMakerTests/SFShadowImageMakerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFShadowImageMakerTests.swift 3 | // SFImageMakerTests 4 | // 5 | // Created by Jiang on 2020/2/21. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SFShadowImageMakerTests: XCTestCase { 12 | let shadow = SFShadowImageMaker.init() 13 | override func setUp() { 14 | shadow.shadowBlurRadius = 20 15 | shadow.shadowOffset = CGSize.zero 16 | shadow.shadowColor = UIColor.black 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | } 23 | 24 | func testAllShadowWithNegativeOffset(){ 25 | shadow.position = .all 26 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 27 | XCTAssertTrue(rect == CGRect(x: -20, y: -20, width: 240, height: 240),rect.debugDescription) 28 | } 29 | func testAllShadowWithPositiveOffset(){ 30 | shadow.position = .all 31 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 32 | XCTAssertTrue(rect == CGRect(x: -20, y: -20, width: 240, height: 240),rect.debugDescription) 33 | } 34 | func testHideLeftShadow(){ 35 | shadow.position = [.top,.bottom,.right] 36 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 37 | XCTAssertTrue(rect == CGRect(x: 0, y: -20, width: 220, height: 240),rect.debugDescription) 38 | } 39 | func testHideRightShadow(){ 40 | shadow.position = [.top,.bottom,.left] 41 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 42 | XCTAssertTrue(rect == CGRect(x: -20, y: -20, width: 220, height: 240),rect.debugDescription) 43 | } 44 | 45 | func testHideVerticalShadow(){ 46 | shadow.position = [.left,.right] 47 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 48 | XCTAssertTrue(rect == CGRect(x: -20, y: 0, width: 240, height: 200),rect.debugDescription) 49 | } 50 | 51 | func testHideTopShadow(){ 52 | shadow.position = [.left,.right,.bottom] 53 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 54 | XCTAssertTrue(rect == CGRect(x: -20, y: 0, width: 240, height: 220),rect.debugDescription) 55 | } 56 | func testHideBottomShadow(){ 57 | shadow.position = [.left,.right,.top] 58 | let rect = shadow.viewRect(for: CGSize(width: 200, height: 200)) 59 | XCTAssertTrue(rect == CGRect(x: -20, y: -20, width: 240, height: 220),rect.debugDescription) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /SFImageMaker/SFImageMakerTests/SFWebImageMakerHelperTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFWebImageMakerHelperTest.swift 3 | // SFImageMakerTests 4 | // 5 | // Created by Jiang on 2020/3/14. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class WebImageFakeMananger:NSObject,SFWebImageManagerDelegate{ 12 | var memCache = [String:UIImage]() 13 | var diskCache = [String:UIImage]() 14 | func download(for url: URL, completed: @escaping SFWebImageCompleteHandler) { 15 | DispatchQueue.global().async{ 16 | Thread.sleep(forTimeInterval: 0.5) 17 | completed(UIImage.init(), url, nil) 18 | } 19 | } 20 | 21 | func key(for url: URL, identifier: String?) -> String { 22 | return identifier == nil ? url.absoluteString : url.absoluteString + identifier! 23 | } 24 | 25 | func memeryCache(forKey key: String) -> UIImage? { 26 | return self.memCache[key] 27 | } 28 | 29 | func saveMemeryCache(_ image: UIImage, forKey key: String) { 30 | DispatchQueue.main.async { 31 | self.memCache[key] = image 32 | } 33 | } 34 | 35 | func saveDiskCache(_ image: UIImage, forKey key: String, completed: ((Error?) -> Void)? = nil) { 36 | DispatchQueue.global().async{ 37 | Thread.sleep(forTimeInterval: 0.5) 38 | DispatchQueue.main.async { 39 | self.diskCache[key] = image 40 | completed?(nil) 41 | } 42 | } 43 | } 44 | func diskCache(forKey key: String, completed: ((UIImage?, Error?) -> Void)? = nil) { 45 | DispatchQueue.global().async{ 46 | Thread.sleep(forTimeInterval: 0.5) 47 | DispatchQueue.main.async { 48 | if let image = self.diskCache[key]{ 49 | completed?(image,nil) 50 | }else{ 51 | completed?(nil,NSError.init(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey:"no disk image"])) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | class SFWebImageMakerHelperTest: XCTestCase { 58 | let fakeManager = WebImageFakeMananger() 59 | var helper: SFWebImageMakerHelper! 60 | var noIdentifierKey: String! 61 | var identifierKey: String! 62 | override func setUp() { 63 | let url = URL.init(string: "https://www.test.com/image.png") 64 | let shadow = SFShadowImageMaker.init() 65 | shadow.position = .all 66 | shadow.shadowColor = UIColor.black 67 | shadow.shadowBlurRadius = 20 68 | helper = SFWebImageMakerHelper.init(url: url, processors: [SFBlockImageMaker.circle(),shadow]) 69 | helper.delegate = fakeManager 70 | noIdentifierKey = fakeManager.key(for: helper.url, identifier: nil) 71 | identifierKey = fakeManager.key(for: helper.url, identifier: helper.identifier) 72 | } 73 | 74 | override func tearDown() { 75 | fakeManager.memCache = [:] 76 | fakeManager.diskCache = [:] 77 | } 78 | 79 | func testCacheOnlyStoreResultImage() { 80 | helper.saveOption = [.resultMemery,.resultDisk] 81 | var exception = self.expectation(description: "") 82 | helper.prcoess { (image, url, error) in 83 | if let image = image{ 84 | XCTAssert(image.size != CGSize.zero) 85 | } 86 | exception.fulfill() 87 | } 88 | self.waitForExpectations(timeout: 2, handler: nil) 89 | // wait for disk save 90 | exception = self.expectation(description: "") 91 | DispatchQueue.global().async { 92 | Thread.sleep(forTimeInterval: 1) 93 | exception.fulfill() 94 | } 95 | self.waitForExpectations(timeout: 2, handler: nil) 96 | XCTAssert(fakeManager.memeryCache(forKey: noIdentifierKey) == nil) 97 | XCTAssert(fakeManager.diskCache[noIdentifierKey] == nil) 98 | 99 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) != nil) 100 | XCTAssert(fakeManager.diskCache[identifierKey] != nil) 101 | } 102 | 103 | func testCacheOnlyStoreOriginalImage() { 104 | helper.saveOption = [.originalMemery,.originalDisk] 105 | var exception = self.expectation(description: "") 106 | helper.prcoess { (image, url, error) in 107 | if let image = image{ 108 | XCTAssert(image.size != CGSize.zero) 109 | } 110 | exception.fulfill() 111 | } 112 | self.waitForExpectations(timeout: 2, handler: nil) 113 | // wait for disk save 114 | exception = self.expectation(description: "") 115 | DispatchQueue.global().async { 116 | Thread.sleep(forTimeInterval: 1) 117 | exception.fulfill() 118 | } 119 | self.waitForExpectations(timeout: 2, handler: nil) 120 | XCTAssert(fakeManager.diskCache[noIdentifierKey] != nil) 121 | XCTAssert(fakeManager.memeryCache(forKey: noIdentifierKey) != nil) 122 | XCTAssert(fakeManager.diskCache[identifierKey] == nil) 123 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) == nil) 124 | } 125 | func testCacheStoreInAll() { 126 | helper.saveOption = .all 127 | var exception = self.expectation(description: "") 128 | helper.prcoess { (image, url, error) in 129 | if let image = image{ 130 | XCTAssert(image.size != CGSize.zero) 131 | } 132 | exception.fulfill() 133 | } 134 | self.waitForExpectations(timeout: 3, handler: nil) 135 | // wait for disk save 136 | exception = self.expectation(description: "") 137 | DispatchQueue.global().async { 138 | Thread.sleep(forTimeInterval: 1) 139 | exception.fulfill() 140 | } 141 | self.waitForExpectations(timeout: 2, handler: nil) 142 | XCTAssert(fakeManager.diskCache[noIdentifierKey] != nil) 143 | XCTAssert(fakeManager.diskCache[identifierKey] != nil) 144 | 145 | XCTAssert(fakeManager.memeryCache(forKey: noIdentifierKey) != nil) 146 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) != nil) 147 | } 148 | func testWhileOriginalInDiskCache(){ 149 | helper.saveOption = .all 150 | self.fakeManager.diskCache[noIdentifierKey] = UIImage.init() 151 | var exception = self.expectation(description: "") 152 | helper.prcoess { (image, url, error) in 153 | if let image = image{ 154 | XCTAssert(image.size != CGSize.zero) 155 | } 156 | exception.fulfill() 157 | } 158 | self.waitForExpectations(timeout: 3, handler: nil) 159 | // wait for disk save 160 | exception = self.expectation(description: "") 161 | DispatchQueue.global().async { 162 | Thread.sleep(forTimeInterval: 1) 163 | exception.fulfill() 164 | } 165 | self.waitForExpectations(timeout: 2, handler: nil) 166 | XCTAssert(fakeManager.diskCache[noIdentifierKey] != nil) 167 | XCTAssert(fakeManager.diskCache[identifierKey] != nil) 168 | 169 | XCTAssert(fakeManager.memeryCache(forKey: noIdentifierKey) != nil) 170 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) != nil) 171 | } 172 | 173 | func testWhileResultInDiskCache(){ 174 | helper.saveOption = .all 175 | self.fakeManager.diskCache[identifierKey] = UIImage.init() 176 | var exception = self.expectation(description: "") 177 | helper.prcoess { (image, url, error) in 178 | if let image = image{ 179 | XCTAssert(image.size == CGSize.zero) 180 | } 181 | exception.fulfill() 182 | } 183 | self.waitForExpectations(timeout: 3, handler: nil) 184 | // wait for disk save 185 | exception = self.expectation(description: "") 186 | DispatchQueue.global().async { 187 | Thread.sleep(forTimeInterval: 1) 188 | exception.fulfill() 189 | } 190 | self.waitForExpectations(timeout: 2, handler: nil) 191 | 192 | XCTAssert(fakeManager.diskCache[identifierKey] != nil) 193 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) != nil) 194 | } 195 | 196 | func testWhileOriginalInMemeryCache(){ 197 | helper.saveOption = .all 198 | self.fakeManager.memCache[noIdentifierKey] = UIImage.init() 199 | var exception = self.expectation(description: "") 200 | helper.prcoess { (image, url, error) in 201 | if let image = image{ 202 | XCTAssert(image.size != CGSize.zero) 203 | } 204 | exception.fulfill() 205 | } 206 | self.waitForExpectations(timeout: 3, handler: nil) 207 | // wait for disk save 208 | exception = self.expectation(description: "") 209 | DispatchQueue.global().async { 210 | Thread.sleep(forTimeInterval: 1) 211 | exception.fulfill() 212 | } 213 | self.waitForExpectations(timeout: 2, handler: nil) 214 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) != nil) 215 | } 216 | 217 | func testWhileResultInMemeryCache(){ 218 | helper.saveOption = .all 219 | self.fakeManager.memCache[identifierKey] = UIImage.init() 220 | var exception = self.expectation(description: "") 221 | helper.prcoess { (image, url, error) in 222 | if let image = image{ 223 | XCTAssert(image.size == CGSize.zero) 224 | } 225 | exception.fulfill() 226 | } 227 | self.waitForExpectations(timeout: 3, handler: nil) 228 | // wait for disk save 229 | exception = self.expectation(description: "") 230 | DispatchQueue.global().async { 231 | Thread.sleep(forTimeInterval: 1) 232 | exception.fulfill() 233 | } 234 | self.waitForExpectations(timeout: 2, handler: nil) 235 | XCTAssert(fakeManager.memeryCache(forKey: identifierKey) != nil) 236 | } 237 | 238 | func testPerformanceExample() { 239 | // This is an example of a performance test case. 240 | self.measure { 241 | // Put the code you want to measure the time of here. 242 | } 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /Space.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Space.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Space.xcworkspace/xcuserdata/Memerys.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/Space.xcworkspace/xcuserdata/Memerys.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Space.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/Space.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Space.xcworkspace/xcuserdata/apple.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Space.xcworkspace/xcuserdata/jiang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Space.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/Space.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 18F13929265CB622006E7730 /* SFImageFlowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F13927265CB622006E7730 /* SFImageFlowController.m */; }; 11 | 18F1392D265CBA82006E7730 /* UIImage+Extentions.m in Sources */ = {isa = PBXBuildFile; fileRef = 18F1392C265CBA82006E7730 /* UIImage+Extentions.m */; }; 12 | 1B55706D23F3000A00B20FD5 /* ViewController1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B55706C23F3000900B20FD5 /* ViewController1.swift */; }; 13 | 1B65C53923F53B62007540F6 /* ViewController2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B65C53823F53B62007540F6 /* ViewController2.swift */; }; 14 | 1B6CC3132403740E00189336 /* SFImageMaker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B6CC3122403740E00189336 /* SFImageMaker.framework */; }; 15 | 1B6CC3142403740E00189336 /* SFImageMaker.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1B6CC3122403740E00189336 /* SFImageMaker.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 1B9FB04323E73A2400AA7D9B /* UICornerShadowViewUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE731EC123CE3A89002ABFB5 /* UICornerShadowViewUITests.swift */; }; 17 | EE731EA023CE3A87002ABFB5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE731E9F23CE3A87002ABFB5 /* AppDelegate.swift */; }; 18 | EE731EA223CE3A87002ABFB5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE731EA123CE3A87002ABFB5 /* SceneDelegate.swift */; }; 19 | EE731EA423CE3A87002ABFB5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE731EA323CE3A87002ABFB5 /* ViewController.swift */; }; 20 | EE731EA723CE3A87002ABFB5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EE731EA523CE3A87002ABFB5 /* Main.storyboard */; }; 21 | EE731EA923CE3A89002ABFB5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EE731EA823CE3A89002ABFB5 /* Assets.xcassets */; }; 22 | EE731EAC23CE3A89002ABFB5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EE731EAA23CE3A89002ABFB5 /* LaunchScreen.storyboard */; }; 23 | EE731EB723CE3A89002ABFB5 /* UICornerShadowViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE731EB623CE3A89002ABFB5 /* UICornerShadowViewTests.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | EE731EB323CE3A89002ABFB5 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = EE731E9423CE3A87002ABFB5 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = EE731E9B23CE3A87002ABFB5; 32 | remoteInfo = UICornerShadowView; 33 | }; 34 | EE731EBE23CE3A89002ABFB5 /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = EE731E9423CE3A87002ABFB5 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = EE731E9B23CE3A87002ABFB5; 39 | remoteInfo = UICornerShadowView; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXCopyFilesBuildPhase section */ 44 | 1B6CC3152403740E00189336 /* Embed Frameworks */ = { 45 | isa = PBXCopyFilesBuildPhase; 46 | buildActionMask = 2147483647; 47 | dstPath = ""; 48 | dstSubfolderSpec = 10; 49 | files = ( 50 | 1B6CC3142403740E00189336 /* SFImageMaker.framework in Embed Frameworks */, 51 | ); 52 | name = "Embed Frameworks"; 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXCopyFilesBuildPhase section */ 56 | 57 | /* Begin PBXFileReference section */ 58 | 18F13926265CB622006E7730 /* SFImageFlowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SFImageFlowController.h; sourceTree = ""; }; 59 | 18F13927265CB622006E7730 /* SFImageFlowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SFImageFlowController.m; sourceTree = ""; }; 60 | 18F1392B265CBA82006E7730 /* UIImage+Extentions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Extentions.h"; sourceTree = ""; }; 61 | 18F1392C265CBA82006E7730 /* UIImage+Extentions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Extentions.m"; sourceTree = ""; }; 62 | 1B2CFBA823FEF28D008DBFC1 /* SFImageProcess.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SFImageProcess.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 1B55706C23F3000900B20FD5 /* ViewController1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController1.swift; sourceTree = ""; }; 64 | 1B65C53823F53B62007540F6 /* ViewController2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController2.swift; sourceTree = ""; }; 65 | 1B6CC30B240373EB00189336 /* SFImageMaker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SFImageMaker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 1B6CC3122403740E00189336 /* SFImageMaker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SFImageMaker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | EE731E9C23CE3A87002ABFB5 /* UICornerShadowView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UICornerShadowView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | EE731E9F23CE3A87002ABFB5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 69 | EE731EA123CE3A87002ABFB5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 70 | EE731EA323CE3A87002ABFB5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 71 | EE731EA623CE3A87002ABFB5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 72 | EE731EA823CE3A89002ABFB5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 73 | EE731EAB23CE3A89002ABFB5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 74 | EE731EAD23CE3A89002ABFB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 75 | EE731EB223CE3A89002ABFB5 /* UICornerShadowViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UICornerShadowViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | EE731EB623CE3A89002ABFB5 /* UICornerShadowViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICornerShadowViewTests.swift; sourceTree = ""; }; 77 | EE731EB823CE3A89002ABFB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | EE731EBD23CE3A89002ABFB5 /* UICornerShadowViewUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UICornerShadowViewUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | EE731EC123CE3A89002ABFB5 /* UICornerShadowViewUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICornerShadowViewUITests.swift; sourceTree = ""; }; 80 | EE731EC323CE3A89002ABFB5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81 | EE731ECF23CE3AA6002ABFB5 /* UICornerShadowView-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UICornerShadowView-Bridging-Header.h"; sourceTree = ""; }; 82 | /* End PBXFileReference section */ 83 | 84 | /* Begin PBXFrameworksBuildPhase section */ 85 | EE731E9923CE3A87002ABFB5 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | 1B6CC3132403740E00189336 /* SFImageMaker.framework in Frameworks */, 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | EE731EAF23CE3A89002ABFB5 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | EE731EBA23CE3A89002ABFB5 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | /* End PBXFrameworksBuildPhase section */ 108 | 109 | /* Begin PBXGroup section */ 110 | 1B2CFBA723FEF28D008DBFC1 /* Frameworks */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 1B6CC3122403740E00189336 /* SFImageMaker.framework */, 114 | 1B6CC30B240373EB00189336 /* SFImageMaker.framework */, 115 | 1B2CFBA823FEF28D008DBFC1 /* SFImageProcess.framework */, 116 | ); 117 | name = Frameworks; 118 | sourceTree = ""; 119 | }; 120 | EE731E9323CE3A87002ABFB5 = { 121 | isa = PBXGroup; 122 | children = ( 123 | EE731E9E23CE3A87002ABFB5 /* UICornerShadowView */, 124 | EE731EB523CE3A89002ABFB5 /* UICornerShadowViewTests */, 125 | EE731EC023CE3A89002ABFB5 /* UICornerShadowViewUITests */, 126 | EE731E9D23CE3A87002ABFB5 /* Products */, 127 | 1B2CFBA723FEF28D008DBFC1 /* Frameworks */, 128 | ); 129 | sourceTree = ""; 130 | }; 131 | EE731E9D23CE3A87002ABFB5 /* Products */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | EE731E9C23CE3A87002ABFB5 /* UICornerShadowView.app */, 135 | EE731EB223CE3A89002ABFB5 /* UICornerShadowViewTests.xctest */, 136 | EE731EBD23CE3A89002ABFB5 /* UICornerShadowViewUITests.xctest */, 137 | ); 138 | name = Products; 139 | sourceTree = ""; 140 | }; 141 | EE731E9E23CE3A87002ABFB5 /* UICornerShadowView */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | EE731E9F23CE3A87002ABFB5 /* AppDelegate.swift */, 145 | EE731EA123CE3A87002ABFB5 /* SceneDelegate.swift */, 146 | EE731EA323CE3A87002ABFB5 /* ViewController.swift */, 147 | 1B55706C23F3000900B20FD5 /* ViewController1.swift */, 148 | 1B65C53823F53B62007540F6 /* ViewController2.swift */, 149 | 18F13926265CB622006E7730 /* SFImageFlowController.h */, 150 | 18F13927265CB622006E7730 /* SFImageFlowController.m */, 151 | 18F1392B265CBA82006E7730 /* UIImage+Extentions.h */, 152 | 18F1392C265CBA82006E7730 /* UIImage+Extentions.m */, 153 | EE731EA523CE3A87002ABFB5 /* Main.storyboard */, 154 | EE731EA823CE3A89002ABFB5 /* Assets.xcassets */, 155 | EE731EAA23CE3A89002ABFB5 /* LaunchScreen.storyboard */, 156 | EE731EAD23CE3A89002ABFB5 /* Info.plist */, 157 | EE731ECF23CE3AA6002ABFB5 /* UICornerShadowView-Bridging-Header.h */, 158 | ); 159 | path = UICornerShadowView; 160 | sourceTree = ""; 161 | }; 162 | EE731EB523CE3A89002ABFB5 /* UICornerShadowViewTests */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | EE731EB623CE3A89002ABFB5 /* UICornerShadowViewTests.swift */, 166 | EE731EB823CE3A89002ABFB5 /* Info.plist */, 167 | ); 168 | path = UICornerShadowViewTests; 169 | sourceTree = ""; 170 | }; 171 | EE731EC023CE3A89002ABFB5 /* UICornerShadowViewUITests */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | EE731EC123CE3A89002ABFB5 /* UICornerShadowViewUITests.swift */, 175 | EE731EC323CE3A89002ABFB5 /* Info.plist */, 176 | ); 177 | path = UICornerShadowViewUITests; 178 | sourceTree = ""; 179 | }; 180 | /* End PBXGroup section */ 181 | 182 | /* Begin PBXNativeTarget section */ 183 | EE731E9B23CE3A87002ABFB5 /* UICornerShadowView */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = EE731EC623CE3A89002ABFB5 /* Build configuration list for PBXNativeTarget "UICornerShadowView" */; 186 | buildPhases = ( 187 | EE731E9823CE3A87002ABFB5 /* Sources */, 188 | EE731E9923CE3A87002ABFB5 /* Frameworks */, 189 | EE731E9A23CE3A87002ABFB5 /* Resources */, 190 | 1B6CC3152403740E00189336 /* Embed Frameworks */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | ); 196 | name = UICornerShadowView; 197 | productName = UICornerShadowView; 198 | productReference = EE731E9C23CE3A87002ABFB5 /* UICornerShadowView.app */; 199 | productType = "com.apple.product-type.application"; 200 | }; 201 | EE731EB123CE3A89002ABFB5 /* UICornerShadowViewTests */ = { 202 | isa = PBXNativeTarget; 203 | buildConfigurationList = EE731EC923CE3A89002ABFB5 /* Build configuration list for PBXNativeTarget "UICornerShadowViewTests" */; 204 | buildPhases = ( 205 | EE731EAE23CE3A89002ABFB5 /* Sources */, 206 | EE731EAF23CE3A89002ABFB5 /* Frameworks */, 207 | EE731EB023CE3A89002ABFB5 /* Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | EE731EB423CE3A89002ABFB5 /* PBXTargetDependency */, 213 | ); 214 | name = UICornerShadowViewTests; 215 | productName = UICornerShadowViewTests; 216 | productReference = EE731EB223CE3A89002ABFB5 /* UICornerShadowViewTests.xctest */; 217 | productType = "com.apple.product-type.bundle.unit-test"; 218 | }; 219 | EE731EBC23CE3A89002ABFB5 /* UICornerShadowViewUITests */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = EE731ECC23CE3A89002ABFB5 /* Build configuration list for PBXNativeTarget "UICornerShadowViewUITests" */; 222 | buildPhases = ( 223 | EE731EB923CE3A89002ABFB5 /* Sources */, 224 | EE731EBA23CE3A89002ABFB5 /* Frameworks */, 225 | EE731EBB23CE3A89002ABFB5 /* Resources */, 226 | ); 227 | buildRules = ( 228 | ); 229 | dependencies = ( 230 | EE731EBF23CE3A89002ABFB5 /* PBXTargetDependency */, 231 | ); 232 | name = UICornerShadowViewUITests; 233 | productName = UICornerShadowViewUITests; 234 | productReference = EE731EBD23CE3A89002ABFB5 /* UICornerShadowViewUITests.xctest */; 235 | productType = "com.apple.product-type.bundle.ui-testing"; 236 | }; 237 | /* End PBXNativeTarget section */ 238 | 239 | /* Begin PBXProject section */ 240 | EE731E9423CE3A87002ABFB5 /* Project object */ = { 241 | isa = PBXProject; 242 | attributes = { 243 | LastSwiftUpdateCheck = 1130; 244 | LastUpgradeCheck = 1130; 245 | ORGANIZATIONNAME = SilverFruity; 246 | TargetAttributes = { 247 | EE731E9B23CE3A87002ABFB5 = { 248 | CreatedOnToolsVersion = 11.3; 249 | LastSwiftMigration = 1130; 250 | }; 251 | EE731EB123CE3A89002ABFB5 = { 252 | CreatedOnToolsVersion = 11.3; 253 | TestTargetID = EE731E9B23CE3A87002ABFB5; 254 | }; 255 | EE731EBC23CE3A89002ABFB5 = { 256 | CreatedOnToolsVersion = 11.3; 257 | TestTargetID = EE731E9B23CE3A87002ABFB5; 258 | }; 259 | }; 260 | }; 261 | buildConfigurationList = EE731E9723CE3A87002ABFB5 /* Build configuration list for PBXProject "UICornerShadowView" */; 262 | compatibilityVersion = "Xcode 9.3"; 263 | developmentRegion = en; 264 | hasScannedForEncodings = 0; 265 | knownRegions = ( 266 | en, 267 | Base, 268 | ); 269 | mainGroup = EE731E9323CE3A87002ABFB5; 270 | productRefGroup = EE731E9D23CE3A87002ABFB5 /* Products */; 271 | projectDirPath = ""; 272 | projectRoot = ""; 273 | targets = ( 274 | EE731E9B23CE3A87002ABFB5 /* UICornerShadowView */, 275 | EE731EB123CE3A89002ABFB5 /* UICornerShadowViewTests */, 276 | EE731EBC23CE3A89002ABFB5 /* UICornerShadowViewUITests */, 277 | ); 278 | }; 279 | /* End PBXProject section */ 280 | 281 | /* Begin PBXResourcesBuildPhase section */ 282 | EE731E9A23CE3A87002ABFB5 /* Resources */ = { 283 | isa = PBXResourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | EE731EAC23CE3A89002ABFB5 /* LaunchScreen.storyboard in Resources */, 287 | EE731EA923CE3A89002ABFB5 /* Assets.xcassets in Resources */, 288 | EE731EA723CE3A87002ABFB5 /* Main.storyboard in Resources */, 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | }; 292 | EE731EB023CE3A89002ABFB5 /* Resources */ = { 293 | isa = PBXResourcesBuildPhase; 294 | buildActionMask = 2147483647; 295 | files = ( 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | EE731EBB23CE3A89002ABFB5 /* Resources */ = { 300 | isa = PBXResourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | /* End PBXResourcesBuildPhase section */ 307 | 308 | /* Begin PBXSourcesBuildPhase section */ 309 | EE731E9823CE3A87002ABFB5 /* Sources */ = { 310 | isa = PBXSourcesBuildPhase; 311 | buildActionMask = 2147483647; 312 | files = ( 313 | 18F13929265CB622006E7730 /* SFImageFlowController.m in Sources */, 314 | 1B65C53923F53B62007540F6 /* ViewController2.swift in Sources */, 315 | 1B55706D23F3000A00B20FD5 /* ViewController1.swift in Sources */, 316 | EE731EA423CE3A87002ABFB5 /* ViewController.swift in Sources */, 317 | 18F1392D265CBA82006E7730 /* UIImage+Extentions.m in Sources */, 318 | EE731EA023CE3A87002ABFB5 /* AppDelegate.swift in Sources */, 319 | EE731EA223CE3A87002ABFB5 /* SceneDelegate.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | EE731EAE23CE3A89002ABFB5 /* Sources */ = { 324 | isa = PBXSourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | EE731EB723CE3A89002ABFB5 /* UICornerShadowViewTests.swift in Sources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | EE731EB923CE3A89002ABFB5 /* Sources */ = { 332 | isa = PBXSourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | 1B9FB04323E73A2400AA7D9B /* UICornerShadowViewUITests.swift in Sources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | /* End PBXSourcesBuildPhase section */ 340 | 341 | /* Begin PBXTargetDependency section */ 342 | EE731EB423CE3A89002ABFB5 /* PBXTargetDependency */ = { 343 | isa = PBXTargetDependency; 344 | target = EE731E9B23CE3A87002ABFB5 /* UICornerShadowView */; 345 | targetProxy = EE731EB323CE3A89002ABFB5 /* PBXContainerItemProxy */; 346 | }; 347 | EE731EBF23CE3A89002ABFB5 /* PBXTargetDependency */ = { 348 | isa = PBXTargetDependency; 349 | target = EE731E9B23CE3A87002ABFB5 /* UICornerShadowView */; 350 | targetProxy = EE731EBE23CE3A89002ABFB5 /* PBXContainerItemProxy */; 351 | }; 352 | /* End PBXTargetDependency section */ 353 | 354 | /* Begin PBXVariantGroup section */ 355 | EE731EA523CE3A87002ABFB5 /* Main.storyboard */ = { 356 | isa = PBXVariantGroup; 357 | children = ( 358 | EE731EA623CE3A87002ABFB5 /* Base */, 359 | ); 360 | name = Main.storyboard; 361 | sourceTree = ""; 362 | }; 363 | EE731EAA23CE3A89002ABFB5 /* LaunchScreen.storyboard */ = { 364 | isa = PBXVariantGroup; 365 | children = ( 366 | EE731EAB23CE3A89002ABFB5 /* Base */, 367 | ); 368 | name = LaunchScreen.storyboard; 369 | sourceTree = ""; 370 | }; 371 | /* End PBXVariantGroup section */ 372 | 373 | /* Begin XCBuildConfiguration section */ 374 | EE731EC423CE3A89002ABFB5 /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 381 | CLANG_CXX_LIBRARY = "libc++"; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_ENABLE_OBJC_WEAK = YES; 385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_COMMA = YES; 388 | CLANG_WARN_CONSTANT_CONVERSION = YES; 389 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 392 | CLANG_WARN_EMPTY_BODY = YES; 393 | CLANG_WARN_ENUM_CONVERSION = YES; 394 | CLANG_WARN_INFINITE_RECURSION = YES; 395 | CLANG_WARN_INT_CONVERSION = YES; 396 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 398 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 399 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 400 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 401 | CLANG_WARN_STRICT_PROTOTYPES = YES; 402 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 403 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 404 | CLANG_WARN_UNREACHABLE_CODE = YES; 405 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 406 | COPY_PHASE_STRIP = NO; 407 | DEBUG_INFORMATION_FORMAT = dwarf; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | ENABLE_TESTABILITY = YES; 410 | GCC_C_LANGUAGE_STANDARD = gnu11; 411 | GCC_DYNAMIC_NO_PIC = NO; 412 | GCC_NO_COMMON_BLOCKS = YES; 413 | GCC_OPTIMIZATION_LEVEL = 0; 414 | GCC_PREPROCESSOR_DEFINITIONS = ( 415 | "DEBUG=1", 416 | "$(inherited)", 417 | ); 418 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 419 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 420 | GCC_WARN_UNDECLARED_SELECTOR = YES; 421 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 422 | GCC_WARN_UNUSED_FUNCTION = YES; 423 | GCC_WARN_UNUSED_VARIABLE = YES; 424 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 425 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 426 | MTL_FAST_MATH = YES; 427 | ONLY_ACTIVE_ARCH = YES; 428 | SDKROOT = iphoneos; 429 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 430 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 431 | }; 432 | name = Debug; 433 | }; 434 | EE731EC523CE3A89002ABFB5 /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | ALWAYS_SEARCH_USER_PATHS = NO; 438 | CLANG_ANALYZER_NONNULL = YES; 439 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 440 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 441 | CLANG_CXX_LIBRARY = "libc++"; 442 | CLANG_ENABLE_MODULES = YES; 443 | CLANG_ENABLE_OBJC_ARC = YES; 444 | CLANG_ENABLE_OBJC_WEAK = YES; 445 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 446 | CLANG_WARN_BOOL_CONVERSION = YES; 447 | CLANG_WARN_COMMA = YES; 448 | CLANG_WARN_CONSTANT_CONVERSION = YES; 449 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 450 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 451 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 452 | CLANG_WARN_EMPTY_BODY = YES; 453 | CLANG_WARN_ENUM_CONVERSION = YES; 454 | CLANG_WARN_INFINITE_RECURSION = YES; 455 | CLANG_WARN_INT_CONVERSION = YES; 456 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 457 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 458 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 459 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 460 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 461 | CLANG_WARN_STRICT_PROTOTYPES = YES; 462 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 463 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 464 | CLANG_WARN_UNREACHABLE_CODE = YES; 465 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 466 | COPY_PHASE_STRIP = NO; 467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 468 | ENABLE_NS_ASSERTIONS = NO; 469 | ENABLE_STRICT_OBJC_MSGSEND = YES; 470 | GCC_C_LANGUAGE_STANDARD = gnu11; 471 | GCC_NO_COMMON_BLOCKS = YES; 472 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 473 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 474 | GCC_WARN_UNDECLARED_SELECTOR = YES; 475 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 476 | GCC_WARN_UNUSED_FUNCTION = YES; 477 | GCC_WARN_UNUSED_VARIABLE = YES; 478 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 479 | MTL_ENABLE_DEBUG_INFO = NO; 480 | MTL_FAST_MATH = YES; 481 | SDKROOT = iphoneos; 482 | SWIFT_COMPILATION_MODE = wholemodule; 483 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 484 | VALIDATE_PRODUCT = YES; 485 | }; 486 | name = Release; 487 | }; 488 | EE731EC723CE3A89002ABFB5 /* Debug */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 492 | CLANG_ENABLE_MODULES = YES; 493 | CODE_SIGN_IDENTITY = "Apple Development"; 494 | CODE_SIGN_STYLE = Automatic; 495 | DEVELOPMENT_TEAM = SH4YVD7HDL; 496 | INFOPLIST_FILE = UICornerShadowView/Info.plist; 497 | LD_RUNPATH_SEARCH_PATHS = ( 498 | "$(inherited)", 499 | "@executable_path/Frameworks", 500 | ); 501 | PRODUCT_BUNDLE_IDENTIFIER = silverfruity.UICornerShadowView; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | PROVISIONING_PROFILE_SPECIFIER = ""; 504 | SWIFT_OBJC_BRIDGING_HEADER = "UICornerShadowView/UICornerShadowView-Bridging-Header.h"; 505 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 506 | SWIFT_VERSION = 5.0; 507 | TARGETED_DEVICE_FAMILY = "1,2"; 508 | }; 509 | name = Debug; 510 | }; 511 | EE731EC823CE3A89002ABFB5 /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 515 | CLANG_ENABLE_MODULES = YES; 516 | CODE_SIGN_IDENTITY = "Apple Development"; 517 | CODE_SIGN_STYLE = Automatic; 518 | DEVELOPMENT_TEAM = SH4YVD7HDL; 519 | INFOPLIST_FILE = UICornerShadowView/Info.plist; 520 | LD_RUNPATH_SEARCH_PATHS = ( 521 | "$(inherited)", 522 | "@executable_path/Frameworks", 523 | ); 524 | PRODUCT_BUNDLE_IDENTIFIER = silverfruity.UICornerShadowView; 525 | PRODUCT_NAME = "$(TARGET_NAME)"; 526 | PROVISIONING_PROFILE_SPECIFIER = ""; 527 | SWIFT_OBJC_BRIDGING_HEADER = "UICornerShadowView/UICornerShadowView-Bridging-Header.h"; 528 | SWIFT_VERSION = 5.0; 529 | TARGETED_DEVICE_FAMILY = "1,2"; 530 | }; 531 | name = Release; 532 | }; 533 | EE731ECA23CE3A89002ABFB5 /* Debug */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 537 | BUNDLE_LOADER = "$(TEST_HOST)"; 538 | CODE_SIGN_STYLE = Automatic; 539 | DEVELOPMENT_TEAM = SH4YVD7HDL; 540 | INFOPLIST_FILE = UICornerShadowViewTests/Info.plist; 541 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 542 | LD_RUNPATH_SEARCH_PATHS = ( 543 | "$(inherited)", 544 | "@executable_path/Frameworks", 545 | "@loader_path/Frameworks", 546 | ); 547 | PRODUCT_BUNDLE_IDENTIFIER = silverfruity.UICornerShadowViewTests; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | SWIFT_VERSION = 5.0; 550 | TARGETED_DEVICE_FAMILY = "1,2"; 551 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UICornerShadowView.app/UICornerShadowView"; 552 | }; 553 | name = Debug; 554 | }; 555 | EE731ECB23CE3A89002ABFB5 /* Release */ = { 556 | isa = XCBuildConfiguration; 557 | buildSettings = { 558 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 559 | BUNDLE_LOADER = "$(TEST_HOST)"; 560 | CODE_SIGN_STYLE = Automatic; 561 | DEVELOPMENT_TEAM = SH4YVD7HDL; 562 | INFOPLIST_FILE = UICornerShadowViewTests/Info.plist; 563 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 564 | LD_RUNPATH_SEARCH_PATHS = ( 565 | "$(inherited)", 566 | "@executable_path/Frameworks", 567 | "@loader_path/Frameworks", 568 | ); 569 | PRODUCT_BUNDLE_IDENTIFIER = silverfruity.UICornerShadowViewTests; 570 | PRODUCT_NAME = "$(TARGET_NAME)"; 571 | SWIFT_VERSION = 5.0; 572 | TARGETED_DEVICE_FAMILY = "1,2"; 573 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/UICornerShadowView.app/UICornerShadowView"; 574 | }; 575 | name = Release; 576 | }; 577 | EE731ECD23CE3A89002ABFB5 /* Debug */ = { 578 | isa = XCBuildConfiguration; 579 | buildSettings = { 580 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 581 | CODE_SIGN_STYLE = Automatic; 582 | DEVELOPMENT_TEAM = SH4YVD7HDL; 583 | INFOPLIST_FILE = UICornerShadowViewUITests/Info.plist; 584 | LD_RUNPATH_SEARCH_PATHS = ( 585 | "$(inherited)", 586 | "@executable_path/Frameworks", 587 | "@loader_path/Frameworks", 588 | ); 589 | PRODUCT_BUNDLE_IDENTIFIER = silverfruity.UICornerShadowViewUITests; 590 | PRODUCT_NAME = "$(TARGET_NAME)"; 591 | SWIFT_VERSION = 5.0; 592 | TARGETED_DEVICE_FAMILY = "1,2"; 593 | TEST_TARGET_NAME = UICornerShadowView; 594 | }; 595 | name = Debug; 596 | }; 597 | EE731ECE23CE3A89002ABFB5 /* Release */ = { 598 | isa = XCBuildConfiguration; 599 | buildSettings = { 600 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 601 | CODE_SIGN_STYLE = Automatic; 602 | DEVELOPMENT_TEAM = SH4YVD7HDL; 603 | INFOPLIST_FILE = UICornerShadowViewUITests/Info.plist; 604 | LD_RUNPATH_SEARCH_PATHS = ( 605 | "$(inherited)", 606 | "@executable_path/Frameworks", 607 | "@loader_path/Frameworks", 608 | ); 609 | PRODUCT_BUNDLE_IDENTIFIER = silverfruity.UICornerShadowViewUITests; 610 | PRODUCT_NAME = "$(TARGET_NAME)"; 611 | SWIFT_VERSION = 5.0; 612 | TARGETED_DEVICE_FAMILY = "1,2"; 613 | TEST_TARGET_NAME = UICornerShadowView; 614 | }; 615 | name = Release; 616 | }; 617 | /* End XCBuildConfiguration section */ 618 | 619 | /* Begin XCConfigurationList section */ 620 | EE731E9723CE3A87002ABFB5 /* Build configuration list for PBXProject "UICornerShadowView" */ = { 621 | isa = XCConfigurationList; 622 | buildConfigurations = ( 623 | EE731EC423CE3A89002ABFB5 /* Debug */, 624 | EE731EC523CE3A89002ABFB5 /* Release */, 625 | ); 626 | defaultConfigurationIsVisible = 0; 627 | defaultConfigurationName = Release; 628 | }; 629 | EE731EC623CE3A89002ABFB5 /* Build configuration list for PBXNativeTarget "UICornerShadowView" */ = { 630 | isa = XCConfigurationList; 631 | buildConfigurations = ( 632 | EE731EC723CE3A89002ABFB5 /* Debug */, 633 | EE731EC823CE3A89002ABFB5 /* Release */, 634 | ); 635 | defaultConfigurationIsVisible = 0; 636 | defaultConfigurationName = Release; 637 | }; 638 | EE731EC923CE3A89002ABFB5 /* Build configuration list for PBXNativeTarget "UICornerShadowViewTests" */ = { 639 | isa = XCConfigurationList; 640 | buildConfigurations = ( 641 | EE731ECA23CE3A89002ABFB5 /* Debug */, 642 | EE731ECB23CE3A89002ABFB5 /* Release */, 643 | ); 644 | defaultConfigurationIsVisible = 0; 645 | defaultConfigurationName = Release; 646 | }; 647 | EE731ECC23CE3A89002ABFB5 /* Build configuration list for PBXNativeTarget "UICornerShadowViewUITests" */ = { 648 | isa = XCConfigurationList; 649 | buildConfigurations = ( 650 | EE731ECD23CE3A89002ABFB5 /* Debug */, 651 | EE731ECE23CE3A89002ABFB5 /* Release */, 652 | ); 653 | defaultConfigurationIsVisible = 0; 654 | defaultConfigurationName = Release; 655 | }; 656 | /* End XCConfigurationList section */ 657 | }; 658 | rootObject = EE731E9423CE3A87002ABFB5 /* Project object */; 659 | } 660 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/project.xcworkspace/xcuserdata/Memerys.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/UICornerShadowView.xcodeproj/project.xcworkspace/xcuserdata/Memerys.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/UICornerShadowView.xcodeproj/project.xcworkspace/xcuserdata/apple.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverFruity/UICornerShadowView/97d23060a8bfc4beb3c8b57d900550f09c8fe91c/UICornerShadowView.xcodeproj/project.xcworkspace/xcuserdata/mac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/xcshareddata/xcschemes/UICornerShadowView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/xcuserdata/Memerys.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/xcuserdata/Memerys.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UICornerShadowView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/xcuserdata/jiang.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/xcuserdata/jiang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UICornerShadowView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | EE731E9B23CE3A87002ABFB5 16 | 17 | primary 18 | 19 | 20 | EE731EB123CE3A89002ABFB5 21 | 22 | primary 23 | 24 | 25 | EE731EBC23CE3A89002ABFB5 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /UICornerShadowView.xcodeproj/xcuserdata/mac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UICornerShadowView.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /UICornerShadowView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/1/15. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFImageMaker 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | let appearance = SFCSBView.appearance() 19 | appearance.cornerRadius = 20 20 | appearance.rectCornner = [.topLeft,.topRight,.bottomLeft,.bottomRight] 21 | appearance.shadowPosition = [.left,.top,.right,.bottom] 22 | appearance.shadowColor = UIColor.black.withAlphaComponent(0.6) 23 | appearance.shadowRadius = 20 24 | appearance.borderColor = UIColor.systemBlue 25 | appearance.borderWidth = 5 26 | appearance.borderPosition = .all 27 | return true 28 | } 29 | 30 | // MARK: UISceneSession Lifecycle 31 | 32 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 33 | // Called when a new scene session is being created. 34 | // Use this method to select a configuration to create the new scene with. 35 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 36 | } 37 | 38 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 39 | // Called when the user discards a scene session. 40 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 41 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /UICornerShadowView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /UICornerShadowView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /UICornerShadowView/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 | -------------------------------------------------------------------------------- /UICornerShadowView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /UICornerShadowView/README.md: -------------------------------------------------------------------------------- 1 | # UICornerShadowView 2 | -------------------------------------------------------------------------------- /UICornerShadowView/SFImageFlowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageFlowController.h 3 | // UICornerShadowView 4 | // 5 | // Created by APPLE on 2021/5/25. 6 | // Copyright © 2021 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SFImageFlowController : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /UICornerShadowView/SFImageFlowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SFImageFlowController.m 3 | // UICornerShadowView 4 | // 5 | // Created by APPLE on 2021/5/25. 6 | // Copyright © 2021 SilverFruity. All rights reserved. 7 | // 8 | 9 | #import "SFImageFlowController.h" 10 | #import 11 | #import "UIImage+Extentions.h" 12 | @interface SFImageFlowController () 13 | @property (weak, nonatomic) IBOutlet UIButton *cornerBorderButton; 14 | @property (weak, nonatomic) IBOutlet UIButton *gradientButton; 15 | @property (weak, nonatomic) IBOutlet UIButton *avatarButton; 16 | @property (weak, nonatomic) IBOutlet SFCSBView *testView; 17 | 18 | @property (weak, nonatomic) IBOutlet SFCSBView *topView; 19 | @property (weak, nonatomic) IBOutlet SFCSBView *bottomView; 20 | @end 21 | 22 | @implementation SFImageFlowController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | [self.cornerBorderButton setBackgroundImage:UIColor.lightGrayColor.sf_flow.corner(10, UIRectCornerAllCorners).border(0.5, UIColor.blackColor).image forState:UIControlStateNormal]; 27 | UIImage *image = @[UIColor.redColor,UIColor.purpleColor].sf_gradientFlow(YES,self.gradientButton.frame.size).corner(10, UIRectCornerTopLeft|UIRectCornerBottomRight).border(1, UIColor.blackColor).image; 28 | [self.gradientButton setBackgroundImage:image forState:UIControlStateNormal]; 29 | // image = @[UIColor.redColor,UIColor.greenColor].sf_gradientFlow(YES,CGSizeMake(40, 40)).blur(SFBlurEffectLight).circle.image; 30 | image = @[UIColor.redColor,UIColor.greenColor].sf_gradientFlow(YES,CGSizeMake(40, 40)).blur(SFBlurEffectLight).circle.border(1, UIColor.blackColor).shadow([UIColor.blackColor colorWithAlphaComponent:0.5], CGSizeMake(10, 10), 10).image; 31 | [self.avatarButton setBackgroundImage:image forState:UIControlStateNormal]; 32 | 33 | image = [UIImage new].sf_flow.corner(10, UIRectCornerAllCorners).border(1, UIColor.redColor).image; 34 | 35 | self.testView.cornerRadius = 8; 36 | self.testView.shadowRadius = 20; 37 | self.testView.shadowColor = UIColor.blackColor; 38 | self.testView.borderWidth = 0; 39 | self.testView.borderColor = [UIColor clearColor]; 40 | 41 | 42 | self.topView.cornerRadius = 0; 43 | self.topView.shadowRadius = 20; 44 | self.topView.shadowColor = UIColor.blackColor; 45 | self.topView.rectCornner = UIRectCornerTopLeft | UIRectCornerBottomRight; 46 | self.topView.shadowPosition = UIShadowPostionLeft | UIShadowPostionRight; 47 | self.topView.shadowFixValue = -0.2455; 48 | self.topView.borderWidth = 0; 49 | self.topView.borderColor = [UIColor clearColor]; 50 | 51 | self.bottomView.cornerRadius = 10; 52 | self.bottomView.shadowRadius = 20; 53 | self.bottomView.shadowFixValue = -0.2455; 54 | self.bottomView.shadowPosition = UIShadowPostionLeft | UIShadowPostionRight; 55 | self.bottomView.rectCornner = UIRectCornerBottomLeft | UIRectCornerTopRight; 56 | self.bottomView.shadowColor = UIColor.blackColor; 57 | self.bottomView.borderWidth = 0; 58 | self.bottomView.borderColor = [UIColor clearColor]; 59 | 60 | } 61 | 62 | /* 63 | #pragma mark - Navigation 64 | 65 | // In a storyboard-based application, you will often want to do a little preparation before navigation 66 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 67 | // Get the new view controller using [segue destinationViewController]. 68 | // Pass the selected object to the new view controller. 69 | } 70 | */ 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /UICornerShadowView/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/1/15. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /UICornerShadowView/UICornerShadowView-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "SFImageFlowController.h" 6 | -------------------------------------------------------------------------------- /UICornerShadowView/UICornerShadowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICornerShadowView.swift 3 | // 4 | // Created by Jiang on 2019/12/5. 5 | // Copyright © 2019 goute. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import CoreGraphics 10 | import SFImageMaker 11 | 12 | class CustomRenderCache{ 13 | static let `default` = CustomRenderCache() 14 | private let cache: NSCache = NSCache.init() 15 | private var hasCost: Int = 0 16 | init() { 17 | //10MB 18 | self.cache.totalCostLimit = 10 * 1024 * 1024 19 | } 20 | open func object(forKey key: String) -> UIImage?{ 21 | return self.cache.object(forKey: NSString.init(string: key)) 22 | } 23 | 24 | open func setObject(_ obj: UIImage?, forKey key: String){ 25 | guard let obj = obj else{ 26 | return 27 | } 28 | // 32: RGBA 29 | var bytes = Int(obj.size.width * obj.size.height * 4 * UIScreen.main.scale * UIScreen.main.scale) 30 | if let cgimg = obj.cgImage{ 31 | bytes = cgimg.height * cgimg.width * (cgimg.bitsPerPixel / cgimg.bitsPerComponent) 32 | } 33 | hasCost += bytes 34 | self.cache.setObject(obj, forKey: NSString.init(string: key), cost: bytes) 35 | } 36 | 37 | open func removeObject(forKey key: String){ 38 | self.cache.removeObject(forKey: NSString.init(string: key)) 39 | } 40 | func clear(){ 41 | self.cache.removeAllObjects() 42 | } 43 | func cacheCost()->Int{ 44 | return hasCost 45 | } 46 | } 47 | 48 | class UICornerShadowView: UIView { 49 | var backGroundImageView: UIImageView! 50 | // 加下划线的原因是与SwifterSwift库有冲突 51 | // FIXME: 后续添加UIAppearance 全局设置默认值 52 | @IBInspectable public var _rectCornner: UIRectCorner = .allCorners 53 | @IBInspectable public var _cornerRadius: CGFloat = 8 54 | @IBInspectable public var _shadowColor: UIColor = UIColor.black.withAlphaComponent(0.08) 55 | @IBInspectable public var _shadowOffset: CGSize = CGSize.init(width: 0, height: 0) 56 | @IBInspectable public var _shadowRadius: CGFloat = 0 57 | @IBInspectable public var _borderWidth: CGFloat = 0 58 | @IBInspectable public var _borderColor: UIColor = UIColor.clear 59 | var _shadowPosition:UIShadowPostion = .all 60 | var _borderPosition:UIBorderPostion = .all 61 | 62 | var shadowProcesser: SFShadowImageMaker{ 63 | let shadow = SFShadowImageMaker.init() 64 | shadow.shadowColor = self._shadowColor 65 | shadow.shadowOffset = self._shadowOffset 66 | shadow.shadowBlurRadius = self._shadowRadius 67 | shadow.position = self._shadowPosition 68 | return shadow; 69 | } 70 | var rectCornerProcesser: SFCornerImageMaker{ 71 | let rectCorner = SFCornerImageMaker.init() 72 | rectCorner.position = self._rectCornner 73 | rectCorner.radius = self._cornerRadius 74 | return rectCorner 75 | } 76 | var borderProcesser: SFBorderImageMaker{ 77 | let border = SFBorderImageMaker.init() 78 | border.width = self._borderWidth 79 | border.color = self._borderColor 80 | border.position = self._borderPosition 81 | border.cornerMaker = self.rectCornerProcesser 82 | return border; 83 | } 84 | var generalImageProcesser: SFColorImageMaker{ 85 | // Radius ShadowRadius BorderWidth 取最大值 86 | var maxValue = self.rectCornerProcesser.radius > (self.borderProcesser.width + 1) && self.rectCornerProcesser.isEnable ? self.rectCornerProcesser.radius : self.borderProcesser.width + 1 87 | maxValue = self.shadowProcesser.shadowBlurRadius > maxValue ? self.shadowProcesser.shadowBlurRadius : maxValue 88 | let size = CGSize.init(width: maxValue * 2, height: maxValue * 2) 89 | return SFColorImageMaker.init(color: self.initailBackGroundColor, size: size) 90 | } 91 | private var initailBackGroundColor = UIColor.white 92 | // 针对上一次图片的identifier的 93 | private var lastBackGroundImageIdentifer = "" 94 | required init?(coder: NSCoder) { 95 | super.init(coder: coder) 96 | self.backGroundImageView = UIImageView.init() 97 | self.addSubview(self.backGroundImageView) 98 | self.backGroundImageView.clipsToBounds = false 99 | self.clipsToBounds = false 100 | } 101 | override init(frame: CGRect) { 102 | super.init(frame: frame) 103 | self.backGroundImageView = UIImageView.init() 104 | self.addSubview(self.backGroundImageView) 105 | self.backGroundImageView.clipsToBounds = false 106 | self.clipsToBounds = false 107 | } 108 | override func layoutSubviews() { 109 | super.layoutSubviews() 110 | self.reloadBackGourndImage() 111 | } 112 | func reloadBackGourndImage(){ 113 | self.sendSubviewToBack(self.backGroundImageView) 114 | if self.backgroundColor != UIColor.clear && self.backgroundColor != nil{ 115 | self.initailBackGroundColor = self.backgroundColor! 116 | } 117 | //保证不会在子线程中调用self获取值 118 | let generalImage = self.generalImageProcesser 119 | let shadow = self.shadowProcesser 120 | let rectCorner = self.rectCornerProcesser 121 | let border = self.borderProcesser 122 | let imageIdentifier = self.identifier() 123 | 124 | //FIXME: 外边框 border和shadow同时存在时,宽高的计算,一大一小。 125 | //FIXME: 外边框 border和shadow只有一者存在时,宽高的计算。 126 | var viewFrame = shadow.viewRect(for: self.bounds.size) 127 | //insetValue: 特意增加的误差 0.o。解决tableView显示时,cell上下阴影衔接时会有一空缺的问题。 128 | let insetValue :CGFloat = -1 129 | // CGRect(2,2,2,2) -> CGRect(1,1,4,4) 130 | viewFrame = viewFrame.inset(by: UIEdgeInsets(top: insetValue, left: insetValue, bottom: insetValue, right: insetValue)) 131 | // 每修改一次subview的frame,view会调用layoutSubviews方法。 132 | // 目的:在高度重用UICornerShadowView的情况,并且每次都更新的情况下,减少frame更新。 133 | // 如果上一次的identifer相同说明是重用图片 134 | // 如果当前frame和需要的frame相同,也不用更新frame 135 | if imageIdentifier != lastBackGroundImageIdentifer || self.backGroundImageView.frame != viewFrame{ 136 | self.backGroundImageView.frame = viewFrame 137 | } 138 | if let cacheImage = CustomRenderCache.default.object(forKey: imageIdentifier){ 139 | self.backGroundImageView.image = cacheImage 140 | self.backgroundColor = UIColor.clear 141 | self.lastBackGroundImageIdentifer = imageIdentifier 142 | return 143 | } 144 | DispatchQueue.global().async { [unowned self] in 145 | let flow = SFImageFlow.init(generator: generalImage) 146 | flow.processors = [rectCorner,border,shadow]; 147 | var image = flow.process() 148 | if shadow.isEnable{ 149 | let insets = shadow.convasEdgeInsets() 150 | image = image.resizableImageCenter(withInset: insets) 151 | }else{ 152 | image = image.resizableImageCenterMode() 153 | } 154 | CustomRenderCache.default.setObject(image, forKey: imageIdentifier) 155 | DispatchQueue.main.async { [weak self] in 156 | if self == nil{ 157 | return 158 | } 159 | self?.lastBackGroundImageIdentifer = imageIdentifier 160 | self?.backGroundImageView.image = image 161 | } 162 | } 163 | self.backgroundColor = UIColor.clear 164 | } 165 | func identifier()->String{ 166 | return self.generalImageProcesser.identifier() + self.rectCornerProcesser.identifier() + self.borderProcesser.identifier() + self.shadowProcesser.identifier() 167 | } 168 | 169 | #if DEBUG 170 | func debugInfo()->String{ 171 | return """ 172 | initailBackGroundColor=\(initailBackGroundColor);\n 173 | cornerRadius=\(_cornerRadius);\n 174 | rectCornner=\(_rectCornner);\n 175 | shadowOffset=\(_shadowOffset);\n 176 | shadowRadius=\(_shadowRadius);\n 177 | shadowColor=\(_shadowColor);\n 178 | shadowPosition=\(_shadowPosition);\n 179 | borderColor=\(_borderColor);\n 180 | borderWidth=\(_borderWidth);\n 181 | borderPosition=\(_borderPosition); 182 | """ 183 | } 184 | #endif 185 | } 186 | -------------------------------------------------------------------------------- /UICornerShadowView/UIImage+Extentions.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+imageExtention.h 3 | // SharedGym 4 | // 5 | // Created by Jiang on 2017/4/18. 6 | // 7 | // 8 | 9 | #import 10 | NS_ASSUME_NONNULL_BEGIN 11 | @interface UIImage (Radius) 12 | ///设置中点为拉伸区域 13 | - (UIImage *)resizableImageCenterMode; 14 | - (UIImage *)resizableImageCenterWithInset:(UIEdgeInsets)inset; 15 | @end 16 | 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /UICornerShadowView/UIImage+Extentions.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+imageExtention.m 3 | // 4 | // Created by Jiang on 2017/4/18. 5 | // 6 | // 7 | 8 | #import "UIImage+Extentions.h" 9 | #import 10 | #import 11 | 12 | #ifndef SCREEN_WIDTH 13 | #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width 14 | #endif 15 | #ifndef SCREEN_HEIGHT 16 | #define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height 17 | #endif 18 | 19 | #ifndef SCREEN_SCALE 20 | #define SCREEN_SCALE [UIScreen mainScreen].scale 21 | #endif 22 | @implementation UIImage (Radius) 23 | - (UIImage *)resizableImageCenterMode{ 24 | return [self resizableImageWithCapInsets:UIEdgeInsetsMake(self.size.height / 2, self.size.width / 2, self.size.height / 2, self.size.width / 2)]; 25 | } 26 | - (UIImage *)resizableImageCenterWithInset:(UIEdgeInsets)inset{ 27 | CGFloat x = (self.size.width - inset.left - inset.right) / 2; 28 | CGFloat y = (self.size.height - inset.top - inset.bottom) / 2; 29 | return [self resizableImageWithCapInsets:UIEdgeInsetsMake(y + inset.top, x + inset.left, y + inset.bottom, x + inset.right)]; 30 | } 31 | @end 32 | -------------------------------------------------------------------------------- /UICornerShadowView/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/1/15. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFImageMaker 11 | class ViewController: UIViewController { 12 | @IBOutlet weak var shadowView: SFCSBView! 13 | 14 | @IBOutlet weak var layerCorner: UIView! 15 | @IBOutlet weak var layerShadow: UIView! 16 | 17 | @IBOutlet weak var leftRightShadowView: SFCSBView! 18 | @IBOutlet weak var topBottomShadowView: SFCSBView! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | self.shadowView.cornerRadius = 20 23 | self.shadowView.rectCornner = [.topLeft,.topRight,.bottomLeft,.bottomRight] 24 | self.shadowView.shadowPosition = [.left,.top,.right,.bottom] 25 | self.shadowView.shadowColor = UIColor.black.withAlphaComponent(0.6) 26 | self.shadowView.shadowRadius = 20 27 | self.shadowView.borderColor = UIColor.systemBlue 28 | self.shadowView.borderWidth = 5 29 | self.shadowView.borderPosition = .all 30 | 31 | self.layerCorner.layer.borderWidth = self.shadowView.borderWidth 32 | self.layerCorner.layer.borderColor = self.shadowView.borderColor.cgColor 33 | self.layerCorner.layer.cornerRadius = self.shadowView.cornerRadius 34 | self.layerCorner.layer.masksToBounds = true 35 | 36 | self.layerShadow.backgroundColor = UIColor.clear 37 | self.layerShadow.layer.shadowOpacity = 1.0 38 | self.layerShadow.layer.shadowOffset = CGSize.zero 39 | //FIXME: 和系统相同需要 /2 40 | self.layerShadow.layer.shadowRadius = self.shadowView.shadowRadius / 2 41 | self.layerShadow.layer.shadowColor = self.shadowView.shadowColor.cgColor 42 | 43 | self.leftRightShadowView.cornerRadius = 0 44 | self.leftRightShadowView.shadowPosition = [.left,.right] 45 | self.leftRightShadowView.shadowColor = UIColor.black.withAlphaComponent(0.6) 46 | self.leftRightShadowView.shadowRadius = 20 47 | self.leftRightShadowView.borderWidth = 5 48 | self.leftRightShadowView.borderColor = UIColor.systemBlue 49 | self.leftRightShadowView.borderPosition = [.left,.right] 50 | 51 | self.topBottomShadowView.cornerRadius = 0 52 | self.topBottomShadowView.shadowPosition = [.top,.bottom] 53 | self.topBottomShadowView.shadowColor = UIColor.black.withAlphaComponent(0.6) 54 | self.topBottomShadowView.shadowRadius = 20 55 | self.topBottomShadowView.borderWidth = 5 56 | self.topBottomShadowView.borderColor = UIColor.systemBlue 57 | self.topBottomShadowView.borderPosition = [.top,.bottom] 58 | 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /UICornerShadowView/ViewController1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController1.swift 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/2/11. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFImageMaker 11 | class RandomCell: UITableViewCell{ 12 | @IBOutlet weak var container: SFCSBView! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | 16 | } 17 | } 18 | class ViewController1: UIViewController,UITableViewDataSource,UITableViewDelegate{ 19 | 20 | @IBOutlet weak var tableView: UITableView! 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | tableView.rowHeight = 100 25 | } 26 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 27 | return 1000; 28 | } 29 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 30 | let cell = tableView.dequeueReusableCell(withIdentifier: "RandomCell") as! RandomCell 31 | cell.container.backgroundColor = UIColor.init(red: CGFloat.random(in: 0...10) / 10, green: CGFloat.random(in: 0...10) / 10, blue: CGFloat.random(in: 0...10) / 10, alpha: 1) 32 | cell.container.cornerRadius = CGFloat(Int.random(in: 10..<20)) 33 | let cornerPositions: [UIRectCorner] = [.topLeft,.topRight,.bottomLeft,.bottomRight] 34 | var cornerResult = [UIRectCorner]() 35 | var cornerCount = Int.random(in: 0..<4) 36 | while cornerCount >= 0 { 37 | cornerResult.append(cornerPositions[Int.random(in: 0..<4)]) 38 | cornerCount -= 1 39 | } 40 | cell.container.rectCornner = UIRectCorner.init(cornerResult) 41 | 42 | let shadowPositions: [UIShadowPostion] = [.left,.top,.right,.bottom] 43 | var shadowResult = [UIShadowPostion]() 44 | var shadowResultCount = Int.random(in: 0..<4) 45 | while shadowResultCount >= 0 { 46 | shadowResult.append(shadowPositions[Int.random(in: 0..<4)]) 47 | shadowResultCount -= 1 48 | } 49 | cell.container.shadowPosition = UIShadowPostion.init(shadowResult) 50 | cell.container.shadowColor = UIColor.black.withAlphaComponent(CGFloat(Int.random(in: 4..<10)) / 10) 51 | cell.container.shadowRadius = CGFloat(Int.random(in: 8..<20)) 52 | cell.container.borderColor = UIColor.init(red: CGFloat(Int.random(in: 0...10)) / 10, green: CGFloat(Int.random(in: 0...10)) / 10, blue: CGFloat(Int.random(in: 0...10)) / 10, alpha: 1) 53 | cell.container.borderWidth = CGFloat(Int.random(in: 0..<8)) 54 | 55 | let borderPositions: [UIBorderPostion] = [.left,.top,.right,.bottom] 56 | var borderResult = [UIBorderPostion]() 57 | var borderCount = Int.random(in: 0..<4) 58 | while borderCount >= 0 { 59 | borderResult.append(borderPositions[Int.random(in: 0..<4)]) 60 | borderCount -= 1 61 | } 62 | if Int.random(in: 0..<5) / 2 == 0{ 63 | cell.container.defautlGeneratorClass = SFGradientImageMaker.self 64 | }else{ 65 | cell.container.defautlGeneratorClass = SFColorImageMaker.self 66 | } 67 | cell.container.handleMakers = { makers in 68 | if let gradientGenerator = makers.filter({ $0 is SFGradientImageMaker }).first as? SFGradientImageMaker{ 69 | let startColor = UIColor.init(red: CGFloat(Int.random(in: 0...10)) / 10, green: CGFloat(Int.random(in: 0...10)) / 10, blue: CGFloat(Int.random(in: 0...10)) / 10, alpha: 1) 70 | let endColor = UIColor.init(red: CGFloat(Int.random(in: 0...10)) / 10, green: CGFloat(Int.random(in: 0...10)) / 10, blue: CGFloat(Int.random(in: 0...10)) / 10, alpha: 1) 71 | gradientGenerator.colors = [startColor, endColor] 72 | gradientGenerator.locations = [NSNumber(0), NSNumber(1)]; 73 | gradientGenerator.isHorizontal = true 74 | gradientGenerator.dependencies.add(SFBlurImageMaker.lightEffect()) 75 | } 76 | if let gradientGenerator = makers.filter({ $0 is SFColorImageMaker }).first as? SFColorImageMaker{ 77 | gradientGenerator.dependencies.add(SFBlurImageMaker.lightEffect()) 78 | } 79 | } 80 | cell.container.borderPosition = UIBorderPostion.init(borderResult) 81 | cell.container.reloadBackGourndImage() 82 | return cell 83 | } 84 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 85 | let cell = tableView.cellForRow(at: indexPath) as! RandomCell 86 | // let alert = UIAlertController.init(title: "DebugInfo", message: cell.container.debugInfo(), preferredStyle: .alert) 87 | // alert.addAction(UIAlertAction.init(title: "cancel", style: .cancel, handler: nil)) 88 | // self.present(alert, animated: true, completion: nil) 89 | } 90 | 91 | /* 92 | // MARK: - Navigation 93 | 94 | // In a storyboard-based application, you will often want to do a little preparation before navigation 95 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 96 | // Get the new view controller using segue.destination. 97 | // Pass the selected object to the new view controller. 98 | } 99 | */ 100 | 101 | } 102 | -------------------------------------------------------------------------------- /UICornerShadowView/ViewController2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController2.swift 3 | // UICornerShadowView 4 | // 5 | // Created by Jiang on 2020/2/13. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SFImageMaker 11 | class ReuseImageCell: UITableViewCell{ 12 | @IBOutlet weak var container: SFCSBView! 13 | override func awakeFromNib() { 14 | super.awakeFromNib() 15 | self.container.shadowColor = UIColor.black.withAlphaComponent(0.6) 16 | self.container.shadowRadius = 10 17 | self.container.cornerRadius = 10 18 | self.container.borderWidth = 0 19 | self.container.shadowFixValue = -0.5; 20 | } 21 | func topStyle(){ 22 | self.container.shadowPosition = [.left,.right,.top] 23 | self.container.rectCornner = [.topLeft,.topRight] 24 | } 25 | func middleStyle(){ 26 | self.container.shadowPosition = [.left,.right] 27 | self.container.rectCornner = UIRectCorner.init(rawValue: 0) 28 | } 29 | func bottomStyle(){ 30 | self.container.shadowPosition = [.left,.right,.bottom] 31 | self.container.rectCornner = [.bottomRight,.bottomLeft] 32 | } 33 | } 34 | class ViewController2: UIViewController,UITableViewDataSource,UITableViewDelegate{ 35 | @IBOutlet weak var tableView: UITableView! 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | let _ = UIColor.red 39 | .sf_flow(with: CGSize.init(width: 40, height: 40)) 40 | .blur(with: SFBlurEffectLight) 41 | .border(withWidth: 1, color: .gray) 42 | .shadow(with: UIColor.black.withAlphaComponent(0.5), offset: .init(width: 10, height: 10), blurRadius: 10) 43 | .resize(with: .init(width: 20, height: 20)) 44 | .circle 45 | .image(); 46 | 47 | tableView.rowHeight = 80 48 | tableView.tableHeaderView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 20)) 49 | tableView.tableFooterView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 20)) 50 | } 51 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return 1000; 53 | } 54 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 55 | let cell = tableView.dequeueReusableCell(withIdentifier: "ReuseImageCell") as! ReuseImageCell 56 | let totalCount = tableView.numberOfRows(inSection: indexPath.section) 57 | if indexPath.row == 0{ 58 | cell.topStyle() 59 | } 60 | if indexPath.row == totalCount - 1{ 61 | cell.bottomStyle() 62 | } 63 | if indexPath.row > 0 && indexPath.row < totalCount - 1{ 64 | cell.middleStyle() 65 | } 66 | cell.container.reloadBackGourndImage() 67 | return cell 68 | } 69 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 70 | let cell = tableView.cellForRow(at: indexPath) as! ReuseImageCell 71 | // let alert = UIAlertController.init(title: "DebugInfo", message: cell.container.debugInfo(), preferredStyle: .alert) 72 | // alert.addAction(UIAlertAction.init(title: "cancel", style: .cancel, handler: nil)) 73 | // self.present(alert, animated: true, completion: nil) 74 | } 75 | 76 | /* 77 | // MARK: - Navigation 78 | 79 | // In a storyboard-based application, you will often want to do a little preparation before navigation 80 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 81 | // Get the new view controller using segue.destination. 82 | // Pass the selected object to the new view controller. 83 | } 84 | */ 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /UICornerShadowViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /UICornerShadowViewTests/UICornerShadowViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICornerShadowViewTests.swift 3 | // UICornerShadowViewTests 4 | // 5 | // Created by Jiang on 2020/1/15. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import UICornerShadowView 11 | 12 | class UICornerShadowViewTests: XCTestCase { 13 | 14 | 15 | override func setUp() { 16 | 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | } 22 | 23 | 24 | func testPerformanceExample() { 25 | // This is an example of a performance test case. 26 | 27 | self.measure { 28 | 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /UICornerShadowViewUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /UICornerShadowViewUITests/UICornerShadowViewUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICornerShadowViewUITests.swift 3 | // UICornerShadowViewUITests 4 | // 5 | // Created by Jiang on 2020/1/15. 6 | // Copyright © 2020 SilverFruity. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class UICornerShadowViewUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | // func testLaunchPerformance() { 36 | // if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // // This measures how long it takes to launch your application. 38 | // measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 39 | // XCUIApplication().launch() 40 | // } 41 | // } 42 | // } 43 | } 44 | --------------------------------------------------------------------------------