├── TimeScrubber.xcodeproj ├── xcuserdata │ └── VSemenchenko.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── TimeScrubber.xcscheme ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── VSemenchenko.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── TimeScrubber.xccheckout └── project.pbxproj ├── TimeScrubber ├── TimeScrubberLabel.h ├── TimeScrubberBorder.h ├── ViewController.h ├── AppDelegate.h ├── main.m ├── TimeScrubberControl.h ├── PopTip │ ├── AMPopTip+Entrance.h │ ├── AMPopTip+Draw.h │ ├── AMPopTip+Animation.h │ ├── AMPopTipDefaults.h │ ├── AMPopTip+Entrance.m │ ├── AMPopTip+Animation.m │ ├── AMPopTip+Draw.m │ ├── AMPopTip.h │ └── AMPopTip.m ├── TimeScrubberBorder.m ├── TimeScrubberLabel.m ├── ScrollWithVideoFragments.h ├── ScrollWithDates.h ├── GlobalDefines.h ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── TimeScrubber.h ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.xib ├── AppDelegate.m ├── TimeScrubberControl.m ├── ViewController.m ├── ScrollWithVideoFragments.m ├── ScrollWithDates.m └── TimeScrubber.m ├── TimeScrubberTests ├── Info.plist └── TimeScrubberTests.m └── README.md /TimeScrubber.xcodeproj/xcuserdata/VSemenchenko.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/project.xcworkspace/xcuserdata/VSemenchenko.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pffan91/TimeScrubber/HEAD/TimeScrubber.xcodeproj/project.xcworkspace/xcuserdata/VSemenchenko.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubberLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberLabel.h 3 | // ALLIE 4 | // 5 | // Created by Vladyslav Semecnhenko on 3/4/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TimeScrubberLabel : UILabel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubberBorder.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberBorder.h 3 | // ALLIE 4 | // 5 | // Created by Vladyslav Semecnhenko on 3/4/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TimeScrubberBorder : UIView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TimeScrubber/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TimeScrubber/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. 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 | -------------------------------------------------------------------------------- /TimeScrubber/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. 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 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/project.xcworkspace/xcuserdata/VSemenchenko.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubberControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberControlView.h 3 | // TimeScrubberExample 4 | // 5 | // Created by Vladyslav Semenchenko on 08/12/14. 6 | // Copyright (c) 2014 Vladyslav Semenchenko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TimeScrubberControl : UIView 12 | 13 | @property (nonatomic) NSDate *date; 14 | 15 | @property (nonatomic) UIColor *outerColor; 16 | @property (nonatomic) UIColor *innerColor; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip+Entrance.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip+Entrance.h 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip.h" 10 | 11 | @interface AMPopTip (Entrance) 12 | 13 | /** Perform entrance animation 14 | * 15 | * Triggers the chosen entrance animation 16 | * 17 | * @param completion Completion handler 18 | */ 19 | - (void)performEntranceAnimation:(void (^)())completion; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubberBorder.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberBorder.m 3 | // ALLIE 4 | // 5 | // Created by Vladyslav Semecnhenko on 3/4/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import "TimeScrubberBorder.h" 10 | 11 | @implementation TimeScrubberBorder 12 | 13 | - (id)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) 17 | { 18 | UIColor* color = [UIColor whiteColor]; 19 | 20 | self.backgroundColor = color; 21 | self.userInteractionEnabled = NO; 22 | } 23 | 24 | return self; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubberLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberLabel.m 3 | // ALLIE 4 | // 5 | // Created by Vladyslav Semecnhenko on 3/4/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import "TimeScrubberLabel.h" 10 | 11 | @implementation TimeScrubberLabel 12 | 13 | - (id)initWithFrame:(CGRect)frame 14 | { 15 | self = [super initWithFrame:frame]; 16 | if (self) 17 | { 18 | self.font = [UIFont systemFontOfSize:8]; 19 | self.textAlignment = NSTextAlignmentCenter; 20 | self.textColor = [UIColor whiteColor]; 21 | } 22 | 23 | return self; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip+Draw.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip+Draw.h 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip.h" 10 | 11 | @interface AMPopTip (Draw) 12 | 13 | /** Poptip's Bezier path 14 | * 15 | * Returns the path used to draw the poptip, used internally by the poptip. 16 | * 17 | * @param rect The rect holding the poptip 18 | * @param direction The direction of the poptip appearance 19 | * @return UIBezierPath The poptip's path 20 | */ 21 | - (UIBezierPath *)pathWithRect:(CGRect)rect direction:(AMPopTipDirection)direction; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip+Animation.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip+Animation.h 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip.h" 10 | 11 | @interface AMPopTip (Animation) 12 | 13 | /** Start the popover action animation 14 | * 15 | * Starts the popover action animation. Does nothing if the popover wasn't animating in the first place. 16 | */ 17 | - (void)performActionAnimation; 18 | 19 | /** Stops the popover action animation 20 | * 21 | * Stops the popover action animation. Does nothing if the popover wasn't animating in the first place. 22 | */ 23 | - (void)dismissActionAnimation; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/xcuserdata/VSemenchenko.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TimeScrubber.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 299EBAE51B270B460069DE26 16 | 17 | primary 18 | 19 | 20 | 299EBAFE1B270B460069DE26 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TimeScrubberTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.vsemenchenko.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TimeScrubber/ScrollWithVideoFragments.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollWithVideoFragments.h 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/10/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ScrollWithVideoFragments : UIView 12 | 13 | @property (nonatomic) NSDate *startDateInitial; 14 | @property (nonatomic) NSDate *endDateInitial; 15 | 16 | - (id)initWithFrame:(CGRect)frame startDate:(NSDate *)startDate endDate:(NSDate *)endDate; 17 | 18 | - (void)updateWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate andDelta:(NSTimeInterval)delta; 19 | 20 | - (void)updateWithOffset:(float)offset; 21 | 22 | - (void)createSubviewsWithVideoFragments:(NSMutableArray *)videoFragments; 23 | - (void)createSubviewsWithVideoFragments:(NSMutableArray *)videoFragments cleanup:(BOOL)cleanup; // not used 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /TimeScrubber/ScrollWithDates.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollWithDates.h 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ScrollWithDates : UIView 12 | 13 | @property (nonatomic) NSDate *startDateInitial; 14 | @property (nonatomic) NSDate *endDateInitial; 15 | @property (nonatomic) int coefficient; 16 | 17 | - (id)initWithFrame:(CGRect)frame startDate:(NSDate *)startDate endDate:(NSDate *)endDate segments:(int)segmentsI oneSegmentTime:(float)oneSegmentTimeI coefficient:(int)coef; 18 | 19 | - (void)updateWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate segments:(int)segmentsI isNeedHours:(BOOL)isNeedHours coefficient:(int)coef animateDirection:(int)direction selectedPoint:(CGPoint)selectedPoint; 20 | 21 | - (void)updateWithOffset:(float)offset; 22 | 23 | - (void)createNewViewWithDate:(NSDate *)date isNeedMinutes:(BOOL)isNeedMinutes; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTipDefaults.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTipDefaults.h 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | /** @constant AMPopTip default values */ 10 | #define kDefaultFont [UIFont systemFontOfSize:[UIFont systemFontSize]] 11 | #define kDefaultTextColor [UIColor whiteColor] 12 | #define kDefaultBackgroundColor [UIColor redColor] 13 | #define kDefaultBorderColor [UIColor colorWithWhite:0.182 alpha:1.000] 14 | #define kDefaultBorderWidth 0 15 | #define kDefaultRadius 4 16 | #define kDefaultPadding 6 17 | #define kDefaultArrowSize CGSizeMake(8, 8) 18 | #define kDefaultAnimationIn 0.4 19 | #define kDefaultAnimationOut 0.2 20 | #define kDefaultBounceAnimationIn 1.2 21 | #define kDefaultBounceAnimationOut 1.0 22 | #define kDefaultEdgeInsets UIEdgeInsetsZero 23 | #define kDefaultEdgeMargin 0 24 | #define kDefaultOffset 0 25 | #define kDefaultBounceOffset 8 26 | #define kDefaultFloatOffset 8 27 | #define kDefaultPulseOffset 1.1 28 | -------------------------------------------------------------------------------- /TimeScrubberTests/TimeScrubberTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberTests.m 3 | // TimeScrubberTests 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface TimeScrubberTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation TimeScrubberTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /TimeScrubber/GlobalDefines.h: -------------------------------------------------------------------------------- 1 | // 2 | // GlobalDefines.h 3 | // ALLIE 4 | // 5 | // Created by Vladyslav Semecnhenko on 3/31/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #define kServerPrefix @"https://testing.alliecam.com/api" 10 | //#define kServerPrefix @"http://allie.ekosarev.http.netlab/api" 11 | #define kDemoLogin @"demo@alliecam.com" 12 | #define kDemoPassword @"123456" 13 | #define kBambooBuildNumber @"###BambooBuildNumber###" 14 | 15 | // Design 16 | #define kColorScheme [UIColor colorWithRed:1.000 green:0.577 blue:0.000 alpha:1.000] 17 | #define kColorGrey [UIColor colorWithRed:0.387 green:0.395 blue:0.405 alpha:1.000] 18 | #define kColorTintNavBar [UIColor blackColor] 19 | 20 | // misc 21 | #define IsPhone ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) 22 | #define IS_PHONEPOD5() ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )568 ) < DBL_EPSILON ) 23 | #define IS_PHONEPOD6() ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )667 ) < DBL_EPSILON ) 24 | #define IS_PHONEPOD6p() ( fabs( ( double )[ [ UIScreen mainScreen ] bounds ].size.height - ( double )736 ) < DBL_EPSILON ) 25 | 26 | // Texts variables 27 | #define kTermsOfServicePage @"terms-of-service" 28 | #define kAboutPage @"about" 29 | #define kPrivacyPolicyPage @"privacy-policy" 30 | -------------------------------------------------------------------------------- /TimeScrubber/Images.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 | } -------------------------------------------------------------------------------- /TimeScrubber/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.icrtech.allie 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 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 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/project.xcworkspace/xcshareddata/TimeScrubber.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 232B4FF1-FD1F-4FC2-A4F6-C07356E4E0EE 9 | IDESourceControlProjectName 10 | TimeScrubber 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C81DF0E44B68D301557202FE21B81877A38E2F04 14 | https://github.com/pffan91/TimeScrubber.git 15 | 16 | IDESourceControlProjectPath 17 | TimeScrubber.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C81DF0E44B68D301557202FE21B81877A38E2F04 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/pffan91/TimeScrubber.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C81DF0E44B68D301557202FE21B81877A38E2F04 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C81DF0E44B68D301557202FE21B81877A38E2F04 36 | IDESourceControlWCCName 37 | TimeScrubber 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubber.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubber.h 3 | // TimeScrubberExample 4 | // 5 | // Created by Vladyslav Semenchenko on 08/12/14. 6 | // Copyright (c) 2014 Vladyslav Semenchenko. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TimeScrubberControl.h" 11 | 12 | @interface TimeScrubber : UIControl 13 | 14 | // contains the current value (must be time), maybe some function that transforms value to time 15 | @property (nonatomic, assign) CGFloat value; 16 | 17 | // the minimum value of the knob. Defaults to 0. 18 | @property (nonatomic, assign) float minimumValue; 19 | 20 | // the maximum value of the knob. Defaults to 24. 21 | @property (nonatomic, assign) float maximumValue; 22 | 23 | // dates 24 | @property (nonatomic) NSTimeInterval endDateIntervalInitial; 25 | @property (nonatomic) NSTimeInterval startDateIntervalInitial; 26 | @property (nonatomic) NSTimeInterval currentDateInterval; 27 | @property (nonatomic) NSTimeInterval currentDateIntervalFixed; 28 | @property (nonatomic) NSDate *endDateInitial; 29 | @property (nonatomic) TimeScrubberControl *thumbControl; 30 | @property (nonatomic) TimeScrubberControl *thumbControlStatic; 31 | @property (nonatomic) NSMutableArray *mArrayWithVideoFragments; 32 | @property (nonatomic) BOOL isCameraOnline; // for other functionality 33 | @property (nonatomic) NSDate *selectedDate; 34 | 35 | // custom init 36 | - (id)initWithFrame:(CGRect)frame withStartDate:(NSDate *)startDate endDate:(NSDate *)endDate segments:(int)segmentsI andVideoBlocks:(NSMutableArray *)videoBlocks; 37 | - (id)initInOfflineModeWithRect:(CGRect)frame; 38 | - (void)updateOfflinePresentation; 39 | 40 | // get real data (needed for server) 41 | - (NSTimeInterval)getRealCurrentDate; 42 | 43 | // update enable 44 | - (void)updateEnable:(BOOL)isEnabled; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /TimeScrubber/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 | -------------------------------------------------------------------------------- /TimeScrubber/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. 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 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Time scrubber 2 | ---------- 3 | Time scrubber for date selection. Can be used to select date for remote video playing. iOS 7-8, Objective-C, Xcode 6.4. 4 | 5 | ![Demo](https://dl.dropbox.com/s/flxvev4qlkt8djx/timescrubber_demo.gif) 6 | Features 7 | ------------- 8 | 9 | - Automatically generated blocks based on date interval 10 | - Customization 11 | - Supports previewing available video from server (grey blocks - available video from server) 12 | - Preview for selected date (AMPopTip - https://github.com/andreamazz/AMPopTip) 13 | - Don't show feature 14 | - Long press - zoom scrubber to one period (one section) 15 | - Dont select not available video (white blocks are not available video) 16 | - Animation 17 | - Update function to update scrubber each second and update date interval 18 | - Automatically moving available blocks per time 19 | 20 | Usage 21 | ------------- 22 | Initialization: 23 | 24 | NSDate *startDate; 25 | NSDate *endDate; 26 | 27 | NSDictionary *d1 = @{@"1" : @"0", @"2" : @"-3600"}; // 1h 28 | NSDictionary *d2 = @{@"1" : @"-72000", @"2" : @"-79200"}; // 2h 29 | NSMutableArray *sampleArrayWithBlocks = [NSMutableArray arrayWithObjects:d1, d2, nil]; // sample video available video blocks 30 | 31 | endDate = [NSDate date]; 32 | startDate = [NSDate dateWithTimeInterval:-(3600 * 24) sinceDate:endDate]; // one day 33 | 34 | mTimeScrubber = [[TimeScrubber alloc] initWithFrame:CGRectMake(30, 50, self.view.bounds.size.width - 60, 50) withStartDate:startDate endDate:endDate segments:12 andVideoBlocks:sampleArrayWithBlocks]; 35 | [self.view addSubview:mTimeScrubber]; 36 | [mTimeScrubber updateEnable:YES]; // automatically tick and update date each second 37 | 38 | Retrieving selected date: 39 | 40 | [mTimeScrubber addTarget:self action:@selector(newValue:) forControlEvents:UIControlEventValueChanged]; // add this to initialization block 41 | 42 | // add this as new method in ViewController 43 | -(void)newValue:(TimeScrubber*)slider 44 | { 45 | NSString *currentSelectedDate = [NSString stringWithFormat:@"Current selected date = %@", slider.selectedDate]; 46 | } 47 | 48 | Contact me 49 | ------------- 50 | You can contact me via email vladyslav.semenchenko.usa@gmail.com or directly from my site: http://www.vsemenchenko.com 51 | 52 | Licenses 53 | ------------- 54 | If you want to use source code please contact me: vladyslav.semenchenko.usa@gmail.com 55 | 56 | 2015, Vladyslav Semenchenko. 57 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubberControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubberControlView.m 3 | // TimeScrubberExample 4 | // 5 | // Created by Vladyslav Semenchenko on 08/12/14. 6 | // Copyright (c) 2014 Vladyslav Semenchenko. All rights reserved. 7 | // 8 | 9 | #import "TimeScrubberControl.h" 10 | 11 | @implementation TimeScrubberControl 12 | { 13 | float selfHeight; 14 | float selfWidth; 15 | } 16 | 17 | - (id)initWithFrame:(CGRect)frame 18 | { 19 | self = [super initWithFrame:frame]; 20 | if (self) 21 | { 22 | self.backgroundColor = [UIColor clearColor]; 23 | 24 | selfHeight = frame.size.height; 25 | selfWidth = frame.size.width; 26 | } 27 | 28 | return self; 29 | } 30 | 31 | - (void)drawRect:(CGRect)rect 32 | { 33 | CGContextRef context = UIGraphicsGetCurrentContext(); 34 | 35 | UIColor* color = self.outerColor; 36 | UIColor* color2 = self.innerColor; 37 | 38 | UIColor* shadow = UIColor.blackColor; 39 | CGSize shadowOffset = CGSizeMake(0.1, -0.1); 40 | CGFloat shadowBlurRadius = 2; 41 | UIColor* shadow2 = UIColor.blackColor; 42 | CGSize shadow2Offset = CGSizeMake(0.1, -0.1); 43 | CGFloat shadow2BlurRadius = 1; 44 | 45 | //// Oval 1 Drawin 46 | UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(rect.origin.x + 5, rect.origin.y + 5, rect.size.width - 10, rect.size.height - 10)]; 47 | 48 | CGContextSaveGState(context); 49 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [shadow CGColor]); 50 | [color setFill]; 51 | [ovalPath fill]; 52 | CGContextRestoreGState(context); 53 | 54 | //// Oval 2 Drawing 55 | UIBezierPath* oval2Path = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(rect.origin.x + 11, rect.origin.y + 11, rect.size.width - 22, rect.size.height - 22)]; 56 | [color2 setFill]; 57 | [oval2Path fill]; 58 | 59 | CGContextSaveGState(context); 60 | UIRectClip(oval2Path.bounds); 61 | CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL); 62 | 63 | CGContextSetAlpha(context, CGColorGetAlpha([shadow2 CGColor])); 64 | CGContextBeginTransparencyLayer(context, NULL); 65 | { 66 | UIColor* opaqueShadow = [shadow2 colorWithAlphaComponent: 1]; 67 | CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, [opaqueShadow CGColor]); 68 | CGContextSetBlendMode(context, kCGBlendModeSourceOut); 69 | CGContextBeginTransparencyLayer(context, NULL); 70 | 71 | [opaqueShadow setFill]; 72 | [oval2Path fill]; 73 | 74 | CGContextEndTransparencyLayer(context); 75 | } 76 | 77 | CGContextEndTransparencyLayer(context); 78 | CGContextRestoreGState(context); 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip+Entrance.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip+Entrance.m 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip+Entrance.h" 10 | 11 | @implementation AMPopTip (Entrance) 12 | 13 | - (void)performEntranceAnimation:(void (^)())completion { 14 | switch (self.entranceAnimation) { 15 | case AMPopTipEntranceAnimationScale: { 16 | [self entranceScale:completion]; 17 | break; 18 | } 19 | case AMPopTipEntranceAnimationTransition: { 20 | [self entranceTransition:completion]; 21 | break; 22 | } 23 | case AMPopTipEntranceAnimationCustom: { 24 | [self.containerView addSubview:self]; 25 | if (self.entranceAnimationHandler) { 26 | self.entranceAnimationHandler(^{ 27 | completion(); 28 | }); 29 | } 30 | } 31 | case AMPopTipEntranceAnimationNone: { 32 | [self.containerView addSubview:self]; 33 | completion(); 34 | break; 35 | } 36 | default: 37 | break; 38 | } 39 | } 40 | 41 | - (void)entranceTransition:(void (^)())completion { 42 | self.transform = CGAffineTransformMakeScale(0.6, 0.6); 43 | switch (self.direction) { 44 | case AMPopTipDirectionUp: 45 | self.transform = CGAffineTransformTranslate(self.transform, 0, -self.fromFrame.origin.y); 46 | break; 47 | case AMPopTipDirectionDown: 48 | self.transform = CGAffineTransformTranslate(self.transform, 0, (self.containerView.frame.size.height - self.fromFrame.origin.y)); 49 | break; 50 | case AMPopTipDirectionLeft: 51 | self.transform = CGAffineTransformTranslate(self.transform, -self.fromFrame.origin.x, 0); 52 | break; 53 | case AMPopTipDirectionRight: 54 | self.transform = CGAffineTransformTranslate(self.transform, (self.containerView.frame.size.width - self.fromFrame.origin.x), 0); 55 | break; 56 | case AMPopTipDirectionNone: 57 | self.transform = CGAffineTransformTranslate(self.transform, 0, (self.containerView.frame.size.height - self.fromFrame.origin.y)); 58 | break; 59 | 60 | default: 61 | break; 62 | } 63 | [self.containerView addSubview:self]; 64 | 65 | [UIView animateWithDuration:self.animationIn delay:self.delayIn usingSpringWithDamping:0.6 initialSpringVelocity:1.5 options:(UIViewAnimationOptionCurveEaseInOut) animations:^{ 66 | self.transform = CGAffineTransformIdentity; 67 | } completion:^(BOOL completed){ 68 | if (completed) { 69 | completion(); 70 | } 71 | }]; 72 | } 73 | 74 | - (void)entranceScale:(void (^)())completion { 75 | self.transform = CGAffineTransformMakeScale(0, 0); 76 | [self.containerView addSubview:self]; 77 | 78 | [UIView animateWithDuration:self.animationIn delay:self.delayIn usingSpringWithDamping:0.6 initialSpringVelocity:1.5 options:(UIViewAnimationOptionCurveEaseInOut) animations:^{ 79 | self.transform = CGAffineTransformIdentity; 80 | } completion:^(BOOL completed){ 81 | if (completed) { 82 | completion(); 83 | } 84 | }]; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /TimeScrubber/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TimeScrubber.h" 11 | 12 | 13 | @interface ViewController () 14 | { 15 | TimeScrubber *mTimeScrubber1; 16 | TimeScrubber *mTimeScrubber2; 17 | TimeScrubber *mTimeScrubber3; 18 | TimeScrubber *mTimeScrubber4; 19 | } 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | // Do any additional setup after loading the view, typically from a nib. 27 | 28 | NSDate *startDate1; 29 | NSDate *endDate1; 30 | 31 | NSDictionary *d1 = @{@"1" : @"0", @"2" : @"-3600"}; // 1h 32 | NSDictionary *d2 = @{@"1" : @"-72000", @"2" : @"-79200"}; // 2h 33 | NSMutableArray *sampleArrayWithBlocks1 = [NSMutableArray arrayWithObjects:d1, d2, nil]; 34 | 35 | endDate1 = [NSDate date]; 36 | startDate1 = [NSDate dateWithTimeInterval:-(3600 * 13) sinceDate:endDate1]; // one day 37 | 38 | mTimeScrubber1 = [[TimeScrubber alloc] initWithFrame:CGRectMake(30, 50, self.view.bounds.size.width - 60, 50) withStartDate:startDate1 endDate:endDate1 segments:12 andVideoBlocks:sampleArrayWithBlocks1]; 39 | // [mTimeScrubber1 addTarget:self action:@selector(newValue:) forControlEvents:UIControlEventValueChanged]; 40 | [self.view addSubview:mTimeScrubber1]; 41 | [mTimeScrubber1 updateEnable:YES]; 42 | 43 | // 2 44 | NSDate *startDate2; 45 | NSDate *endDate2; 46 | 47 | NSDictionary *d3 = @{@"1" : @"0", @"2" : @"-3600"}; // 1h 48 | NSDictionary *d4 = @{@"1" : @"-72000", @"2" : @"-79200"}; // 2h 49 | NSMutableArray *sampleArrayWithBlocks2 = [NSMutableArray arrayWithObjects:d3, d4, nil]; 50 | 51 | endDate2 = [NSDate date]; 52 | startDate2 = [NSDate dateWithTimeInterval:-(3600 * 24) sinceDate:endDate2]; // two days 53 | 54 | mTimeScrubber2 = [[TimeScrubber alloc] initWithFrame:CGRectMake(30, 125, self.view.bounds.size.width - 60, 50) withStartDate:startDate2 endDate:endDate2 segments:12 andVideoBlocks:sampleArrayWithBlocks2]; 55 | [self.view addSubview:mTimeScrubber2]; 56 | [mTimeScrubber2 updateEnable:YES]; 57 | 58 | // 3 59 | NSDate *startDate3; 60 | NSDate *endDate3; 61 | 62 | NSDictionary *d5 = @{@"1" : @"0", @"2" : @"-3600"}; // 1h 63 | NSDictionary *d6 = @{@"1" : @"-72000", @"2" : @"-79200"}; // 2h 64 | NSMutableArray *sampleArrayWithBlocks3 = [NSMutableArray arrayWithObjects:d5, d6, nil]; 65 | 66 | endDate3 = [NSDate date]; 67 | startDate3 = [NSDate dateWithTimeInterval:-(3600 * 72) sinceDate:endDate3]; // three days 68 | 69 | mTimeScrubber3 = [[TimeScrubber alloc] initWithFrame:CGRectMake(30, 200, self.view.bounds.size.width - 60, 50) withStartDate:startDate3 endDate:endDate3 segments:12 andVideoBlocks:sampleArrayWithBlocks3]; 70 | [self.view addSubview:mTimeScrubber3]; 71 | [mTimeScrubber3 updateEnable:YES]; 72 | 73 | // 4 offline 74 | mTimeScrubber4 = [[TimeScrubber alloc] initInOfflineModeWithRect:CGRectMake(30, 275, self.view.bounds.size.width - 60, 50)]; 75 | [mTimeScrubber4 updateOfflinePresentation]; 76 | [self.view addSubview:mTimeScrubber4]; 77 | [mTimeScrubber4 updateEnable:YES]; 78 | } 79 | 80 | - (void)didReceiveMemoryWarning { 81 | [super didReceiveMemoryWarning]; 82 | // Dispose of any resources that can be recreated. 83 | } 84 | 85 | //-(void)newValue:(TimeScrubber*)slider 86 | //{ 87 | // mLabelDebugLabel.text = [NSString stringWithFormat:@"Current selected date = %@", slider.selectedDate]; 88 | //} 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /TimeScrubber/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /TimeScrubber/ScrollWithVideoFragments.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollWithVideoFragments.m 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/10/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | #import "ScrollWithVideoFragments.h" 10 | #import "GlobalDefines.h" 11 | 12 | @interface ScrollWithVideoFragments () 13 | { 14 | NSMutableArray *mArrayWithViews; 15 | 16 | float timeDelta; 17 | float deltaFromScrubber; 18 | } 19 | 20 | @end 21 | 22 | @implementation ScrollWithVideoFragments 23 | 24 | - (id)initWithFrame:(CGRect)frame startDate:(NSDate *)startDate endDate:(NSDate *)endDate 25 | { 26 | self = [super initWithFrame:frame]; 27 | 28 | if (self) 29 | { 30 | mArrayWithViews = [NSMutableArray array]; 31 | 32 | self.startDateInitial = startDate; 33 | self.endDateInitial = endDate; 34 | 35 | timeDelta = endDate.timeIntervalSinceNow - startDate.timeIntervalSinceNow; 36 | deltaFromScrubber = 0; 37 | } 38 | 39 | return self; 40 | } 41 | 42 | - (void)updateWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate andDelta:(NSTimeInterval)delta 43 | { 44 | for (UIView *view in mArrayWithViews) 45 | { 46 | [UIView animateWithDuration:0.3 animations:^{ 47 | view.alpha = 0.0; 48 | } completion:^(BOOL finished) { 49 | if (finished) 50 | { 51 | [view removeFromSuperview]; 52 | } 53 | }]; 54 | } 55 | 56 | [mArrayWithViews removeAllObjects]; 57 | 58 | self.startDateInitial = startDate; 59 | self.endDateInitial = endDate; 60 | 61 | timeDelta = endDate.timeIntervalSinceNow - startDate.timeIntervalSinceNow; 62 | deltaFromScrubber = delta; 63 | } 64 | 65 | - (void)createSubviewsWithVideoFragments:(NSMutableArray *)videoFragments 66 | { 67 | NSTimeInterval dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 68 | float datePerPixel = self.bounds.size.width / dateDifference; 69 | 70 | for (NSDictionary *dict in videoFragments) 71 | { 72 | // calculate position 73 | float x1 = [[dict objectForKey:@"1"] floatValue] - fabsf(deltaFromScrubber); 74 | float x2 = [[dict objectForKey:@"2"] floatValue] - fabsf(deltaFromScrubber); 75 | 76 | float x1dif = x1 - self.startDateInitial.timeIntervalSinceNow; 77 | float x2dif = x2 - self.startDateInitial.timeIntervalSinceNow; 78 | 79 | float x1pos = x1dif * datePerPixel - (fabsf((deltaFromScrubber) * datePerPixel)); 80 | float x2pos = x2dif * datePerPixel - (fabsf((deltaFromScrubber) * datePerPixel)); 81 | 82 | // add new view - available region 83 | UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x1pos, self.bounds.size.height - 32.5, x2pos - x1pos, 15)]; 84 | view.backgroundColor = [UIColor colorWithWhite:0.876 alpha:1.000]; 85 | view.alpha = 0.0; 86 | view.userInteractionEnabled = NO; 87 | [self insertSubview:view atIndex:0]; 88 | 89 | [mArrayWithViews addObject:view]; 90 | 91 | [UIView animateWithDuration:0.3 animations:^{ 92 | view.alpha = 1.0; 93 | }]; 94 | } 95 | } 96 | 97 | // not used 98 | - (void)createSubviewsWithVideoFragments:(NSMutableArray *)videoFragments cleanup:(BOOL)cleanup 99 | { 100 | 101 | } 102 | 103 | - (void)updateWithOffset:(float)offset 104 | { 105 | NSTimeInterval dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow + 2; // 2 - correction 106 | float datePerPixel = self.bounds.size.width / dateDifference; 107 | 108 | for (UIView *view in mArrayWithViews) 109 | { 110 | view.center = CGPointMake(view.center.x - datePerPixel, view.center.y); 111 | } 112 | } 113 | 114 | - (void)hitTest:(UIView *)view 115 | { 116 | CGPoint p = view.center; 117 | 118 | CGRect trackingFrame = CGRectMake(-10, 0, 25 , self.bounds.size.height); 119 | 120 | if (CGRectContainsPoint(trackingFrame, p)) 121 | { 122 | [UIView animateWithDuration:1.0 animations:^{ 123 | view.alpha = 0.0; 124 | } completion:^(BOOL finished) { 125 | if (!finished) 126 | { 127 | [view removeFromSuperview]; 128 | } 129 | }]; 130 | } 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip+Animation.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip+Animation.m 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip+Animation.h" 10 | #import 11 | 12 | @implementation AMPopTip (Animation) 13 | 14 | - (void)setShouldBounce:(BOOL)shouldBounce { objc_setAssociatedObject(self, @selector(shouldBounce), [NSNumber numberWithBool:shouldBounce], OBJC_ASSOCIATION_RETAIN);} 15 | - (BOOL)shouldBounce { return [objc_getAssociatedObject(self, @selector(shouldBounce)) boolValue]; } 16 | 17 | - (void)performActionAnimation { 18 | switch (self.actionAnimation) { 19 | case AMPopTipActionAnimationBounce: 20 | self.shouldBounce = YES; 21 | [self bounceAnimation]; 22 | break; 23 | case AMPopTipActionAnimationFloat: 24 | [self floatAnimation]; 25 | break; 26 | case AMPopTipActionAnimationPulse: 27 | [self pulseAnimation]; 28 | break; 29 | case AMPopTipActionAnimationNone: 30 | return; 31 | break; 32 | default: 33 | break; 34 | } 35 | } 36 | 37 | - (void)floatAnimation { 38 | CGFloat xOffset = 0; 39 | CGFloat yOffset = 0; 40 | switch (self.direction) { 41 | case AMPopTipDirectionUp: 42 | yOffset = -self.actionFloatOffset; 43 | break; 44 | case AMPopTipDirectionDown: 45 | yOffset = self.actionFloatOffset; 46 | break; 47 | case AMPopTipDirectionLeft: 48 | xOffset = -self.actionFloatOffset; 49 | break; 50 | case AMPopTipDirectionRight: 51 | xOffset = self.actionFloatOffset; 52 | break; 53 | case AMPopTipDirectionNone: 54 | yOffset = -self.actionFloatOffset; 55 | break; 56 | } 57 | 58 | [UIView animateWithDuration:(self.actionAnimationIn / 2) delay:self.actionDelayIn options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction) animations:^{ 59 | self.transform = CGAffineTransformMakeTranslation(xOffset, yOffset); 60 | } completion:nil]; 61 | } 62 | 63 | - (void)bounceAnimation { 64 | CGFloat xOffset = 0; 65 | CGFloat yOffset = 0; 66 | switch (self.direction) { 67 | case AMPopTipDirectionUp: 68 | yOffset = -self.actionBounceOffset; 69 | break; 70 | case AMPopTipDirectionDown: 71 | yOffset = self.actionBounceOffset; 72 | break; 73 | case AMPopTipDirectionLeft: 74 | xOffset = -self.actionBounceOffset; 75 | break; 76 | case AMPopTipDirectionRight: 77 | xOffset = self.actionBounceOffset; 78 | break; 79 | case AMPopTipDirectionNone: 80 | yOffset = -self.actionBounceOffset; 81 | break; 82 | } 83 | 84 | [UIView animateWithDuration:(self.actionAnimationIn / 10) delay:self.actionDelayIn options:(UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionAllowUserInteraction) animations:^{ 85 | self.transform = CGAffineTransformMakeTranslation(xOffset, yOffset); 86 | } completion:^(BOOL finished) { 87 | [UIView animateWithDuration:(self.actionAnimationIn - self.actionAnimationIn / 10) delay:0 usingSpringWithDamping:0.4 initialSpringVelocity:1 options:0 animations:^{ 88 | self.transform = CGAffineTransformIdentity; 89 | } completion:^(BOOL done) { 90 | if (self.shouldBounce && done) { 91 | [self bounceAnimation]; 92 | } 93 | }]; 94 | }]; 95 | } 96 | 97 | - (void)pulseAnimation { 98 | [UIView animateWithDuration:(self.actionAnimationIn / 2) delay:self.actionDelayIn options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionAllowUserInteraction) animations:^{ 99 | self.transform = CGAffineTransformMakeScale(self.actionPulseOffset, self.actionPulseOffset); 100 | } completion:nil]; 101 | } 102 | 103 | - (void)dismissActionAnimation { 104 | self.shouldBounce = NO; 105 | [UIView animateWithDuration:(self.actionAnimationOut / 2) delay:self.actionDelayOut options:UIViewAnimationOptionBeginFromCurrentState animations:^{ 106 | self.transform = CGAffineTransformIdentity; 107 | } completion:^(BOOL finished) { 108 | [self.layer removeAllAnimations]; 109 | }]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/xcuserdata/VSemenchenko.xcuserdatad/xcschemes/TimeScrubber.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip+Draw.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip+Draw.m 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 10/06/15. 6 | // Copyright (c) 2015 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip+Draw.h" 10 | 11 | #define DEGREES_TO_RADIANS(degrees) ((3.14159265359 * degrees)/ 180) 12 | 13 | @implementation AMPopTip (Draw) 14 | 15 | - (UIBezierPath *)pathWithRect:(CGRect)rect direction:(AMPopTipDirection)direction { 16 | UIBezierPath *path = [[UIBezierPath alloc] init]; 17 | CGRect baloonFrame; 18 | 19 | // Drawing a round rect and the arrow alone sometime shows a white halfpixel line, so here's a fun bit of code... feel free to fall asleep 20 | switch (direction) { 21 | case AMPopTipDirectionNone: { 22 | baloonFrame = (CGRect){ (CGPoint) { self.borderWidth, self.borderWidth }, (CGSize){ self.frame.size.width - self.borderWidth * 2, self.frame.size.height - self.borderWidth * 2} }; 23 | path = [UIBezierPath bezierPathWithRoundedRect:baloonFrame cornerRadius:self.radius]; 24 | 25 | break; 26 | } 27 | case AMPopTipDirectionDown: { 28 | baloonFrame = (CGRect){ (CGPoint) { 0, self.arrowSize.height }, (CGSize){ rect.size.width - self.borderWidth * 2, rect.size.height - self.arrowSize.height - self.borderWidth * 2} }; 29 | 30 | [path moveToPoint:(CGPoint){ self.arrowPosition.x + self.borderWidth, self.arrowPosition.y }]; 31 | [path addLineToPoint:(CGPoint){ self.borderWidth + self.arrowPosition.x + self.arrowSize.width / 2, self.arrowPosition.y + self.arrowSize.height }]; 32 | [path addLineToPoint:(CGPoint){ baloonFrame.size.width - self.radius, self.arrowSize.height }]; 33 | [path addArcWithCenter:(CGPoint){ baloonFrame.size.width - self.radius, self.arrowSize.height + self.radius } radius:self.radius startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(0) clockwise:YES]; 34 | [path addLineToPoint:(CGPoint){ baloonFrame.size.width, self.arrowSize.height + baloonFrame.size.height - self.radius }]; 35 | [path addArcWithCenter:(CGPoint){ baloonFrame.size.width - self.radius, self.arrowSize.height + baloonFrame.size.height - self.radius } radius:self.radius startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(90) clockwise:YES]; 36 | [path addLineToPoint:(CGPoint){ self.borderWidth + self.radius, self.arrowSize.height + baloonFrame.size.height }]; 37 | [path addArcWithCenter:(CGPoint){ self.borderWidth + self.radius, self.arrowSize.height + baloonFrame.size.height - self.radius } radius:self.radius startAngle:DEGREES_TO_RADIANS(90) endAngle:DEGREES_TO_RADIANS(180) clockwise:YES]; 38 | [path addLineToPoint:(CGPoint){ self.borderWidth, self.arrowSize.height + self.radius }]; 39 | [path addArcWithCenter:(CGPoint){ self.borderWidth + self.radius, self.arrowSize.height + self.radius } radius:self.radius startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(270) clockwise:YES]; 40 | [path addLineToPoint:(CGPoint){ self.borderWidth + self.arrowPosition.x - self.arrowSize.width / 2, self.arrowPosition.y + self.arrowSize.height }]; 41 | [path closePath]; 42 | 43 | break; 44 | } 45 | case AMPopTipDirectionUp: { 46 | baloonFrame = (CGRect){ (CGPoint) { 0, 0 }, (CGSize){ rect.size.width - self.borderWidth * 2, rect.size.height - self.arrowSize.height - self.borderWidth * 2 } }; 47 | 48 | [path moveToPoint:(CGPoint){ self.arrowPosition.x + self.borderWidth, self.arrowPosition.y - self.borderWidth }]; 49 | [path addLineToPoint:(CGPoint){ self.borderWidth + self.arrowPosition.x + self.arrowSize.width / 2, self.arrowPosition.y - self.arrowSize.height - self.borderWidth }]; 50 | [path addLineToPoint:(CGPoint){ baloonFrame.size.width - self.radius, baloonFrame.origin.y + baloonFrame.size.height + self.borderWidth }]; 51 | [path addArcWithCenter:(CGPoint){ baloonFrame.size.width - self.radius, baloonFrame.origin.y + baloonFrame.size.height - self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(90) endAngle:DEGREES_TO_RADIANS(0) clockwise:NO]; 52 | [path addLineToPoint:(CGPoint){ baloonFrame.size.width, baloonFrame.origin.y + self.radius + self.borderWidth }]; 53 | [path addArcWithCenter:(CGPoint){ baloonFrame.size.width - self.radius, baloonFrame.origin.y + self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(270) clockwise:NO]; 54 | [path addLineToPoint:(CGPoint){ self.borderWidth + self.radius, baloonFrame.origin.y + self.borderWidth }]; 55 | [path addArcWithCenter:(CGPoint){ self.borderWidth + self.radius, baloonFrame.origin.y + self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(180) clockwise:NO]; 56 | [path addLineToPoint:(CGPoint){ self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius + self.borderWidth }]; 57 | [path addArcWithCenter:(CGPoint){ self.borderWidth + self.radius, baloonFrame.origin.y + baloonFrame.size.height - self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(90) clockwise:NO]; 58 | [path addLineToPoint:(CGPoint){ self.borderWidth + self.arrowPosition.x - self.arrowSize.width / 2, self.arrowPosition.y - self.arrowSize.height - self.borderWidth }]; 59 | [path closePath]; 60 | 61 | break; 62 | } 63 | case AMPopTipDirectionLeft: { 64 | // Flip the size around for the left/right poptip 65 | CGSize arrowSize = CGSizeMake(self.arrowSize.height, self.arrowSize.width); 66 | baloonFrame = (CGRect){ (CGPoint) { 0, 0 }, (CGSize){ rect.size.width - arrowSize.width - self.borderWidth * 2, rect.size.height - self.borderWidth * 2} }; 67 | 68 | [path moveToPoint:(CGPoint){ self.arrowPosition.x - self.borderWidth, self.arrowPosition.y }]; 69 | [path addLineToPoint:(CGPoint){ self.arrowPosition.x - arrowSize.width - self.borderWidth, self.arrowPosition.y - arrowSize.height / 2 }]; 70 | [path addLineToPoint:(CGPoint){ baloonFrame.size.width - self.borderWidth, baloonFrame.origin.y + self.radius }]; 71 | [path addArcWithCenter:(CGPoint){ baloonFrame.size.width - self.radius - self.borderWidth, baloonFrame.origin.y + self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(270) clockwise:NO]; 72 | [path addLineToPoint:(CGPoint){ self.radius + self.borderWidth, baloonFrame.origin.y + self.borderWidth}]; 73 | [path addArcWithCenter:(CGPoint){ self.radius + self.borderWidth, baloonFrame.origin.y + self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(180) clockwise:NO]; 74 | [path addLineToPoint:(CGPoint){ self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius - self.borderWidth }]; 75 | [path addArcWithCenter:(CGPoint){ self.radius + self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius - self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(90) clockwise:NO]; 76 | [path addLineToPoint:(CGPoint){ baloonFrame.size.width - self.radius - self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.borderWidth }]; 77 | [path addArcWithCenter:(CGPoint){ baloonFrame.size.width - self.radius - self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius - self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(90) endAngle:DEGREES_TO_RADIANS(0) clockwise:NO]; 78 | [path addLineToPoint:(CGPoint){ self.arrowPosition.x - arrowSize.width - self.borderWidth, self.arrowPosition.y + arrowSize.height / 2 }]; 79 | [path closePath]; 80 | 81 | break; 82 | } 83 | case AMPopTipDirectionRight: { 84 | // Flip the size around for the left/right poptip 85 | CGSize arrowSize = CGSizeMake(self.arrowSize.height, self.arrowSize.width); 86 | baloonFrame = (CGRect){ (CGPoint) { arrowSize.width, 0 }, (CGSize){ rect.size.width - arrowSize.width - self.borderWidth * 2, rect.size.height - self.borderWidth * 2} }; 87 | 88 | [path moveToPoint:(CGPoint){ self.arrowPosition.x + self.borderWidth, self.arrowPosition.y }]; 89 | [path addLineToPoint:(CGPoint){ self.arrowPosition.x + arrowSize.width + self.borderWidth, self.arrowPosition.y - arrowSize.height / 2 }]; 90 | [path addLineToPoint:(CGPoint){ baloonFrame.origin.x + self.borderWidth, baloonFrame.origin.y + self.radius + self.borderWidth }]; 91 | [path addArcWithCenter:(CGPoint){ baloonFrame.origin.x + self.radius + self.borderWidth, baloonFrame.origin.y + self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(180) endAngle:DEGREES_TO_RADIANS(270) clockwise:YES]; 92 | [path addLineToPoint:(CGPoint){ baloonFrame.origin.x + baloonFrame.size.width - self.radius - self.borderWidth, baloonFrame.origin.y + self.borderWidth}]; 93 | [path addArcWithCenter:(CGPoint){ baloonFrame.origin.x + baloonFrame.size.width - self.radius - self.borderWidth, baloonFrame.origin.y + self.radius + self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(270) endAngle:DEGREES_TO_RADIANS(0) clockwise:YES]; 94 | [path addLineToPoint:(CGPoint){ baloonFrame.origin.x + baloonFrame.size.width - self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius - self.borderWidth }]; 95 | [path addArcWithCenter:(CGPoint){ baloonFrame.origin.x + baloonFrame.size.width - self.radius - self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius - self.borderWidth} radius:self.radius startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(90) clockwise:YES]; 96 | [path addLineToPoint:(CGPoint){ baloonFrame.origin.x + self.radius + self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.borderWidth}]; 97 | [path addArcWithCenter:(CGPoint){ baloonFrame.origin.x + self.radius + self.borderWidth, baloonFrame.origin.y + baloonFrame.size.height - self.radius - self.borderWidth } radius:self.radius startAngle:DEGREES_TO_RADIANS(90) endAngle:DEGREES_TO_RADIANS(180) clockwise:YES]; 98 | [path addLineToPoint:(CGPoint){ self.arrowPosition.x + arrowSize.width + self.borderWidth, self.arrowPosition.y + arrowSize.height / 2 }]; 99 | [path closePath]; 100 | 101 | break; 102 | } 103 | } 104 | return path; 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip.h 3 | // AMPopTip 4 | // 5 | // Created by Andrea Mazzini on 11/07/14. 6 | // Copyright (c) 2014 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | /**----------------------------------------------------------------------------- 12 | * @name AMPopTip Options 13 | * ----------------------------------------------------------------------------- 14 | */ 15 | 16 | /** @enum AMPopTipDirection 17 | * 18 | * Enum that specifies the direction of the poptip. 19 | */ 20 | typedef NS_ENUM(NSInteger, AMPopTipDirection) { 21 | /** Shows the poptip up */ 22 | AMPopTipDirectionUp, 23 | /** Shows the poptip down */ 24 | AMPopTipDirectionDown, 25 | /** Shows the poptip to the left */ 26 | AMPopTipDirectionLeft, 27 | /** Shows the poptip to the right */ 28 | AMPopTipDirectionRight, 29 | /** Shows the poptip up, with no arrow */ 30 | AMPopTipDirectionNone 31 | }; 32 | 33 | /** @enum AMPopTipEntranceAnimation 34 | * 35 | * Enum that specifies the type of entrance animation. Entrance animations are performed 36 | * while showing the poptip. 37 | */ 38 | typedef NS_ENUM(NSInteger, AMPopTipEntranceAnimation) { 39 | /** The poptip scales from 0% to 100% */ 40 | AMPopTipEntranceAnimationScale, 41 | /** The poptip moves in position from the edge of the screen */ 42 | AMPopTipEntranceAnimationTransition, 43 | /** No animation */ 44 | AMPopTipEntranceAnimationNone, 45 | /** The Animation is provided by the user */ 46 | AMPopTipEntranceAnimationCustom 47 | }; 48 | 49 | /** @enum AMPopTipActionAnimation 50 | * 51 | * Enum that specifies the type of action animation. Action animations are performed 52 | * after the poptip is visible and the entrance animation completed. 53 | */ 54 | typedef NS_ENUM(NSInteger, AMPopTipActionAnimation) { 55 | /** The poptip bounces following its direction */ 56 | AMPopTipActionAnimationBounce, 57 | /** The poptip floats in place */ 58 | AMPopTipActionAnimationFloat, 59 | /** The poptip pulsates by changing its size */ 60 | AMPopTipActionAnimationPulse, 61 | /** No animation */ 62 | AMPopTipActionAnimationNone 63 | }; 64 | 65 | @interface AMPopTip : UIView 66 | 67 | /**----------------------------------------------------------------------------- 68 | * @name AMPopTip 69 | * ----------------------------------------------------------------------------- 70 | */ 71 | 72 | /** Create a popotip 73 | * 74 | * Create a new popotip object 75 | */ 76 | + (instancetype)popTip; 77 | 78 | /** Show the popover 79 | * 80 | * Shows an animated popover in a given view, from a given rectangle. 81 | * The property isVisible will be set as YES as soon as the popover is added to the given view. 82 | * 83 | * @param text The text displayed. 84 | * @param direction The direction of the popover. 85 | * @param maxWidth The maximum width of the popover. If the popover won't fit in the given space, this will be overridden. 86 | * @param view The view that will hold the popover. 87 | * @param frame The originating frame. The popover's arrow will point to the center of this frame. 88 | */ 89 | - (void)showText:(NSString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame; 90 | 91 | /** Show the popover 92 | * 93 | * Shows an animated popover in a given view, from a given rectangle. 94 | * The property isVisible will be set as YES as soon as the popover is added to the given view. 95 | * 96 | * @param text The attributed text displayed. 97 | * @param direction The direction of the popover. 98 | * @param maxWidth The maximum width of the popover. If the popover won't fit in the given space, this will be overridden. 99 | * @param view The view that will hold the popover. 100 | * @param frame The originating frame. The popover's arrow will point to the center of this frame. 101 | */ 102 | - (void)showAttributedText:(NSAttributedString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame; 103 | 104 | 105 | /** Show the popover 106 | * 107 | * Shows an animated popover in a given view, from a given rectangle. 108 | * The property isVisible will be set as YES as soon as the popover is added to the given view. 109 | * 110 | * @param text The text displayed. 111 | * @param direction The direction of the popover. 112 | * @param maxWidth The maximum width of the popover. If the popover won't fit in the given space, this will be overridden. 113 | * @param view The view that will hold the popover. 114 | * @param frame The originating frame. The popover's arrow will point to the center of this frame. 115 | * @param interval The time interval that determines when the poptip will self-dismiss 116 | */ 117 | - (void)showText:(NSString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame duration:(NSTimeInterval)interval; 118 | 119 | /** Show the popover 120 | * 121 | * Shows an animated popover in a given view, from a given rectangle. 122 | * The property isVisible will be set as YES as soon as the popover is added to the given view. 123 | * 124 | * @param text The attributed text displayed. 125 | * @param direction The direction of the popover. 126 | * @param maxWidth The maximum width of the popover. If the popover won't fit in the given space, this will be overridden. 127 | * @param view The view that will hold the popover. 128 | * @param frame The originating frame. The popover's arrow will point to the center of this frame. 129 | * @param interval The time interval that determines when the poptip will self-dismiss 130 | */ 131 | - (void)showAttributedText:(NSAttributedString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame duration:(NSTimeInterval)interval; 132 | 133 | /** Hide the popover 134 | * 135 | * Hides the popover and removes it from the view. 136 | * The property isVisible will be set to NO when the animation is complete and the popover is removed from the parent view. 137 | */ 138 | - (void)hide; 139 | 140 | /** Update the text 141 | * 142 | * Set the new text shown in the poptip 143 | * @param text The new text 144 | */ 145 | - (void)updateText:(NSString *)text; 146 | 147 | /** Makes the popover perform the action animation 148 | * 149 | * Makes the popover perform the action indefinitely. The action animation calls for the user's attention after the popover is shown 150 | */ 151 | - (void)startActionAnimation; 152 | 153 | /** Stops the popover action animation 154 | * 155 | * Stops the popover action animation. Does nothing if the popover wasn't animating in the first place. 156 | */ 157 | - (void)stopActionAnimation; 158 | 159 | /**----------------------------------------------------------------------------- 160 | * @name AMPopTip Properties 161 | * ----------------------------------------------------------------------------- 162 | */ 163 | 164 | /** Font 165 | * 166 | * Holds the UIFont used in the popover 167 | */ 168 | @property (nonatomic, strong) UIFont *font UI_APPEARANCE_SELECTOR; 169 | 170 | /** Text Color 171 | * 172 | * Holds the UIColor of the text 173 | */ 174 | @property (nonatomic, strong) UIColor *textColor UI_APPEARANCE_SELECTOR; 175 | 176 | /** Text Alignment 177 | * Holds the NSTextAlignment of the text 178 | */ 179 | @property (nonatomic, assign) NSTextAlignment textAlignment UI_APPEARANCE_SELECTOR; 180 | 181 | /** Popover Background Color 182 | * 183 | * Holds the UIColor for the popover's background 184 | */ 185 | @property (nonatomic, strong) UIColor *popoverColor UI_APPEARANCE_SELECTOR; 186 | 187 | /** Popover Border Color 188 | * 189 | * Holds the UIColor for the popover's bordedr 190 | */ 191 | @property (nonatomic, strong) UIColor *borderColor UI_APPEARANCE_SELECTOR; 192 | 193 | /** Popover Border Width 194 | * 195 | * Holds the width for the popover's border 196 | */ 197 | @property (nonatomic, assign) CGFloat borderWidth UI_APPEARANCE_SELECTOR; 198 | 199 | /** Popover border radius 200 | * 201 | * Holds the CGFloat with the popover's border radius 202 | */ 203 | @property (nonatomic, assign) CGFloat radius UI_APPEARANCE_SELECTOR; 204 | 205 | /** Rounded popover 206 | * 207 | * Holds the BOOL that determines wether the popover is rounded. If set to YES the radius will equal frame.height / 2 208 | */ 209 | @property (nonatomic, assign, getter=isRounded) BOOL rounded UI_APPEARANCE_SELECTOR; 210 | 211 | /** Offset from the origin 212 | * 213 | * Holds the offset between the popover and origin 214 | */ 215 | @property (nonatomic, assign) CGFloat offset UI_APPEARANCE_SELECTOR; 216 | 217 | /** Text Padding 218 | * 219 | * Holds the CGFloat with the padding used for the inner text 220 | */ 221 | @property (nonatomic, assign) CGFloat padding UI_APPEARANCE_SELECTOR; 222 | 223 | /** Text EdgeInsets 224 | * 225 | * Holds the insets setting for padding different direction 226 | */ 227 | @property (nonatomic, assign) UIEdgeInsets edgeInsets UI_APPEARANCE_SELECTOR; 228 | 229 | /** Arrow size 230 | * 231 | * Holds the CGSize with the width and height of the arrow 232 | */ 233 | @property (nonatomic, assign) CGSize arrowSize UI_APPEARANCE_SELECTOR; 234 | 235 | /** Revealing Animation time 236 | * 237 | * Holds the NSTimeInterval with the duration of the revealing animation 238 | */ 239 | @property (nonatomic, assign) NSTimeInterval animationIn UI_APPEARANCE_SELECTOR; 240 | 241 | /** Disappearing Animation time 242 | * 243 | * Holds the NSTimeInterval with the duration of the disappearing animation 244 | */ 245 | @property (nonatomic, assign) NSTimeInterval animationOut UI_APPEARANCE_SELECTOR; 246 | 247 | /** Revealing Animation delay 248 | * 249 | * Holds the NSTimeInterval with the delay of the revealing animation 250 | */ 251 | @property (nonatomic, assign) NSTimeInterval delayIn UI_APPEARANCE_SELECTOR; 252 | 253 | /** Disappearing Animation delay 254 | * 255 | * Holds the NSTimeInterval with the delay of the disappearing animation 256 | */ 257 | @property (nonatomic, assign) NSTimeInterval delayOut UI_APPEARANCE_SELECTOR; 258 | 259 | /** Entrance animation type 260 | * 261 | * Holds the enum with the type of entrance animation (triggered once the popover is shown) 262 | */ 263 | @property (nonatomic, assign) AMPopTipEntranceAnimation entranceAnimation UI_APPEARANCE_SELECTOR; 264 | 265 | /** Action animation type 266 | * 267 | * Holds the enum with the type of action animation (triggered once the popover is shown) 268 | */ 269 | @property (nonatomic, assign) AMPopTipActionAnimation actionAnimation UI_APPEARANCE_SELECTOR; 270 | 271 | /** Offset for the float action animation 272 | * 273 | * Holds the offset between the popover initial and ending state during the float action animation 274 | */ 275 | @property (nonatomic, assign) CGFloat actionFloatOffset UI_APPEARANCE_SELECTOR; 276 | 277 | /** Offset for the float action animation 278 | * 279 | * Holds the offset between the popover initial and ending state during the float action animation 280 | */ 281 | @property (nonatomic, assign) CGFloat actionBounceOffset UI_APPEARANCE_SELECTOR; 282 | 283 | /** Offset for the pulse action animation 284 | * 285 | * Holds the offset in the popover size during the pulse action animation 286 | */ 287 | @property (nonatomic, assign) CGFloat actionPulseOffset UI_APPEARANCE_SELECTOR; 288 | 289 | /** Action Animation time 290 | * 291 | * Holds the NSTimeInterval with the duration of the action animation 292 | */ 293 | @property (nonatomic, assign) NSTimeInterval actionAnimationIn UI_APPEARANCE_SELECTOR; 294 | 295 | /** Action Animation stop time 296 | * 297 | * Holds the NSTimeInterval with the duration of the action stop animation 298 | */ 299 | @property (nonatomic, assign) NSTimeInterval actionAnimationOut UI_APPEARANCE_SELECTOR; 300 | 301 | /** Action Animation delay 302 | * 303 | * Holds the NSTimeInterval with the delay of the action animation 304 | */ 305 | @property (nonatomic, assign) NSTimeInterval actionDelayIn UI_APPEARANCE_SELECTOR; 306 | 307 | /** Action Animation stop delay 308 | * 309 | * Holds the NSTimeInterval with the delay of the action animation stop 310 | */ 311 | @property (nonatomic, assign) NSTimeInterval actionDelayOut UI_APPEARANCE_SELECTOR; 312 | 313 | /** Margin from the left efge 314 | * 315 | * CGfloat value that determines the leftmost margin from the screen 316 | */ 317 | @property (nonatomic, assign) CGFloat edgeMargin UI_APPEARANCE_SELECTOR; 318 | 319 | /** The frame the poptip is pointing to 320 | * 321 | * Holds the CGrect with the rect the tip is pointing to 322 | */ 323 | @property (nonatomic, assign) CGRect fromFrame; 324 | 325 | /** Visibility 326 | * 327 | * Holds the readonly BOOL with the popover visiblity. The popover is considered visible as soon as 328 | * it's added as a subview, and invisible when the subview is removed from its parent. 329 | */ 330 | @property (nonatomic, assign, readonly) BOOL isVisible; 331 | 332 | /** Dismiss on tap 333 | * 334 | * A boolean value that determines whether the poptip is dismissed on tap. 335 | */ 336 | @property (nonatomic, assign) BOOL shouldDismissOnTap; 337 | 338 | /** Dismiss on tap outside 339 | * 340 | * A boolean value that determines whether to dismiss when tapping outside the popover. 341 | */ 342 | @property (nonatomic, assign) BOOL shouldDismissOnTapOutside; 343 | 344 | /** Tap handler 345 | * 346 | * A block that will be fired when the user taps the popover. 347 | */ 348 | @property (nonatomic, copy) void (^tapHandler)(); 349 | 350 | /** Dismiss handler 351 | * 352 | * A block that will be fired when the popover appears. 353 | */ 354 | @property (nonatomic, copy) void (^appearHandler)(); 355 | 356 | /** Dismiss handler 357 | * 358 | * A block that will be fired when the popover is dismissed. 359 | */ 360 | @property (nonatomic, copy) void (^dismissHandler)(); 361 | 362 | /** Entrance animation 363 | * 364 | * A block block that handles the entrance animation of the poptip. Should be provided 365 | * when using a AMPopTipActionAnimationCustom entrance animation type. 366 | * Please note that the poptip will be automatically added as a subview before firing the block 367 | * Remember to call the completion block provided 368 | */ 369 | @property (nonatomic, copy) void (^entranceAnimationHandler)(void (^completion)(void)); 370 | 371 | /** Arrow position 372 | * 373 | * The CGPoint originating the arrow. Read only. 374 | */ 375 | @property (nonatomic, readonly) CGPoint arrowPosition; 376 | 377 | /** Container View 378 | * 379 | * A read only reference to the view containing the poptip 380 | */ 381 | @property (nonatomic, weak, readonly) UIView *containerView; 382 | 383 | /** Direction 384 | * 385 | * The direction from which the poptip is shown. Read only. 386 | */ 387 | @property (nonatomic, assign, readonly) AMPopTipDirection direction; 388 | 389 | @end 390 | -------------------------------------------------------------------------------- /TimeScrubber/PopTip/AMPopTip.m: -------------------------------------------------------------------------------- 1 | // 2 | // AMPopTip.m 3 | // PopTipDemo 4 | // 5 | // Created by Andrea Mazzini on 11/07/14. 6 | // Copyright (c) 2014 Fancy Pixel. All rights reserved. 7 | // 8 | 9 | #import "AMPopTip.h" 10 | #import "AMPopTipDefaults.h" 11 | #import "AMPopTip+Draw.h" 12 | #import "AMPopTip+Entrance.h" 13 | #import "AMPopTip+Animation.h" 14 | 15 | @interface AMPopTip() 16 | 17 | @property (nonatomic, strong) NSString *text; 18 | @property (nonatomic, strong) NSAttributedString *attributedText; 19 | @property (nonatomic, strong) NSMutableParagraphStyle *paragraphStyle; 20 | @property (nonatomic, strong) UITapGestureRecognizer *gestureRecognizer; 21 | @property (nonatomic, strong) UITapGestureRecognizer *removeGesture; 22 | @property (nonatomic, strong) NSTimer *dismissTimer; 23 | @property (nonatomic, weak, readwrite) UIView *containerView; 24 | @property (nonatomic, assign, readwrite) AMPopTipDirection direction; 25 | @property (nonatomic, assign, readwrite) CGPoint arrowPosition; 26 | @property (nonatomic, assign, readwrite) BOOL isVisible; 27 | @property (nonatomic, assign) CGRect textBounds; 28 | @property (nonatomic, assign) CGFloat maxWidth; 29 | 30 | @end 31 | 32 | @implementation AMPopTip 33 | 34 | + (instancetype)popTip { 35 | return [[AMPopTip alloc] init]; 36 | } 37 | 38 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 39 | self = [super initWithCoder:aDecoder]; 40 | if (self) { 41 | [self commonInit]; 42 | } 43 | return self; 44 | } 45 | 46 | - (instancetype)initWithFrame:(CGRect)ignoredFrame { 47 | self = [super initWithFrame:CGRectZero]; 48 | if (self) { 49 | [self commonInit]; 50 | } 51 | return self; 52 | } 53 | 54 | - (instancetype)init { 55 | self = [super initWithFrame:CGRectZero]; 56 | if (self) { 57 | [self commonInit]; 58 | } 59 | return self; 60 | } 61 | 62 | - (void)commonInit { 63 | _paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 64 | _textAlignment = NSTextAlignmentCenter; 65 | _font = kDefaultFont; 66 | _textColor = kDefaultTextColor; 67 | _popoverColor = kDefaultBackgroundColor; 68 | _borderColor = kDefaultBorderColor; 69 | _borderWidth = kDefaultBorderWidth; 70 | _radius = kDefaultRadius; 71 | _padding = kDefaultPadding; 72 | _arrowSize = kDefaultArrowSize; 73 | _animationIn = kDefaultAnimationIn; 74 | _animationOut = kDefaultAnimationOut; 75 | _isVisible = NO; 76 | _shouldDismissOnTapOutside = YES; 77 | _edgeMargin = kDefaultEdgeMargin; 78 | _edgeInsets = kDefaultEdgeInsets; 79 | _rounded = NO; 80 | _offset = kDefaultOffset; 81 | _entranceAnimation = AMPopTipEntranceAnimationScale; 82 | _actionAnimation = AMPopTipActionAnimationNone; 83 | _actionFloatOffset = kDefaultFloatOffset; 84 | _actionBounceOffset = kDefaultBounceOffset; 85 | _actionPulseOffset = kDefaultPulseOffset; 86 | _actionAnimationIn = kDefaultBounceAnimationIn; 87 | _actionAnimationOut = kDefaultBounceAnimationOut; 88 | _removeGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removeGestureHandler)]; 89 | } 90 | 91 | - (void)layoutSubviews { 92 | [self setup]; 93 | } 94 | 95 | - (void)setup { 96 | if (self.direction == AMPopTipDirectionLeft) { 97 | self.maxWidth = MIN(self.maxWidth, self.fromFrame.origin.x - self.padding * 2 - self.edgeInsets.left - self.edgeInsets.right - self.arrowSize.width); 98 | } 99 | if (self.direction == AMPopTipDirectionRight) { 100 | self.maxWidth = MIN(self.maxWidth, self.containerView.bounds.size.width - self.fromFrame.origin.x - self.fromFrame.size.width - self.padding * 2 - self.edgeInsets.left - self.edgeInsets.right - self.arrowSize.width); 101 | } 102 | 103 | if (self.text != nil) { 104 | self.textBounds = [self.text boundingRectWithSize:(CGSize){self.maxWidth, DBL_MAX } 105 | options:NSStringDrawingUsesLineFragmentOrigin 106 | attributes:@{NSFontAttributeName: self.font} 107 | context:nil]; 108 | } else if (self.attributedText != nil) { 109 | self.textBounds = [self.attributedText boundingRectWithSize:(CGSize){self.maxWidth, DBL_MAX } 110 | options:NSStringDrawingUsesLineFragmentOrigin 111 | context:nil]; 112 | } 113 | 114 | _textBounds.origin = (CGPoint){self.padding + self.edgeInsets.left, self.padding + self.edgeInsets.top}; 115 | 116 | CGRect frame = CGRectZero; 117 | float offset = self.offset * ((self.direction == AMPopTipDirectionUp || self.direction == AMPopTipDirectionLeft || self.direction == AMPopTipDirectionNone) ? -1 : 1); 118 | 119 | if (self.direction == AMPopTipDirectionUp || self.direction == AMPopTipDirectionDown) { 120 | frame.size = (CGSize){self.textBounds.size.width + self.padding * 2.0 + self.edgeInsets.left + self.edgeInsets.right, self.textBounds.size.height + self.padding * 2.0 + self.edgeInsets.top + self.edgeInsets.bottom + self.arrowSize.height}; 121 | 122 | CGFloat x = self.fromFrame.origin.x + self.fromFrame.size.width / 2 - frame.size.width / 2; 123 | if (x < 0) { x = self.edgeMargin; } 124 | if (x + frame.size.width > self.containerView.bounds.size.width) { x = self.containerView.bounds.size.width - frame.size.width - self.edgeMargin; } 125 | if (self.direction == AMPopTipDirectionDown) { 126 | frame.origin = (CGPoint){ x, self.fromFrame.origin.y + self.fromFrame.size.height }; 127 | } else { 128 | frame.origin = (CGPoint){ x, self.fromFrame.origin.y - frame.size.height}; 129 | } 130 | 131 | frame.origin.y += offset; 132 | 133 | } else if (self.direction == AMPopTipDirectionLeft || self.direction == AMPopTipDirectionRight) { 134 | frame.size = (CGSize){ self.textBounds.size.width + self.padding * 2.0 + self.edgeInsets.left + self.edgeInsets.right + self.arrowSize.height, self.textBounds.size.height + self.padding * 2.0 + self.edgeInsets.top + self.edgeInsets.bottom}; 135 | 136 | CGFloat x = 0; 137 | if (self.direction == AMPopTipDirectionLeft) { 138 | x = self.fromFrame.origin.x - frame.size.width; 139 | } 140 | if (self.direction == AMPopTipDirectionRight) { 141 | x = self.fromFrame.origin.x + self.fromFrame.size.width; 142 | } 143 | 144 | x += offset; 145 | 146 | CGFloat y = self.fromFrame.origin.y + self.fromFrame.size.height / 2 - frame.size.height / 2; 147 | 148 | if (y < 0) { y = self.edgeMargin; } 149 | if (y + frame.size.height > self.containerView.bounds.size.height) { y = self.containerView.bounds.size.height - frame.size.height - self.edgeMargin; } 150 | frame.origin = (CGPoint){ x, y }; 151 | } else { 152 | frame.size = (CGSize){ self.textBounds.size.width + self.padding * 2.0 + self.edgeInsets.left + self.edgeInsets.right, self.textBounds.size.height + self.padding * 2.0 + self.edgeInsets.top + self.edgeInsets.bottom }; 153 | frame.origin = (CGPoint){ CGRectGetMidX(self.fromFrame) - frame.size.width / 2, CGRectGetMidY(self.fromFrame) - frame.size.height / 2 + offset }; 154 | } 155 | 156 | frame.size = (CGSize){ frame.size.width + self.borderWidth * 2, frame.size.height + self.borderWidth * 2 }; 157 | 158 | switch (self.direction) { 159 | case AMPopTipDirectionNone: { 160 | self.arrowPosition = CGPointZero; 161 | self.layer.anchorPoint = (CGPoint){ 0.5, 0.5 }; 162 | self.layer.position = (CGPoint){ CGRectGetMidX(self.fromFrame), CGRectGetMidY(self.fromFrame) }; 163 | break; 164 | } 165 | case AMPopTipDirectionDown: { 166 | self.arrowPosition = (CGPoint){ 167 | self.fromFrame.origin.x + self.fromFrame.size.width / 2 - frame.origin.x, 168 | self.fromFrame.origin.y + self.fromFrame.size.height - frame.origin.y + offset 169 | }; 170 | CGFloat anchor = self.arrowPosition.x / frame.size.width; 171 | _textBounds.origin = (CGPoint){ self.textBounds.origin.x, self.textBounds.origin.y + self.arrowSize.height }; 172 | self.layer.anchorPoint = (CGPoint){ anchor, 0 }; 173 | self.layer.position = (CGPoint){ self.layer.position.x + frame.size.width * anchor, self.layer.position.y - frame.size.height / 2 }; 174 | 175 | break; 176 | } 177 | case AMPopTipDirectionUp: { 178 | self.arrowPosition = (CGPoint){ 179 | self.fromFrame.origin.x + self.fromFrame.size.width / 2 - frame.origin.x, 180 | frame.size.height 181 | }; 182 | CGFloat anchor = self.arrowPosition.x / frame.size.width; 183 | self.layer.anchorPoint = (CGPoint){ anchor, 1 }; 184 | self.layer.position = (CGPoint){ self.layer.position.x + frame.size.width * anchor, self.layer.position.y + frame.size.height / 2 }; 185 | 186 | break; 187 | } 188 | case AMPopTipDirectionLeft: { 189 | self.arrowPosition = (CGPoint){ 190 | self.fromFrame.origin.x - frame.origin.x + offset, 191 | self.fromFrame.origin.y + self.fromFrame.size.height / 2 - frame.origin.y 192 | }; 193 | CGFloat anchor = self.arrowPosition.y / frame.size.height; 194 | self.layer.anchorPoint = (CGPoint){ 1, anchor }; 195 | self.layer.position = (CGPoint){ self.layer.position.x - frame.size.width / 2, self.layer.position.y + frame.size.height * anchor }; 196 | 197 | break; 198 | } 199 | case AMPopTipDirectionRight: { 200 | self.arrowPosition = (CGPoint){ 201 | self.fromFrame.origin.x + self.fromFrame.size.width - frame.origin.x + offset, 202 | self.fromFrame.origin.y + self.fromFrame.size.height / 2 - frame.origin.y 203 | }; 204 | _textBounds.origin = (CGPoint){ self.textBounds.origin.x + self.arrowSize.height, self.textBounds.origin.y }; 205 | CGFloat anchor = self.arrowPosition.y / frame.size.height; 206 | self.layer.anchorPoint = (CGPoint){ 0, anchor }; 207 | self.layer.position = (CGPoint){ self.layer.position.x + frame.size.width / 2, self.layer.position.y + frame.size.height * anchor }; 208 | 209 | break; 210 | } 211 | } 212 | 213 | self.backgroundColor = [UIColor clearColor]; 214 | self.frame = frame; 215 | 216 | self.gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 217 | [self addGestureRecognizer:self.gestureRecognizer]; 218 | [self setNeedsDisplay]; 219 | } 220 | 221 | - (void)handleTap:(UITapGestureRecognizer *)gesture { 222 | if (self.shouldDismissOnTap) { 223 | [self hide]; 224 | } 225 | if (self.tapHandler) { 226 | self.tapHandler(); 227 | } 228 | } 229 | 230 | - (void)removeGestureHandler { 231 | if (self.shouldDismissOnTapOutside) { 232 | [self hide]; 233 | } 234 | } 235 | 236 | - (void)drawRect:(CGRect)rect { 237 | 238 | if (self.isRounded) { 239 | BOOL showHorizontally = self.direction == AMPopTipDirectionLeft || self.direction == AMPopTipDirectionRight; 240 | self.radius = (self.frame.size.height - (showHorizontally ? 0 : self.arrowSize.height)) / 2 ; 241 | } 242 | 243 | UIBezierPath *path = [self pathWithRect:rect direction:self.direction]; 244 | 245 | [self.popoverColor setFill]; 246 | [path fill]; 247 | 248 | [self.borderColor setStroke]; 249 | [path setLineWidth:self.borderWidth]; 250 | [path stroke]; 251 | 252 | self.paragraphStyle.alignment = self.textAlignment; 253 | 254 | NSDictionary *titleAttributes = @{ 255 | NSParagraphStyleAttributeName: self.paragraphStyle, 256 | NSFontAttributeName: self.font, 257 | NSForegroundColorAttributeName: self.textColor 258 | }; 259 | 260 | if (self.text != nil) { 261 | [self.text drawInRect:self.textBounds withAttributes:titleAttributes]; 262 | } else if (self.attributedText != nil) { 263 | [self.attributedText drawInRect:self.textBounds]; 264 | } 265 | } 266 | 267 | - (void)show { 268 | [self setNeedsLayout]; 269 | __weak AMPopTip *weakSelf = self; 270 | [self performEntranceAnimation:^{ 271 | weakSelf.isVisible = YES; 272 | [self.containerView addGestureRecognizer:self.removeGesture]; 273 | if (self.appearHandler) { 274 | self.appearHandler(); 275 | } 276 | if (self.actionAnimation != AMPopTipActionAnimationNone) { 277 | [self startActionAnimation]; 278 | } 279 | }]; 280 | } 281 | 282 | - (void)showText:(NSString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame { 283 | self.attributedText = nil; 284 | self.text = text; 285 | self.accessibilityLabel = text; 286 | self.direction = direction; 287 | self.containerView = view; 288 | self.maxWidth = maxWidth; 289 | _fromFrame = frame; 290 | 291 | [self show]; 292 | } 293 | 294 | - (void)showAttributedText:(NSAttributedString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame { 295 | self.text = nil; 296 | self.attributedText = text; 297 | self.accessibilityLabel = [text string]; 298 | self.direction = direction; 299 | self.containerView = view; 300 | self.maxWidth = maxWidth; 301 | _fromFrame = frame; 302 | 303 | [self show]; 304 | } 305 | 306 | - (void)setFromFrame:(CGRect)fromFrame { 307 | _fromFrame = fromFrame; 308 | [self setup]; 309 | } 310 | 311 | - (void)showText:(NSString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame duration:(NSTimeInterval)interval { 312 | [self showText:text direction:direction maxWidth:maxWidth inView:view fromFrame:frame]; 313 | [self.dismissTimer invalidate]; 314 | if (interval > 0) { 315 | self.dismissTimer = [NSTimer scheduledTimerWithTimeInterval:interval 316 | target:self 317 | selector:@selector(hide) 318 | userInfo:nil 319 | repeats:NO]; 320 | } 321 | } 322 | 323 | - (void)showAttributedText:(NSAttributedString *)text direction:(AMPopTipDirection)direction maxWidth:(CGFloat)maxWidth inView:(UIView *)view fromFrame:(CGRect)frame duration:(NSTimeInterval)interval { 324 | [self showAttributedText:text direction:direction maxWidth:maxWidth inView:view fromFrame:frame]; 325 | [self.dismissTimer invalidate]; 326 | if(interval > 0){ 327 | self.dismissTimer = [NSTimer scheduledTimerWithTimeInterval:interval 328 | target:self 329 | selector:@selector(hide) 330 | userInfo:nil 331 | repeats:NO]; 332 | } 333 | } 334 | 335 | - (void)hide { 336 | [self.dismissTimer invalidate]; 337 | self.dismissTimer = nil; 338 | [self.containerView removeGestureRecognizer:self.removeGesture]; 339 | if (self.superview) { 340 | self.transform = CGAffineTransformIdentity; 341 | [UIView animateWithDuration:self.animationOut delay:self.delayOut options:(UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState) animations:^{ 342 | self.transform = CGAffineTransformMakeScale(0.000001, 0.000001); 343 | } completion:^(BOOL finished) { 344 | [self stopActionAnimation]; 345 | [self removeFromSuperview]; 346 | [self.layer removeAllAnimations]; 347 | self.transform = CGAffineTransformIdentity; 348 | self->_isVisible = NO; 349 | if (self.dismissHandler) { 350 | self.dismissHandler(); 351 | } 352 | }]; 353 | } 354 | } 355 | 356 | - (void)updateText:(NSString *)text { 357 | self.text = text; 358 | self.accessibilityLabel = text; 359 | [self setNeedsLayout]; 360 | } 361 | 362 | - (void)startActionAnimation { 363 | [self performActionAnimation]; 364 | } 365 | 366 | - (void)stopActionAnimation { 367 | [self dismissActionAnimation]; 368 | } 369 | 370 | - (void)setShouldDismissOnTapOutside:(BOOL)shouldDismissOnTapOutside { 371 | _shouldDismissOnTapOutside = shouldDismissOnTapOutside; 372 | _removeGesture.enabled = shouldDismissOnTapOutside; 373 | } 374 | 375 | - (void)dealloc { 376 | [_removeGesture removeTarget:self action:@selector(removeGestureHandler)]; 377 | _removeGesture = nil; 378 | } 379 | 380 | @end 381 | -------------------------------------------------------------------------------- /TimeScrubber/ScrollWithDates.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollWithDates.m 3 | // TimeScrubber 4 | // 5 | // Created by Vladyslav Semecnhenko on 6/9/15. 6 | // Copyright (c) 2015 Vladyslav Semecnhenko. All rights reserved. 7 | // 8 | 9 | 10 | #import "ScrollWithDates.h" 11 | #import "TimeScrubberBorder.h" 12 | #import "TimeScrubberLabel.h" 13 | 14 | @interface ScrollWithDates () 15 | { 16 | NSMutableArray *mArrayWithDates; 17 | NSMutableArray *mArrayWithViews; 18 | NSMutableArray *mArrayWithOffset; 19 | NSMutableArray *mArrayWithFinalDatesStrings; 20 | 21 | int segments; 22 | float oneSegmentTime; 23 | BOOL isNeedHoursL; 24 | int timeDelta; 25 | int correctedSegments; 26 | 27 | int count; 28 | 29 | CGPoint currentSelectedPoint; 30 | } 31 | 32 | @end 33 | 34 | @implementation ScrollWithDates 35 | 36 | - (id)initWithFrame:(CGRect)frame startDate:(NSDate *)startDate endDate:(NSDate *)endDate segments:(int)segmentsI oneSegmentTime:(float)oneSegmentTimeI coefficient:(int)coef 37 | { 38 | self = [super initWithFrame:frame]; 39 | 40 | if (self) 41 | { 42 | mArrayWithDates = [NSMutableArray array]; 43 | mArrayWithViews = [NSMutableArray array]; 44 | mArrayWithOffset = [NSMutableArray array]; 45 | mArrayWithFinalDatesStrings = [NSMutableArray array]; 46 | 47 | segments = segmentsI; 48 | isNeedHoursL = YES; 49 | self.endDateInitial = endDate; 50 | self.startDateInitial = startDate; 51 | self.coefficient = coef; 52 | oneSegmentTime = oneSegmentTimeI; 53 | timeDelta = endDate.timeIntervalSinceNow - startDate.timeIntervalSinceNow; 54 | correctedSegments = (timeDelta) / oneSegmentTime; 55 | count = 0; 56 | 57 | [self generateStartDates]; 58 | } 59 | 60 | return self; 61 | } 62 | 63 | - (void)updateWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate segments:(int)segmentsI isNeedHours:(BOOL)isNeedHours coefficient:(int)coef animateDirection:(int)direction selectedPoint:(CGPoint)selectedPoint 64 | { 65 | if (direction == 1) // zoom out 66 | { 67 | [self zoomOutAnimationWithPoint:selectedPoint]; 68 | } 69 | else // zoom in 70 | { 71 | [self zoomInAnimationWithPoint:selectedPoint]; 72 | } 73 | 74 | [mArrayWithDates removeAllObjects]; 75 | [mArrayWithOffset removeAllObjects]; 76 | [mArrayWithFinalDatesStrings removeAllObjects]; 77 | 78 | segments = segmentsI; 79 | isNeedHoursL = isNeedHours; 80 | self.endDateInitial = endDate; 81 | self.startDateInitial = startDate; 82 | self.coefficient = coef; 83 | timeDelta = endDate.timeIntervalSinceNow - startDate.timeIntervalSinceNow; 84 | correctedSegments = timeDelta / oneSegmentTime; 85 | currentSelectedPoint = selectedPoint; 86 | 87 | [self generateStartDates]; 88 | } 89 | 90 | - (void)generateStartDates 91 | { 92 | NSDateFormatter *dateFormatters = [[NSDateFormatter alloc] init]; 93 | [dateFormatters setDateFormat:@"mm"]; 94 | 95 | if (isNeedHoursL) 96 | { 97 | for (int i = 1; i <= correctedSegments; i++) 98 | { 99 | if (self.coefficient == 0) // by hours 100 | { 101 | float deltaForDate = 0; 102 | 103 | NSDate *tempDate = [NSDate dateWithTimeInterval:(self.endDateInitial.timeIntervalSinceNow - oneSegmentTime * i) sinceDate:self.endDateInitial]; 104 | 105 | NSString *tempString = [dateFormatters stringFromDate:tempDate]; 106 | deltaForDate = 60 * [tempString intValue]; 107 | 108 | float dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 109 | float datePerPixel = self.bounds.size.width / dateDifference; 110 | float finalValue = datePerPixel * deltaForDate; 111 | 112 | [mArrayWithOffset addObject:[NSNumber numberWithFloat:finalValue]]; 113 | [mArrayWithDates addObject:[NSDate dateWithTimeInterval:-deltaForDate sinceDate:tempDate]]; 114 | } 115 | else if (self.coefficient == 1) 116 | { 117 | float deltaForDate = 0; 118 | 119 | NSDate *tempDate = [NSDate dateWithTimeInterval:(self.endDateInitial.timeIntervalSinceNow - oneSegmentTime * i) sinceDate:self.endDateInitial]; 120 | 121 | NSString *tempString = [dateFormatters stringFromDate:tempDate]; 122 | // get difference 15 mins 123 | int mins = [tempString intValue]; 124 | float diff = mins % 30; 125 | 126 | deltaForDate = 60 * diff; 127 | 128 | float dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 129 | float datePerPixel = self.bounds.size.width / dateDifference; 130 | float finalValue = datePerPixel * deltaForDate; 131 | 132 | [mArrayWithOffset addObject:[NSNumber numberWithFloat:finalValue]]; 133 | [mArrayWithDates addObject:[NSDate dateWithTimeInterval:-deltaForDate sinceDate:tempDate]]; 134 | } 135 | else if (self.coefficient == 2) 136 | { 137 | float deltaForDate = 0; 138 | 139 | NSDate *tempDate = [NSDate dateWithTimeInterval:(self.endDateInitial.timeIntervalSinceNow - oneSegmentTime * i) sinceDate:self.endDateInitial]; 140 | 141 | NSString *tempString = [dateFormatters stringFromDate:tempDate]; 142 | // get difference 15 mins 143 | int mins = [tempString intValue]; 144 | float diff = mins % 15; 145 | 146 | deltaForDate = 60 * diff; 147 | 148 | float dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 149 | float datePerPixel = self.bounds.size.width / dateDifference; 150 | float finalValue = datePerPixel * deltaForDate; 151 | 152 | [mArrayWithOffset addObject:[NSNumber numberWithFloat:finalValue]]; 153 | [mArrayWithDates addObject:[NSDate dateWithTimeInterval:-deltaForDate sinceDate:tempDate]]; 154 | } 155 | } 156 | } 157 | else 158 | { 159 | for (int i = 0; i < segments; i++) 160 | { 161 | if (i == 0) 162 | { 163 | [mArrayWithDates addObject:self.startDateInitial]; 164 | } 165 | else if (i == segments-1) 166 | { 167 | // pre end date 168 | [mArrayWithDates addObject:[NSDate dateWithTimeInterval:((timeDelta / segments) * i - 0) sinceDate:self.startDateInitial]]; 169 | } 170 | else 171 | { 172 | [mArrayWithDates addObject:[NSDate dateWithTimeInterval:((timeDelta / segments-1) * i) - 0 sinceDate:self.startDateInitial]]; 173 | } 174 | 175 | [mArrayWithOffset addObject:[NSNumber numberWithFloat:0]]; 176 | } 177 | } 178 | 179 | [self createScroll]; 180 | } 181 | 182 | - (void)createScroll 183 | { 184 | NSDateFormatter *dateFormatters = [[NSDateFormatter alloc] init]; 185 | [dateFormatters setDateFormat:@"HH:mm"]; 186 | 187 | if (isNeedHoursL) 188 | { 189 | for (int i = 1; i <= correctedSegments; i++) 190 | { 191 | float deltaForDate = [[mArrayWithOffset objectAtIndex:correctedSegments-i] floatValue]; 192 | 193 | UIView *masterView = [[UIView alloc] initWithFrame:CGRectMake((self.frame.size.width / (correctedSegments+1)) * i - deltaForDate, self.frame.origin.y, 40, self.bounds.size.height)]; 194 | masterView.alpha = 0.0; 195 | TimeScrubberBorder *border = [[TimeScrubberBorder alloc] initWithFrame:CGRectMake(0, 20, 2.5, self.frame.size.height / 2)]; 196 | [masterView addSubview:border]; 197 | TimeScrubberLabel *label = [[TimeScrubberLabel alloc] initWithFrame:CGRectMake(0 - 20, 40, 40, 20)]; 198 | label.text = [dateFormatters stringFromDate:[mArrayWithDates objectAtIndex:correctedSegments-i]]; 199 | [masterView addSubview:label]; 200 | 201 | NSDateFormatter *dateFormattersTest = [[NSDateFormatter alloc] init]; 202 | [dateFormattersTest setDateFormat:@"HH"]; 203 | NSString *tempString = [dateFormattersTest stringFromDate:[mArrayWithDates objectAtIndex:correctedSegments-i]]; 204 | 205 | [mArrayWithFinalDatesStrings addObject:tempString]; 206 | [self addSubview:masterView]; 207 | [mArrayWithViews addObject:masterView]; 208 | 209 | [UIView animateWithDuration:0.5 animations:^{ 210 | masterView.alpha = 1.0; 211 | }]; 212 | } 213 | } 214 | else 215 | { 216 | for (int i = 0; i < segments; i++) 217 | { 218 | float deltaForDate = [[mArrayWithOffset objectAtIndex:i] floatValue]; 219 | 220 | UIView *masterView = [[UIView alloc] initWithFrame:CGRectMake((self.frame.size.width / segments) * i - deltaForDate, self.frame.origin.y, 40, self.bounds.size.height)]; 221 | masterView.alpha = 0.0; 222 | TimeScrubberBorder *border = [[TimeScrubberBorder alloc] initWithFrame:CGRectMake(0, 20, 2.5, self.frame.size.height / 2)]; 223 | [masterView addSubview:border]; 224 | TimeScrubberLabel *label = [[TimeScrubberLabel alloc] initWithFrame:CGRectMake(0 - 20, 40, 40, 20)]; 225 | label.text = [dateFormatters stringFromDate:[mArrayWithDates objectAtIndex:i]]; 226 | [masterView addSubview:label]; 227 | 228 | NSDateFormatter *dateFormattersTest = [[NSDateFormatter alloc] init]; 229 | [dateFormattersTest setDateFormat:@"HH"]; 230 | NSString *tempString = [dateFormattersTest stringFromDate:[mArrayWithDates objectAtIndex:i]]; 231 | 232 | [mArrayWithFinalDatesStrings addObject:tempString]; 233 | [self addSubview:masterView]; 234 | [mArrayWithViews addObject:masterView]; 235 | 236 | [UIView animateWithDuration:0.5 animations:^{ 237 | masterView.alpha = 1.0; 238 | }]; 239 | } 240 | } 241 | } 242 | 243 | - (void)createNewViewWithDate:(NSDate *)date isNeedMinutes:(BOOL)isNeedMinutes 244 | { 245 | float finalValue = 0; 246 | NSDate *finalDate; 247 | 248 | NSDateFormatter *dateFormatters = [[NSDateFormatter alloc] init]; 249 | [dateFormatters setDateFormat:@"HH"]; 250 | 251 | if (isNeedHoursL) 252 | { 253 | NSDateFormatter *dateFormatters2 = [[NSDateFormatter alloc] init]; 254 | [dateFormatters2 setDateFormat:@"mm"]; 255 | 256 | if (self.coefficient == 0) // by hours 257 | { 258 | float deltaForDate = 0; 259 | 260 | NSString *tempString = [dateFormatters2 stringFromDate:date]; 261 | deltaForDate = 60 * [tempString intValue]; 262 | count = deltaForDate; 263 | 264 | float dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 265 | float datePerPixel = self.bounds.size.width / dateDifference; 266 | finalValue = datePerPixel * deltaForDate; 267 | 268 | finalDate = [NSDate dateWithTimeInterval:-deltaForDate sinceDate:date]; 269 | } 270 | else if (self.coefficient == 1) 271 | { 272 | float deltaForDate = 0; 273 | 274 | NSString *tempString = [dateFormatters2 stringFromDate:date]; 275 | // get difference 30 mins 276 | int mins = [tempString intValue]; 277 | float diff = mins % 30; 278 | 279 | deltaForDate = 60 * diff; 280 | count = deltaForDate; 281 | 282 | float dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 283 | float datePerPixel = self.bounds.size.width / dateDifference; 284 | finalValue = datePerPixel * deltaForDate; 285 | 286 | finalDate = [NSDate dateWithTimeInterval:-deltaForDate sinceDate:date]; 287 | } 288 | else if (self.coefficient == 2) 289 | { 290 | float deltaForDate = 0; 291 | 292 | NSString *tempString = [dateFormatters2 stringFromDate:date]; 293 | // get difference 15 mins 294 | int mins = [tempString intValue]; 295 | float diff = mins % 15; 296 | 297 | deltaForDate = 60 * diff; 298 | count = deltaForDate; 299 | 300 | float dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow; 301 | float datePerPixel = self.bounds.size.width / dateDifference; 302 | finalValue = datePerPixel * deltaForDate; 303 | 304 | finalDate = [NSDate dateWithTimeInterval:-deltaForDate sinceDate:date]; 305 | } 306 | } 307 | 308 | UIView *masterView = [[UIView alloc] initWithFrame:CGRectMake(self.frame.size.width - 2.5 - finalValue, self.frame.origin.y, 40, self.bounds.size.height)]; 309 | masterView.alpha = 0.0; 310 | TimeScrubberBorder *border = [[TimeScrubberBorder alloc] initWithFrame:CGRectMake(0, 20, 2.5, self.frame.size.height / 2)]; 311 | [masterView addSubview:border]; 312 | TimeScrubberLabel *label = [[TimeScrubberLabel alloc] initWithFrame:CGRectMake(0 - 20, 40, 40, 20)]; 313 | 314 | if (isNeedHoursL) 315 | { 316 | if (isNeedMinutes) 317 | { 318 | [dateFormatters setDateFormat:@"HH:mm"]; 319 | label.text = [dateFormatters stringFromDate:finalDate]; 320 | } 321 | else 322 | { 323 | [dateFormatters setDateFormat:@"HH"]; 324 | label.text = [NSString stringWithFormat:@"%@:00",[dateFormatters stringFromDate:finalDate]]; 325 | } 326 | } 327 | else 328 | { 329 | [dateFormatters setDateFormat:@"HH:mm"]; 330 | label.text = [dateFormatters stringFromDate:date]; 331 | } 332 | 333 | [masterView addSubview:label]; 334 | 335 | [self addSubview:masterView]; 336 | [mArrayWithViews addObject:masterView]; 337 | 338 | [UIView animateWithDuration:1.0 animations:^{ 339 | masterView.alpha = 1.0; 340 | } completion:^(BOOL finished) { 341 | 342 | }]; 343 | 344 | } 345 | 346 | - (void)updateWithOffset:(float)offset 347 | { 348 | count++; 349 | 350 | if (count == oneSegmentTime) 351 | { 352 | if (oneSegmentTime > 3500) 353 | { 354 | [self createNewViewWithDate:[NSDate date] isNeedMinutes:NO]; 355 | } 356 | else 357 | { 358 | [self createNewViewWithDate:[NSDate date] isNeedMinutes:YES]; 359 | } 360 | } 361 | 362 | NSTimeInterval dateDifference = self.endDateInitial.timeIntervalSinceNow - self.startDateInitial.timeIntervalSinceNow + 2; // 2 - correction 363 | float datePerPixel = self.bounds.size.width / dateDifference; 364 | 365 | for (UIView *view in mArrayWithViews) 366 | { 367 | view.center = CGPointMake(view.center.x - datePerPixel, view.center.y); 368 | [self hitTest:view]; 369 | } 370 | } 371 | 372 | - (void)hitTest:(UIView *)view 373 | { 374 | CGPoint p = view.center; 375 | 376 | CGRect trackingFrame = CGRectMake(-10, 0, 25 , self.bounds.size.height); 377 | 378 | if (CGRectContainsPoint(trackingFrame, p)) 379 | { 380 | [UIView animateWithDuration:1.0 animations:^{ 381 | view.alpha = 0.0; 382 | } completion:^(BOOL finished) { 383 | if (!finished) 384 | { 385 | [view removeFromSuperview]; 386 | } 387 | }]; 388 | } 389 | } 390 | 391 | - (void)zoomOutAnimationWithPoint:(CGPoint)selectedPoint 392 | { 393 | for (UIView *view in mArrayWithViews) 394 | { 395 | [UIView animateWithDuration:0.5 animations:^{ 396 | if (view.center.x > selectedPoint.x) 397 | { 398 | view.center = CGPointMake(self.bounds.size.width, view.center.y); 399 | view.alpha = 0.0; 400 | } 401 | else 402 | { 403 | view.center = CGPointMake(0, view.center.y); 404 | view.alpha = 0.0; 405 | } 406 | } completion:^(BOOL finished) { 407 | if (finished) 408 | { 409 | [view removeFromSuperview]; 410 | } 411 | }]; 412 | } 413 | 414 | [mArrayWithViews removeAllObjects]; 415 | } 416 | 417 | - (void)zoomInAnimationWithPoint:(CGPoint)selectedPoint 418 | { 419 | for (UIView *view in mArrayWithViews) 420 | { 421 | [UIView animateWithDuration:0.5 animations:^{ 422 | view.center = CGPointMake(self.bounds.size.width * 0.5, view.center.y); 423 | view.alpha = 0.0; 424 | } completion:^(BOOL finished) { 425 | if (finished) 426 | { 427 | [view removeFromSuperview]; 428 | } 429 | }]; 430 | } 431 | 432 | [mArrayWithViews removeAllObjects]; 433 | } 434 | 435 | @end 436 | -------------------------------------------------------------------------------- /TimeScrubber/TimeScrubber.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeScrubber.m 3 | // TimeScrubberExample 4 | // 5 | // Created by Vladyslav Semenchenko on 08/12/14. 6 | // Copyright (c) 2014 Vladyslav Semenchenko. All rights reserved. 7 | // 8 | 9 | #import "TimeScrubber.h" 10 | #import "TimeScrubberControl.h" 11 | #import "TimeScrubberBorder.h" 12 | #import "TimeScrubberLabel.h" 13 | #import "ScrollWithDates.h" 14 | #import "ScrollWithVideoFragments.h" 15 | #import "AMPopTip.h" 16 | 17 | #define kUpdateTimerInterval 1 18 | 19 | @implementation TimeScrubber 20 | { 21 | //helper sizes 22 | float selfHeight; 23 | float selfWidth; 24 | 25 | // helpers 26 | BOOL isTouchEnded; 27 | BOOL isDragging; 28 | BOOL isLongPressStart; 29 | BOOL isLongPressedFired; 30 | 31 | NSInteger secondsFromGMT; 32 | 33 | NSTimer *updateTimer; 34 | NSTimer *longPressTimer; 35 | 36 | ScrollWithDates *scrollWithDate; 37 | ScrollWithVideoFragments *scrollWithVideo; 38 | 39 | AMPopTip *popTip; 40 | 41 | NSDate *creationDate; 42 | 43 | int segments; 44 | float oneSegmentTime; 45 | int masterTimeDifference; 46 | 47 | int hoursInInSelectedInterval; 48 | 49 | CGPoint currentPoint; 50 | 51 | // offline view 52 | UIView *offlineView; 53 | } 54 | 55 | #pragma mark Init 56 | - (id)initWithFrame:(CGRect)frame withStartDate:(NSDate *)startDate endDate:(NSDate *)endDate segments:(int)segmentsI andVideoBlocks:(NSMutableArray *)videoBlocks 57 | { 58 | self = [super initWithFrame:frame]; 59 | 60 | if (self) 61 | { 62 | self.backgroundColor = [UIColor clearColor]; 63 | selfHeight = frame.size.height; 64 | selfWidth = frame.size.width; 65 | isTouchEnded = NO; 66 | isDragging = NO; 67 | 68 | _minimumValue = 0.0; 69 | _maximumValue = 24.0; 70 | _value = 24.0; 71 | segments = segmentsI; 72 | masterTimeDifference = fabs(round(endDate.timeIntervalSinceNow - startDate.timeIntervalSinceNow)); 73 | 74 | hoursInInSelectedInterval = masterTimeDifference / 3600; 75 | 76 | self.endDateInitial = endDate; 77 | creationDate = self.endDateInitial; 78 | 79 | if (videoBlocks != nil) 80 | { 81 | self.mArrayWithVideoFragments = videoBlocks; 82 | } 83 | 84 | self.startDateIntervalInitial = -masterTimeDifference; 85 | self.endDateIntervalInitial = 0; 86 | 87 | int coef = 0; 88 | 89 | if (segments / hoursInInSelectedInterval < 1.5) // by 1 hour 90 | { 91 | coef = 0; 92 | oneSegmentTime = (floor(hoursInInSelectedInterval / segments) + 1) * 3600; 93 | } 94 | else if (segments / hoursInInSelectedInterval < 2.5) // by 30 mins 95 | { 96 | coef = 1; 97 | oneSegmentTime = 60 * 30; 98 | } 99 | else if (segments / hoursInInSelectedInterval > 2.5) // by 15 mins 100 | { 101 | coef = 2; 102 | oneSegmentTime = 60 * 15; 103 | } 104 | 105 | scrollWithDate = [[ScrollWithDates alloc] initWithFrame:self.bounds startDate:[NSDate dateWithTimeIntervalSinceNow:self.startDateIntervalInitial] endDate:self.endDateInitial segments:segments oneSegmentTime:oneSegmentTime coefficient:coef]; 106 | scrollWithDate.userInteractionEnabled = NO; 107 | NSDate *tempDate = [NSDate dateWithTimeInterval:0 sinceDate:self.endDateInitial]; 108 | [scrollWithDate createNewViewWithDate:tempDate isNeedMinutes:YES]; 109 | [self addSubview:scrollWithDate]; 110 | 111 | scrollWithVideo = [[ScrollWithVideoFragments alloc] initWithFrame:CGRectMake(2.5, 0, self.bounds.size.width - 5, self.bounds.size.height) startDate:[NSDate dateWithTimeIntervalSinceNow:self.startDateIntervalInitial] endDate:self.endDateInitial]; 112 | scrollWithVideo.userInteractionEnabled = NO; 113 | scrollWithVideo.clipsToBounds = YES; 114 | [scrollWithVideo createSubviewsWithVideoFragments:self.mArrayWithVideoFragments]; 115 | [self addSubview:scrollWithVideo]; 116 | 117 | // compute current date based on self.value 118 | [self update]; 119 | 120 | self.thumbControl = [[TimeScrubberControl alloc] initWithFrame:CGRectMake(selfWidth - (selfHeight * 0.7) / 2, selfHeight / 2 - (selfHeight * 0.7) / 2, selfHeight * 0.7, selfHeight * 0.7)]; 121 | self.thumbControlStatic = [[TimeScrubberControl alloc] initWithFrame:CGRectMake(selfWidth - (selfHeight * 0.7) / 2, selfHeight / 2 - (selfHeight * 0.7) / 2, selfHeight * 0.7, selfHeight * 0.7)]; 122 | self.thumbControlStatic.outerColor = [UIColor colorWithRed:0.263 green:0.501 blue:0.935 alpha:1.000]; 123 | self.thumbControlStatic.innerColor = [UIColor whiteColor]; 124 | self.thumbControl.outerColor = [UIColor whiteColor]; 125 | self.thumbControl.innerColor = [UIColor colorWithRed:0.263 green:0.501 blue:0.935 alpha:1.000]; 126 | self.thumbControl.userInteractionEnabled = NO; 127 | self.thumbControlStatic.userInteractionEnabled = NO; 128 | self.thumbControlStatic.date = [NSDate date]; 129 | self.thumbControl.date = [NSDate date]; 130 | [self addSubview:self.thumbControl]; 131 | // [self addSubview:self.thumbControlStatic]; 132 | 133 | popTip = [AMPopTip popTip]; 134 | popTip.popoverColor = [UIColor colorWithRed:0.263 green:0.501 blue:0.935 alpha:1.000]; 135 | } 136 | 137 | return self; 138 | } 139 | 140 | - (id)initInOfflineModeWithRect:(CGRect)frame 141 | { 142 | self = [super initWithFrame:frame]; 143 | 144 | if (self) 145 | { 146 | self.backgroundColor = [UIColor clearColor]; 147 | selfHeight = frame.size.height; 148 | selfWidth = frame.size.width; 149 | isTouchEnded = NO; 150 | isDragging = NO; 151 | self.userInteractionEnabled = NO; 152 | _minimumValue = 0.0; 153 | _maximumValue = 24.0; 154 | _value = 24.0; 155 | } 156 | 157 | return self; 158 | } 159 | 160 | #pragma mark Drawning 161 | - (void)drawRect:(CGRect)rect 162 | { 163 | UIColor* color3 = [UIColor whiteColor]; 164 | 165 | UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(rect.origin.x, rect.size.height * 0.5 - 10, rect.size.width, 20)]; 166 | [color3 setFill]; 167 | [rectanglePath fill]; 168 | } 169 | 170 | #pragma mark User interactions 171 | - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 172 | { 173 | isTouchEnded = NO; 174 | isDragging = YES; 175 | isLongPressedFired = NO; 176 | isLongPressStart = YES; 177 | longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kUpdateTimerInterval target:self selector:@selector(handleLongPress) userInfo:nil repeats:NO]; 178 | 179 | CGPoint l = [touch locationInView:self]; 180 | currentPoint = l; 181 | if ([self markerHitTest:l]) 182 | { 183 | [self moveRedControl:l]; 184 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 185 | 186 | [popTip showText:[NSString stringWithFormat:@"%@", [NSDate dateWithTimeInterval:self.currentDateIntervalFixed sinceDate:self.endDateInitial]] direction:AMPopTipDirectionUp maxWidth:200 inView:self fromFrame:self.thumbControl.frame]; 187 | 188 | return YES; 189 | } else 190 | { 191 | return NO; 192 | } 193 | 194 | return NO; 195 | } 196 | 197 | - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 198 | { 199 | isLongPressStart = NO; 200 | [longPressTimer invalidate]; 201 | 202 | CGPoint p = [touch locationInView:self]; 203 | currentPoint = p; 204 | 205 | CGRect trackingFrame = self.bounds; 206 | 207 | if (!CGRectContainsPoint(trackingFrame, p)) 208 | { 209 | return NO; 210 | } 211 | 212 | [self moveRedControl:p]; 213 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 214 | 215 | if (!isLongPressedFired) 216 | { 217 | isLongPressStart = YES; 218 | longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kUpdateTimerInterval target:self selector:@selector(handleLongPress) userInfo:nil repeats:NO]; 219 | } 220 | 221 | return YES; 222 | } 223 | 224 | - (void)cancelTrackingWithEvent:(UIEvent *)event 225 | { 226 | [self handleLongPressFinished]; 227 | 228 | isTouchEnded = YES; 229 | isDragging = NO; 230 | 231 | isLongPressStart = NO; 232 | isLongPressedFired = NO; 233 | [longPressTimer invalidate]; 234 | longPressTimer = nil; 235 | 236 | [popTip hide]; 237 | 238 | [super cancelTrackingWithEvent:event]; 239 | } 240 | 241 | 242 | - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 243 | { 244 | if (isLongPressedFired) 245 | { 246 | [self handleLongPressFinished]; 247 | } 248 | 249 | isTouchEnded = YES; 250 | isDragging = NO; 251 | 252 | isLongPressStart = NO; 253 | isLongPressedFired = NO; 254 | [longPressTimer invalidate]; 255 | longPressTimer = nil; 256 | 257 | [popTip hide]; 258 | 259 | [self updateMarker]; 260 | 261 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 262 | [super endTrackingWithTouch:touch withEvent:event]; 263 | } 264 | 265 | #pragma mark UI updates from user interactions 266 | - (BOOL)markerHitTest:(CGPoint) point 267 | { 268 | if (point.x < self.bounds.origin.x || point.x > selfWidth) // x test 269 | { 270 | return NO; 271 | } 272 | 273 | return YES; 274 | } 275 | 276 | -(void)moveRedControl:(CGPoint)lastPoint 277 | { 278 | CGRect newFrameForControl = CGRectMake(lastPoint.x - self.thumbControl.frame.size.width / 2, self.thumbControl.frame.origin.y, self.thumbControl.bounds.size.width, self.thumbControl.bounds.size.height); 279 | self.thumbControl.frame =newFrameForControl; 280 | 281 | // update current value 282 | float oneValuePerPixel = self.maximumValue / selfWidth; 283 | self.value = lastPoint.x * oneValuePerPixel; 284 | 285 | if (lastPoint.x > selfWidth || lastPoint.x < 0) 286 | { 287 | 288 | } 289 | else 290 | { 291 | [self computeCurrentDate]; 292 | 293 | popTip.fromFrame = self.thumbControl.frame; 294 | } 295 | 296 | [self setNeedsDisplay]; 297 | } 298 | 299 | #pragma mark Operations with Date 300 | - (void)computeCurrentDate 301 | { 302 | if (!isLongPressedFired) 303 | { 304 | self.endDateInitial = [NSDate date]; 305 | self.endDateIntervalInitial = self.endDateInitial.timeIntervalSinceNow; 306 | 307 | self.startDateIntervalInitial = [[NSDate dateWithTimeInterval:-masterTimeDifference sinceDate:self.endDateInitial] timeIntervalSinceDate:self.endDateInitial]; 308 | 309 | float dateDifference = self.endDateIntervalInitial - self.startDateIntervalInitial; 310 | float onePart = dateDifference / self.maximumValue; 311 | 312 | self.currentDateInterval = self.startDateIntervalInitial + (onePart * self.value); 313 | 314 | [self getRealCurrentDate]; 315 | } 316 | else 317 | { 318 | float dateDifference = self.endDateIntervalInitial - self.startDateIntervalInitial; 319 | float onePart = dateDifference / self.maximumValue; 320 | 321 | self.currentDateInterval = self.startDateIntervalInitial + (onePart * self.value); 322 | 323 | [self getRealCurrentDate]; 324 | } 325 | } 326 | 327 | - (NSTimeInterval)getRealCurrentDate 328 | { 329 | if (!isLongPressedFired) 330 | { 331 | secondsFromGMT = [[NSTimeZone localTimeZone] secondsFromGMT]; 332 | self.currentDateIntervalFixed = self.currentDateInterval + secondsFromGMT; 333 | 334 | [popTip updateText:[NSString stringWithFormat:@"%@", [NSDate dateWithTimeInterval:self.currentDateIntervalFixed sinceDate:self.endDateInitial]]]; 335 | 336 | self.selectedDate = [NSDate dateWithTimeInterval:self.currentDateIntervalFixed sinceDate:self.endDateInitial]; 337 | 338 | return self.currentDateIntervalFixed; 339 | } 340 | else 341 | { 342 | secondsFromGMT = [[NSTimeZone localTimeZone] secondsFromGMT]; 343 | self.currentDateIntervalFixed = self.currentDateInterval + secondsFromGMT; 344 | 345 | [popTip updateText:[NSString stringWithFormat:@"%@", [NSDate dateWithTimeIntervalSinceNow:self.currentDateInterval + secondsFromGMT]]]; 346 | 347 | self.selectedDate = [NSDate dateWithTimeIntervalSinceNow:self.currentDateInterval + secondsFromGMT]; 348 | 349 | return self.currentDateInterval; 350 | } 351 | } 352 | 353 | #pragma mark Updates 354 | - (void)updateEnable:(BOOL)isEnabled 355 | { 356 | if (isEnabled) 357 | { 358 | updateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(update) userInfo:nil repeats:YES]; 359 | } 360 | else 361 | { 362 | [updateTimer invalidate]; 363 | updateTimer = nil; 364 | } 365 | } 366 | 367 | - (void)update 368 | { 369 | if (!isDragging) 370 | { 371 | [self computeCurrentDate]; 372 | 373 | float datePerPixel = selfWidth / masterTimeDifference; 374 | [scrollWithDate updateWithOffset:0.25 * datePerPixel]; 375 | [scrollWithVideo updateWithOffset:creationDate.timeIntervalSinceNow]; 376 | } 377 | } 378 | 379 | - (void)updateMarker 380 | { 381 | float dateDifference = self.endDateIntervalInitial - self.startDateIntervalInitial; 382 | float datePerPixel = selfWidth / dateDifference; 383 | 384 | float x1 = self.selectedDate.timeIntervalSinceNow - secondsFromGMT; 385 | 386 | float delta = INFINITY; 387 | 388 | for (NSDictionary *dict in self.mArrayWithVideoFragments) 389 | { 390 | float x22 = [[dict objectForKey:@"2"] floatValue] - fabs(creationDate.timeIntervalSinceNow); 391 | 392 | if (fabs(x22) < fabs(x1)) 393 | { 394 | if (fabs(x1) - fabs(x22) < delta) 395 | { 396 | delta = fabs(x1) - fabs(x22); 397 | } 398 | } 399 | } 400 | 401 | if (delta == INFINITY) 402 | { 403 | delta = 0; 404 | } 405 | 406 | self.currentDateInterval += delta; 407 | self.selectedDate = [NSDate dateWithTimeIntervalSinceNow:self.currentDateInterval + secondsFromGMT]; 408 | 409 | NSLog(@"Current selected date = %@", self.selectedDate); 410 | 411 | float x1dif = x1 + delta - self.startDateIntervalInitial; 412 | float x1pos = x1dif * datePerPixel; 413 | 414 | [UIView animateWithDuration:0.1 animations:^{ 415 | self.thumbControl.frame = CGRectMake(x1pos - self.thumbControl.frame.size.width * 0.5, self.thumbControl.frame.origin.y, self.thumbControl.frame.size.width, self.thumbControl.frame.size.height); 416 | }]; 417 | } 418 | 419 | - (void)updateStaticMarker 420 | { 421 | float endDateInterval = self.endDateIntervalInitial; 422 | 423 | if (0 > self.startDateIntervalInitial && 0 < endDateInterval) 424 | { 425 | float dateDifference = self.endDateIntervalInitial - self.startDateIntervalInitial; 426 | float datePerPixel = selfWidth / dateDifference; 427 | 428 | float x1 = 0; 429 | float x1dif = x1 - self.startDateIntervalInitial; 430 | float x1pos = x1dif * datePerPixel; 431 | 432 | [UIView animateWithDuration:0.1 animations:^{ 433 | self.thumbControlStatic.frame = CGRectMake(x1pos - self.thumbControlStatic.frame.size.width * 0.5, self.thumbControlStatic.frame.origin.y, self.thumbControlStatic.frame.size.width, self.thumbControlStatic.frame.size.height); 434 | }]; 435 | } 436 | else 437 | { 438 | [UIView animateWithDuration:0.1 animations:^{ 439 | self.thumbControlStatic.hidden = YES; 440 | }]; 441 | } 442 | } 443 | 444 | #pragma mark Long press gesture 445 | -(void)handleLongPress 446 | { 447 | isLongPressedFired = YES; 448 | 449 | // stop date update 450 | [self updateEnable:NO]; 451 | 452 | // update start end date based on current selected date 453 | float testDevider = oneSegmentTime / 2; 454 | 455 | NSDate *currentDate; 456 | NSDate *startDate; 457 | 458 | if (fabs(self.currentDateInterval) < oneSegmentTime) 459 | { 460 | currentDate = [NSDate dateWithTimeInterval:-testDevider sinceDate:[NSDate date]]; 461 | self.endDateInitial = [NSDate date]; 462 | startDate = [NSDate dateWithTimeInterval:-testDevider sinceDate:currentDate]; 463 | 464 | } 465 | else 466 | { 467 | currentDate = [NSDate dateWithTimeIntervalSinceNow:self.currentDateInterval]; 468 | self.endDateInitial = [[NSDate alloc] initWithTimeInterval:testDevider sinceDate:currentDate]; 469 | startDate = [NSDate dateWithTimeInterval:-testDevider sinceDate:currentDate]; 470 | } 471 | 472 | self.startDateIntervalInitial = startDate.timeIntervalSinceNow; 473 | self.endDateIntervalInitial = self.endDateInitial.timeIntervalSinceNow; 474 | 475 | // update scroll and labels 476 | [scrollWithVideo updateWithStartDate:[NSDate dateWithTimeIntervalSinceNow:self.startDateIntervalInitial] endDate:self.endDateInitial andDelta:creationDate.timeIntervalSinceNow]; 477 | [scrollWithVideo createSubviewsWithVideoFragments:self.mArrayWithVideoFragments]; 478 | 479 | int coef = 0; 480 | 481 | if (segments / hoursInInSelectedInterval < 1.5) // by 1 hour 482 | { 483 | coef = 0; 484 | oneSegmentTime = masterTimeDifference / segments; 485 | } 486 | else if (segments / hoursInInSelectedInterval < 2.5) // by 30 mins 487 | { 488 | coef = 1; 489 | oneSegmentTime = 60 * 30; 490 | } 491 | else if (segments / hoursInInSelectedInterval > 2.5) // by 15 mins 492 | { 493 | coef = 2; 494 | oneSegmentTime = 60 * 15; 495 | } 496 | 497 | [scrollWithDate updateWithStartDate:[NSDate dateWithTimeIntervalSinceNow:self.startDateIntervalInitial] endDate:self.endDateInitial segments:segments isNeedHours:NO coefficient:coef animateDirection:1 selectedPoint:currentPoint]; 498 | 499 | NSDate *tempDate = [NSDate dateWithTimeInterval:0 sinceDate:self.endDateInitial]; 500 | [scrollWithDate createNewViewWithDate:tempDate isNeedMinutes:YES]; 501 | 502 | // update time / hide marker 503 | [self updateStaticMarker]; 504 | [self update]; 505 | [self updateEnable:NO]; 506 | } 507 | 508 | -(void)handleLongPressFinished 509 | { 510 | self.endDateInitial = [NSDate date]; 511 | 512 | self.startDateIntervalInitial = -masterTimeDifference; 513 | self.endDateIntervalInitial = 0; 514 | 515 | // update scroll and labels 516 | int coef = 0; 517 | 518 | if (segments / hoursInInSelectedInterval < 1.5) // by 1 hour 519 | { 520 | coef = 0; 521 | oneSegmentTime = masterTimeDifference / segments; 522 | } 523 | else if (segments / hoursInInSelectedInterval < 2.5) // by 30 mins 524 | { 525 | coef = 1; 526 | oneSegmentTime = 60 * 30; 527 | } 528 | else if (segments / hoursInInSelectedInterval > 2.5) // by 15 mins 529 | { 530 | coef = 2; 531 | oneSegmentTime = 60 * 15; 532 | } 533 | 534 | [scrollWithDate updateWithStartDate:[NSDate dateWithTimeIntervalSinceNow:self.startDateIntervalInitial] endDate:self.endDateInitial segments:segments isNeedHours:YES coefficient:coef animateDirection:0 selectedPoint:currentPoint]; 535 | [scrollWithVideo updateWithStartDate:[NSDate dateWithTimeIntervalSinceNow:self.startDateIntervalInitial] endDate:self.endDateInitial andDelta:creationDate.timeIntervalSinceNow]; 536 | [scrollWithVideo createSubviewsWithVideoFragments:self.mArrayWithVideoFragments]; 537 | 538 | // update time / move marker 539 | self.thumbControlStatic.frame = CGRectMake(selfWidth - (selfHeight * 0.7) / 2, selfHeight / 2 - (selfHeight * 0.7) / 2, selfHeight * 0.7, selfHeight * 0.7); 540 | [UIView animateWithDuration:0.1 animations:^{ 541 | self.thumbControlStatic.hidden = NO; 542 | }]; 543 | 544 | NSDate *tempDate = [NSDate dateWithTimeInterval:0 sinceDate:self.endDateInitial]; 545 | [scrollWithDate createNewViewWithDate:tempDate isNeedMinutes:YES]; 546 | 547 | [self update]; 548 | [self updateEnable:YES]; 549 | } 550 | 551 | #pragma mark Offline Mode 552 | - (void)updateOfflinePresentation 553 | { 554 | self.value = (([self totalDiskspace] - [self freeDiskspace]) * 24) / [self totalDiskspace]; 555 | 556 | if (!offlineView) 557 | { 558 | offlineView = [[UIView alloc] initWithFrame:CGRectMake(2.5, selfHeight * 0.5 - 7.5, (self.bounds.size.width / 24) * self.value, 15)]; 559 | offlineView.clipsToBounds = YES; 560 | offlineView.backgroundColor = [UIColor greenColor]; 561 | [self addSubview:offlineView]; 562 | } 563 | else 564 | { 565 | offlineView.frame = CGRectMake(2.5, selfHeight * 0.5 - 7.5, (self.bounds.size.width / 24) * self.value, 15); 566 | } 567 | } 568 | 569 | - (uint64_t)freeDiskspace{ 570 | uint64_t totalSpace = 0; 571 | uint64_t totalFreeSpace = 0; 572 | 573 | __autoreleasing NSError *error = nil; 574 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 575 | NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error]; 576 | 577 | if (dictionary) { 578 | NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize]; 579 | NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize]; 580 | totalSpace = [fileSystemSizeInBytes unsignedLongLongValue]; 581 | totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue]; 582 | NSLog(@"Memory Capacity of %llu MiB with %llu MiB Free memory available.", ((totalSpace/1024ll)/1024ll), ((totalFreeSpace/1024ll)/1024ll)); 583 | } 584 | else { 585 | NSLog(@"Error Obtaining System Memory Info: Domain = %@, Code = %ld", [error domain], (long)[error code]); 586 | } 587 | 588 | return totalFreeSpace; 589 | } 590 | 591 | - (uint64_t)totalDiskspace{ 592 | uint64_t totalSpace = 0; 593 | uint64_t totalFreeSpace = 0; 594 | 595 | __autoreleasing NSError *error = nil; 596 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 597 | NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error]; 598 | 599 | if (dictionary) { 600 | NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize]; 601 | NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize]; 602 | totalSpace = [fileSystemSizeInBytes unsignedLongLongValue]; 603 | totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue]; 604 | NSLog(@"Memory Capacity of %llu MiB with %llu MiB Free memory available.", ((totalSpace/1024ll)/1024ll), ((totalFreeSpace/1024ll)/1024ll)); 605 | } 606 | else { 607 | NSLog(@"Error Obtaining System Memory Info: Domain = %@, Code = %ld", [error domain], (long)[error code]); 608 | } 609 | 610 | return totalSpace; 611 | } 612 | 613 | @end 614 | -------------------------------------------------------------------------------- /TimeScrubber.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 29645C001B5CCF7D00BB2DD7 /* AMPopTip+Animation.m in Sources */ = {isa = PBXBuildFile; fileRef = 29645BF81B5CCF7D00BB2DD7 /* AMPopTip+Animation.m */; }; 11 | 29645C011B5CCF7D00BB2DD7 /* AMPopTip+Draw.m in Sources */ = {isa = PBXBuildFile; fileRef = 29645BFA1B5CCF7D00BB2DD7 /* AMPopTip+Draw.m */; }; 12 | 29645C021B5CCF7D00BB2DD7 /* AMPopTip+Entrance.m in Sources */ = {isa = PBXBuildFile; fileRef = 29645BFC1B5CCF7D00BB2DD7 /* AMPopTip+Entrance.m */; }; 13 | 29645C031B5CCF7D00BB2DD7 /* AMPopTip.m in Sources */ = {isa = PBXBuildFile; fileRef = 29645BFE1B5CCF7D00BB2DD7 /* AMPopTip.m */; }; 14 | 299EBAEC1B270B460069DE26 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBAEB1B270B460069DE26 /* main.m */; }; 15 | 299EBAEF1B270B460069DE26 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBAEE1B270B460069DE26 /* AppDelegate.m */; }; 16 | 299EBAF21B270B460069DE26 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBAF11B270B460069DE26 /* ViewController.m */; }; 17 | 299EBAF51B270B460069DE26 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 299EBAF31B270B460069DE26 /* Main.storyboard */; }; 18 | 299EBAF71B270B460069DE26 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 299EBAF61B270B460069DE26 /* Images.xcassets */; }; 19 | 299EBAFA1B270B460069DE26 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 299EBAF81B270B460069DE26 /* LaunchScreen.xib */; }; 20 | 299EBB061B270B460069DE26 /* TimeScrubberTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB051B270B460069DE26 /* TimeScrubberTests.m */; }; 21 | 299EBB171B270BBF0069DE26 /* TimeScrubber.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB101B270BBF0069DE26 /* TimeScrubber.m */; }; 22 | 299EBB181B270BBF0069DE26 /* TimeScrubberBorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB121B270BBF0069DE26 /* TimeScrubberBorder.m */; }; 23 | 299EBB191B270BBF0069DE26 /* TimeScrubberControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB141B270BBF0069DE26 /* TimeScrubberControl.m */; }; 24 | 299EBB1A1B270BBF0069DE26 /* TimeScrubberLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB161B270BBF0069DE26 /* TimeScrubberLabel.m */; }; 25 | 299EBB201B2725E50069DE26 /* ScrollWithDates.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB1F1B2725E50069DE26 /* ScrollWithDates.m */; }; 26 | 299EBB3E1B281A110069DE26 /* ScrollWithVideoFragments.m in Sources */ = {isa = PBXBuildFile; fileRef = 299EBB3D1B281A110069DE26 /* ScrollWithVideoFragments.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | 299EBB001B270B460069DE26 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 299EBADE1B270B460069DE26 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 299EBAE51B270B460069DE26; 35 | remoteInfo = TimeScrubber; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 29645BF71B5CCF7D00BB2DD7 /* AMPopTip+Animation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AMPopTip+Animation.h"; sourceTree = ""; }; 41 | 29645BF81B5CCF7D00BB2DD7 /* AMPopTip+Animation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AMPopTip+Animation.m"; sourceTree = ""; }; 42 | 29645BF91B5CCF7D00BB2DD7 /* AMPopTip+Draw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AMPopTip+Draw.h"; sourceTree = ""; }; 43 | 29645BFA1B5CCF7D00BB2DD7 /* AMPopTip+Draw.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AMPopTip+Draw.m"; sourceTree = ""; }; 44 | 29645BFB1B5CCF7D00BB2DD7 /* AMPopTip+Entrance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AMPopTip+Entrance.h"; sourceTree = ""; }; 45 | 29645BFC1B5CCF7D00BB2DD7 /* AMPopTip+Entrance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AMPopTip+Entrance.m"; sourceTree = ""; }; 46 | 29645BFD1B5CCF7D00BB2DD7 /* AMPopTip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMPopTip.h; sourceTree = ""; }; 47 | 29645BFE1B5CCF7D00BB2DD7 /* AMPopTip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMPopTip.m; sourceTree = ""; }; 48 | 29645BFF1B5CCF7D00BB2DD7 /* AMPopTipDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMPopTipDefaults.h; sourceTree = ""; }; 49 | 29645C041B5CCF9300BB2DD7 /* GlobalDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlobalDefines.h; sourceTree = ""; }; 50 | 299EBAE61B270B460069DE26 /* TimeScrubber.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TimeScrubber.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 299EBAEA1B270B460069DE26 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | 299EBAEB1B270B460069DE26 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 53 | 299EBAED1B270B460069DE26 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 54 | 299EBAEE1B270B460069DE26 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 55 | 299EBAF01B270B460069DE26 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 56 | 299EBAF11B270B460069DE26 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 57 | 299EBAF41B270B460069DE26 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 58 | 299EBAF61B270B460069DE26 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 59 | 299EBAF91B270B460069DE26 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 60 | 299EBAFF1B270B460069DE26 /* TimeScrubberTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TimeScrubberTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 299EBB041B270B460069DE26 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 299EBB051B270B460069DE26 /* TimeScrubberTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TimeScrubberTests.m; sourceTree = ""; }; 63 | 299EBB0F1B270BBF0069DE26 /* TimeScrubber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeScrubber.h; sourceTree = ""; }; 64 | 299EBB101B270BBF0069DE26 /* TimeScrubber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeScrubber.m; sourceTree = ""; }; 65 | 299EBB111B270BBF0069DE26 /* TimeScrubberBorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeScrubberBorder.h; sourceTree = ""; }; 66 | 299EBB121B270BBF0069DE26 /* TimeScrubberBorder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeScrubberBorder.m; sourceTree = ""; }; 67 | 299EBB131B270BBF0069DE26 /* TimeScrubberControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeScrubberControl.h; sourceTree = ""; }; 68 | 299EBB141B270BBF0069DE26 /* TimeScrubberControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeScrubberControl.m; sourceTree = ""; }; 69 | 299EBB151B270BBF0069DE26 /* TimeScrubberLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeScrubberLabel.h; sourceTree = ""; }; 70 | 299EBB161B270BBF0069DE26 /* TimeScrubberLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimeScrubberLabel.m; sourceTree = ""; }; 71 | 299EBB1E1B2725E50069DE26 /* ScrollWithDates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollWithDates.h; sourceTree = ""; }; 72 | 299EBB1F1B2725E50069DE26 /* ScrollWithDates.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScrollWithDates.m; sourceTree = ""; }; 73 | 299EBB3C1B281A110069DE26 /* ScrollWithVideoFragments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollWithVideoFragments.h; sourceTree = ""; }; 74 | 299EBB3D1B281A110069DE26 /* ScrollWithVideoFragments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScrollWithVideoFragments.m; sourceTree = ""; }; 75 | /* End PBXFileReference section */ 76 | 77 | /* Begin PBXFrameworksBuildPhase section */ 78 | 299EBAE31B270B460069DE26 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | 299EBAFC1B270B460069DE26 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | 29645BF61B5CCF7D00BB2DD7 /* PopTip */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 29645BF71B5CCF7D00BB2DD7 /* AMPopTip+Animation.h */, 99 | 29645BF81B5CCF7D00BB2DD7 /* AMPopTip+Animation.m */, 100 | 29645BF91B5CCF7D00BB2DD7 /* AMPopTip+Draw.h */, 101 | 29645BFA1B5CCF7D00BB2DD7 /* AMPopTip+Draw.m */, 102 | 29645BFB1B5CCF7D00BB2DD7 /* AMPopTip+Entrance.h */, 103 | 29645BFC1B5CCF7D00BB2DD7 /* AMPopTip+Entrance.m */, 104 | 29645BFD1B5CCF7D00BB2DD7 /* AMPopTip.h */, 105 | 29645BFE1B5CCF7D00BB2DD7 /* AMPopTip.m */, 106 | 29645BFF1B5CCF7D00BB2DD7 /* AMPopTipDefaults.h */, 107 | ); 108 | path = PopTip; 109 | sourceTree = ""; 110 | }; 111 | 299EBADD1B270B460069DE26 = { 112 | isa = PBXGroup; 113 | children = ( 114 | 299EBAE81B270B460069DE26 /* TimeScrubber */, 115 | 299EBB021B270B460069DE26 /* TimeScrubberTests */, 116 | 299EBAE71B270B460069DE26 /* Products */, 117 | ); 118 | sourceTree = ""; 119 | }; 120 | 299EBAE71B270B460069DE26 /* Products */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 299EBAE61B270B460069DE26 /* TimeScrubber.app */, 124 | 299EBAFF1B270B460069DE26 /* TimeScrubberTests.xctest */, 125 | ); 126 | name = Products; 127 | sourceTree = ""; 128 | }; 129 | 299EBAE81B270B460069DE26 /* TimeScrubber */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 29645C041B5CCF9300BB2DD7 /* GlobalDefines.h */, 133 | 29645BF61B5CCF7D00BB2DD7 /* PopTip */, 134 | 299EBB0F1B270BBF0069DE26 /* TimeScrubber.h */, 135 | 299EBB101B270BBF0069DE26 /* TimeScrubber.m */, 136 | 299EBB1C1B270E7D0069DE26 /* UI */, 137 | 299EBAED1B270B460069DE26 /* AppDelegate.h */, 138 | 299EBAEE1B270B460069DE26 /* AppDelegate.m */, 139 | 299EBAF01B270B460069DE26 /* ViewController.h */, 140 | 299EBAF11B270B460069DE26 /* ViewController.m */, 141 | 299EBAF31B270B460069DE26 /* Main.storyboard */, 142 | 299EBAF61B270B460069DE26 /* Images.xcassets */, 143 | 299EBAF81B270B460069DE26 /* LaunchScreen.xib */, 144 | 299EBAE91B270B460069DE26 /* Supporting Files */, 145 | ); 146 | path = TimeScrubber; 147 | sourceTree = ""; 148 | }; 149 | 299EBAE91B270B460069DE26 /* Supporting Files */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 299EBAEA1B270B460069DE26 /* Info.plist */, 153 | 299EBAEB1B270B460069DE26 /* main.m */, 154 | ); 155 | name = "Supporting Files"; 156 | sourceTree = ""; 157 | }; 158 | 299EBB021B270B460069DE26 /* TimeScrubberTests */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 299EBB051B270B460069DE26 /* TimeScrubberTests.m */, 162 | 299EBB031B270B460069DE26 /* Supporting Files */, 163 | ); 164 | path = TimeScrubberTests; 165 | sourceTree = ""; 166 | }; 167 | 299EBB031B270B460069DE26 /* Supporting Files */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 299EBB041B270B460069DE26 /* Info.plist */, 171 | ); 172 | name = "Supporting Files"; 173 | sourceTree = ""; 174 | }; 175 | 299EBB1C1B270E7D0069DE26 /* UI */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | 299EBB131B270BBF0069DE26 /* TimeScrubberControl.h */, 179 | 299EBB141B270BBF0069DE26 /* TimeScrubberControl.m */, 180 | 299EBB111B270BBF0069DE26 /* TimeScrubberBorder.h */, 181 | 299EBB121B270BBF0069DE26 /* TimeScrubberBorder.m */, 182 | 299EBB151B270BBF0069DE26 /* TimeScrubberLabel.h */, 183 | 299EBB161B270BBF0069DE26 /* TimeScrubberLabel.m */, 184 | 299EBB1E1B2725E50069DE26 /* ScrollWithDates.h */, 185 | 299EBB1F1B2725E50069DE26 /* ScrollWithDates.m */, 186 | 299EBB3C1B281A110069DE26 /* ScrollWithVideoFragments.h */, 187 | 299EBB3D1B281A110069DE26 /* ScrollWithVideoFragments.m */, 188 | ); 189 | name = UI; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXGroup section */ 193 | 194 | /* Begin PBXNativeTarget section */ 195 | 299EBAE51B270B460069DE26 /* TimeScrubber */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = 299EBB091B270B460069DE26 /* Build configuration list for PBXNativeTarget "TimeScrubber" */; 198 | buildPhases = ( 199 | 299EBAE21B270B460069DE26 /* Sources */, 200 | 299EBAE31B270B460069DE26 /* Frameworks */, 201 | 299EBAE41B270B460069DE26 /* Resources */, 202 | ); 203 | buildRules = ( 204 | ); 205 | dependencies = ( 206 | ); 207 | name = TimeScrubber; 208 | productName = TimeScrubber; 209 | productReference = 299EBAE61B270B460069DE26 /* TimeScrubber.app */; 210 | productType = "com.apple.product-type.application"; 211 | }; 212 | 299EBAFE1B270B460069DE26 /* TimeScrubberTests */ = { 213 | isa = PBXNativeTarget; 214 | buildConfigurationList = 299EBB0C1B270B460069DE26 /* Build configuration list for PBXNativeTarget "TimeScrubberTests" */; 215 | buildPhases = ( 216 | 299EBAFB1B270B460069DE26 /* Sources */, 217 | 299EBAFC1B270B460069DE26 /* Frameworks */, 218 | 299EBAFD1B270B460069DE26 /* Resources */, 219 | ); 220 | buildRules = ( 221 | ); 222 | dependencies = ( 223 | 299EBB011B270B460069DE26 /* PBXTargetDependency */, 224 | ); 225 | name = TimeScrubberTests; 226 | productName = TimeScrubberTests; 227 | productReference = 299EBAFF1B270B460069DE26 /* TimeScrubberTests.xctest */; 228 | productType = "com.apple.product-type.bundle.unit-test"; 229 | }; 230 | /* End PBXNativeTarget section */ 231 | 232 | /* Begin PBXProject section */ 233 | 299EBADE1B270B460069DE26 /* Project object */ = { 234 | isa = PBXProject; 235 | attributes = { 236 | LastUpgradeCheck = 0630; 237 | ORGANIZATIONNAME = "Vladyslav Semecnhenko"; 238 | TargetAttributes = { 239 | 299EBAE51B270B460069DE26 = { 240 | CreatedOnToolsVersion = 6.3.2; 241 | }; 242 | 299EBAFE1B270B460069DE26 = { 243 | CreatedOnToolsVersion = 6.3.2; 244 | TestTargetID = 299EBAE51B270B460069DE26; 245 | }; 246 | }; 247 | }; 248 | buildConfigurationList = 299EBAE11B270B460069DE26 /* Build configuration list for PBXProject "TimeScrubber" */; 249 | compatibilityVersion = "Xcode 3.2"; 250 | developmentRegion = English; 251 | hasScannedForEncodings = 0; 252 | knownRegions = ( 253 | en, 254 | Base, 255 | ); 256 | mainGroup = 299EBADD1B270B460069DE26; 257 | productRefGroup = 299EBAE71B270B460069DE26 /* Products */; 258 | projectDirPath = ""; 259 | projectRoot = ""; 260 | targets = ( 261 | 299EBAE51B270B460069DE26 /* TimeScrubber */, 262 | 299EBAFE1B270B460069DE26 /* TimeScrubberTests */, 263 | ); 264 | }; 265 | /* End PBXProject section */ 266 | 267 | /* Begin PBXResourcesBuildPhase section */ 268 | 299EBAE41B270B460069DE26 /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | 299EBAF51B270B460069DE26 /* Main.storyboard in Resources */, 273 | 299EBAFA1B270B460069DE26 /* LaunchScreen.xib in Resources */, 274 | 299EBAF71B270B460069DE26 /* Images.xcassets in Resources */, 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | 299EBAFD1B270B460069DE26 /* Resources */ = { 279 | isa = PBXResourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | /* End PBXResourcesBuildPhase section */ 286 | 287 | /* Begin PBXSourcesBuildPhase section */ 288 | 299EBAE21B270B460069DE26 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 299EBB201B2725E50069DE26 /* ScrollWithDates.m in Sources */, 293 | 299EBB181B270BBF0069DE26 /* TimeScrubberBorder.m in Sources */, 294 | 299EBB171B270BBF0069DE26 /* TimeScrubber.m in Sources */, 295 | 29645C001B5CCF7D00BB2DD7 /* AMPopTip+Animation.m in Sources */, 296 | 29645C031B5CCF7D00BB2DD7 /* AMPopTip.m in Sources */, 297 | 299EBB3E1B281A110069DE26 /* ScrollWithVideoFragments.m in Sources */, 298 | 299EBAF21B270B460069DE26 /* ViewController.m in Sources */, 299 | 299EBAEF1B270B460069DE26 /* AppDelegate.m in Sources */, 300 | 299EBB1A1B270BBF0069DE26 /* TimeScrubberLabel.m in Sources */, 301 | 299EBB191B270BBF0069DE26 /* TimeScrubberControl.m in Sources */, 302 | 299EBAEC1B270B460069DE26 /* main.m in Sources */, 303 | 29645C011B5CCF7D00BB2DD7 /* AMPopTip+Draw.m in Sources */, 304 | 29645C021B5CCF7D00BB2DD7 /* AMPopTip+Entrance.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | 299EBAFB1B270B460069DE26 /* Sources */ = { 309 | isa = PBXSourcesBuildPhase; 310 | buildActionMask = 2147483647; 311 | files = ( 312 | 299EBB061B270B460069DE26 /* TimeScrubberTests.m in Sources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXSourcesBuildPhase section */ 317 | 318 | /* Begin PBXTargetDependency section */ 319 | 299EBB011B270B460069DE26 /* PBXTargetDependency */ = { 320 | isa = PBXTargetDependency; 321 | target = 299EBAE51B270B460069DE26 /* TimeScrubber */; 322 | targetProxy = 299EBB001B270B460069DE26 /* PBXContainerItemProxy */; 323 | }; 324 | /* End PBXTargetDependency section */ 325 | 326 | /* Begin PBXVariantGroup section */ 327 | 299EBAF31B270B460069DE26 /* Main.storyboard */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | 299EBAF41B270B460069DE26 /* Base */, 331 | ); 332 | name = Main.storyboard; 333 | sourceTree = ""; 334 | }; 335 | 299EBAF81B270B460069DE26 /* LaunchScreen.xib */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | 299EBAF91B270B460069DE26 /* Base */, 339 | ); 340 | name = LaunchScreen.xib; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXVariantGroup section */ 344 | 345 | /* Begin XCBuildConfiguration section */ 346 | 299EBB071B270B460069DE26 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 351 | CLANG_CXX_LIBRARY = "libc++"; 352 | CLANG_ENABLE_MODULES = YES; 353 | CLANG_ENABLE_OBJC_ARC = YES; 354 | CLANG_WARN_BOOL_CONVERSION = YES; 355 | CLANG_WARN_CONSTANT_CONVERSION = YES; 356 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 357 | CLANG_WARN_EMPTY_BODY = YES; 358 | CLANG_WARN_ENUM_CONVERSION = YES; 359 | CLANG_WARN_INT_CONVERSION = YES; 360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 361 | CLANG_WARN_UNREACHABLE_CODE = YES; 362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 363 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 364 | COPY_PHASE_STRIP = NO; 365 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 366 | ENABLE_STRICT_OBJC_MSGSEND = YES; 367 | GCC_C_LANGUAGE_STANDARD = gnu99; 368 | GCC_DYNAMIC_NO_PIC = NO; 369 | GCC_NO_COMMON_BLOCKS = YES; 370 | GCC_OPTIMIZATION_LEVEL = 0; 371 | GCC_PREPROCESSOR_DEFINITIONS = ( 372 | "DEBUG=1", 373 | "$(inherited)", 374 | ); 375 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 383 | MTL_ENABLE_DEBUG_INFO = YES; 384 | ONLY_ACTIVE_ARCH = YES; 385 | SDKROOT = iphoneos; 386 | TARGETED_DEVICE_FAMILY = "1,2"; 387 | }; 388 | name = Debug; 389 | }; 390 | 299EBB081B270B460069DE26 /* Release */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ALWAYS_SEARCH_USER_PATHS = NO; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_WARN_BOOL_CONVERSION = YES; 399 | CLANG_WARN_CONSTANT_CONVERSION = YES; 400 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 401 | CLANG_WARN_EMPTY_BODY = YES; 402 | CLANG_WARN_ENUM_CONVERSION = YES; 403 | CLANG_WARN_INT_CONVERSION = YES; 404 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 405 | CLANG_WARN_UNREACHABLE_CODE = YES; 406 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 407 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 408 | COPY_PHASE_STRIP = NO; 409 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 410 | ENABLE_NS_ASSERTIONS = NO; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | GCC_C_LANGUAGE_STANDARD = gnu99; 413 | GCC_NO_COMMON_BLOCKS = YES; 414 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 415 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 416 | GCC_WARN_UNDECLARED_SELECTOR = YES; 417 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 418 | GCC_WARN_UNUSED_FUNCTION = YES; 419 | GCC_WARN_UNUSED_VARIABLE = YES; 420 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 421 | MTL_ENABLE_DEBUG_INFO = NO; 422 | SDKROOT = iphoneos; 423 | TARGETED_DEVICE_FAMILY = "1,2"; 424 | VALIDATE_PRODUCT = YES; 425 | }; 426 | name = Release; 427 | }; 428 | 299EBB0A1B270B460069DE26 /* Debug */ = { 429 | isa = XCBuildConfiguration; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CODE_SIGN_IDENTITY = "iPhone Developer: Vladyslav Semenchenko (RN3E99U4SG)"; 433 | INFOPLIST_FILE = TimeScrubber/Info.plist; 434 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 435 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | PROVISIONING_PROFILE = "fc2a4b2a-52c2-4db4-affb-edb6961fb36c"; 438 | }; 439 | name = Debug; 440 | }; 441 | 299EBB0B1B270B460069DE26 /* Release */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | CODE_SIGN_IDENTITY = "iPhone Developer: Vladyslav Semenchenko (RN3E99U4SG)"; 446 | INFOPLIST_FILE = TimeScrubber/Info.plist; 447 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | PROVISIONING_PROFILE = "fc2a4b2a-52c2-4db4-affb-edb6961fb36c"; 451 | }; 452 | name = Release; 453 | }; 454 | 299EBB0D1B270B460069DE26 /* Debug */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | BUNDLE_LOADER = "$(TEST_HOST)"; 458 | FRAMEWORK_SEARCH_PATHS = ( 459 | "$(SDKROOT)/Developer/Library/Frameworks", 460 | "$(inherited)", 461 | ); 462 | GCC_PREPROCESSOR_DEFINITIONS = ( 463 | "DEBUG=1", 464 | "$(inherited)", 465 | ); 466 | INFOPLIST_FILE = TimeScrubberTests/Info.plist; 467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 468 | PRODUCT_NAME = "$(TARGET_NAME)"; 469 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TimeScrubber.app/TimeScrubber"; 470 | }; 471 | name = Debug; 472 | }; 473 | 299EBB0E1B270B460069DE26 /* Release */ = { 474 | isa = XCBuildConfiguration; 475 | buildSettings = { 476 | BUNDLE_LOADER = "$(TEST_HOST)"; 477 | FRAMEWORK_SEARCH_PATHS = ( 478 | "$(SDKROOT)/Developer/Library/Frameworks", 479 | "$(inherited)", 480 | ); 481 | INFOPLIST_FILE = TimeScrubberTests/Info.plist; 482 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 483 | PRODUCT_NAME = "$(TARGET_NAME)"; 484 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TimeScrubber.app/TimeScrubber"; 485 | }; 486 | name = Release; 487 | }; 488 | /* End XCBuildConfiguration section */ 489 | 490 | /* Begin XCConfigurationList section */ 491 | 299EBAE11B270B460069DE26 /* Build configuration list for PBXProject "TimeScrubber" */ = { 492 | isa = XCConfigurationList; 493 | buildConfigurations = ( 494 | 299EBB071B270B460069DE26 /* Debug */, 495 | 299EBB081B270B460069DE26 /* Release */, 496 | ); 497 | defaultConfigurationIsVisible = 0; 498 | defaultConfigurationName = Release; 499 | }; 500 | 299EBB091B270B460069DE26 /* Build configuration list for PBXNativeTarget "TimeScrubber" */ = { 501 | isa = XCConfigurationList; 502 | buildConfigurations = ( 503 | 299EBB0A1B270B460069DE26 /* Debug */, 504 | 299EBB0B1B270B460069DE26 /* Release */, 505 | ); 506 | defaultConfigurationIsVisible = 0; 507 | defaultConfigurationName = Release; 508 | }; 509 | 299EBB0C1B270B460069DE26 /* Build configuration list for PBXNativeTarget "TimeScrubberTests" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 299EBB0D1B270B460069DE26 /* Debug */, 513 | 299EBB0E1B270B460069DE26 /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | /* End XCConfigurationList section */ 519 | }; 520 | rootObject = 299EBADE1B270B460069DE26 /* Project object */; 521 | } 522 | --------------------------------------------------------------------------------