├── .gitignore ├── Classes ├── DBCameraButton.h └── DBCameraButton.m ├── DBCameraButton.podspec ├── Example ├── DBCameraButton.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── DBCameraButton │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData/ 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | *.xcuserstate 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | # macOS 26 | # 27 | 28 | *.DS_Store 29 | .AppleDouble 30 | .LSOverride 31 | 32 | # Icon must end with two \r 33 | Icon 34 | 35 | # Thumbnails 36 | ._* 37 | 38 | # Files that might appear in the root of a volume 39 | .DocumentRevisions-V100 40 | .fseventsd 41 | .Spotlight-V100 42 | .TemporaryItems 43 | .Trashes 44 | .VolumeIcon.icns 45 | .com.apple.timemachine.donotpresent 46 | 47 | # Directories potentially created on remote AFP share 48 | .AppleDB 49 | .AppleDesktop 50 | .apdisk 51 | -------------------------------------------------------------------------------- /Classes/DBCameraButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBCameraButton.h 3 | // DBCameraButton 4 | // 5 | // Created by Dmitry Byankin on 06.08.17. 6 | // Copyright © 2017 Dmitry Byankin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | IB_DESIGNABLE 12 | @interface DBCameraButton : UIButton 13 | { 14 | CAShapeLayer *pathLayer; 15 | } 16 | 17 | /// Duration of animation 18 | @property (nonatomic) IBInspectable CGFloat animationDuration; 19 | 20 | /// If YES display "stop" square, else display "capture" circle 21 | @property (nonatomic) IBInspectable BOOL isRecording; 22 | 23 | /// Color of "capture" circle 24 | @property (nonatomic, strong) IBInspectable UIColor *circleColor; 25 | 26 | /// Color of "stop" square 27 | @property (nonatomic, strong) IBInspectable UIColor *squareColor; 28 | 29 | /// Color of outer ring 30 | @property (nonatomic, strong) IBInspectable UIColor *outerRingColor; 31 | 32 | /// Color in disabled state 33 | @property (nonatomic, strong) IBInspectable UIColor *disabledColor; 34 | 35 | /// Width of outer ring 36 | @property (nonatomic) IBInspectable CGFloat outerRingWidth; 37 | 38 | /// Space between outer ring and inner shape 39 | @property (nonatomic) IBInspectable CGFloat outerRingSpacing; 40 | 41 | /// Corner radius of "stop" square 42 | @property (nonatomic) IBInspectable CGFloat squareCornerRadius; 43 | 44 | /// Change state when button pressed. Default YES. Set NO to manual state change 45 | @property (nonatomic) IBInspectable BOOL autoStateChange; 46 | 47 | /// Margin to rect bounds 48 | @property (nonatomic) IBInspectable CGFloat margin; 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /Classes/DBCameraButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBCameraButton.m 3 | // DBCameraButton 4 | // 5 | // Created by Dmitry Byankin on 06.08.17. 6 | // Copyright © 2017 Dmitry Byankin. All rights reserved. 7 | // 8 | 9 | #import "DBCameraButton.h" 10 | 11 | @implementation DBCameraButton 12 | 13 | #pragma mark - Init 14 | 15 | - (instancetype)initWithFrame:(CGRect)frame 16 | { 17 | self = [super initWithFrame:frame]; 18 | if (self) { 19 | [self setup]; 20 | } 21 | return self; 22 | } 23 | 24 | - (instancetype)init 25 | { 26 | self = [super init]; 27 | if (self) { 28 | [self setup]; 29 | } 30 | return self; 31 | } 32 | 33 | 34 | - (instancetype)initWithCoder:(NSCoder *)coder 35 | { 36 | self = [super initWithCoder:coder]; 37 | if (self) { 38 | [self setup]; 39 | } 40 | return self; 41 | } 42 | 43 | -(void)setup { 44 | 45 | if(!self.circleColor){ 46 | self.circleColor = [UIColor redColor]; 47 | } 48 | 49 | if(!self.squareColor){ 50 | self.squareColor = [UIColor redColor]; 51 | } 52 | 53 | if(!self.outerRingColor){ 54 | self.outerRingColor = [UIColor whiteColor]; 55 | } 56 | 57 | if(!self.disabledColor){ 58 | self.disabledColor = [UIColor grayColor]; 59 | } 60 | 61 | self.outerRingWidth = 6.f; 62 | self.outerRingSpacing = 3.f; 63 | self.squareCornerRadius = 4.0f; 64 | self.animationDuration = 0.4f; 65 | self.autoStateChange = YES; 66 | self.margin = 5.f; 67 | 68 | // setup inner shape 69 | pathLayer = [[CAShapeLayer alloc]init]; 70 | pathLayer.path = [self currentInnerPath].CGPath; 71 | pathLayer.strokeColor = [UIColor clearColor].CGColor; 72 | pathLayer.fillColor = [self innerColor].CGColor; 73 | 74 | [self.layer addSublayer:pathLayer]; 75 | } 76 | 77 | -(void)awakeFromNib { 78 | [super awakeFromNib]; 79 | 80 | 81 | [self addConstraint:[NSLayoutConstraint constraintWithItem:self 82 | attribute:NSLayoutAttributeHeight 83 | relatedBy:NSLayoutRelationEqual 84 | toItem:self 85 | attribute:NSLayoutAttributeWidth 86 | multiplier:1 87 | constant:0]]; 88 | 89 | [self setTitle:@"" forState:UIControlStateNormal]; 90 | pathLayer.fillColor = [self innerColor].CGColor; 91 | pathLayer.path = [self currentInnerPath].CGPath; 92 | 93 | 94 | [self addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside]; 95 | [self addTarget:self action:@selector(touchDown:) forControlEvents:UIControlEventTouchDown]; 96 | } 97 | 98 | 99 | 100 | -(void)prepareForInterfaceBuilder { 101 | 102 | [self setTitle:@"" forState:UIControlStateNormal]; 103 | pathLayer.fillColor = [self innerColor].CGColor; 104 | pathLayer.path = [self currentInnerPath].CGPath; 105 | } 106 | 107 | -(void)setIsRecording:(BOOL)isRecording { 108 | _isRecording = isRecording; 109 | 110 | // If isRecording == YES display square, else circle 111 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"]; 112 | animation.duration = self.animationDuration; 113 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 114 | animation.toValue = (__bridge id _Nullable)([self currentInnerPath].CGPath); 115 | animation.fillMode = kCAFillModeForwards; 116 | animation.removedOnCompletion = NO; 117 | 118 | [pathLayer addAnimation:animation forKey:@""]; 119 | 120 | // color 121 | CABasicAnimation *colorChange = [CABasicAnimation animationWithKeyPath:@"fillColor"]; 122 | colorChange.duration = self.animationDuration; 123 | colorChange.toValue = (__bridge id _Nullable)([self innerColor].CGColor); 124 | colorChange.fillMode = kCAFillModeForwards; 125 | colorChange.removedOnCompletion = NO; 126 | colorChange.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 127 | 128 | [pathLayer addAnimation:colorChange forKey:@"fillColor"]; 129 | } 130 | 131 | #pragma mark - BezierPath 132 | 133 | -(UIBezierPath*)currentInnerPath { 134 | 135 | return self.isRecording ? [self innerSquarePath] : [self innerCirclePath]; 136 | 137 | } 138 | 139 | -(UIBezierPath*)innerCirclePath { 140 | CGFloat size = self.frame.size.width - self.outerRingWidth*2 - self.outerRingSpacing*2 - self.margin*2; 141 | return [UIBezierPath bezierPathWithRoundedRect:CGRectMake(self.outerRingWidth + self.outerRingSpacing + self.margin, 142 | self.outerRingWidth + self.outerRingSpacing + self.margin, 143 | size, size) cornerRadius:size/2.f]; 144 | } 145 | 146 | -(UIBezierPath*)innerSquarePath { 147 | CGFloat circleSize = self.frame.size.width - self.outerRingWidth*2 - self.outerRingSpacing*2 - self.margin*2; 148 | CGFloat size = circleSize * sqrt(2) / 2.f; 149 | CGFloat margin = (circleSize - size) / 2.f + self.outerRingWidth + self.outerRingSpacing + self.margin; 150 | 151 | return [UIBezierPath bezierPathWithRoundedRect:CGRectMake(margin, margin, size, size) cornerRadius:self.squareCornerRadius]; 152 | } 153 | 154 | -(UIColor*)innerColor { 155 | 156 | return self.enabled ? (self.isRecording ? self.squareColor 157 | : self.circleColor) 158 | : self.disabledColor; 159 | } 160 | 161 | #pragma mark - Setters 162 | 163 | -(void)setEnabled:(BOOL)enabled { 164 | [super setEnabled:enabled]; 165 | pathLayer.fillColor = [self innerColor].CGColor; 166 | 167 | } 168 | 169 | #pragma mark - Touches 170 | 171 | -(void)touchUpInside:(UIButton*)sender { 172 | 173 | // Touch ended. Display view as usual 174 | if(self.autoStateChange){ 175 | self.isRecording = !self.isRecording; 176 | } 177 | 178 | CABasicAnimation *colorChange = [CABasicAnimation animationWithKeyPath:@"fillColor"]; 179 | colorChange.duration = self.animationDuration; 180 | colorChange.toValue = (__bridge id _Nullable)([self innerColor].CGColor); 181 | colorChange.fillMode = kCAFillModeForwards; 182 | colorChange.removedOnCompletion = NO; 183 | colorChange.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 184 | 185 | [pathLayer addAnimation:colorChange forKey:@"darkColor"]; 186 | } 187 | 188 | -(void)touchDown:(UIButton*)sender { 189 | 190 | // Touch start. Display button pressed state. 191 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"fillColor"]; 192 | animation.duration = self.animationDuration; 193 | animation.toValue = (__bridge id _Nullable)([[self innerColor] colorWithAlphaComponent:0.5f].CGColor); 194 | animation.fillMode = kCAFillModeForwards; 195 | animation.removedOnCompletion = NO; 196 | animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; 197 | 198 | [pathLayer addAnimation:animation forKey:@""]; 199 | } 200 | 201 | 202 | #pragma mark - Draw methods 203 | 204 | -(void)drawRect:(CGRect)rect { 205 | 206 | UIBezierPath *outerRing = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(self.outerRingWidth/2.f + self.margin, 207 | self.outerRingWidth/2.f + self.margin, 208 | self.frame.size.width - self.outerRingWidth - self.margin*2, 209 | self.frame.size.width - self.outerRingWidth - self.margin*2)]; 210 | outerRing.lineWidth = self.outerRingWidth; 211 | [(self.enabled ? self.outerRingColor : self.disabledColor) setStroke]; 212 | [outerRing stroke]; 213 | } 214 | 215 | 216 | 217 | @end 218 | -------------------------------------------------------------------------------- /DBCameraButton.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'DBCameraButton' 3 | s.version = '1.0.0' 4 | s.summary = 'Customizable, IB_DESIGNABLE, iOS style camera button.' 5 | s.description = <<-DESC 6 | Сamera button in the style of a standard iOS camera. Customizable, IB_DESIGNABLE. UIButton category (extension). 7 | DESC 8 | 9 | s.homepage = 'https://github.com/immago/DBCameraButton' 10 | s.screenshots = 'https://user-images.githubusercontent.com/5740772/29002628-5ec56d38-7aaf-11e7-85a4-4810e918784f.gif' 11 | s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } 12 | s.author = { 'Dmitry Byankin' => 'the.immago@gmail.com' } 13 | s.source = { :git => 'https://github.com/immago/DBCameraButton.git', :tag => s.version.to_s } 14 | s.ios.deployment_target = '9.0' 15 | s.source_files = 'Classes/**/*' 16 | end 17 | -------------------------------------------------------------------------------- /Example/DBCameraButton.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C54DA3AD1F418073006F0D65 /* DBCameraButton.m in Sources */ = {isa = PBXBuildFile; fileRef = C54DA3AC1F418073006F0D65 /* DBCameraButton.m */; }; 11 | C5EFF7F01F370BAD003DE8F5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C5EFF7EF1F370BAD003DE8F5 /* main.m */; }; 12 | C5EFF7F31F370BAD003DE8F5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C5EFF7F21F370BAD003DE8F5 /* AppDelegate.m */; }; 13 | C5EFF7F91F370BAE003DE8F5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5EFF7F71F370BAE003DE8F5 /* Main.storyboard */; }; 14 | C5EFF7FB1F370BAE003DE8F5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5EFF7FA1F370BAE003DE8F5 /* Assets.xcassets */; }; 15 | C5EFF7FE1F370BAE003DE8F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5EFF7FC1F370BAE003DE8F5 /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | C54DA3AB1F418073006F0D65 /* DBCameraButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DBCameraButton.h; path = ../../Classes/DBCameraButton.h; sourceTree = ""; }; 20 | C54DA3AC1F418073006F0D65 /* DBCameraButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DBCameraButton.m; path = ../../Classes/DBCameraButton.m; sourceTree = ""; }; 21 | C5EFF7EB1F370BAD003DE8F5 /* DBCameraButton.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DBCameraButton.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | C5EFF7EF1F370BAD003DE8F5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 23 | C5EFF7F11F370BAD003DE8F5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 24 | C5EFF7F21F370BAD003DE8F5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 25 | C5EFF7F81F370BAE003DE8F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | C5EFF7FA1F370BAE003DE8F5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | C5EFF7FD1F370BAE003DE8F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | C5EFF7FF1F370BAE003DE8F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | C5EFF7E81F370BAD003DE8F5 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | C5EFF7E21F370BAD003DE8F5 = { 43 | isa = PBXGroup; 44 | children = ( 45 | C5EFF7ED1F370BAD003DE8F5 /* DBCameraButton */, 46 | C5EFF7EC1F370BAD003DE8F5 /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | C5EFF7EC1F370BAD003DE8F5 /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | C5EFF7EB1F370BAD003DE8F5 /* DBCameraButton.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | C5EFF7ED1F370BAD003DE8F5 /* DBCameraButton */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | C54DA3AB1F418073006F0D65 /* DBCameraButton.h */, 62 | C54DA3AC1F418073006F0D65 /* DBCameraButton.m */, 63 | C5EFF7F11F370BAD003DE8F5 /* AppDelegate.h */, 64 | C5EFF7F21F370BAD003DE8F5 /* AppDelegate.m */, 65 | C5EFF7F71F370BAE003DE8F5 /* Main.storyboard */, 66 | C5EFF7FC1F370BAE003DE8F5 /* LaunchScreen.storyboard */, 67 | C5EFF7FA1F370BAE003DE8F5 /* Assets.xcassets */, 68 | C5EFF7FF1F370BAE003DE8F5 /* Info.plist */, 69 | C5EFF7EE1F370BAD003DE8F5 /* Supporting Files */, 70 | ); 71 | path = DBCameraButton; 72 | sourceTree = ""; 73 | }; 74 | C5EFF7EE1F370BAD003DE8F5 /* Supporting Files */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | C5EFF7EF1F370BAD003DE8F5 /* main.m */, 78 | ); 79 | name = "Supporting Files"; 80 | sourceTree = ""; 81 | }; 82 | /* End PBXGroup section */ 83 | 84 | /* Begin PBXNativeTarget section */ 85 | C5EFF7EA1F370BAD003DE8F5 /* DBCameraButton */ = { 86 | isa = PBXNativeTarget; 87 | buildConfigurationList = C5EFF8021F370BAE003DE8F5 /* Build configuration list for PBXNativeTarget "DBCameraButton" */; 88 | buildPhases = ( 89 | C5EFF7E71F370BAD003DE8F5 /* Sources */, 90 | C5EFF7E81F370BAD003DE8F5 /* Frameworks */, 91 | C5EFF7E91F370BAD003DE8F5 /* Resources */, 92 | ); 93 | buildRules = ( 94 | ); 95 | dependencies = ( 96 | ); 97 | name = DBCameraButton; 98 | productName = DBCameraButton; 99 | productReference = C5EFF7EB1F370BAD003DE8F5 /* DBCameraButton.app */; 100 | productType = "com.apple.product-type.application"; 101 | }; 102 | /* End PBXNativeTarget section */ 103 | 104 | /* Begin PBXProject section */ 105 | C5EFF7E31F370BAD003DE8F5 /* Project object */ = { 106 | isa = PBXProject; 107 | attributes = { 108 | LastUpgradeCheck = 0830; 109 | ORGANIZATIONNAME = "Dmitry Byankin"; 110 | TargetAttributes = { 111 | C5EFF7EA1F370BAD003DE8F5 = { 112 | CreatedOnToolsVersion = 8.3.2; 113 | ProvisioningStyle = Automatic; 114 | }; 115 | }; 116 | }; 117 | buildConfigurationList = C5EFF7E61F370BAD003DE8F5 /* Build configuration list for PBXProject "DBCameraButton" */; 118 | compatibilityVersion = "Xcode 3.2"; 119 | developmentRegion = English; 120 | hasScannedForEncodings = 0; 121 | knownRegions = ( 122 | en, 123 | Base, 124 | ); 125 | mainGroup = C5EFF7E21F370BAD003DE8F5; 126 | productRefGroup = C5EFF7EC1F370BAD003DE8F5 /* Products */; 127 | projectDirPath = ""; 128 | projectRoot = ""; 129 | targets = ( 130 | C5EFF7EA1F370BAD003DE8F5 /* DBCameraButton */, 131 | ); 132 | }; 133 | /* End PBXProject section */ 134 | 135 | /* Begin PBXResourcesBuildPhase section */ 136 | C5EFF7E91F370BAD003DE8F5 /* Resources */ = { 137 | isa = PBXResourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | C5EFF7FE1F370BAE003DE8F5 /* LaunchScreen.storyboard in Resources */, 141 | C5EFF7FB1F370BAE003DE8F5 /* Assets.xcassets in Resources */, 142 | C5EFF7F91F370BAE003DE8F5 /* Main.storyboard in Resources */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXResourcesBuildPhase section */ 147 | 148 | /* Begin PBXSourcesBuildPhase section */ 149 | C5EFF7E71F370BAD003DE8F5 /* Sources */ = { 150 | isa = PBXSourcesBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | C5EFF7F31F370BAD003DE8F5 /* AppDelegate.m in Sources */, 154 | C54DA3AD1F418073006F0D65 /* DBCameraButton.m in Sources */, 155 | C5EFF7F01F370BAD003DE8F5 /* main.m in Sources */, 156 | ); 157 | runOnlyForDeploymentPostprocessing = 0; 158 | }; 159 | /* End PBXSourcesBuildPhase section */ 160 | 161 | /* Begin PBXVariantGroup section */ 162 | C5EFF7F71F370BAE003DE8F5 /* Main.storyboard */ = { 163 | isa = PBXVariantGroup; 164 | children = ( 165 | C5EFF7F81F370BAE003DE8F5 /* Base */, 166 | ); 167 | name = Main.storyboard; 168 | sourceTree = ""; 169 | }; 170 | C5EFF7FC1F370BAE003DE8F5 /* LaunchScreen.storyboard */ = { 171 | isa = PBXVariantGroup; 172 | children = ( 173 | C5EFF7FD1F370BAE003DE8F5 /* Base */, 174 | ); 175 | name = LaunchScreen.storyboard; 176 | sourceTree = ""; 177 | }; 178 | /* End PBXVariantGroup section */ 179 | 180 | /* Begin XCBuildConfiguration section */ 181 | C5EFF8001F370BAE003DE8F5 /* Debug */ = { 182 | isa = XCBuildConfiguration; 183 | buildSettings = { 184 | ALWAYS_SEARCH_USER_PATHS = NO; 185 | CLANG_ANALYZER_NONNULL = YES; 186 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 188 | CLANG_CXX_LIBRARY = "libc++"; 189 | CLANG_ENABLE_MODULES = YES; 190 | CLANG_ENABLE_OBJC_ARC = YES; 191 | CLANG_WARN_BOOL_CONVERSION = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 194 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 195 | CLANG_WARN_EMPTY_BODY = YES; 196 | CLANG_WARN_ENUM_CONVERSION = YES; 197 | CLANG_WARN_INFINITE_RECURSION = YES; 198 | CLANG_WARN_INT_CONVERSION = YES; 199 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 200 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 201 | CLANG_WARN_UNREACHABLE_CODE = YES; 202 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 203 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 204 | COPY_PHASE_STRIP = NO; 205 | DEBUG_INFORMATION_FORMAT = dwarf; 206 | ENABLE_STRICT_OBJC_MSGSEND = YES; 207 | ENABLE_TESTABILITY = YES; 208 | GCC_C_LANGUAGE_STANDARD = gnu99; 209 | GCC_DYNAMIC_NO_PIC = NO; 210 | GCC_NO_COMMON_BLOCKS = YES; 211 | GCC_OPTIMIZATION_LEVEL = 0; 212 | GCC_PREPROCESSOR_DEFINITIONS = ( 213 | "DEBUG=1", 214 | "$(inherited)", 215 | ); 216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 218 | GCC_WARN_UNDECLARED_SELECTOR = YES; 219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 220 | GCC_WARN_UNUSED_FUNCTION = YES; 221 | GCC_WARN_UNUSED_VARIABLE = YES; 222 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 223 | MTL_ENABLE_DEBUG_INFO = YES; 224 | ONLY_ACTIVE_ARCH = YES; 225 | SDKROOT = iphoneos; 226 | TARGETED_DEVICE_FAMILY = "1,2"; 227 | }; 228 | name = Debug; 229 | }; 230 | C5EFF8011F370BAE003DE8F5 /* Release */ = { 231 | isa = XCBuildConfiguration; 232 | buildSettings = { 233 | ALWAYS_SEARCH_USER_PATHS = NO; 234 | CLANG_ANALYZER_NONNULL = YES; 235 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_ENABLE_MODULES = YES; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INFINITE_RECURSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 253 | COPY_PHASE_STRIP = NO; 254 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 255 | ENABLE_NS_ASSERTIONS = NO; 256 | ENABLE_STRICT_OBJC_MSGSEND = YES; 257 | GCC_C_LANGUAGE_STANDARD = gnu99; 258 | GCC_NO_COMMON_BLOCKS = YES; 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 266 | MTL_ENABLE_DEBUG_INFO = NO; 267 | SDKROOT = iphoneos; 268 | TARGETED_DEVICE_FAMILY = "1,2"; 269 | VALIDATE_PRODUCT = YES; 270 | }; 271 | name = Release; 272 | }; 273 | C5EFF8031F370BAE003DE8F5 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 277 | INFOPLIST_FILE = DBCameraButton/Info.plist; 278 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 279 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 280 | PRODUCT_BUNDLE_IDENTIFIER = immago.DBCameraButton; 281 | PRODUCT_NAME = "$(TARGET_NAME)"; 282 | }; 283 | name = Debug; 284 | }; 285 | C5EFF8041F370BAE003DE8F5 /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | INFOPLIST_FILE = DBCameraButton/Info.plist; 290 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 291 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 292 | PRODUCT_BUNDLE_IDENTIFIER = immago.DBCameraButton; 293 | PRODUCT_NAME = "$(TARGET_NAME)"; 294 | }; 295 | name = Release; 296 | }; 297 | /* End XCBuildConfiguration section */ 298 | 299 | /* Begin XCConfigurationList section */ 300 | C5EFF7E61F370BAD003DE8F5 /* Build configuration list for PBXProject "DBCameraButton" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | C5EFF8001F370BAE003DE8F5 /* Debug */, 304 | C5EFF8011F370BAE003DE8F5 /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | defaultConfigurationName = Release; 308 | }; 309 | C5EFF8021F370BAE003DE8F5 /* Build configuration list for PBXNativeTarget "DBCameraButton" */ = { 310 | isa = XCConfigurationList; 311 | buildConfigurations = ( 312 | C5EFF8031F370BAE003DE8F5 /* Debug */, 313 | C5EFF8041F370BAE003DE8F5 /* Release */, 314 | ); 315 | defaultConfigurationIsVisible = 0; 316 | defaultConfigurationName = Release; 317 | }; 318 | /* End XCConfigurationList section */ 319 | }; 320 | rootObject = C5EFF7E31F370BAD003DE8F5 /* Project object */; 321 | } 322 | -------------------------------------------------------------------------------- /Example/DBCameraButton.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DBCameraButton/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // DBCameraButton 4 | // 5 | // Created by Dmitry Byankin on 06.08.17. 6 | // Copyright © 2017 Dmitry Byankin. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Example/DBCameraButton/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // DBCameraButton 4 | // 5 | // Created by Dmitry Byankin on 06.08.17. 6 | // Copyright © 2017 Dmitry Byankin. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Example/DBCameraButton/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Example/DBCameraButton/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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Example/DBCameraButton/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 60 | 66 | 101 | 107 | 142 | 148 | 183 | 192 | 227 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /Example/DBCameraButton/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example/DBCameraButton/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DBCameraButton 4 | // 5 | // Created by Dmitry Byankin on 06.08.17. 6 | // Copyright © 2017 Dmitry Byankin. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Dmitry Byankin 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | this file except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DBCameraButton 2 | [![podver](https://img.shields.io/cocoapods/v/DBCameraButton.png)](https://cocoapods.org/pods/DBCameraButton) [![podver](https://img.shields.io/cocoapods/p/DBCameraButton.png)](https://cocoapods.org/pods/DBCameraButton) [![podver](https://img.shields.io/cocoapods/l/DBCameraButton.png)](https://cocoapods.org/pods/DBCameraButton) 3 | 4 | Customizable, IB_DESIGNABLE, iOS style camera button. 5 | 6 | ![image](https://user-images.githubusercontent.com/5740772/29002628-5ec56d38-7aaf-11e7-85a4-4810e918784f.gif) 7 | 8 | ## Installation 9 | #### CocoaPods 10 | ``` 11 | pod 'DBCameraButton' 12 | ``` 13 | If you have IB Designables render error add use_frameworks! to your pod file. 14 | ![image](https://user-images.githubusercontent.com/5740772/29263793-19fc9be2-80e3-11e7-9fdc-c2755cf366c8.png) 15 | 16 | #### Manual 17 | Copy DBCameraButton.h and DBCameraButton.m from Classes directory to your project. 18 | 19 | ## Usage 20 | Create DBCameraButton button (subclass of UIButton) without title in storyboard or programmatically. No additional configuration needed. 21 | 22 | ## Properties 23 | ``` 24 | CGFloat animationDuration - duration of animation 25 | BOOL isRecording - if YES display "stop" square, else display "capture" circle 26 | UIColor *circleColor - color of "capture" circle 27 | UIColor *squareColor - color of "stop" square 28 | UIColor *outerRingColor - color of outer ring 29 | UIColor *disabledColor - color in disabled state 30 | CGFloat outerRingWidth - width of outer ring 31 | CGFloat outerRingSpacing - space between outer ring and inner shape 32 | CGFloat squareCornerRadius - corner radius of "stop" square 33 | BOOL autoStateChange - change state when button pressed. Default YES. Set NO to manual state change. 34 | CGFloat margin - margin to rect bounds. 35 | ``` 36 | --------------------------------------------------------------------------------