├── LICENSE.md ├── ORBVisualTimer ├── ORBVisualTimer.h └── ORBVisualTimer.m ├── ORBVisualTimerDemo ├── ORBVisualTimerDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── ORBVisualTimerDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── Resources │ │ ├── 1.png │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── pattern_1@3x.png │ │ ├── pattern_2@3x.png │ │ └── pattern_3@3x.png │ ├── ViewController.h │ ├── ViewController.m │ └── main.m └── ORBVisualTimerDemoUITests │ ├── Info.plist │ └── ORBVisualTimerDemoUITests.m ├── README.md └── Screenshots ├── bars.jpg ├── flow_w180.gif └── flow_w300.gif /LICENSE.md: -------------------------------------------------------------------------------- 1 | ORBVisualTimer 2 | 3 | Copyright (C) 2016 0xNSHuman 4 | 5 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 8 | 9 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 10 | 11 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 12 | 13 | 3. This notice may not be removed or altered from any source distribution. 14 | -------------------------------------------------------------------------------- /ORBVisualTimer/ORBVisualTimer.h: -------------------------------------------------------------------------------- 1 | // 2 | // ORBVisualTimer.h 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/03/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). All rights reserved. 7 | // 8 | // Distributed under the permissive zlib License 9 | // Get the latest version from here: 10 | // 11 | // https://github.com/0xNSHuman/ORBVisualTimer 12 | // 13 | // This software is provided 'as-is', without any express or implied 14 | // warranty. In no event will the authors be held liable for any damages 15 | // arising from the use of this software. 16 | // 17 | // Permission is granted to anyone to use this software for any purpose, 18 | // including commercial applications, and to alter it and redistribute it 19 | // freely, subject to the following restrictions: 20 | // 21 | // 1. The origin of this software must not be misrepresented; you must not 22 | // claim that you wrote the original software. If you use this software 23 | // in a product, an acknowledgment in the product documentation would be 24 | // appreciated but is not required. 25 | // 26 | // 2. Altered source versions must be plainly marked as such, and must not be 27 | // misrepresented as being the original software. 28 | // 29 | // 3. This notice may not be removed or altered from any source distribution. 30 | 31 | #import 32 | 33 | typedef NS_ENUM(NSUInteger) { 34 | ORBVisualTimerStyleBar = 0 35 | /* More styles to come */ 36 | } ORBVisualTimerStyle; 37 | 38 | typedef NS_ENUM(NSUInteger) { 39 | ORBVisualTimerBarAnimationStyleStraight = 0, 40 | ORBVisualTimerBarAnimationStyleBackwards, 41 | ORBVisualTimerBarAnimationStyleReflection 42 | } ORBVisualTimerBarAnimationStyle; 43 | 44 | 45 | @class ORBVisualTimer; 46 | 47 | @protocol ORBVisualTimerDelegate 48 | 49 | @required 50 | - (void)visualTimerFired:(ORBVisualTimer *)timerView; 51 | 52 | @end 53 | 54 | 55 | /** 56 | \class ORBVisualTimer 57 | \brief Base class for all visual timers. 58 | */ 59 | @interface ORBVisualTimer : UIView 60 | 61 | #pragma mark - Properties 62 | 63 | /** 64 | Style of the timer to construct. In v1.0 only ORBVisualTimerStyleBar is implemented. 65 | */ 66 | @property (nonatomic, assign, readonly) ORBVisualTimerStyle style; 67 | 68 | /** 69 | Delegate object to receive events from timer. 70 | */ 71 | @property (nonatomic, weak) id delegate; 72 | 73 | /** 74 | Use this readonly property to track currently remaining time. 75 | */ 76 | @property (nonatomic, assign, readonly) NSTimeInterval timeRemaining; 77 | 78 | /** 79 | Indicates if timer is currently running or stopped. 80 | */ 81 | @property (nonatomic, assign, readonly) BOOL timerIsActive; 82 | 83 | /* --- CUSTOMIZATION --- */ 84 | 85 | /** 86 | Color of the containing view around timer itself. Default value is black color. 87 | */ 88 | @property (nonatomic, strong) UIColor *backgroundViewColor; 89 | 90 | /** 91 | Corner radius for containing view around timer. Default value is 0.0f. 92 | */ 93 | @property (nonatomic, assign) CGFloat backgroundViewCornerRadius; 94 | 95 | /** 96 | Color of the timer shape's (bar, circle, etc) background component, which stays inactive all the time. Default value is light gray color. 97 | */ 98 | @property (nonatomic, strong) UIColor *timerShapeInactiveColor; 99 | 100 | /** 101 | Color of the timer shape's (bar, circle, etc) foreground component, which is animated according to time remaining. Default value is green color. 102 | */ 103 | @property (nonatomic, strong) UIColor *timerShapeActiveColor; 104 | 105 | /** 106 | Whether or not to show label with time remaining. Default value is YES; 107 | */ 108 | @property (nonatomic, assign) BOOL showTimerLabel; 109 | 110 | /** 111 | Color of the timer label text. Default value is white color. 112 | */ 113 | @property (nonatomic, strong) UIColor *timerLabelColor; 114 | 115 | 116 | /** 117 | Whether or not to hide timer view after firing. Default value is NO. 118 | */ 119 | @property (nonatomic, assign) BOOL autohideWhenFired; 120 | 121 | #pragma mark - Methods 122 | 123 | /** 124 | \brief Constructs timer view with given style. 125 | \param style Style of the timer. 126 | \param frame Frame of the view. 127 | \param timeRemaining Time to initialize timer with. 128 | \returns Timer object of correspondent class. 129 | */ 130 | + (instancetype)timerWithStyle:(ORBVisualTimerStyle)style 131 | frame:(CGRect)frame 132 | timeRemaining:(NSTimeInterval)timeRemaining; 133 | 134 | /** 135 | \brief Starts the timer. 136 | */ 137 | - (void)start; 138 | 139 | /** 140 | \brief Stops the timer and resets time setting. 141 | */ 142 | - (void)stopTimerView; 143 | 144 | /** 145 | \brief Stops the timer and hides its view. 146 | */ 147 | - (void)stopAndHide; 148 | 149 | @end 150 | 151 | /** 152 | \class ORBVisualTimerBar 153 | \brief Bar style implementation of the visual timer. 154 | */ 155 | @interface ORBVisualTimerBar : ORBVisualTimer 156 | 157 | #pragma mark - Properties 158 | 159 | /** 160 | Style of bar timer animation. Default value is ORBVisualTimerBarAnimationStyleStraight. 161 | */ 162 | @property (nonatomic, assign) ORBVisualTimerBarAnimationStyle barAnimationStyle; 163 | 164 | /** 165 | Thikness of bar. Default value is 5.0f. 166 | */ 167 | @property (nonatomic, assign) CGFloat barThickness; 168 | 169 | /** 170 | Horizontal padding for both left and right bar ends. Default value is 10.0f; 171 | */ 172 | @property (nonatomic, assign) CGFloat barPadding; 173 | 174 | /** 175 | Cap style for both bar ends. Default value is kCALineCapRound. 176 | \discussion Possible pre-defined values include kCALineCapButt, kCALineCapRound and kCALineCapSquare. 177 | */ 178 | @property (nonatomic, assign) NSString *barCapStyle; 179 | 180 | #pragma mark - Methods 181 | 182 | /** 183 | \brief Initializes bar timer with given style. 184 | \param barAnimationStyle Style of bar animation. 185 | \param frame Frame of the view. 186 | \param timeRemaining Time to initialize timer with. 187 | */ 188 | - (instancetype)initWithBarAnimationStyle:(ORBVisualTimerBarAnimationStyle)barAnimationStyle 189 | frame:(CGRect)frame 190 | timeRemaining:(NSTimeInterval)timeRemaining; 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /ORBVisualTimer/ORBVisualTimer.m: -------------------------------------------------------------------------------- 1 | // 2 | // ORBVisualTimer.m 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/03/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). All rights reserved. 7 | // 8 | // Distributed under the permissive zlib License 9 | // Get the latest version from here: 10 | // 11 | // https://github.com/0xNSHuman/ORBVisualTimer 12 | // 13 | // This software is provided 'as-is', without any express or implied 14 | // warranty. In no event will the authors be held liable for any damages 15 | // arising from the use of this software. 16 | // 17 | // Permission is granted to anyone to use this software for any purpose, 18 | // including commercial applications, and to alter it and redistribute it 19 | // freely, subject to the following restrictions: 20 | // 21 | // 1. The origin of this software must not be misrepresented; you must not 22 | // claim that you wrote the original software. If you use this software 23 | // in a product, an acknowledgment in the product documentation would be 24 | // appreciated but is not required. 25 | // 26 | // 2. Altered source versions must be plainly marked as such, and must not be 27 | // misrepresented as being the original software. 28 | // 29 | // 3. This notice may not be removed or altered from any source distribution. 30 | 31 | #import "ORBVisualTimer.h" 32 | 33 | #define ORBVisualTimerDefaultHeight 60.0f 34 | #define ORBVisualTimerTimeRemainingDefault 15.0f 35 | 36 | #define ORBScreenDimensions ([[UIScreen mainScreen] bounds].size) 37 | #define ORBVisualTimerBarDefaultFrame CGRectMake(0, ORBScreenDimensions.height - ORBVisualTimerDefaultHeight, ORBScreenDimensions.width, ORBVisualTimerDefaultHeight) 38 | #define ORBVisualTimerBarDefaultHeight 5.0f 39 | #define ORBVisualTimerBarDefaultPadding 10.0f 40 | 41 | /* ORBVisualTimer Class */ 42 | 43 | @interface ORBVisualTimer () { 44 | 45 | @protected 46 | UIView *_timerView; 47 | NSTimer *_internalTimer; 48 | 49 | UILabel *_timerLabel; 50 | UIBezierPath *_timerPath; 51 | 52 | CAShapeLayer *_backShapeLayer; 53 | CAShapeLayer *_shapeLayer; 54 | 55 | NSTimeInterval _timeRemaining; 56 | NSTimeInterval _lastTimeSetting; 57 | 58 | BOOL _showTimerLabel; 59 | } 60 | 61 | @property (nonatomic, assign, readwrite) ORBVisualTimerStyle style; 62 | @property (nonatomic, assign, readwrite) NSTimeInterval timeRemaining; 63 | 64 | @end 65 | 66 | @implementation ORBVisualTimer 67 | 68 | @synthesize timeRemaining = _timeRemaining; 69 | @synthesize backgroundViewColor = _backgroundViewColor; 70 | @synthesize backgroundViewCornerRadius = _backgroundViewCornerRadius; 71 | @synthesize timerShapeInactiveColor = _timerShapeInactiveColor; 72 | @synthesize timerShapeActiveColor = _timerShapeActiveColor; 73 | @synthesize autohideWhenFired = _autohideWhenFired; 74 | @synthesize showTimerLabel = _showTimerLabel; 75 | @synthesize timerLabelColor = _timerLabelColor; 76 | 77 | #pragma mark - Visual timer fabric 78 | 79 | + (instancetype)timerWithStyle:(ORBVisualTimerStyle)style 80 | frame:(CGRect)frame 81 | timeRemaining:(NSTimeInterval)timeRemaining { 82 | 83 | switch (style) { 84 | 85 | case ORBVisualTimerStyleBar: { 86 | return [[ORBVisualTimerBar alloc] initWithBarAnimationStyle:ORBVisualTimerBarAnimationStyleStraight frame:frame timeRemaining:timeRemaining]; 87 | break; 88 | } 89 | 90 | default: 91 | break; 92 | } 93 | 94 | return nil; 95 | } 96 | 97 | #pragma mark - Init 98 | 99 | - (instancetype)initWithTimeRemaining:(NSTimeInterval)timeRemaining frame:(CGRect)frame { 100 | if (self = [super initWithFrame:frame]) { 101 | self.backgroundViewColor = [UIColor blackColor]; 102 | self.backgroundViewCornerRadius = 0.0f; 103 | self.timerShapeInactiveColor = [UIColor lightGrayColor]; 104 | self.timerShapeActiveColor = [UIColor greenColor]; 105 | self.autohideWhenFired = NO; 106 | self.showTimerLabel = YES; 107 | self.timerLabelColor = [UIColor whiteColor]; 108 | 109 | _timeRemaining = timeRemaining; 110 | _lastTimeSetting = _timeRemaining; 111 | } 112 | 113 | return self; 114 | } 115 | 116 | #pragma mark - Virtual methods 117 | 118 | - (void)reconstructTimerView { 119 | NSLog(@"Implemented in subclass"); 120 | } 121 | 122 | - (void)startTimerProgressAnimating { 123 | NSLog(@"Implemented in subclass"); 124 | } 125 | 126 | #pragma mark - Setup 127 | 128 | - (void)setupView { 129 | [self reconstructTimerView]; 130 | } 131 | 132 | #pragma mark - Custom accessors 133 | 134 | - (BOOL)timerIsActive { 135 | return _internalTimer.valid; 136 | } 137 | 138 | - (void)setBackgroundViewColor:(UIColor *)backgroundViewColor { 139 | _backgroundViewColor = backgroundViewColor; 140 | _timerView.backgroundColor = self.backgroundViewColor; 141 | } 142 | 143 | - (void)setBackgroundViewCornerRadius:(CGFloat)backgroundViewCornerRadius { 144 | _backgroundViewCornerRadius = backgroundViewCornerRadius; 145 | _timerView.layer.cornerRadius = self.backgroundViewCornerRadius; 146 | } 147 | 148 | - (void)setTimerShapeInactiveColor:(UIColor *)timerShapeInactiveColor { 149 | _timerShapeInactiveColor = timerShapeInactiveColor; 150 | _backShapeLayer.strokeColor = [self.timerShapeInactiveColor CGColor]; 151 | } 152 | 153 | - (void)setTimerShapeActiveColor:(UIColor *)timerShapeActiveColor { 154 | _timerShapeActiveColor = timerShapeActiveColor; 155 | _shapeLayer.strokeColor = [self.timerShapeActiveColor CGColor]; 156 | } 157 | 158 | - (void)setTimerLabelColor:(UIColor *)timerLabelColor { 159 | _timerLabelColor = timerLabelColor; 160 | _timerLabel.textColor = self.timerLabelColor; 161 | } 162 | 163 | #pragma mark - Timer view changes 164 | 165 | - (void)start { 166 | [self resetTimerViewWithTimeRemaining:_timeRemaining]; 167 | } 168 | 169 | - (void)stopAndHide { 170 | [self stopTimerView]; 171 | 172 | if (self.superview) { 173 | [self removeFromSuperview]; 174 | } 175 | } 176 | 177 | - (void)resetTimerViewWithTimeRemaining:(NSTimeInterval)time { 178 | _timeRemaining = time; 179 | _lastTimeSetting = _timeRemaining; 180 | 181 | _timerLabel.text = [self minutesSecondsStringWithTimeInterval:time]; 182 | [self startTimerProgressAnimating]; 183 | [self rechargeTimerWithTime:time]; 184 | } 185 | 186 | - (void)stopTimerView { 187 | [self invalidateTimer]; 188 | [_shapeLayer removeFromSuperlayer]; 189 | _timerLabel.text = @"00:00"; 190 | 191 | _timeRemaining = _lastTimeSetting; 192 | } 193 | 194 | #pragma mark - Timer 195 | 196 | - (void)invalidateTimer { 197 | [_internalTimer invalidate]; 198 | } 199 | 200 | - (void)rechargeTimerWithTime:(NSTimeInterval)time { 201 | _internalTimer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(internalTimerFired:) userInfo:nil repeats:YES]; 202 | [[NSRunLoop mainRunLoop] addTimer:_internalTimer forMode:NSDefaultRunLoopMode]; 203 | } 204 | 205 | - (void)timerFired { 206 | [self stopTimerView]; 207 | 208 | if (self.superview) { 209 | if (self.autohideWhenFired) { [self removeFromSuperview]; } 210 | 211 | if ([self.delegate respondsToSelector:@selector(visualTimerFired:)]) { 212 | [self.delegate visualTimerFired:self]; 213 | } 214 | } 215 | } 216 | 217 | - (void)internalTimerFired:(NSTimer *)timer { 218 | self.timeRemaining--; 219 | [self updateViewWithTimeRemaining:_timeRemaining]; 220 | if (_timeRemaining < 1) { 221 | [self timerFired]; 222 | } 223 | } 224 | 225 | #pragma mark - View updates 226 | 227 | - (void)updateViewWithTimeRemaining:(NSTimeInterval)time { 228 | _timerLabel.text = [self minutesSecondsStringWithTimeInterval:time]; 229 | } 230 | 231 | #pragma mark - Helpers 232 | 233 | - (NSString *)minutesSecondsStringWithTimeInterval:(NSTimeInterval)interval { 234 | NSInteger ti = (NSInteger)interval; 235 | NSInteger minutes = (ti / 60) % 60; 236 | NSInteger seconds = ti % 60; 237 | 238 | NSString *minStr = [NSString stringWithFormat:(minutes < 10) ? @"0%li" : @"%li", minutes]; 239 | NSString *secStr = [NSString stringWithFormat:(seconds < 10) ? @"0%li" : @"%li", seconds]; 240 | 241 | return [NSString stringWithFormat:@"%@:%@", minStr, secStr]; 242 | } 243 | 244 | @end 245 | 246 | /* ORBVisualTimerBar Class */ 247 | 248 | @interface ORBVisualTimerBar () 249 | 250 | @end 251 | 252 | @implementation ORBVisualTimerBar 253 | 254 | @synthesize barAnimationStyle = _barAnimationStyle; 255 | @synthesize barThickness = _barThickness; 256 | @synthesize barCapStyle = _barCapStyle; 257 | @synthesize barPadding = _barPadding; 258 | 259 | #pragma mark - Init 260 | 261 | - (instancetype)init { 262 | return [self initWithBarAnimationStyle:ORBVisualTimerBarAnimationStyleStraight frame:ORBVisualTimerBarDefaultFrame timeRemaining:ORBVisualTimerTimeRemainingDefault]; 263 | } 264 | 265 | - (instancetype)initWithFrame:(CGRect)frame { 266 | return [self initWithBarAnimationStyle:ORBVisualTimerBarAnimationStyleStraight frame:frame timeRemaining:ORBVisualTimerTimeRemainingDefault]; 267 | } 268 | 269 | - (instancetype)initWithBarAnimationStyle:(ORBVisualTimerBarAnimationStyle)barAnimationStyle 270 | frame:(CGRect)frame 271 | timeRemaining:(NSTimeInterval)timeRemaining { 272 | 273 | if (self = [super initWithTimeRemaining:timeRemaining frame:frame]) { 274 | _barAnimationStyle = barAnimationStyle; 275 | _barThickness = ORBVisualTimerBarDefaultHeight; 276 | _barCapStyle = kCALineCapRound; 277 | _barPadding = ORBVisualTimerBarDefaultPadding; 278 | 279 | [self setupView]; 280 | } 281 | 282 | return self; 283 | } 284 | 285 | #pragma mark - Timer view construction 286 | 287 | - (void)reloadBarPathWithUpdatedParameters { 288 | _timerPath = [UIBezierPath bezierPath]; 289 | _timerPath.lineWidth = self.barThickness; 290 | 291 | [_timerPath moveToPoint:CGPointMake(_timerView.bounds.origin.x + self.barPadding, 292 | _timerView.center.y + ((self.showTimerLabel) ? 10 : 0))]; 293 | [_timerPath addLineToPoint:CGPointMake(_timerView.bounds.size.width - self.barPadding, _timerView.center.y + ((self.showTimerLabel) ? 10 : 0))]; 294 | } 295 | 296 | - (void)reconstructTimerView { 297 | for (UIView *subview in self.subviews) { 298 | [subview removeFromSuperview]; 299 | } 300 | 301 | _timerView = [[UIView alloc] initWithFrame:CGRectMake(0, 302 | 0, 303 | self.bounds.size.width, 304 | self.bounds.size.height)]; 305 | _timerView.backgroundColor = self.backgroundViewColor; 306 | _timerView.layer.cornerRadius = self.backgroundViewCornerRadius; 307 | 308 | [self reloadBarPathWithUpdatedParameters]; 309 | 310 | [self resetProgressBar]; 311 | 312 | if (self.showTimerLabel) { 313 | _timerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 314 | 0, 315 | self.bounds.size.width/2, 316 | 20)]; 317 | _timerLabel.font = [UIFont fontWithName:@"HelveticaNeue-Regular" size:14.0f]; 318 | _timerLabel.textAlignment = NSTextAlignmentCenter; 319 | _timerLabel.textColor = self.timerLabelColor; 320 | _timerLabel.text = @"00:00"; 321 | _timerLabel.center = CGPointMake(_timerView.center.x, _timerView.center.y - self.barThickness/2 - 5); 322 | [_timerView addSubview:_timerLabel]; 323 | } 324 | 325 | [self addSubview:_timerView]; 326 | } 327 | 328 | #pragma mark - Custom Accessors 329 | 330 | - (void)setBarAnimationStyle:(ORBVisualTimerBarAnimationStyle)barAnimationStyle { 331 | if (_internalTimer.valid) { 332 | NSLog(@"Modifying animation style on the fly is not supported at the moment. Please stop the timer first"); 333 | return; 334 | } 335 | 336 | _barAnimationStyle = barAnimationStyle; 337 | } 338 | 339 | - (void)setBarThickness:(CGFloat)barThickness { 340 | _barThickness = barThickness; 341 | _backShapeLayer.lineWidth = self.barThickness; 342 | _shapeLayer.lineWidth = self.barThickness; 343 | 344 | if (self.showTimerLabel) { 345 | _timerLabel.center = CGPointMake(_timerView.center.x, 346 | _timerView.center.y - self.barThickness/2 - 5); 347 | } 348 | } 349 | 350 | - (void)setBarPadding:(CGFloat)barPadding { 351 | _barPadding = barPadding; 352 | 353 | [self reloadBarPathWithUpdatedParameters]; 354 | 355 | _backShapeLayer.path = [_timerPath CGPath]; 356 | _shapeLayer.path = [_timerPath CGPath]; 357 | } 358 | 359 | - (void)setBarCapStyle:(NSString *)barCapStyle { 360 | _barCapStyle = barCapStyle; 361 | 362 | _backShapeLayer.lineCap = _barCapStyle; 363 | _shapeLayer.lineCap = _barCapStyle; 364 | } 365 | 366 | - (void)setShowTimerLabel:(BOOL)showTimerLabel { 367 | _showTimerLabel = showTimerLabel; 368 | 369 | [self reloadBarPathWithUpdatedParameters]; 370 | 371 | _backShapeLayer.path = [_timerPath CGPath]; 372 | _shapeLayer.path = [_timerPath CGPath]; 373 | 374 | [_timerLabel removeFromSuperview]; 375 | if (_showTimerLabel) { 376 | _timerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 377 | 0, 378 | self.bounds.size.width/2, 379 | 20)]; 380 | _timerLabel.font = [UIFont fontWithName:@"HelveticaNeue-Regular" size:14.0f]; 381 | _timerLabel.textAlignment = NSTextAlignmentCenter; 382 | _timerLabel.textColor = self.timerLabelColor; 383 | _timerLabel.text = @"00:00"; 384 | _timerLabel.center = CGPointMake(_timerView.center.x, _timerView.center.y - self.barThickness/2 - 5); 385 | [_timerView addSubview:_timerLabel]; 386 | } 387 | } 388 | 389 | #pragma mark - Animations 390 | 391 | - (void)resetProgressBar { 392 | [_backShapeLayer removeFromSuperlayer]; 393 | 394 | _backShapeLayer = [CAShapeLayer layer]; 395 | _backShapeLayer.path = [_timerPath CGPath]; 396 | _backShapeLayer.strokeColor = [self.timerShapeInactiveColor CGColor]; 397 | _backShapeLayer.fillColor = nil; 398 | _backShapeLayer.lineWidth = self.barThickness; 399 | _backShapeLayer.lineCap = _barCapStyle; 400 | _backShapeLayer.zPosition = -1; 401 | 402 | [_timerView.layer insertSublayer:_backShapeLayer atIndex:0]; 403 | 404 | [_shapeLayer removeFromSuperlayer]; 405 | _shapeLayer = [CAShapeLayer layer]; 406 | _shapeLayer.path = [_timerPath CGPath]; 407 | _shapeLayer.strokeColor = [self.timerShapeActiveColor CGColor]; 408 | _shapeLayer.fillColor = nil; 409 | _shapeLayer.lineWidth = self.barThickness; 410 | _shapeLayer.lineCap = _barCapStyle; 411 | _shapeLayer.zPosition = 0; 412 | 413 | [_timerView.layer insertSublayer:_shapeLayer atIndex:1]; 414 | } 415 | 416 | - (void)startTimerProgressAnimating { 417 | [self resetProgressBar]; 418 | 419 | switch (self.barAnimationStyle) { 420 | case ORBVisualTimerBarAnimationStyleStraight: { 421 | CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; 422 | pathAnimation.duration = _timeRemaining; 423 | pathAnimation.fromValue = @(1.0f); 424 | pathAnimation.toValue = @(0.0f); 425 | [_shapeLayer addAnimation:pathAnimation forKey:@"strokeEnd"]; 426 | 427 | break; 428 | } 429 | 430 | case ORBVisualTimerBarAnimationStyleBackwards: { 431 | CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; 432 | pathAnimation.duration = _timeRemaining; 433 | pathAnimation.fromValue = @(0.0f); 434 | pathAnimation.toValue = @(1.0f); 435 | [_shapeLayer addAnimation:pathAnimation forKey:@"strokeStart"]; 436 | 437 | break; 438 | } 439 | 440 | case ORBVisualTimerBarAnimationStyleReflection: { 441 | CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"]; 442 | pathAnimation.duration = _timeRemaining; 443 | pathAnimation.fromValue = @(0.0f); 444 | pathAnimation.toValue = @(0.5f); 445 | [_shapeLayer addAnimation:pathAnimation forKey:@"strokeStart"]; 446 | 447 | CABasicAnimation *pathAnimation1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; 448 | pathAnimation1.duration = _timeRemaining; 449 | pathAnimation1.fromValue = @(1.0f); 450 | pathAnimation1.toValue = @(0.5f); 451 | [_shapeLayer addAnimation:pathAnimation1 forKey:@"strokeEnd"]; 452 | 453 | break; 454 | } 455 | 456 | default: 457 | break; 458 | } 459 | } 460 | 461 | @end 462 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FF4C866F1DEEC9E2003F9E4C /* pattern_1@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = FF4C866C1DEEC9E2003F9E4C /* pattern_1@3x.png */; }; 11 | FF4C86701DEEC9E2003F9E4C /* pattern_2@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = FF4C866D1DEEC9E2003F9E4C /* pattern_2@3x.png */; }; 12 | FF4C86711DEEC9E2003F9E4C /* pattern_3@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = FF4C866E1DEEC9E2003F9E4C /* pattern_3@3x.png */; }; 13 | FF518ECB1DEE0E880050178B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FF518ECA1DEE0E880050178B /* main.m */; }; 14 | FF518ECE1DEE0E880050178B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = FF518ECD1DEE0E880050178B /* AppDelegate.m */; }; 15 | FF518ED11DEE0E880050178B /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FF518ED01DEE0E880050178B /* ViewController.m */; }; 16 | FF518ED41DEE0E880050178B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FF518ED21DEE0E880050178B /* Main.storyboard */; }; 17 | FF518ED61DEE0E880050178B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FF518ED51DEE0E880050178B /* Assets.xcassets */; }; 18 | FF518ED91DEE0E880050178B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FF518ED71DEE0E880050178B /* LaunchScreen.storyboard */; }; 19 | FF518EE41DEE0E880050178B /* ORBVisualTimerDemoUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = FF518EE31DEE0E880050178B /* ORBVisualTimerDemoUITests.m */; }; 20 | FF518EF11DEE0E9F0050178B /* ORBVisualTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = FF518EF01DEE0E9F0050178B /* ORBVisualTimer.m */; }; 21 | FF6877971E6F5DBC00475A58 /* 1.png in Resources */ = {isa = PBXBuildFile; fileRef = FF6877941E6F5DBC00475A58 /* 1.png */; }; 22 | FF6877981E6F5DBC00475A58 /* 2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FF6877951E6F5DBC00475A58 /* 2.jpg */; }; 23 | FF6877991E6F5DBC00475A58 /* 3.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FF6877961E6F5DBC00475A58 /* 3.jpg */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | FF518EE01DEE0E880050178B /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = FF518EBE1DEE0E880050178B /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = FF518EC51DEE0E880050178B; 32 | remoteInfo = ORBVisualTimerDemo; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | FF4C866C1DEEC9E2003F9E4C /* pattern_1@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pattern_1@3x.png"; sourceTree = ""; }; 38 | FF4C866D1DEEC9E2003F9E4C /* pattern_2@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pattern_2@3x.png"; sourceTree = ""; }; 39 | FF4C866E1DEEC9E2003F9E4C /* pattern_3@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pattern_3@3x.png"; sourceTree = ""; }; 40 | FF518EC61DEE0E880050178B /* ORBVisualTimerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ORBVisualTimerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | FF518ECA1DEE0E880050178B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 42 | FF518ECC1DEE0E880050178B /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | FF518ECD1DEE0E880050178B /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | FF518ECF1DEE0E880050178B /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 45 | FF518ED01DEE0E880050178B /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 46 | FF518ED31DEE0E880050178B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | FF518ED51DEE0E880050178B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | FF518ED81DEE0E880050178B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | FF518EDA1DEE0E880050178B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | FF518EDF1DEE0E880050178B /* ORBVisualTimerDemoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ORBVisualTimerDemoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | FF518EE31DEE0E880050178B /* ORBVisualTimerDemoUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ORBVisualTimerDemoUITests.m; sourceTree = ""; }; 52 | FF518EE51DEE0E880050178B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | FF518EEF1DEE0E9F0050178B /* ORBVisualTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ORBVisualTimer.h; sourceTree = ""; }; 54 | FF518EF01DEE0E9F0050178B /* ORBVisualTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ORBVisualTimer.m; sourceTree = ""; }; 55 | FF6877941E6F5DBC00475A58 /* 1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 1.png; sourceTree = ""; }; 56 | FF6877951E6F5DBC00475A58 /* 2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 2.jpg; sourceTree = ""; }; 57 | FF6877961E6F5DBC00475A58 /* 3.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = 3.jpg; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | FF518EC31DEE0E880050178B /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | FF518EDC1DEE0E880050178B /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXFrameworksBuildPhase section */ 76 | 77 | /* Begin PBXGroup section */ 78 | FF4C866B1DEEC9E2003F9E4C /* Resources */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | FF4C866C1DEEC9E2003F9E4C /* pattern_1@3x.png */, 82 | FF4C866D1DEEC9E2003F9E4C /* pattern_2@3x.png */, 83 | FF4C866E1DEEC9E2003F9E4C /* pattern_3@3x.png */, 84 | FF6877941E6F5DBC00475A58 /* 1.png */, 85 | FF6877951E6F5DBC00475A58 /* 2.jpg */, 86 | FF6877961E6F5DBC00475A58 /* 3.jpg */, 87 | ); 88 | path = Resources; 89 | sourceTree = ""; 90 | }; 91 | FF518EBD1DEE0E880050178B = { 92 | isa = PBXGroup; 93 | children = ( 94 | FF518EEE1DEE0E9F0050178B /* ORBVisualTimer */, 95 | FF518EC81DEE0E880050178B /* ORBVisualTimerDemo */, 96 | FF518EE21DEE0E880050178B /* ORBVisualTimerDemoUITests */, 97 | FF518EC71DEE0E880050178B /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | FF518EC71DEE0E880050178B /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | FF518EC61DEE0E880050178B /* ORBVisualTimerDemo.app */, 105 | FF518EDF1DEE0E880050178B /* ORBVisualTimerDemoUITests.xctest */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | FF518EC81DEE0E880050178B /* ORBVisualTimerDemo */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | FF4C866B1DEEC9E2003F9E4C /* Resources */, 114 | FF518ECC1DEE0E880050178B /* AppDelegate.h */, 115 | FF518ECD1DEE0E880050178B /* AppDelegate.m */, 116 | FF518ECF1DEE0E880050178B /* ViewController.h */, 117 | FF518ED01DEE0E880050178B /* ViewController.m */, 118 | FF518ED21DEE0E880050178B /* Main.storyboard */, 119 | FF518ED51DEE0E880050178B /* Assets.xcassets */, 120 | FF518ED71DEE0E880050178B /* LaunchScreen.storyboard */, 121 | FF518EDA1DEE0E880050178B /* Info.plist */, 122 | FF518EC91DEE0E880050178B /* Supporting Files */, 123 | ); 124 | path = ORBVisualTimerDemo; 125 | sourceTree = ""; 126 | }; 127 | FF518EC91DEE0E880050178B /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | FF518ECA1DEE0E880050178B /* main.m */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | FF518EE21DEE0E880050178B /* ORBVisualTimerDemoUITests */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | FF518EE31DEE0E880050178B /* ORBVisualTimerDemoUITests.m */, 139 | FF518EE51DEE0E880050178B /* Info.plist */, 140 | ); 141 | path = ORBVisualTimerDemoUITests; 142 | sourceTree = ""; 143 | }; 144 | FF518EEE1DEE0E9F0050178B /* ORBVisualTimer */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | FF518EEF1DEE0E9F0050178B /* ORBVisualTimer.h */, 148 | FF518EF01DEE0E9F0050178B /* ORBVisualTimer.m */, 149 | ); 150 | name = ORBVisualTimer; 151 | path = ../ORBVisualTimer; 152 | sourceTree = ""; 153 | }; 154 | /* End PBXGroup section */ 155 | 156 | /* Begin PBXNativeTarget section */ 157 | FF518EC51DEE0E880050178B /* ORBVisualTimerDemo */ = { 158 | isa = PBXNativeTarget; 159 | buildConfigurationList = FF518EE81DEE0E880050178B /* Build configuration list for PBXNativeTarget "ORBVisualTimerDemo" */; 160 | buildPhases = ( 161 | FF518EC21DEE0E880050178B /* Sources */, 162 | FF518EC31DEE0E880050178B /* Frameworks */, 163 | FF518EC41DEE0E880050178B /* Resources */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = ORBVisualTimerDemo; 170 | productName = ORBVisualTimerDemo; 171 | productReference = FF518EC61DEE0E880050178B /* ORBVisualTimerDemo.app */; 172 | productType = "com.apple.product-type.application"; 173 | }; 174 | FF518EDE1DEE0E880050178B /* ORBVisualTimerDemoUITests */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = FF518EEB1DEE0E880050178B /* Build configuration list for PBXNativeTarget "ORBVisualTimerDemoUITests" */; 177 | buildPhases = ( 178 | FF518EDB1DEE0E880050178B /* Sources */, 179 | FF518EDC1DEE0E880050178B /* Frameworks */, 180 | FF518EDD1DEE0E880050178B /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | FF518EE11DEE0E880050178B /* PBXTargetDependency */, 186 | ); 187 | name = ORBVisualTimerDemoUITests; 188 | productName = ORBVisualTimerDemoUITests; 189 | productReference = FF518EDF1DEE0E880050178B /* ORBVisualTimerDemoUITests.xctest */; 190 | productType = "com.apple.product-type.bundle.ui-testing"; 191 | }; 192 | /* End PBXNativeTarget section */ 193 | 194 | /* Begin PBXProject section */ 195 | FF518EBE1DEE0E880050178B /* Project object */ = { 196 | isa = PBXProject; 197 | attributes = { 198 | LastUpgradeCheck = 0810; 199 | ORGANIZATIONNAME = "0xNSHuman (hello@vladaverin.me)"; 200 | TargetAttributes = { 201 | FF518EC51DEE0E880050178B = { 202 | CreatedOnToolsVersion = 8.1; 203 | DevelopmentTeam = T55G7JLVK5; 204 | ProvisioningStyle = Automatic; 205 | }; 206 | FF518EDE1DEE0E880050178B = { 207 | CreatedOnToolsVersion = 8.1; 208 | DevelopmentTeam = T55G7JLVK5; 209 | ProvisioningStyle = Automatic; 210 | TestTargetID = FF518EC51DEE0E880050178B; 211 | }; 212 | }; 213 | }; 214 | buildConfigurationList = FF518EC11DEE0E880050178B /* Build configuration list for PBXProject "ORBVisualTimerDemo" */; 215 | compatibilityVersion = "Xcode 3.2"; 216 | developmentRegion = English; 217 | hasScannedForEncodings = 0; 218 | knownRegions = ( 219 | en, 220 | Base, 221 | ); 222 | mainGroup = FF518EBD1DEE0E880050178B; 223 | productRefGroup = FF518EC71DEE0E880050178B /* Products */; 224 | projectDirPath = ""; 225 | projectRoot = ""; 226 | targets = ( 227 | FF518EC51DEE0E880050178B /* ORBVisualTimerDemo */, 228 | FF518EDE1DEE0E880050178B /* ORBVisualTimerDemoUITests */, 229 | ); 230 | }; 231 | /* End PBXProject section */ 232 | 233 | /* Begin PBXResourcesBuildPhase section */ 234 | FF518EC41DEE0E880050178B /* Resources */ = { 235 | isa = PBXResourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | FF6877991E6F5DBC00475A58 /* 3.jpg in Resources */, 239 | FF4C86711DEEC9E2003F9E4C /* pattern_3@3x.png in Resources */, 240 | FF4C86701DEEC9E2003F9E4C /* pattern_2@3x.png in Resources */, 241 | FF4C866F1DEEC9E2003F9E4C /* pattern_1@3x.png in Resources */, 242 | FF518ED91DEE0E880050178B /* LaunchScreen.storyboard in Resources */, 243 | FF518ED61DEE0E880050178B /* Assets.xcassets in Resources */, 244 | FF518ED41DEE0E880050178B /* Main.storyboard in Resources */, 245 | FF6877981E6F5DBC00475A58 /* 2.jpg in Resources */, 246 | FF6877971E6F5DBC00475A58 /* 1.png in Resources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | FF518EDD1DEE0E880050178B /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXResourcesBuildPhase section */ 258 | 259 | /* Begin PBXSourcesBuildPhase section */ 260 | FF518EC21DEE0E880050178B /* Sources */ = { 261 | isa = PBXSourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | FF518EF11DEE0E9F0050178B /* ORBVisualTimer.m in Sources */, 265 | FF518ED11DEE0E880050178B /* ViewController.m in Sources */, 266 | FF518ECE1DEE0E880050178B /* AppDelegate.m in Sources */, 267 | FF518ECB1DEE0E880050178B /* main.m in Sources */, 268 | ); 269 | runOnlyForDeploymentPostprocessing = 0; 270 | }; 271 | FF518EDB1DEE0E880050178B /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | FF518EE41DEE0E880050178B /* ORBVisualTimerDemoUITests.m in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXSourcesBuildPhase section */ 280 | 281 | /* Begin PBXTargetDependency section */ 282 | FF518EE11DEE0E880050178B /* PBXTargetDependency */ = { 283 | isa = PBXTargetDependency; 284 | target = FF518EC51DEE0E880050178B /* ORBVisualTimerDemo */; 285 | targetProxy = FF518EE01DEE0E880050178B /* PBXContainerItemProxy */; 286 | }; 287 | /* End PBXTargetDependency section */ 288 | 289 | /* Begin PBXVariantGroup section */ 290 | FF518ED21DEE0E880050178B /* Main.storyboard */ = { 291 | isa = PBXVariantGroup; 292 | children = ( 293 | FF518ED31DEE0E880050178B /* Base */, 294 | ); 295 | name = Main.storyboard; 296 | sourceTree = ""; 297 | }; 298 | FF518ED71DEE0E880050178B /* LaunchScreen.storyboard */ = { 299 | isa = PBXVariantGroup; 300 | children = ( 301 | FF518ED81DEE0E880050178B /* Base */, 302 | ); 303 | name = LaunchScreen.storyboard; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXVariantGroup section */ 307 | 308 | /* Begin XCBuildConfiguration section */ 309 | FF518EE61DEE0E880050178B /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BOOL_CONVERSION = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 322 | CLANG_WARN_EMPTY_BODY = YES; 323 | CLANG_WARN_ENUM_CONVERSION = YES; 324 | CLANG_WARN_INFINITE_RECURSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 327 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = dwarf; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_TESTABILITY = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_DYNAMIC_NO_PIC = NO; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_OPTIMIZATION_LEVEL = 0; 339 | GCC_PREPROCESSOR_DEFINITIONS = ( 340 | "DEBUG=1", 341 | "$(inherited)", 342 | ); 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 350 | MTL_ENABLE_DEBUG_INFO = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | FF518EE71DEE0E880050178B /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BOOL_CONVERSION = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 369 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 370 | CLANG_WARN_EMPTY_BODY = YES; 371 | CLANG_WARN_ENUM_CONVERSION = YES; 372 | CLANG_WARN_INFINITE_RECURSION = YES; 373 | CLANG_WARN_INT_CONVERSION = YES; 374 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 375 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 376 | CLANG_WARN_UNREACHABLE_CODE = YES; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 379 | COPY_PHASE_STRIP = NO; 380 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 381 | ENABLE_NS_ASSERTIONS = NO; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_NO_COMMON_BLOCKS = YES; 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 392 | MTL_ENABLE_DEBUG_INFO = NO; 393 | SDKROOT = iphoneos; 394 | TARGETED_DEVICE_FAMILY = "1,2"; 395 | VALIDATE_PRODUCT = YES; 396 | }; 397 | name = Release; 398 | }; 399 | FF518EE91DEE0E880050178B /* Debug */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 403 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 404 | DEVELOPMENT_TEAM = T55G7JLVK5; 405 | INFOPLIST_FILE = ORBVisualTimerDemo/Info.plist; 406 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 407 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 408 | PRODUCT_BUNDLE_IDENTIFIER = com.orbitum.ORBVisualTimerDemo; 409 | PRODUCT_NAME = "$(TARGET_NAME)"; 410 | }; 411 | name = Debug; 412 | }; 413 | FF518EEA1DEE0E880050178B /* Release */ = { 414 | isa = XCBuildConfiguration; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 418 | DEVELOPMENT_TEAM = T55G7JLVK5; 419 | INFOPLIST_FILE = ORBVisualTimerDemo/Info.plist; 420 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 421 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.orbitum.ORBVisualTimerDemo; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | }; 425 | name = Release; 426 | }; 427 | FF518EEC1DEE0E880050178B /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | buildSettings = { 430 | DEVELOPMENT_TEAM = T55G7JLVK5; 431 | INFOPLIST_FILE = ORBVisualTimerDemoUITests/Info.plist; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 433 | PRODUCT_BUNDLE_IDENTIFIER = com.orbitum.ORBVisualTimerDemoUITests; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | TEST_TARGET_NAME = ORBVisualTimerDemo; 436 | }; 437 | name = Debug; 438 | }; 439 | FF518EED1DEE0E880050178B /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | DEVELOPMENT_TEAM = T55G7JLVK5; 443 | INFOPLIST_FILE = ORBVisualTimerDemoUITests/Info.plist; 444 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 445 | PRODUCT_BUNDLE_IDENTIFIER = com.orbitum.ORBVisualTimerDemoUITests; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | TEST_TARGET_NAME = ORBVisualTimerDemo; 448 | }; 449 | name = Release; 450 | }; 451 | /* End XCBuildConfiguration section */ 452 | 453 | /* Begin XCConfigurationList section */ 454 | FF518EC11DEE0E880050178B /* Build configuration list for PBXProject "ORBVisualTimerDemo" */ = { 455 | isa = XCConfigurationList; 456 | buildConfigurations = ( 457 | FF518EE61DEE0E880050178B /* Debug */, 458 | FF518EE71DEE0E880050178B /* Release */, 459 | ); 460 | defaultConfigurationIsVisible = 0; 461 | defaultConfigurationName = Release; 462 | }; 463 | FF518EE81DEE0E880050178B /* Build configuration list for PBXNativeTarget "ORBVisualTimerDemo" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | FF518EE91DEE0E880050178B /* Debug */, 467 | FF518EEA1DEE0E880050178B /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | FF518EEB1DEE0E880050178B /* Build configuration list for PBXNativeTarget "ORBVisualTimerDemoUITests" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | FF518EEC1DEE0E880050178B /* Debug */, 476 | FF518EED1DEE0E880050178B /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | /* End XCConfigurationList section */ 482 | }; 483 | rootObject = FF518EBE1DEE0E880050178B /* Project object */; 484 | } 485 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/11/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). 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 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/11/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). 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 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/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 | } -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/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 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/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 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 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 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIViewControllerBasedStatusBarAppearance 6 | 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/1.png -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/2.jpg -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/3.jpg -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/pattern_1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/pattern_1@3x.png -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/pattern_2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/pattern_2@3x.png -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/pattern_3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/ORBVisualTimerDemo/ORBVisualTimerDemo/Resources/pattern_3@3x.png -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/11/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/11/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ORBVisualTimer.h" 11 | 12 | @interface ViewController () 13 | 14 | @property (strong, nonatomic) IBOutletCollection(UIView) NSArray *sampleViews; 15 | 16 | @end 17 | 18 | @implementation ViewController 19 | 20 | #pragma mark - View life cycle 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do any additional setup after loading the view, typically from a nib. 25 | } 26 | 27 | - (void)viewDidAppear:(BOOL)animated { 28 | [super viewDidAppear:animated]; 29 | [self prepareUI]; 30 | 31 | //[self minimalDemo]; 32 | [self comprehensiveDemo]; 33 | } 34 | 35 | #pragma mark - UI 36 | 37 | - (BOOL)prefersStatusBarHidden { 38 | return YES; 39 | } 40 | 41 | - (void)prepareUI { 42 | for (UIView *container in self.sampleViews) { 43 | if (container == self.sampleViews.lastObject) { return; } 44 | 45 | UIView *sep = [[UIView alloc] initWithFrame:CGRectMake(0, container.bounds.size.height - 1, container.bounds.size.width, 1)]; 46 | sep.backgroundColor = [UIColor whiteColor]; 47 | [container addSubview:sep]; 48 | } 49 | } 50 | 51 | #pragma mark - Demo cases 52 | 53 | - (void)minimalDemo { 54 | /* Required minimum */ 55 | ORBVisualTimerBar *barTimer = (ORBVisualTimerBar *)[ORBVisualTimer timerWithStyle:ORBVisualTimerStyleBar frame:CGRectMake(0, 0, self.view.bounds.size.width * 0.8, 200) timeRemaining:8.0f]; 56 | barTimer.center = self.view.center; 57 | barTimer.delegate = self; 58 | 59 | /* Further customizations */ 60 | 61 | barTimer.barAnimationStyle = ORBVisualTimerBarAnimationStyleReflection; 62 | 63 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 64 | //[barTimer stopAndHide]; 65 | 66 | barTimer.backgroundViewColor = [UIColor darkGrayColor]; 67 | barTimer.backgroundViewCornerRadius = 20.0f; 68 | barTimer.timerShapeInactiveColor = [UIColor lightGrayColor]; 69 | barTimer.timerShapeActiveColor = [UIColor orangeColor]; 70 | barTimer.timerLabelColor = [UIColor orangeColor]; 71 | barTimer.showTimerLabel = YES; 72 | barTimer.autohideWhenFired = NO; 73 | barTimer.barCapStyle = kCALineCapSquare; 74 | barTimer.barThickness = 100.0f; 75 | barTimer.barPadding = 60.0f; 76 | 77 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(111.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 78 | barTimer.showTimerLabel = YES; 79 | [barTimer stopTimerView]; 80 | 81 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(111.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 82 | barTimer.showTimerLabel = NO; 83 | }); 84 | }); 85 | }); 86 | 87 | /* Optionally we can add observer to track exact time remaining */ 88 | [barTimer addObserver:self forKeyPath:@"timeRemaining" 89 | options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:nil]; 90 | 91 | /* Kickstarting timer */ 92 | 93 | [self.view addSubview:barTimer]; 94 | [barTimer start]; 95 | } 96 | 97 | - (void)comprehensiveDemo { 98 | NSUInteger numOfBars = 3; 99 | 100 | for (NSUInteger i = 0; i < numOfBars; i++) { 101 | CGRect containerBounds = [self.sampleViews[i] bounds]; 102 | 103 | ORBVisualTimerBar *barTimer; 104 | 105 | switch (i) { 106 | case 0: { 107 | CGRect barFrame = CGRectMake(0, 0, 108 | containerBounds.size.width, 109 | containerBounds.size.height * 0.24); 110 | 111 | barTimer = (ORBVisualTimerBar *)[ORBVisualTimer timerWithStyle:ORBVisualTimerStyleBar frame:barFrame timeRemaining:(5)]; 112 | 113 | barTimer.center = CGPointMake(containerBounds.size.width / 2, 114 | containerBounds.size.height * 0.88); 115 | 116 | barTimer.barAnimationStyle = ORBVisualTimerBarAnimationStyleStraight; 117 | 118 | barTimer.backgroundViewColor = [UIColor colorWithWhite:0.0f alpha:0.40f]; 119 | barTimer.backgroundViewCornerRadius = 0.0f; 120 | barTimer.timerShapeInactiveColor = [UIColor darkGrayColor]; 121 | barTimer.timerShapeActiveColor = [UIColor orangeColor]; 122 | barTimer.timerLabelColor = [UIColor whiteColor]; 123 | //barTimer.showTimerLabel = NO; 124 | //barTimer.autohideWhenFired = YES; 125 | barTimer.barCapStyle = kCALineCapSquare; 126 | barTimer.barThickness = 10.0f; 127 | barTimer.barPadding = 20.0f; 128 | 129 | break; 130 | } 131 | 132 | case 1: { 133 | CGRect barFrame = CGRectMake(0, 0, 134 | containerBounds.size.width * 0.80, 135 | containerBounds.size.height * 0.4); 136 | 137 | barTimer = (ORBVisualTimerBar *)[ORBVisualTimer timerWithStyle:ORBVisualTimerStyleBar frame:barFrame timeRemaining:(7)]; 138 | 139 | barTimer.center = CGPointMake(containerBounds.size.width / 2, 140 | containerBounds.size.height * 0.5); 141 | 142 | barTimer.barAnimationStyle = ORBVisualTimerBarAnimationStyleReflection; 143 | 144 | barTimer.backgroundViewColor = [UIColor clearColor]; 145 | //barTimer.backgroundViewCornerRadius = 20.0f; 146 | barTimer.timerShapeInactiveColor = [UIColor colorWithWhite:1.0f alpha:0.4f]; 147 | barTimer.timerShapeActiveColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.9f alpha:0.2f]; 148 | //barTimer.timerLabelColor = [UIColor whiteColor]; 149 | barTimer.showTimerLabel = NO; 150 | //barTimer.autohideWhenFired = YES; 151 | barTimer.barCapStyle = kCALineCapRound; 152 | barTimer.barThickness = 30.0f; 153 | barTimer.barPadding = 10.0f; 154 | 155 | break; 156 | } 157 | 158 | case 2: { 159 | CGRect barFrame = CGRectMake(0, 0, 160 | containerBounds.size.width * 0.50, 161 | containerBounds.size.height * 0.3); 162 | 163 | barTimer = (ORBVisualTimerBar *)[ORBVisualTimer timerWithStyle:ORBVisualTimerStyleBar frame:barFrame timeRemaining:(9)]; 164 | 165 | barTimer.center = CGPointMake(containerBounds.size.width * 0.3, 166 | containerBounds.size.height * 0.25); 167 | 168 | barTimer.barAnimationStyle = ORBVisualTimerBarAnimationStyleBackwards; 169 | 170 | int random = ((int)(5.0f * (i+1)) % 2) + 1; 171 | NSString *pattern = [NSString stringWithFormat:@"pattern_%i", random]; 172 | 173 | barTimer.timerShapeInactiveColor = [UIColor colorWithPatternImage: 174 | [UIImage imageNamed:pattern]]; 175 | barTimer.timerShapeActiveColor = [UIColor colorWithPatternImage: 176 | [UIImage imageNamed:@"pattern_3"]]; 177 | 178 | barTimer.backgroundViewColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.5 alpha:1.0f]; 179 | barTimer.backgroundViewCornerRadius = 15.0f; 180 | //barTimer.timerLabelColor = [UIColor whiteColor]; 181 | barTimer.showTimerLabel = NO; 182 | //barTimer.autohideWhenFired = YES; 183 | barTimer.barCapStyle = kCALineCapSquare; 184 | barTimer.barThickness = containerBounds.size.height * 0.25 * 0.9; 185 | barTimer.barPadding = 40.0f; 186 | 187 | break; 188 | } 189 | 190 | default: 191 | break; 192 | } 193 | 194 | barTimer.delegate = self; 195 | barTimer.tag = i; 196 | 197 | [barTimer addObserver:self forKeyPath:@"timeRemaining" 198 | options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:nil]; 199 | 200 | [self.sampleViews[i] addSubview:barTimer]; 201 | [barTimer start]; 202 | } 203 | } 204 | 205 | #pragma mark - Observer 206 | 207 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 208 | 209 | ORBVisualTimerBar *bar = (ORBVisualTimerBar *)object; 210 | 211 | if ([keyPath isEqualToString:@"timeRemaining"] && [bar timerIsActive]) { 212 | CGFloat timeRemaining = [[change valueForKey:NSKeyValueChangeNewKey] doubleValue]; 213 | NSLog(@"Time remaining: %.1f", timeRemaining); 214 | 215 | switch (bar.tag) { 216 | case 0: { 217 | if (timeRemaining <= 3) { 218 | UIColor *barLabelColor = bar.timerLabelColor; 219 | bar.timerLabelColor = [UIColor redColor]; 220 | 221 | UIColor *barColor = bar.timerShapeActiveColor; 222 | bar.timerShapeActiveColor = [UIColor redColor]; 223 | 224 | CGFloat barThickness = bar.barThickness; 225 | bar.barThickness += 2.0f; 226 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 227 | bar.timerLabelColor = barLabelColor; 228 | bar.timerShapeActiveColor = barColor; 229 | bar.barThickness = barThickness; 230 | 231 | if (timeRemaining <= 0) { 232 | bar.timerShapeInactiveColor = [UIColor redColor]; 233 | bar.barThickness += 10.0f; 234 | //bar.barPadding += 10.0f; 235 | } 236 | }); 237 | } 238 | 239 | break; 240 | } 241 | 242 | case 1: { 243 | CGFloat hue = ( arc4random() % 256 / 256.0 ); 244 | CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; 245 | CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; 246 | UIColor *color = [UIColor colorWithHue:hue 247 | saturation:saturation 248 | brightness:brightness alpha:1]; 249 | 250 | bar.timerShapeActiveColor = color; 251 | 252 | if (timeRemaining <= 0) { 253 | //bar.backgroundViewColor = color; 254 | bar.barThickness -= bar.barThickness; 255 | bar.barPadding += 60.0f; 256 | bar.showTimerLabel = YES; 257 | } 258 | 259 | break; 260 | } 261 | 262 | case 2: { 263 | if (timeRemaining <= 0) { 264 | int random = ((int)timeRemaining % 2) + 1; 265 | NSString *pattern = [NSString stringWithFormat:@"pattern_%i", random]; 266 | 267 | bar.timerShapeInactiveColor = [UIColor colorWithPatternImage: 268 | [UIImage imageNamed:pattern]]; 269 | 270 | bar.barThickness += 10.0f; 271 | bar.barCapStyle = kCALineCapRound; 272 | bar.backgroundViewColor = [UIColor clearColor]; 273 | bar.barPadding -= 1.0f; 274 | } 275 | 276 | break; 277 | } 278 | 279 | default: 280 | break; 281 | } 282 | } 283 | } 284 | 285 | #pragma mark - ORBVisualTimerDelegate 286 | 287 | - (void)visualTimerFired:(ORBVisualTimer *)timerView { 288 | NSLog(@"FIRED!"); 289 | } 290 | 291 | @end 292 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ORBVisualTimerDemo 4 | // 5 | // Created by 0xNSHuman on 29/11/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). 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 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemoUITests/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 | -------------------------------------------------------------------------------- /ORBVisualTimerDemo/ORBVisualTimerDemoUITests/ORBVisualTimerDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ORBVisualTimerDemoUITests.m 3 | // ORBVisualTimerDemoUITests 4 | // 5 | // Created by 0xNSHuman on 29/11/2016. 6 | // Copyright © 2016 0xNSHuman (hello@vladaverin.me). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ORBVisualTimerDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation ORBVisualTimerDemoUITests 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 | # ORBVisualTimer [![License: Zlib](https://img.shields.io/badge/License-Zlib-orange.svg)](https://opensource.org/licenses/Zlib) ![Version: 1.0](https://img.shields.io/badge/version-1.0-blue.svg) 2 | 3 | ORBVisualTimer is a highly customizable timer view for iOS. Currently it only has Bar style implementation, but extendability was kept in mind. New styles are coming. 4 | 5 | ![demo](Screenshots/flow_w300.gif) 6 | 7 | ## Usage 8 | 9 | ### Create timer 10 | Using `ORBVisualTimer` fabric: 11 | ```objective-c 12 | /* Required minimum */ 13 | ORBVisualTimerBar *barTimer = (ORBVisualTimerBar *)[ORBVisualTimer timerWithStyle:ORBVisualTimerStyleBar frame:myFrame timeRemaining:8.0f]; 14 | barTimer.delegate = self; 15 | ``` 16 | Or using `ORBVisualTimerBar` initializer: 17 | ```objective-c 18 | ORBVisualTimerBar *barTimer = [[ORBVisualTimerBar alloc] initWithBarAnimationStyle:ORBVisualTimerBarAnimationStyleReflection frame:myFrame timeRemaining:10.0f]; 19 | barTimer.delegate = self; 20 | ``` 21 | 22 | ### Customize 23 | All properties have default values, so this step is optional. 24 | ```objective-c 25 | barTimer.barAnimationStyle = ORBVisualTimerBarAnimationStyleStraight; 26 | barTimer.backgroundViewColor = [UIColor darkGrayColor]; 27 | barTimer.backgroundViewCornerRadius = 20.0f; 28 | barTimer.timerShapeInactiveColor = [UIColor lightGrayColor]; 29 | barTimer.timerLabelColor = [UIColor orangeColor]; 30 | barTimer.showTimerLabel = YES; 31 | barTimer.autohideWhenFired = NO; 32 | barTimer.barCapStyle = kCALineCapSquare; 33 | barTimer.barThickness = 100.0f; 34 | barTimer.barPadding = 60.0f; 35 | ``` 36 | 37 | ### Run 38 | ```objective-c 39 | [self.view addSubview:barTimer]; 40 | [barTimer start]; 41 | ``` 42 | 43 | ### Stop and Hide 44 | ```objective-c 45 | [barTimer stopTimerView]; 46 | ``` 47 | ##### or 48 | ```objective-c 49 | [barTimer stopAndHide]; 50 | ``` 51 | 52 |
53 | 54 | ### Using Delegate 55 | Delegate method delivers event when timer is fired. 56 | ```objective-c 57 | - (void)visualTimerFired:(ORBVisualTimer *)timerView { 58 | NSLog(@"FIRED!"); 59 | } 60 | ``` 61 | 62 | ### Key-Value Observing 63 | You can use **readonly** property `timeRemaining` to track every second of timer activity and handle every state. 64 | 65 | ##### Subscribe to `timeRemaining` property changes: 66 | ```objective-c 67 | [barTimer addObserver:self forKeyPath:@"timeRemaining" 68 | options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:nil]; 69 | ``` 70 | ##### Add observer callback to handle new values: 71 | ```objective-c 72 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 73 | 74 | /* Check out Demo for more examples */ 75 | 76 | ORBVisualTimerBar *bar = (ORBVisualTimerBar *)object; 77 | 78 | if ([keyPath isEqualToString:@"timeRemaining"] && [bar timerIsActive]) { 79 | CGFloat timeRemaining = [[change valueForKey:NSKeyValueChangeNewKey] doubleValue]; 80 | NSLog(@"Time remaining: %.1f", timeRemaining); 81 | 82 | CGFloat hue = ( arc4random() % 256 / 256.0 ); 83 | CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; 84 | CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; 85 | 86 | UIColor *color = [UIColor colorWithHue:hue 87 | saturation:saturation 88 | brightness:brightness alpha:1]; 89 | 90 | bar.timerShapeActiveColor = color; 91 | 92 | if (timeRemaining <= 0) { 93 | bar.barThickness -= 30.0f; 94 | bar.barPadding += 60.0f; 95 | } 96 | } 97 | } 98 | ``` 99 | 100 | ## Properties and Methods Reference 101 | ### `ORBVisualTimer` Class 102 | ```objective-c 103 | #pragma mark - Properties 104 | 105 | /** 106 | Style of the timer to construct. In v1.0 only ORBVisualTimerStyleBar is implemented. 107 | */ 108 | @property (nonatomic, assign, readonly) ORBVisualTimerStyle style; 109 | 110 | /** 111 | Delegate object to receive events from timer. 112 | */ 113 | @property (nonatomic, weak) id delegate; 114 | 115 | /** 116 | Use this readonly property to track currently remaining time. 117 | */ 118 | @property (nonatomic, assign, readonly) NSTimeInterval timeRemaining; 119 | 120 | /** 121 | Indicates if timer is currently running or stopped. 122 | */ 123 | @property (nonatomic, assign, readonly) BOOL timerIsActive; 124 | 125 | /* --- CUSTOMIZATION --- */ 126 | 127 | /** 128 | Color of the containing view around timer itself. Default value is black color. 129 | */ 130 | @property (nonatomic, strong) UIColor *backgroundViewColor; 131 | 132 | /** 133 | Corner radius for containing view around timer. Default value is 0.0f. 134 | */ 135 | @property (nonatomic, assign) CGFloat backgroundViewCornerRadius; 136 | 137 | /** 138 | Color of the timer shape's (bar, circle, etc) background component, which stays inactive all the time. Default value is light gray color. 139 | */ 140 | @property (nonatomic, strong) UIColor *timerShapeInactiveColor; 141 | 142 | /** 143 | Color of the timer shape's (bar, circle, etc) foreground component, which is animated according to time remaining. Default value is green color. 144 | */ 145 | @property (nonatomic, strong) UIColor *timerShapeActiveColor; 146 | 147 | /** 148 | Whether or not to show label with time remaining. Default value is YES; 149 | */ 150 | @property (nonatomic, assign) BOOL showTimerLabel; 151 | 152 | /** 153 | Color of the timer label text. Default value is white color. 154 | */ 155 | @property (nonatomic, strong) UIColor *timerLabelColor; 156 | 157 | 158 | /** 159 | Whether or not to hide timer view after firing. Default value is NO. 160 | */ 161 | @property (nonatomic, assign) BOOL autohideWhenFired; 162 | 163 | #pragma mark - Methods 164 | 165 | /** 166 | \brief Constructs timer view with given style. 167 | \param style Style of the timer. 168 | \param frame Frame of the view. 169 | \param timeRemaining Time to initialize timer with. 170 | \returns Timer object of correspondent class. 171 | */ 172 | + (instancetype)timerWithStyle:(ORBVisualTimerStyle)style 173 | frame:(CGRect)frame 174 | timeRemaining:(NSTimeInterval)timeRemaining; 175 | 176 | /** 177 | \brief Starts the timer. 178 | */ 179 | - (void)start; 180 | 181 | /** 182 | \brief Stops the timer and resets time setting. 183 | */ 184 | - (void)stopTimerView; 185 | 186 | /** 187 | \brief Stops the timer and hides its view. 188 | */ 189 | - (void)stopAndHide; 190 | ``` 191 | 192 | ### `ORBVisualTimerBar` Class 193 | ```objective-c 194 | #pragma mark - Properties 195 | 196 | /** 197 | Style of bar timer animation. Default value is ORBVisualTimerBarAnimationStyleStraight. 198 | */ 199 | @property (nonatomic, assign) ORBVisualTimerBarAnimationStyle barAnimationStyle; 200 | 201 | /** 202 | Thikness of bar. Default value is 5.0f. 203 | */ 204 | @property (nonatomic, assign) CGFloat barThickness; 205 | 206 | /** 207 | Horizontal padding for both left and right bar ends. Default value is 10.0f; 208 | */ 209 | @property (nonatomic, assign) CGFloat barPadding; 210 | 211 | /** 212 | Cap style for both bar ends. Default value is kCALineCapRound. 213 | \discussion Possible pre-defined values include kCALineCapButt, kCALineCapRound and kCALineCapSquare. 214 | */ 215 | @property (nonatomic, assign) NSString *barCapStyle; 216 | 217 | #pragma mark - Methods 218 | 219 | /** 220 | \brief Initializes bar timer with given style. 221 | \param barAnimationStyle Style of bar animation. 222 | \param frame Frame of the view. 223 | \param timeRemaining Time to initialize timer with. 224 | */ 225 | - (instancetype)initWithBarAnimationStyle:(ORBVisualTimerBarAnimationStyle)barAnimationStyle 226 | frame:(CGRect)frame 227 | timeRemaining:(NSTimeInterval)timeRemaining; 228 | ``` 229 | 230 | ## ToDo's 231 | + Extend with other timer styles; 232 | 233 | ## License 234 | ORBVisualTimer is released under the permissive zlib License. See the [LICENSE](https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/master/LICENSE.md) file. 235 | -------------------------------------------------------------------------------- /Screenshots/bars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/Screenshots/bars.jpg -------------------------------------------------------------------------------- /Screenshots/flow_w180.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/Screenshots/flow_w180.gif -------------------------------------------------------------------------------- /Screenshots/flow_w300.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xNSHuman/ORBVisualTimer/d6dc9bbaceccf08341505e45347f787478b8e193/Screenshots/flow_w300.gif --------------------------------------------------------------------------------