├── CircularSlider ├── UICircularSlider.h └── UICircularSlider.m ├── LICENSE ├── MultlipleTimer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── gh.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── gh.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── MultlipleTimer.xcscheme │ └── xcschememanagement.plist ├── MultlipleTimer ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── MultlipleTimerTests ├── Info.plist └── MultlipleTimerTests.m ├── MultlipleTimerUITests ├── Info.plist └── MultlipleTimerUITests.m ├── README.md └── Timer ├── Timer.h └── Timer.m /CircularSlider/UICircularSlider.h: -------------------------------------------------------------------------------- 1 | // 2 | /// UICircularSlider.h 3 | /// UICircularSlider 4 | // 5 | // Created by Zouhair Mahieddine on 02/03/12. 6 | // Copyright (c) 2012 Zouhair Mahieddine. 7 | // http://www.zedenem.com 8 | // 9 | // This file is part of the UICircularSlider Library, released under the MIT License. 10 | // 11 | 12 | #if ! __has_feature(objc_arc) 13 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 14 | #endif 15 | 16 | #import 17 | 18 | /** @name Constants */ 19 | /** 20 | * The styles permitted for the circular progress view. 21 | * 22 | * You can set and retrieve the current style of progress view through the progressViewStyle property. 23 | */ 24 | typedef enum { 25 | UICircularSliderStyleCircle, 26 | UICircularSliderStylePie, 27 | } UICircularSliderStyle; 28 | 29 | @interface UICircularSlider : UIControl 30 | 31 | /** 32 | * The current value of the receiver. 33 | * 34 | * Setting this property causes the receiver to redraw itself using the new value. 35 | * If you try to set a value that is below the minimum or above the maximum value, the minimum or maximum value is set instead. The default value of this property is 0.0. 36 | */ 37 | @property (nonatomic) float value; 38 | 39 | /** 40 | * The minimum value of the receiver. 41 | * 42 | * If you change the value of this property, and the current value of the receiver is below the new minimum, the current value is adjusted to match the new minimum value automatically. 43 | * The default value of this property is 0.0. 44 | */ 45 | @property (nonatomic) float minimumValue; 46 | 47 | /** 48 | * The maximum value of the receiver. 49 | * 50 | * If you change the value of this property, and the current value of the receiver is above the new maximum, the current value is adjusted to match the new maximum value automatically. 51 | * The default value of this property is 1.0. 52 | */ 53 | @property (nonatomic) float maximumValue; 54 | 55 | /** 56 | * The color shown for the portion of the slider that is filled. 57 | */ 58 | @property(nonatomic, retain) UIColor *minimumTrackTintColor; 59 | 60 | /** 61 | * The color shown for the portion of the slider that is not filled. 62 | */ 63 | @property(nonatomic, retain) UIColor *maximumTrackTintColor; 64 | 65 | /** 66 | * The color used to tint the standard thumb. 67 | */ 68 | @property(nonatomic, retain) UIColor *thumbTintColor; 69 | 70 | /** 71 | * Contains a Boolean value indicating whether changes in the sliders value generate continuous update events. 72 | * 73 | * If YES, the slider sends update events continuously to the associated target’s action method. 74 | * If NO, the slider only sends an action event when the user releases the slider’s thumb control to set the final value. 75 | * The default value of this property is YES. 76 | */ 77 | @property(nonatomic, getter=isContinuous) BOOL continuous; 78 | 79 | /** 80 | * The current graphical style of the receiver. 81 | * 82 | * The value of this property is a constant that specifies the style of the slider. 83 | The default style is UICircularSliderStyleCircle. 84 | * For more on these constants, see UICircularSliderStyle. 85 | */ 86 | @property (nonatomic) UICircularSliderStyle sliderStyle; 87 | 88 | @end 89 | 90 | 91 | /** @name Utility Functions */ 92 | #pragma mark - Utility Functions 93 | /** 94 | * Translate a value in a source interval to a destination interval 95 | * @param sourceValue The source value to translate 96 | * @param sourceIntervalMinimum The minimum value in the source interval 97 | * @param sourceIntervalMaximum The maximum value in the source interval 98 | * @param destinationIntervalMinimum The minimum value in the destination interval 99 | * @param destinationIntervalMaximum The maximum value in the destination interval 100 | * @return The value in the destination interval 101 | * 102 | * This function uses the linear function method, a.k.a. resolves the y=ax+b equation where y is a destination value and x a source value 103 | * Formulas : a = (dMax - dMin) / (sMax - sMin) 104 | * b = dMax - a*sMax = dMin - a*sMin 105 | */ 106 | float translateValueFromSourceIntervalToDestinationInterval(float sourceValue, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum); 107 | /** 108 | * Returns the smallest angle between three points, one of them clearly indicated as the "junction point" or "center point". 109 | * @param centerPoint The "center point" or "junction point" 110 | * @param p1 The first point, member of the [centerPoint p1] segment 111 | * @param p2 The second point, member of the [centerPoint p2] segment 112 | * @return The angle between those two segments 113 | 114 | * This function uses the properties of the triangle and arctan (atan2f) function to calculate the angle. 115 | */ 116 | 117 | float translateProgreessValueFromSourceIntervalToDestinationInterval(float progress, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum); 118 | 119 | 120 | CGFloat angleBetweenThreePoints(CGPoint centerPoint, CGPoint p1, CGPoint p2); 121 | -------------------------------------------------------------------------------- /CircularSlider/UICircularSlider.m: -------------------------------------------------------------------------------- 1 | // 2 | // UICircularSlider.m 3 | // UICircularSlider 4 | // 5 | // Created by Zouhair Mahieddine on 02/03/12. 6 | // Copyright (c) 2012 Zouhair Mahieddine. 7 | // http://www.zedenem.com 8 | // 9 | // This file is part of the UICircularSlider Library, released under the MIT License. 10 | // 11 | 12 | #import "UICircularSlider.h" 13 | 14 | @interface UICircularSlider() 15 | 16 | @property (nonatomic) CGPoint thumbCenterPoint; 17 | 18 | #pragma mark - Init and Setup methods 19 | - (void)setup; 20 | 21 | #pragma mark - Thumb management methods 22 | - (BOOL)isPointInThumb:(CGPoint)point; 23 | 24 | #pragma mark - Drawing methods 25 | - (CGFloat)sliderRadius; 26 | - (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context; 27 | - (CGPoint)drawCircularTrack:(float)track atPoint:(CGPoint)point withRadius:(CGFloat)radius inContext:(CGContextRef)context; 28 | - (CGPoint)drawPieTrack:(float)track atPoint:(CGPoint)point withRadius:(CGFloat)radius inContext:(CGContextRef)context; 29 | 30 | @end 31 | 32 | #pragma mark - 33 | @implementation UICircularSlider 34 | 35 | @synthesize value = _value; 36 | - (void)setValue:(float)value { 37 | if (value != _value) { 38 | if (value > self.maximumValue) { value = self.maximumValue; } 39 | if (value < self.minimumValue) { value = self.minimumValue; } 40 | _value = value; 41 | [self setNeedsDisplay]; 42 | if (self.isContinuous) { 43 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 44 | } 45 | } 46 | } 47 | @synthesize minimumValue = _minimumValue; 48 | - (void)setMinimumValue:(float)minimumValue { 49 | if (minimumValue != _minimumValue) { 50 | _minimumValue = minimumValue; 51 | if (self.maximumValue < self.minimumValue) { self.maximumValue = self.minimumValue; } 52 | if (self.value < self.minimumValue) { self.value = self.minimumValue; } 53 | } 54 | } 55 | @synthesize maximumValue = _maximumValue; 56 | - (void)setMaximumValue:(float)maximumValue { 57 | if (maximumValue != _maximumValue) { 58 | _maximumValue = maximumValue; 59 | if (self.minimumValue > self.maximumValue) { self.minimumValue = self.maximumValue; } 60 | if (self.value > self.maximumValue) { self.value = self.maximumValue; } 61 | } 62 | } 63 | 64 | @synthesize minimumTrackTintColor = _minimumTrackTintColor; 65 | - (void)setMinimumTrackTintColor:(UIColor *)minimumTrackTintColor { 66 | if (![minimumTrackTintColor isEqual:_minimumTrackTintColor]) { 67 | _minimumTrackTintColor = minimumTrackTintColor; 68 | [self setNeedsDisplay]; 69 | } 70 | } 71 | 72 | @synthesize maximumTrackTintColor = _maximumTrackTintColor; 73 | - (void)setMaximumTrackTintColor:(UIColor *)maximumTrackTintColor { 74 | if (![maximumTrackTintColor isEqual:_maximumTrackTintColor]) { 75 | _maximumTrackTintColor = maximumTrackTintColor; 76 | [self setNeedsDisplay]; 77 | } 78 | } 79 | 80 | @synthesize thumbTintColor = _thumbTintColor; 81 | - (void)setThumbTintColor:(UIColor *)thumbTintColor { 82 | if (![thumbTintColor isEqual:_thumbTintColor]) { 83 | _thumbTintColor = [UIColor clearColor]; 84 | [self setNeedsDisplay]; 85 | } 86 | } 87 | 88 | @synthesize continuous = _continuous; 89 | 90 | @synthesize sliderStyle = _sliderStyle; 91 | - (void)setSliderStyle:(UICircularSliderStyle)sliderStyle { 92 | if (sliderStyle != _sliderStyle) { 93 | _sliderStyle = sliderStyle; 94 | [self setNeedsDisplay]; 95 | } 96 | } 97 | 98 | @synthesize thumbCenterPoint = _thumbCenterPoint; 99 | 100 | /** @name Init and Setup methods */ 101 | #pragma mark - Init and Setup methods 102 | - (id)initWithFrame:(CGRect)frame { 103 | self = [super initWithFrame:frame]; 104 | if (self) { 105 | [self setup]; 106 | } 107 | return self; 108 | } 109 | - (void)awakeFromNib { 110 | [self setup]; 111 | [super awakeFromNib]; 112 | } 113 | 114 | - (void)setup { 115 | self.value = 0.0; 116 | self.minimumValue = 0.0; 117 | self.maximumValue = 1.0; 118 | self.minimumTrackTintColor = [UIColor colorWithRed:0.0/255.0 green:186.0/255.0 blue:140.0/255.0 alpha:1.0]; 119 | self.maximumTrackTintColor = [UIColor colorWithRed:242.0/255.0 green:242.0/255.0 blue:242.0/255.0 alpha:1.0]; 120 | self.thumbTintColor = [UIColor colorWithRed:242.0/255.0 green:242.0/255.0 blue:242.0/255.0 alpha:1.0]; 121 | self.continuous = YES; 122 | self.thumbCenterPoint = CGPointZero; 123 | 124 | /** 125 | * This tapGesture isn't used yet but will allow to jump to a specific location in the circle 126 | */ 127 | UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHappened:)]; 128 | [self addGestureRecognizer:tapGestureRecognizer]; 129 | 130 | UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureHappened:)]; 131 | panGestureRecognizer.maximumNumberOfTouches = panGestureRecognizer.minimumNumberOfTouches; 132 | [self addGestureRecognizer:panGestureRecognizer]; 133 | } 134 | 135 | /** @name Drawing methods */ 136 | #pragma mark - Drawing methods 137 | #define kLineWidth 2.0 138 | #define kThumbRadius 2.0 139 | - (CGFloat)sliderRadius { 140 | CGFloat radius = MIN(self.bounds.size.width/2, self.bounds.size.height/2); 141 | radius -= MAX(kLineWidth, kThumbRadius); 142 | return radius; 143 | } 144 | - (void)drawThumbAtPoint:(CGPoint)sliderButtonCenterPoint inContext:(CGContextRef)context { 145 | UIGraphicsPushContext(context); 146 | CGContextBeginPath(context); 147 | 148 | CGContextMoveToPoint(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y); 149 | CGContextAddArc(context, sliderButtonCenterPoint.x, sliderButtonCenterPoint.y, kThumbRadius, 0.0, 2*M_PI, NO); 150 | 151 | CGContextFillPath(context); 152 | UIGraphicsPopContext(); 153 | } 154 | 155 | - (CGPoint)drawCircularTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius inContext:(CGContextRef)context { 156 | UIGraphicsPushContext(context); 157 | CGContextBeginPath(context); 158 | 159 | float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, 2*M_PI); 160 | 161 | CGFloat startAngle = -M_PI_2; 162 | CGFloat endAngle = startAngle + angleFromTrack; 163 | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, NO); 164 | 165 | CGPoint arcEndPoint = CGContextGetPathCurrentPoint(context); 166 | 167 | CGContextStrokePath(context); 168 | UIGraphicsPopContext(); 169 | 170 | return arcEndPoint; 171 | } 172 | 173 | - (CGPoint)drawPieTrack:(float)track atPoint:(CGPoint)center withRadius:(CGFloat)radius inContext:(CGContextRef)context { 174 | UIGraphicsPushContext(context); 175 | 176 | float angleFromTrack = translateValueFromSourceIntervalToDestinationInterval(track, self.minimumValue, self.maximumValue, 0, 2*M_PI); 177 | 178 | CGFloat startAngle = -M_PI_2; 179 | CGFloat endAngle = startAngle + angleFromTrack; 180 | CGContextMoveToPoint(context, center.x, center.y); 181 | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, NO); 182 | 183 | CGPoint arcEndPoint = CGContextGetPathCurrentPoint(context); 184 | 185 | CGContextClosePath(context); 186 | CGContextFillPath(context); 187 | UIGraphicsPopContext(); 188 | 189 | return arcEndPoint; 190 | } 191 | 192 | - (void)drawRect:(CGRect)rect { 193 | CGContextRef context = UIGraphicsGetCurrentContext(); 194 | CGPoint middlePoint; 195 | middlePoint.x = self.bounds.origin.x + self.bounds.size.width/2; 196 | middlePoint.y = self.bounds.origin.y + self.bounds.size.height/2; 197 | 198 | CGContextSetLineWidth(context, kLineWidth); 199 | 200 | // CGFloat radius = [self sliderRadius]; 201 | 202 | CGFloat radius = 25; 203 | switch (self.sliderStyle) { 204 | case UICircularSliderStylePie: 205 | [self.maximumTrackTintColor setFill]; 206 | [self drawPieTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context]; 207 | [self.minimumTrackTintColor setStroke]; 208 | [self drawCircularTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context]; 209 | [self.minimumTrackTintColor setFill]; 210 | self.thumbCenterPoint = [self drawPieTrack:self.value atPoint:middlePoint withRadius:radius inContext:context]; 211 | break; 212 | case UICircularSliderStyleCircle: 213 | default: 214 | [self.maximumTrackTintColor setStroke]; 215 | [self drawCircularTrack:self.maximumValue atPoint:middlePoint withRadius:radius inContext:context]; 216 | [self.minimumTrackTintColor setStroke]; 217 | self.thumbCenterPoint = [self drawCircularTrack:self.value atPoint:middlePoint withRadius:radius inContext:context]; 218 | break; 219 | } 220 | 221 | 222 | 223 | [self.thumbTintColor setFill]; 224 | [self drawThumbAtPoint:self.thumbCenterPoint inContext:context]; 225 | } 226 | 227 | /** @name Thumb management methods */ 228 | #pragma mark - Thumb management methods 229 | - (BOOL)isPointInThumb:(CGPoint)point { 230 | CGRect thumbTouchRect = CGRectMake(self.thumbCenterPoint.x - kThumbRadius, self.thumbCenterPoint.y - kThumbRadius, kThumbRadius*2, kThumbRadius*2); 231 | return CGRectContainsPoint(thumbTouchRect, point); 232 | } 233 | 234 | /** @name UIGestureRecognizer management methods */ 235 | #pragma mark - UIGestureRecognizer management methods 236 | - (void)panGestureHappened:(UIPanGestureRecognizer *)panGestureRecognizer { 237 | CGPoint tapLocation = [panGestureRecognizer locationInView:self]; 238 | switch (panGestureRecognizer.state) { 239 | case UIGestureRecognizerStateChanged: { 240 | CGFloat radius = [self sliderRadius]; 241 | CGPoint sliderCenter = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 242 | CGPoint sliderStartPoint = CGPointMake(sliderCenter.x, sliderCenter.y - radius); 243 | CGFloat angle = angleBetweenThreePoints(sliderCenter, sliderStartPoint, tapLocation); 244 | 245 | if (angle < 0) { 246 | angle = -angle; 247 | } 248 | else { 249 | angle = 2*M_PI - angle; 250 | } 251 | 252 | self.value = translateValueFromSourceIntervalToDestinationInterval(angle, 0, 2*M_PI, self.minimumValue, self.maximumValue); 253 | break; 254 | } 255 | case UIGestureRecognizerStateEnded: 256 | if (!self.isContinuous) { 257 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 258 | } 259 | if ([self isPointInThumb:tapLocation]) { 260 | [self sendActionsForControlEvents:UIControlEventTouchUpInside]; 261 | } 262 | else { 263 | [self sendActionsForControlEvents:UIControlEventTouchUpOutside]; 264 | } 265 | break; 266 | default: 267 | break; 268 | } 269 | } 270 | - (void)tapGestureHappened:(UITapGestureRecognizer *)tapGestureRecognizer { 271 | if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) { 272 | CGPoint tapLocation = [tapGestureRecognizer locationInView:self]; 273 | if ([self isPointInThumb:tapLocation]) { 274 | } 275 | else { 276 | } 277 | } 278 | } 279 | 280 | /** @name Touches Methods */ 281 | #pragma mark - Touches Methods 282 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 283 | [super touchesBegan:touches withEvent:event]; 284 | 285 | UITouch *touch = [touches anyObject]; 286 | CGPoint touchLocation = [touch locationInView:self]; 287 | if ([self isPointInThumb:touchLocation]) { 288 | [self sendActionsForControlEvents:UIControlEventTouchUpInside]; 289 | } 290 | } 291 | 292 | @end 293 | 294 | /** @name Utility Functions */ 295 | #pragma mark - Utility Functions 296 | float translateValueFromSourceIntervalToDestinationInterval(float sourceValue, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum) { 297 | float a, b, destinationValue; 298 | 299 | a = (destinationIntervalMaximum - destinationIntervalMinimum) / (sourceIntervalMaximum - sourceIntervalMinimum); 300 | b = destinationIntervalMaximum - a*sourceIntervalMaximum; 301 | 302 | destinationValue = a*sourceValue + b; 303 | 304 | return destinationValue; 305 | } 306 | float translateProgreessValueFromSourceIntervalToDestinationInterval(float progress, float sourceIntervalMinimum, float sourceIntervalMaximum, float destinationIntervalMinimum, float destinationIntervalMaximum) { 307 | float a, b, destinationValue; 308 | 309 | a = (destinationIntervalMaximum - destinationIntervalMinimum) / (sourceIntervalMaximum - sourceIntervalMinimum); 310 | b = destinationIntervalMaximum - a*sourceIntervalMaximum; 311 | 312 | destinationValue = (progress - b)/a; 313 | 314 | return destinationValue; 315 | } 316 | 317 | 318 | CGFloat angleBetweenThreePoints(CGPoint centerPoint, CGPoint p1, CGPoint p2) { 319 | CGPoint v1 = CGPointMake(p1.x - centerPoint.x, p1.y - centerPoint.y); 320 | CGPoint v2 = CGPointMake(p2.x - centerPoint.x, p2.y - centerPoint.y); 321 | 322 | CGFloat angle = atan2f(v2.x*v1.y - v1.x*v2.y, v1.x*v2.x + v1.y*v2.y); 323 | 324 | return angle; 325 | } 326 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Avish Manocha 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 | -------------------------------------------------------------------------------- /MultlipleTimer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E6E8D8391EA4988500C8E2E4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D8381EA4988500C8E2E4 /* main.m */; }; 11 | E6E8D83C1EA4988500C8E2E4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D83B1EA4988500C8E2E4 /* AppDelegate.m */; }; 12 | E6E8D83F1EA4988500C8E2E4 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D83E1EA4988500C8E2E4 /* ViewController.m */; }; 13 | E6E8D8421EA4988500C8E2E4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E6E8D8401EA4988500C8E2E4 /* Main.storyboard */; }; 14 | E6E8D8441EA4988500C8E2E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E6E8D8431EA4988500C8E2E4 /* Assets.xcassets */; }; 15 | E6E8D8471EA4988500C8E2E4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E6E8D8451EA4988500C8E2E4 /* LaunchScreen.storyboard */; }; 16 | E6E8D8521EA4988500C8E2E4 /* MultlipleTimerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D8511EA4988500C8E2E4 /* MultlipleTimerTests.m */; }; 17 | E6E8D85D1EA4988500C8E2E4 /* MultlipleTimerUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D85C1EA4988500C8E2E4 /* MultlipleTimerUITests.m */; }; 18 | E6E8D8701EA499B300C8E2E4 /* UICircularSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D86C1EA499B300C8E2E4 /* UICircularSlider.m */; }; 19 | E6E8D8711EA499B300C8E2E4 /* Timer.m in Sources */ = {isa = PBXBuildFile; fileRef = E6E8D86F1EA499B300C8E2E4 /* Timer.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | E6E8D84E1EA4988500C8E2E4 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = E6E8D82C1EA4988500C8E2E4 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = E6E8D8331EA4988500C8E2E4; 28 | remoteInfo = MultlipleTimer; 29 | }; 30 | E6E8D8591EA4988500C8E2E4 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = E6E8D82C1EA4988500C8E2E4 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = E6E8D8331EA4988500C8E2E4; 35 | remoteInfo = MultlipleTimer; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | E6E8D8341EA4988500C8E2E4 /* MultlipleTimer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MultlipleTimer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | E6E8D8381EA4988500C8E2E4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 42 | E6E8D83A1EA4988500C8E2E4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | E6E8D83B1EA4988500C8E2E4 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | E6E8D83D1EA4988500C8E2E4 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 45 | E6E8D83E1EA4988500C8E2E4 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 46 | E6E8D8411EA4988500C8E2E4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | E6E8D8431EA4988500C8E2E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | E6E8D8461EA4988500C8E2E4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | E6E8D8481EA4988500C8E2E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | E6E8D84D1EA4988500C8E2E4 /* MultlipleTimerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MultlipleTimerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | E6E8D8511EA4988500C8E2E4 /* MultlipleTimerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MultlipleTimerTests.m; sourceTree = ""; }; 52 | E6E8D8531EA4988500C8E2E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | E6E8D8581EA4988500C8E2E4 /* MultlipleTimerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MultlipleTimerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | E6E8D85C1EA4988500C8E2E4 /* MultlipleTimerUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MultlipleTimerUITests.m; sourceTree = ""; }; 55 | E6E8D85E1EA4988500C8E2E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | E6E8D86B1EA499B300C8E2E4 /* UICircularSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICircularSlider.h; sourceTree = ""; }; 57 | E6E8D86C1EA499B300C8E2E4 /* UICircularSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICircularSlider.m; sourceTree = ""; }; 58 | E6E8D86E1EA499B300C8E2E4 /* Timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Timer.h; sourceTree = ""; }; 59 | E6E8D86F1EA499B300C8E2E4 /* Timer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Timer.m; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | E6E8D8311EA4988500C8E2E4 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | ); 68 | runOnlyForDeploymentPostprocessing = 0; 69 | }; 70 | E6E8D84A1EA4988500C8E2E4 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | E6E8D8551EA4988500C8E2E4 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | /* End PBXFrameworksBuildPhase section */ 85 | 86 | /* Begin PBXGroup section */ 87 | E6E8D82B1EA4988500C8E2E4 = { 88 | isa = PBXGroup; 89 | children = ( 90 | E6E8D86A1EA499B300C8E2E4 /* CircularSlider */, 91 | E6E8D86D1EA499B300C8E2E4 /* Timer */, 92 | E6E8D8361EA4988500C8E2E4 /* MultlipleTimer */, 93 | E6E8D8501EA4988500C8E2E4 /* MultlipleTimerTests */, 94 | E6E8D85B1EA4988500C8E2E4 /* MultlipleTimerUITests */, 95 | E6E8D8351EA4988500C8E2E4 /* Products */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | E6E8D8351EA4988500C8E2E4 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | E6E8D8341EA4988500C8E2E4 /* MultlipleTimer.app */, 103 | E6E8D84D1EA4988500C8E2E4 /* MultlipleTimerTests.xctest */, 104 | E6E8D8581EA4988500C8E2E4 /* MultlipleTimerUITests.xctest */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | E6E8D8361EA4988500C8E2E4 /* MultlipleTimer */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | E6E8D83A1EA4988500C8E2E4 /* AppDelegate.h */, 113 | E6E8D83B1EA4988500C8E2E4 /* AppDelegate.m */, 114 | E6E8D83D1EA4988500C8E2E4 /* ViewController.h */, 115 | E6E8D83E1EA4988500C8E2E4 /* ViewController.m */, 116 | E6E8D8401EA4988500C8E2E4 /* Main.storyboard */, 117 | E6E8D8431EA4988500C8E2E4 /* Assets.xcassets */, 118 | E6E8D8451EA4988500C8E2E4 /* LaunchScreen.storyboard */, 119 | E6E8D8481EA4988500C8E2E4 /* Info.plist */, 120 | E6E8D8371EA4988500C8E2E4 /* Supporting Files */, 121 | ); 122 | path = MultlipleTimer; 123 | sourceTree = ""; 124 | }; 125 | E6E8D8371EA4988500C8E2E4 /* Supporting Files */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | E6E8D8381EA4988500C8E2E4 /* main.m */, 129 | ); 130 | name = "Supporting Files"; 131 | sourceTree = ""; 132 | }; 133 | E6E8D8501EA4988500C8E2E4 /* MultlipleTimerTests */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | E6E8D8511EA4988500C8E2E4 /* MultlipleTimerTests.m */, 137 | E6E8D8531EA4988500C8E2E4 /* Info.plist */, 138 | ); 139 | path = MultlipleTimerTests; 140 | sourceTree = ""; 141 | }; 142 | E6E8D85B1EA4988500C8E2E4 /* MultlipleTimerUITests */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | E6E8D85C1EA4988500C8E2E4 /* MultlipleTimerUITests.m */, 146 | E6E8D85E1EA4988500C8E2E4 /* Info.plist */, 147 | ); 148 | path = MultlipleTimerUITests; 149 | sourceTree = ""; 150 | }; 151 | E6E8D86A1EA499B300C8E2E4 /* CircularSlider */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | E6E8D86B1EA499B300C8E2E4 /* UICircularSlider.h */, 155 | E6E8D86C1EA499B300C8E2E4 /* UICircularSlider.m */, 156 | ); 157 | path = CircularSlider; 158 | sourceTree = ""; 159 | }; 160 | E6E8D86D1EA499B300C8E2E4 /* Timer */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | E6E8D86E1EA499B300C8E2E4 /* Timer.h */, 164 | E6E8D86F1EA499B300C8E2E4 /* Timer.m */, 165 | ); 166 | path = Timer; 167 | sourceTree = ""; 168 | }; 169 | /* End PBXGroup section */ 170 | 171 | /* Begin PBXNativeTarget section */ 172 | E6E8D8331EA4988500C8E2E4 /* MultlipleTimer */ = { 173 | isa = PBXNativeTarget; 174 | buildConfigurationList = E6E8D8611EA4988500C8E2E4 /* Build configuration list for PBXNativeTarget "MultlipleTimer" */; 175 | buildPhases = ( 176 | E6E8D8301EA4988500C8E2E4 /* Sources */, 177 | E6E8D8311EA4988500C8E2E4 /* Frameworks */, 178 | E6E8D8321EA4988500C8E2E4 /* Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | ); 184 | name = MultlipleTimer; 185 | productName = MultlipleTimer; 186 | productReference = E6E8D8341EA4988500C8E2E4 /* MultlipleTimer.app */; 187 | productType = "com.apple.product-type.application"; 188 | }; 189 | E6E8D84C1EA4988500C8E2E4 /* MultlipleTimerTests */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = E6E8D8641EA4988500C8E2E4 /* Build configuration list for PBXNativeTarget "MultlipleTimerTests" */; 192 | buildPhases = ( 193 | E6E8D8491EA4988500C8E2E4 /* Sources */, 194 | E6E8D84A1EA4988500C8E2E4 /* Frameworks */, 195 | E6E8D84B1EA4988500C8E2E4 /* Resources */, 196 | ); 197 | buildRules = ( 198 | ); 199 | dependencies = ( 200 | E6E8D84F1EA4988500C8E2E4 /* PBXTargetDependency */, 201 | ); 202 | name = MultlipleTimerTests; 203 | productName = MultlipleTimerTests; 204 | productReference = E6E8D84D1EA4988500C8E2E4 /* MultlipleTimerTests.xctest */; 205 | productType = "com.apple.product-type.bundle.unit-test"; 206 | }; 207 | E6E8D8571EA4988500C8E2E4 /* MultlipleTimerUITests */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = E6E8D8671EA4988500C8E2E4 /* Build configuration list for PBXNativeTarget "MultlipleTimerUITests" */; 210 | buildPhases = ( 211 | E6E8D8541EA4988500C8E2E4 /* Sources */, 212 | E6E8D8551EA4988500C8E2E4 /* Frameworks */, 213 | E6E8D8561EA4988500C8E2E4 /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | E6E8D85A1EA4988500C8E2E4 /* PBXTargetDependency */, 219 | ); 220 | name = MultlipleTimerUITests; 221 | productName = MultlipleTimerUITests; 222 | productReference = E6E8D8581EA4988500C8E2E4 /* MultlipleTimerUITests.xctest */; 223 | productType = "com.apple.product-type.bundle.ui-testing"; 224 | }; 225 | /* End PBXNativeTarget section */ 226 | 227 | /* Begin PBXProject section */ 228 | E6E8D82C1EA4988500C8E2E4 /* Project object */ = { 229 | isa = PBXProject; 230 | attributes = { 231 | LastUpgradeCheck = 0830; 232 | ORGANIZATIONNAME = Slack; 233 | TargetAttributes = { 234 | E6E8D8331EA4988500C8E2E4 = { 235 | CreatedOnToolsVersion = 8.3; 236 | ProvisioningStyle = Automatic; 237 | }; 238 | E6E8D84C1EA4988500C8E2E4 = { 239 | CreatedOnToolsVersion = 8.3; 240 | ProvisioningStyle = Automatic; 241 | TestTargetID = E6E8D8331EA4988500C8E2E4; 242 | }; 243 | E6E8D8571EA4988500C8E2E4 = { 244 | CreatedOnToolsVersion = 8.3; 245 | ProvisioningStyle = Automatic; 246 | TestTargetID = E6E8D8331EA4988500C8E2E4; 247 | }; 248 | }; 249 | }; 250 | buildConfigurationList = E6E8D82F1EA4988500C8E2E4 /* Build configuration list for PBXProject "MultlipleTimer" */; 251 | compatibilityVersion = "Xcode 3.2"; 252 | developmentRegion = English; 253 | hasScannedForEncodings = 0; 254 | knownRegions = ( 255 | en, 256 | Base, 257 | ); 258 | mainGroup = E6E8D82B1EA4988500C8E2E4; 259 | productRefGroup = E6E8D8351EA4988500C8E2E4 /* Products */; 260 | projectDirPath = ""; 261 | projectRoot = ""; 262 | targets = ( 263 | E6E8D8331EA4988500C8E2E4 /* MultlipleTimer */, 264 | E6E8D84C1EA4988500C8E2E4 /* MultlipleTimerTests */, 265 | E6E8D8571EA4988500C8E2E4 /* MultlipleTimerUITests */, 266 | ); 267 | }; 268 | /* End PBXProject section */ 269 | 270 | /* Begin PBXResourcesBuildPhase section */ 271 | E6E8D8321EA4988500C8E2E4 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | E6E8D8471EA4988500C8E2E4 /* LaunchScreen.storyboard in Resources */, 276 | E6E8D8441EA4988500C8E2E4 /* Assets.xcassets in Resources */, 277 | E6E8D8421EA4988500C8E2E4 /* Main.storyboard in Resources */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | E6E8D84B1EA4988500C8E2E4 /* Resources */ = { 282 | isa = PBXResourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | E6E8D8561EA4988500C8E2E4 /* Resources */ = { 289 | isa = PBXResourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXResourcesBuildPhase section */ 296 | 297 | /* Begin PBXSourcesBuildPhase section */ 298 | E6E8D8301EA4988500C8E2E4 /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | E6E8D8711EA499B300C8E2E4 /* Timer.m in Sources */, 303 | E6E8D83F1EA4988500C8E2E4 /* ViewController.m in Sources */, 304 | E6E8D83C1EA4988500C8E2E4 /* AppDelegate.m in Sources */, 305 | E6E8D8391EA4988500C8E2E4 /* main.m in Sources */, 306 | E6E8D8701EA499B300C8E2E4 /* UICircularSlider.m in Sources */, 307 | ); 308 | runOnlyForDeploymentPostprocessing = 0; 309 | }; 310 | E6E8D8491EA4988500C8E2E4 /* Sources */ = { 311 | isa = PBXSourcesBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | E6E8D8521EA4988500C8E2E4 /* MultlipleTimerTests.m in Sources */, 315 | ); 316 | runOnlyForDeploymentPostprocessing = 0; 317 | }; 318 | E6E8D8541EA4988500C8E2E4 /* Sources */ = { 319 | isa = PBXSourcesBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | E6E8D85D1EA4988500C8E2E4 /* MultlipleTimerUITests.m in Sources */, 323 | ); 324 | runOnlyForDeploymentPostprocessing = 0; 325 | }; 326 | /* End PBXSourcesBuildPhase section */ 327 | 328 | /* Begin PBXTargetDependency section */ 329 | E6E8D84F1EA4988500C8E2E4 /* PBXTargetDependency */ = { 330 | isa = PBXTargetDependency; 331 | target = E6E8D8331EA4988500C8E2E4 /* MultlipleTimer */; 332 | targetProxy = E6E8D84E1EA4988500C8E2E4 /* PBXContainerItemProxy */; 333 | }; 334 | E6E8D85A1EA4988500C8E2E4 /* PBXTargetDependency */ = { 335 | isa = PBXTargetDependency; 336 | target = E6E8D8331EA4988500C8E2E4 /* MultlipleTimer */; 337 | targetProxy = E6E8D8591EA4988500C8E2E4 /* PBXContainerItemProxy */; 338 | }; 339 | /* End PBXTargetDependency section */ 340 | 341 | /* Begin PBXVariantGroup section */ 342 | E6E8D8401EA4988500C8E2E4 /* Main.storyboard */ = { 343 | isa = PBXVariantGroup; 344 | children = ( 345 | E6E8D8411EA4988500C8E2E4 /* Base */, 346 | ); 347 | name = Main.storyboard; 348 | sourceTree = ""; 349 | }; 350 | E6E8D8451EA4988500C8E2E4 /* LaunchScreen.storyboard */ = { 351 | isa = PBXVariantGroup; 352 | children = ( 353 | E6E8D8461EA4988500C8E2E4 /* Base */, 354 | ); 355 | name = LaunchScreen.storyboard; 356 | sourceTree = ""; 357 | }; 358 | /* End PBXVariantGroup section */ 359 | 360 | /* Begin XCBuildConfiguration section */ 361 | E6E8D85F1EA4988500C8E2E4 /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | CLANG_ANALYZER_NONNULL = YES; 366 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 368 | CLANG_CXX_LIBRARY = "libc++"; 369 | CLANG_ENABLE_MODULES = YES; 370 | CLANG_ENABLE_OBJC_ARC = YES; 371 | CLANG_WARN_BOOL_CONVERSION = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 374 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = dwarf; 386 | ENABLE_STRICT_OBJC_MSGSEND = YES; 387 | ENABLE_TESTABILITY = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu99; 389 | GCC_DYNAMIC_NO_PIC = NO; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_OPTIMIZATION_LEVEL = 0; 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 403 | MTL_ENABLE_DEBUG_INFO = YES; 404 | ONLY_ACTIVE_ARCH = YES; 405 | SDKROOT = iphoneos; 406 | TARGETED_DEVICE_FAMILY = "1,2"; 407 | }; 408 | name = Debug; 409 | }; 410 | E6E8D8601EA4988500C8E2E4 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ALWAYS_SEARCH_USER_PATHS = NO; 414 | CLANG_ANALYZER_NONNULL = YES; 415 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 423 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 424 | CLANG_WARN_EMPTY_BODY = YES; 425 | CLANG_WARN_ENUM_CONVERSION = YES; 426 | CLANG_WARN_INFINITE_RECURSION = YES; 427 | CLANG_WARN_INT_CONVERSION = YES; 428 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 435 | ENABLE_NS_ASSERTIONS = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu99; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 441 | GCC_WARN_UNDECLARED_SELECTOR = YES; 442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 443 | GCC_WARN_UNUSED_FUNCTION = YES; 444 | GCC_WARN_UNUSED_VARIABLE = YES; 445 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 446 | MTL_ENABLE_DEBUG_INFO = NO; 447 | SDKROOT = iphoneos; 448 | TARGETED_DEVICE_FAMILY = "1,2"; 449 | VALIDATE_PRODUCT = YES; 450 | }; 451 | name = Release; 452 | }; 453 | E6E8D8621EA4988500C8E2E4 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | buildSettings = { 456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 457 | DEVELOPMENT_TEAM = ""; 458 | INFOPLIST_FILE = MultlipleTimer/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | PRODUCT_BUNDLE_IDENTIFIER = com.slack.com.MultlipleTimer; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | TARGETED_DEVICE_FAMILY = 1; 463 | }; 464 | name = Debug; 465 | }; 466 | E6E8D8631EA4988500C8E2E4 /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 470 | DEVELOPMENT_TEAM = ""; 471 | INFOPLIST_FILE = MultlipleTimer/Info.plist; 472 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 473 | PRODUCT_BUNDLE_IDENTIFIER = com.slack.com.MultlipleTimer; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | TARGETED_DEVICE_FAMILY = 1; 476 | }; 477 | name = Release; 478 | }; 479 | E6E8D8651EA4988500C8E2E4 /* Debug */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | BUNDLE_LOADER = "$(TEST_HOST)"; 483 | INFOPLIST_FILE = MultlipleTimerTests/Info.plist; 484 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 485 | PRODUCT_BUNDLE_IDENTIFIER = com.slack.com.MultlipleTimerTests; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MultlipleTimer.app/MultlipleTimer"; 488 | }; 489 | name = Debug; 490 | }; 491 | E6E8D8661EA4988500C8E2E4 /* Release */ = { 492 | isa = XCBuildConfiguration; 493 | buildSettings = { 494 | BUNDLE_LOADER = "$(TEST_HOST)"; 495 | INFOPLIST_FILE = MultlipleTimerTests/Info.plist; 496 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 497 | PRODUCT_BUNDLE_IDENTIFIER = com.slack.com.MultlipleTimerTests; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MultlipleTimer.app/MultlipleTimer"; 500 | }; 501 | name = Release; 502 | }; 503 | E6E8D8681EA4988500C8E2E4 /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | INFOPLIST_FILE = MultlipleTimerUITests/Info.plist; 507 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 508 | PRODUCT_BUNDLE_IDENTIFIER = com.slack.com.MultlipleTimerUITests; 509 | PRODUCT_NAME = "$(TARGET_NAME)"; 510 | TEST_TARGET_NAME = MultlipleTimer; 511 | }; 512 | name = Debug; 513 | }; 514 | E6E8D8691EA4988500C8E2E4 /* Release */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | INFOPLIST_FILE = MultlipleTimerUITests/Info.plist; 518 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 519 | PRODUCT_BUNDLE_IDENTIFIER = com.slack.com.MultlipleTimerUITests; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | TEST_TARGET_NAME = MultlipleTimer; 522 | }; 523 | name = Release; 524 | }; 525 | /* End XCBuildConfiguration section */ 526 | 527 | /* Begin XCConfigurationList section */ 528 | E6E8D82F1EA4988500C8E2E4 /* Build configuration list for PBXProject "MultlipleTimer" */ = { 529 | isa = XCConfigurationList; 530 | buildConfigurations = ( 531 | E6E8D85F1EA4988500C8E2E4 /* Debug */, 532 | E6E8D8601EA4988500C8E2E4 /* Release */, 533 | ); 534 | defaultConfigurationIsVisible = 0; 535 | defaultConfigurationName = Release; 536 | }; 537 | E6E8D8611EA4988500C8E2E4 /* Build configuration list for PBXNativeTarget "MultlipleTimer" */ = { 538 | isa = XCConfigurationList; 539 | buildConfigurations = ( 540 | E6E8D8621EA4988500C8E2E4 /* Debug */, 541 | E6E8D8631EA4988500C8E2E4 /* Release */, 542 | ); 543 | defaultConfigurationIsVisible = 0; 544 | defaultConfigurationName = Release; 545 | }; 546 | E6E8D8641EA4988500C8E2E4 /* Build configuration list for PBXNativeTarget "MultlipleTimerTests" */ = { 547 | isa = XCConfigurationList; 548 | buildConfigurations = ( 549 | E6E8D8651EA4988500C8E2E4 /* Debug */, 550 | E6E8D8661EA4988500C8E2E4 /* Release */, 551 | ); 552 | defaultConfigurationIsVisible = 0; 553 | defaultConfigurationName = Release; 554 | }; 555 | E6E8D8671EA4988500C8E2E4 /* Build configuration list for PBXNativeTarget "MultlipleTimerUITests" */ = { 556 | isa = XCConfigurationList; 557 | buildConfigurations = ( 558 | E6E8D8681EA4988500C8E2E4 /* Debug */, 559 | E6E8D8691EA4988500C8E2E4 /* Release */, 560 | ); 561 | defaultConfigurationIsVisible = 0; 562 | defaultConfigurationName = Release; 563 | }; 564 | /* End XCConfigurationList section */ 565 | }; 566 | rootObject = E6E8D82C1EA4988500C8E2E4 /* Project object */; 567 | } 568 | -------------------------------------------------------------------------------- /MultlipleTimer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MultlipleTimer.xcodeproj/project.xcworkspace/xcuserdata/gh.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avish07/MultipleTimers/d64307daaa1bd26882542ea013479ca74881eb25/MultlipleTimer.xcodeproj/project.xcworkspace/xcuserdata/gh.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /MultlipleTimer.xcodeproj/xcuserdata/gh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /MultlipleTimer.xcodeproj/xcuserdata/gh.xcuserdatad/xcschemes/MultlipleTimer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /MultlipleTimer.xcodeproj/xcuserdata/gh.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | MultlipleTimer.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | E6E8D8331EA4988500C8E2E4 16 | 17 | primary 18 | 19 | 20 | E6E8D84C1EA4988500C8E2E4 21 | 22 | primary 23 | 24 | 25 | E6E8D8571EA4988500C8E2E4 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /MultlipleTimer/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // MultlipleTimer 4 | // 5 | // Created by Avish Manocha on 4/17/17. 6 | // Copyright © 2017 Slack. 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 | -------------------------------------------------------------------------------- /MultlipleTimer/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // MultlipleTimer 4 | // 5 | // Created by Avish Manocha on 4/17/17. 6 | // Copyright © 2017 Slack. 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 | +(AppDelegate *)appDeleagte{ 52 | return [[UIApplication sharedApplication] delegate]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /MultlipleTimer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /MultlipleTimer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /MultlipleTimer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 53 | 54 | 55 | 56 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /MultlipleTimer/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 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /MultlipleTimer/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // MultlipleTimer 4 | // 5 | // Created by Avish Manocha on 4/17/17. 6 | // Copyright © 2017 Slack. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController{ 12 | __weak IBOutlet UITableView *tblObj; 13 | } 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /MultlipleTimer/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MultlipleTimer 4 | // 5 | // Created by Avish Manocha on 4/17/17. 6 | // Copyright © 2017 Slack. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "UICircularSlider.h" 11 | #import "Timer.h" 12 | 13 | @interface ViewController (){ 14 | NSTimer *refreshTimer; 15 | NSMutableArray *timerArr, *secondsArr; 16 | } 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view, typically from a nib. 25 | 26 | [self initialiseData]; 27 | } 28 | 29 | 30 | - (void)didReceiveMemoryWarning { 31 | [super didReceiveMemoryWarning]; 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | -(void)initialiseData{ 36 | timerArr = [[NSMutableArray alloc] init]; 37 | secondsArr = [[NSMutableArray alloc] init]; 38 | 39 | // expireInArr (time left), expireTimeArr (Total time) 40 | NSMutableArray *expireArr; 41 | expireArr = [[NSMutableArray alloc] init]; 42 | 43 | for (int i = 0; i < 15; i++) { 44 | if (i == 5 || i == 10) { 45 | [expireArr addObject:@{@"expire_in": @"-1", @"expire_time": @"1800"}]; 46 | } 47 | 48 | if (i < 5 || i > 13) { 49 | [expireArr addObject:@{@"expire_in" : @"1800", @"expire_time": @"1800"}]; 50 | } 51 | else 52 | { 53 | [expireArr addObject:@{@"expire_in" : @"900", @"expire_time": @"1800"}]; 54 | } 55 | 56 | } 57 | 58 | for (NSDictionary *dict in expireArr) { 59 | NSDate *newDate; 60 | if ([dict[@"expire_in"] integerValue] < 0) { 61 | [timerArr addObject:@"-1"]; 62 | } 63 | else 64 | { 65 | newDate = [NSDate dateWithTimeIntervalSinceNow: [dict[@"expire_in"] integerValue]]; 66 | [timerArr addObject: newDate]; 67 | } 68 | 69 | if (newDate > 0) { 70 | NSInteger seconds = [[NSDate dateWithTimeIntervalSinceNow:[dict[@"expire_time"] intValue]] timeIntervalSinceDate:[NSDate date]]; 71 | [secondsArr addObject: [NSString stringWithFormat:@"%ld", (long)seconds]]; 72 | } 73 | else 74 | { 75 | [secondsArr addObject:@"-1"]; 76 | } 77 | } 78 | [tblObj setDataSource:self]; 79 | [tblObj setDelegate:self]; 80 | [tblObj reloadData]; 81 | [self validateTimer]; 82 | } 83 | 84 | // timer 85 | -(void)validateTimer{ 86 | refreshTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(reloadTableCells:) userInfo:nil repeats:YES]; 87 | } 88 | 89 | -(void)reloadTableCells:(NSTimer *)timer{ 90 | for (UITableViewCell *cell in tblObj.visibleCells) { 91 | NSIndexPath *path = [tblObj indexPathForCell:cell]; 92 | if (path.row < timerArr.count) { 93 | [Timer configureCell:cell withTimerArr:timerArr withSecondsArr:secondsArr forAtIndexPath:path]; 94 | } 95 | } 96 | } 97 | 98 | #pragma mark - UITableview Datasource and Delegates 99 | 100 | -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 101 | return 1; 102 | } 103 | 104 | -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 105 | return 140.0f; 106 | } 107 | 108 | -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 109 | return timerArr.count; 110 | } 111 | 112 | -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 113 | static NSString *cellIdentifier = @"TimerCell"; 114 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 115 | if (!cell) { 116 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 117 | } 118 | 119 | [Timer configureCell:cell withTimerArr:timerArr withSecondsArr:secondsArr forAtIndexPath:indexPath]; 120 | 121 | return cell; 122 | 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /MultlipleTimer/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // MultlipleTimer 4 | // 5 | // Created by gh on 4/17/17. 6 | // Copyright © 2017 Slack. 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 | -------------------------------------------------------------------------------- /MultlipleTimerTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MultlipleTimerTests/MultlipleTimerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MultlipleTimerTests.m 3 | // MultlipleTimerTests 4 | // 5 | // Created by gh on 4/17/17. 6 | // Copyright © 2017 Slack. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MultlipleTimerTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation MultlipleTimerTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /MultlipleTimerUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MultlipleTimerUITests/MultlipleTimerUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // MultlipleTimerUITests.m 3 | // MultlipleTimerUITests 4 | // 5 | // Created by gh on 4/17/17. 6 | // Copyright © 2017 Slack. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MultlipleTimerUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation MultlipleTimerUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // 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. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multiple Timers 2 | 3 | Multiple Timers is a custom view for iOS that enables circular progress animation with respect to given duration. 4 | 5 | Multiple Timers supports following features - 6 | 7 | Progress animation in clockwise or counter clockwise direction. 8 | Get time elapsed since timer started or time left for the counter to complete. 9 | Provide Start and finish points for the animation. 10 | Pre-fill the progress up till a certain point. 11 | Resume the animation on the basis of duration elapsed from total duration. ...and much more. 12 | -------------------------------------------------------------------------------- /Timer/Timer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Timer.h 3 | // MultipleTimer 4 | // 5 | // Created by Avish Manocha on 16/04/17. 6 | // Copyright © 2017 Avish Manocha. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface Timer : NSObject 13 | 14 | + (void)configureCell:(UITableViewCell *)cell withTimerArr:(NSMutableArray *)timerArr withSecondsArr:(NSMutableArray *)secondsArr forAtIndexPath:(NSIndexPath *)indexPath; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Timer/Timer.m: -------------------------------------------------------------------------------- 1 | // 2 | // Timer.m 3 | // MultipleTimer 4 | // 5 | // Created by Avish Manocha on 16/04/17. 6 | // Copyright © 2017 Avish Manocha. All rights reserved. 7 | // 8 | 9 | #import "Timer.h" 10 | #import "UICircularSlider.h" 11 | 12 | @implementation Timer 13 | 14 | + (void)configureCell:(UITableViewCell *)cell withTimerArr:(NSMutableArray *)timerArr withSecondsArr:(NSMutableArray *)secondsArr forAtIndexPath:(NSIndexPath *)indexPath{ 15 | 16 | if (timerArr.count) { 17 | dispatch_async(dispatch_get_main_queue(), ^{ 18 | 19 | UILabel *timerLbl = (UILabel *)[cell.contentView viewWithTag:1]; 20 | UILabel *circularTimerLbl = (UILabel *)[cell.contentView viewWithTag:2]; 21 | UICircularSlider *slider = (UICircularSlider *)[cell.contentView viewWithTag:3]; 22 | 23 | if (![timerArr[indexPath.row] isKindOfClass:[NSDate class]]) { 24 | [timerLbl setText:@"Timer Over"]; 25 | [circularTimerLbl setText:@"00:00"]; 26 | slider.value = 0; 27 | [slider setHidden:false]; 28 | return ; 29 | } 30 | 31 | NSInteger time = [timerArr[indexPath.row] timeIntervalSinceDate: [NSDate date]]; 32 | NSInteger hour = (time / 3600) % 60; 33 | NSInteger minute = (time / 60) % 60; 34 | NSInteger second = time % 60; 35 | 36 | if (hour <= 0 && minute <= 0 && second <= 0) { 37 | [timerLbl setText:@"Timer Over"]; 38 | [circularTimerLbl setText:@"Over"]; 39 | [slider setHidden:true]; 40 | return ; 41 | } 42 | 43 | NSString *hrs, *minutes, *seconds; 44 | if (hour > 0) { 45 | hrs = [NSString stringWithFormat:@"%ld", (long)hour]; 46 | if (hrs.length < 2) { 47 | hrs = [NSString stringWithFormat:@"0%@", hrs]; 48 | } 49 | 50 | minutes = [NSString stringWithFormat:@"%ld", (long)minute]; 51 | if (minutes.length < 2) { 52 | minutes = [NSString stringWithFormat:@"0%@", minutes]; 53 | } 54 | 55 | [timerLbl setText:[NSString stringWithFormat:@"%@:%@", hrs, minutes]]; 56 | [circularTimerLbl setText:[NSString stringWithFormat:@"%@:%@", hrs, minutes]]; 57 | } 58 | else 59 | { 60 | minutes = [NSString stringWithFormat:@"%ld", (long)minute]; 61 | if (minutes.length < 2) { 62 | minutes = [NSString stringWithFormat:@"0%@", minutes]; 63 | } 64 | 65 | seconds = [NSString stringWithFormat:@"%ld", (long)second]; 66 | if (seconds.length < 2) { 67 | seconds = [NSString stringWithFormat:@"0%@", seconds]; 68 | } 69 | 70 | [timerLbl setText:[NSString stringWithFormat:@"%@:%@", minutes, seconds]]; 71 | [circularTimerLbl setText:[NSString stringWithFormat:@"%@:%@", minutes, seconds]]; 72 | } 73 | 74 | float sliderValue = (float)1/[secondsArr[indexPath.row] integerValue]; 75 | slider.value = sliderValue * ([secondsArr[indexPath.row] integerValue] - time); 76 | }); 77 | } 78 | } 79 | 80 | 81 | @end 82 | --------------------------------------------------------------------------------