├── 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 | 
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 |
--------------------------------------------------------------------------------