├── _config.yml
├── Screenshots
├── screenshot1.png
└── screenshot2.png
├── LMGraphViewDemo
├── LMGraphViewDemo.xcodeproj
│ ├── xcuserdata
│ │ └── lminh.xcuserdatad
│ │ │ ├── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ │ └── xcschemes
│ │ │ ├── xcschememanagement.plist
│ │ │ └── LMGraphViewDemo.xcscheme
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcuserdata
│ │ │ └── lminh.xcuserdatad
│ │ │ ├── UserInterfaceState.xcuserstate
│ │ │ └── WorkspaceSettings.xcsettings
│ └── project.pbxproj
└── LMGraphViewDemo
│ ├── LMViewController.h
│ ├── LMAppDelegate.h
│ ├── main.m
│ ├── Images.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Info.plist
│ ├── LMAppDelegate.m
│ ├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
│ └── LMViewController.m
├── LMGraphView.podspec
├── LMGraphView
├── LMGraphPointView.h
├── LMGraphPoint.m
├── Vender
│ ├── PopoverViewCompatibility.h
│ ├── PopoverView_Configuration.h
│ ├── PopoverView.h
│ └── PopoverView.m
├── LMGraphPlot.m
├── LMGraphPoint.h
├── LMGraphPlot.h
├── LMGraphPointView.m
├── LMLineGraphView.h
├── LMGraphLayout.m
├── LMGraphLayout.h
└── LMLineGraphView.m
├── LICENSE.txt
└── README.md
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-hacker
--------------------------------------------------------------------------------
/Screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lminhtm/LMGraphView/HEAD/Screenshots/screenshot1.png
--------------------------------------------------------------------------------
/Screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lminhtm/LMGraphView/HEAD/Screenshots/screenshot2.png
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/xcuserdata/lminh.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/project.xcworkspace/xcuserdata/lminh.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lminhtm/LMGraphView/HEAD/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/project.xcworkspace/xcuserdata/lminh.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/LMViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 7/25/15.
6 | // Copyright (c) 2015 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface LMViewController : UIViewController
12 |
13 | @end
14 |
15 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/LMAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 7/25/15.
6 | // Copyright (c) 2015 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface LMAppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
17 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 7/25/15.
6 | // Copyright (c) 2015 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "LMAppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([LMAppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/project.xcworkspace/xcuserdata/lminh.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges
6 |
7 | SnapshotAutomaticallyBeforeSignificantChanges
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LMGraphView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 |
3 | s.name = "LMGraphView"
4 | s.version = "1.0.0"
5 | s.summary = "LMGraphView is a simple and customizable graph view for iOS."
6 | s.homepage = "https://github.com/lminhtm/LMGraphView"
7 | s.license = 'MIT'
8 | s.author = { "LMinh" => "lminhtm@gmail.com" }
9 | s.source = { :git => "https://github.com/lminhtm/LMGraphView.git", :tag => s.version.to_s }
10 |
11 | s.platform = :ios, '8.0'
12 | s.requires_arc = true
13 |
14 | s.source_files = 'LMGraphView/**/*.{h,m}'
15 |
16 | end
17 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphPointView.h:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphPointView.h
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 25/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "LMGraphPoint.h"
11 |
12 | @interface LMGraphPointView : UIView
13 |
14 | @property (nonatomic, strong) LMGraphPoint *graphPoint;
15 | @property (nonatomic, assign) BOOL isSelected;
16 |
17 | - (id)initWithFrame:(CGRect)frame
18 | graphPoint:(LMGraphPoint *)graphPoint
19 | color:(UIColor *)color
20 | highlightColor:(UIColor *)highlightColor;
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphPoint.m:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphPoint.m
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 24/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMGraphPoint.h"
10 |
11 | @implementation LMGraphPoint
12 |
13 | + (instancetype)graphPointWithPoint:(CGPoint)point
14 | title:(NSString *)title
15 | value:(NSString *)value
16 | {
17 | LMGraphPoint *graphPoint = [[LMGraphPoint alloc] init];
18 | graphPoint.point = point;
19 | graphPoint.title = title;
20 | graphPoint.value = value;
21 | return graphPoint;
22 | }
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/LMGraphView/Vender/PopoverViewCompatibility.h:
--------------------------------------------------------------------------------
1 | //
2 | // PopoverViewCompatibility.h
3 | // popover
4 | //
5 | // Created by alanduncan on 7/22/13.
6 | // Copyright (c) 2013 Oliver Rickard. All rights reserved.
7 | //
8 |
9 | #ifndef popover_PopoverViewCompatibility_h
10 | #define popover_PopoverViewCompatibility_h
11 |
12 | #ifdef __IPHONE_6_0
13 |
14 | #define UITextAlignmentCenter NSTextAlignmentCenter
15 | #define UITextAlignmentLeft NSTextAlignmentLeft
16 | #define UITextAlignmentRight NSTextAlignmentRight
17 | #define UILineBreakModeTailTruncation NSLineBreakByTruncatingTail
18 | #define UILineBreakModeMiddleTruncation NSLineBreakByTruncatingMiddle
19 | #define UILineBreakModeWordWrap NSLineBreakByWordWrapping
20 |
21 | #endif
22 |
23 | #endif
24 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/xcuserdata/lminh.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | LMGraphViewDemo.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 5CF204891B6378D700AF1D7B
16 |
17 | primary
18 |
19 |
20 | 5CF204A21B6378D700AF1D7B
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/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 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/LMGraphView/LMGraphPlot.m:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphPlot.m
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 24/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMGraphPlot.h"
10 |
11 | @implementation LMGraphPlot
12 |
13 | - (id)init
14 | {
15 | self = [super init];
16 | if (self) {
17 | _fillColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
18 | _strokeColor = [UIColor clearColor];
19 | _lineWidth = 2;
20 |
21 | _graphPointSize = 10;
22 | _graphPointColor = [UIColor whiteColor];
23 | _graphPointHighlightColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
24 |
25 | _strokeCircleAroundGraphPoint = NO;
26 | _fillCircleAroundGraphPoint = YES;
27 | }
28 | return self;
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphPoint.h:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphPoint.h
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 24/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface LMGraphPoint : NSObject
12 |
13 | @property (nonatomic, assign) CGPoint point;
14 | @property (nonatomic, copy) NSString *title;
15 | @property (nonatomic, copy) NSString *value;
16 |
17 | + (instancetype)graphPointWithPoint:(CGPoint)point
18 | title:(NSString *)title
19 | value:(NSString *)value;
20 |
21 | @end
22 |
23 |
24 | /*** Definitions of inline functions. ***/
25 |
26 | CG_INLINE LMGraphPoint*
27 | LMGraphPointMake(CGPoint point, NSString *title, NSString *value)
28 | {
29 | return [LMGraphPoint graphPointWithPoint:point title:title value:value];
30 | }
31 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphPlot.h:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphPlot.h
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 24/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "LMGraphPoint.h"
11 |
12 | @interface LMGraphPlot : NSObject
13 |
14 | @property (nonatomic, strong) NSArray *graphPoints;
15 |
16 | @property (nonatomic, strong) UIColor *fillColor;
17 | @property (nonatomic, strong) UIColor *strokeColor;
18 | @property (nonatomic, assign) CGFloat lineWidth;
19 |
20 | @property (nonatomic, assign) CGFloat graphPointSize;
21 | @property (nonatomic, strong) UIColor *graphPointColor;
22 | @property (nonatomic, strong) UIColor *graphPointHighlightColor;
23 |
24 | @property (nonatomic, assign) BOOL strokeCircleAroundGraphPoint;
25 | @property (nonatomic, assign) BOOL fillCircleAroundGraphPoint;
26 | @property (nonatomic, assign) BOOL curveLine;
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) {{{2014}}} {{{LMinh}}}
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | 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 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphPointView.m:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphPointView.m
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 25/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMGraphPointView.h"
10 |
11 | @interface LMGraphPointView ()
12 |
13 | @property (nonatomic, strong) UIView *innerView;
14 |
15 | @end
16 |
17 | @implementation LMGraphPointView
18 |
19 | - (id)initWithFrame:(CGRect)frame
20 | graphPoint:(LMGraphPoint *)graphPoint
21 | color:(UIColor *)color
22 | highlightColor:(UIColor *)highlightColor
23 | {
24 | self = [super initWithFrame:frame];
25 | if (self) {
26 | self.graphPoint = graphPoint;
27 |
28 | self.clipsToBounds = YES;
29 | self.layer.cornerRadius = self.frame.size.width/2;
30 | self.backgroundColor = color;
31 |
32 | self.innerView = [[UIView alloc] initWithFrame:CGRectMake(2, 2, self.frame.size.width - 2*2, self.frame.size.height - 2*2)];
33 | self.innerView.clipsToBounds = YES;
34 | self.innerView.layer.cornerRadius = self.innerView.frame.size.width/2;
35 | self.innerView.backgroundColor = highlightColor;
36 | self.innerView.hidden = YES;
37 | [self addSubview:self.innerView];
38 | }
39 | return self;
40 | }
41 |
42 | - (void)setIsSelected:(BOOL)isSelected
43 | {
44 | _isSelected = isSelected;
45 |
46 | if (isSelected) {
47 | self.innerView.hidden = NO;
48 | }
49 | else {
50 | self.innerView.hidden = YES;
51 | }
52 | }
53 |
54 | @end
55 |
--------------------------------------------------------------------------------
/LMGraphView/LMLineGraphView.h:
--------------------------------------------------------------------------------
1 | //
2 | // LMLineGraphView.h
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 17/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "LMGraphPointView.h"
11 | #import "LMGraphPlot.h"
12 | #import "LMGraphPoint.h"
13 | #import "PopoverView.h"
14 | #import "LMGraphLayout.h"
15 |
16 | @protocol LMLineGraphViewDelegate;
17 |
18 | @interface LMLineGraphView : UIView
19 |
20 | @property (nonatomic, assign) CGFloat xAxisStartGraphPoint;
21 | @property (nonatomic, strong) NSArray *xAxisValues;
22 | @property (nonatomic, assign) CGFloat xAxisInterval;
23 | @property (nonatomic, assign) CGFloat yAxisMaxValue;
24 | @property (nonatomic, assign) CGFloat yAxisMinValue;
25 | @property (nonatomic, strong) NSArray *graphPlots;
26 |
27 | @property (nonatomic, strong) NSString *yAxisSuffix;
28 | @property (nonatomic, strong) NSString *yAxisUnit;
29 | @property (nonatomic, strong) NSString *noDataText;
30 | @property (nonatomic, strong) NSString *title;
31 |
32 | @property (nonatomic, strong) LMGraphLayout *layout;
33 |
34 | @property (nonatomic, weak) id delegate;
35 |
36 | - (void)animateWithDuration:(NSTimeInterval)duration;
37 |
38 | - (UIImage *)graphImage;
39 |
40 | @end
41 |
42 | @protocol LMLineGraphViewDelegate
43 |
44 | @optional
45 | - (NSString *)lineGraphView:(LMLineGraphView *)lineGraphView yAxisLabelTitleFromValue:(CGFloat)value index:(int)index;
46 | - (void)lineGraphView:(LMLineGraphView *)lineGraphView beganTouchWithGraphPoint:(LMGraphPoint *)graphPoint;
47 | - (void)lineGraphView:(LMLineGraphView *)lineGraphView moveTouchWithGraphPoint:(LMGraphPoint *)graphPoint;
48 | - (void)lineGraphView:(LMLineGraphView *)lineGraphView endTouchWithGraphPoint:(LMGraphPoint *)graphPoint;
49 |
50 | @end
51 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphLayout.m:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphLayout.m
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 2/1/18.
6 | // Copyright © 2018 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMGraphLayout.h"
10 |
11 | @implementation LMGraphLayout
12 |
13 | - (id)init
14 | {
15 | self = [super init];
16 | if (self)
17 | {
18 | _drawMovement = YES;
19 |
20 | _xAxisScrollableOnly = YES;
21 | _xAxisGridDashLine = YES;
22 | _xAxisIntervalInPx = 60;
23 | _xAxisMargin = 0;
24 |
25 | _xAxisZeroHidden = NO;
26 | _xAxisZeroDashLine = NO;
27 | _xAxisLinesWidth = 0.5;
28 | _xAxisLinesStrokeColor = [UIColor lightGrayColor];
29 | _xAxisLabelHeight = 40;
30 | _xAxisLabelFont = [UIFont systemFontOfSize:12];
31 | _xAxisLabelColor = [UIColor darkGrayColor];
32 |
33 | _yAxisSegmentCount = 3;
34 | _yAxisZeroHidden = NO;
35 | _yAxisZeroDashLine = NO;
36 | _yAxisLinesWidth = 0.5;
37 | _xAxisGridHidden = NO;
38 | _yAxisGridHidden = NO;
39 | _yAxisGridDashLine = YES;
40 | _yAxisLabelWidth = 40;
41 | _yAxisLinesStrokeColor = [UIColor lightGrayColor];
42 | _yAxisLabelHeight = 20;
43 | _yAxisLabelFont = [UIFont systemFontOfSize:12];
44 | _yAxisLabelColor = [UIColor darkGrayColor];
45 |
46 | _yAxisUnitLabelFont = [UIFont systemFontOfSize:10];
47 | _yAxisUnitLabelColor = [UIColor darkGrayColor];
48 |
49 | _titleLabelHeight = 25;
50 | _titleLabelFont = [UIFont systemFontOfSize:14];
51 | _titleLabelColor = [UIColor darkGrayColor];
52 |
53 | _movementLineWidth = 2;
54 | _movementLineColor = [UIColor lightGrayColor];
55 | _movementDotColor = [UIColor redColor];
56 | }
57 | return self;
58 | }
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/LMAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 7/25/15.
6 | // Copyright (c) 2015 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMAppDelegate.h"
10 |
11 | @interface LMAppDelegate ()
12 |
13 | @end
14 |
15 | @implementation LMAppDelegate
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 |
--------------------------------------------------------------------------------
/LMGraphView/LMGraphLayout.h:
--------------------------------------------------------------------------------
1 | //
2 | // LMGraphLayout.h
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 2/1/18.
6 | // Copyright © 2018 LMinh. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 |
12 | @interface LMGraphLayout : NSObject
13 |
14 | @property (nonatomic, assign) BOOL startPlotFromZero;
15 | @property (nonatomic, assign) BOOL drawMovement;
16 |
17 | @property (nonatomic, assign) BOOL xAxisScrollableOnly;
18 | @property (nonatomic, assign) BOOL xAxisGridHidden;
19 | @property (nonatomic, assign) BOOL xAxisGridDashLine;
20 | @property (nonatomic, assign) CGFloat xAxisIntervalInPx;
21 | @property (nonatomic, assign) CGFloat xAxisMargin;
22 |
23 | @property (nonatomic, assign) BOOL xAxisZeroHidden;
24 | @property (nonatomic, assign) BOOL xAxisZeroDashLine;
25 | @property (nonatomic, assign) CGFloat xAxisLinesWidth;
26 | @property (nonatomic, strong) UIColor *xAxisLinesStrokeColor;
27 | @property (nonatomic, assign) CGFloat xAxisLabelHeight;
28 | @property (nonatomic, strong) UIFont *xAxisLabelFont;
29 | @property (nonatomic, strong) UIColor *xAxisLabelColor;
30 |
31 | @property (nonatomic, assign) NSUInteger yAxisSegmentCount;
32 | @property (nonatomic, assign) BOOL yAxisZeroHidden;
33 | @property (nonatomic, assign) BOOL yAxisZeroDashLine;
34 | @property (nonatomic, assign) BOOL yAxisGridHidden;
35 | @property (nonatomic, assign) BOOL yAxisGridDashLine;
36 | @property (nonatomic, strong) UIColor *yAxisLinesStrokeColor;
37 | @property (nonatomic, assign) CGFloat yAxisLinesWidth;
38 | @property (nonatomic, assign) CGFloat yAxisLabelWidth;
39 | @property (nonatomic, assign) CGFloat yAxisLabelHeight;
40 | @property (nonatomic, strong) UIFont *yAxisLabelFont;
41 | @property (nonatomic, strong) UIColor *yAxisLabelColor;
42 |
43 | @property (nonatomic, strong) UIFont *yAxisUnitLabelFont;
44 | @property (nonatomic, strong) UIColor *yAxisUnitLabelColor;
45 |
46 | @property (nonatomic, assign) CGFloat titleLabelHeight;
47 | @property (nonatomic, strong) UIFont *titleLabelFont;
48 | @property (nonatomic, strong) UIColor *titleLabelColor;
49 |
50 | @property (nonatomic, assign) CGFloat movementLineWidth;
51 | @property (nonatomic, strong) UIColor *movementLineColor;
52 | @property (nonatomic, strong) UIColor *movementDotColor;
53 |
54 | @end
55 |
--------------------------------------------------------------------------------
/LMGraphView/Vender/PopoverView_Configuration.h:
--------------------------------------------------------------------------------
1 | //
2 | // PopoverView_Configuration.h
3 | // popover
4 | //
5 | // Created by Bas Pellis on 12/25/12.
6 | // Copyright (c) 2012 Oliver Rickard. All rights reserved.
7 | //
8 |
9 | #pragma mark Constants - Configure look/feel
10 |
11 | // BOX GEOMETRY
12 |
13 | //Height/width of the actual arrow
14 | #define kArrowHeight 8.f
15 |
16 | //padding within the box for the contentView
17 | #define kBoxPadding 5.f
18 |
19 | //control point offset for rounding corners of the main popover box
20 | #define kCPOffset 1.8f
21 |
22 | //radius for the rounded corners of the main popover box
23 | #define kBoxRadius 4.f
24 |
25 | //Curvature value for the arrow. Set to 0.f to make it linear.
26 | #define kArrowCurvature 0.f
27 |
28 | //Minimum distance from the side of the arrow to the beginning of curvature for the box
29 | #define kArrowHorizontalPadding 3.f
30 |
31 | //Alpha value for the shadow behind the PopoverView
32 | #define kShadowAlpha 0.4f
33 |
34 | //Blur for the shadow behind the PopoverView
35 | #define kShadowBlur 3.f;
36 |
37 | //Box gradient bg alpha
38 | #define kBoxAlpha 0.95f
39 |
40 | //Padding along top of screen to allow for any nav/status bars
41 | #define kTopMargin 20.f
42 |
43 | //margin along the left and right of the box
44 | #define kHorizontalMargin 10.f
45 |
46 | //padding along top of icons/images
47 | #define kImageTopPadding 3.f
48 |
49 | //padding along bottom of icons/images
50 | #define kImageBottomPadding 3.f
51 |
52 |
53 | // DIVIDERS BETWEEN VIEWS
54 |
55 | //Bool that turns off/on the dividers
56 | #define kShowDividersBetweenViews NO
57 |
58 | //color for the divider fill
59 | #define kDividerColor [UIColor colorWithRed:0.329 green:0.341 blue:0.353 alpha:0.15f]
60 |
61 |
62 | // BACKGROUND GRADIENT
63 |
64 | //bottom color white in gradient bg
65 | //#define kGradientBottomColor [UIColor colorWithRed:0.98f green:0.98f blue:0.98f alpha:kBoxAlpha]
66 | #define kGradientBottomColor [UIColor colorWithRed:0.f green:0.f blue:0.f alpha:0.7]
67 |
68 | //top color white value in gradient bg
69 | //#define kGradientTopColor [UIColor colorWithRed:1.f green:1.f blue:1.f alpha:kBoxAlpha]
70 | #define kGradientTopColor [UIColor colorWithRed:0.f green:0.f blue:0.f alpha:0.7]
71 |
72 | // TITLE GRADIENT
73 |
74 | //bool that turns off/on title gradient
75 | #define kDrawTitleGradient YES
76 |
77 | //bottom color white value in title gradient bg
78 | #define kGradientTitleBottomColor [UIColor colorWithRed:0.93f green:0.93f blue:0.93f alpha:kBoxAlpha]
79 |
80 | //top color white value in title gradient bg
81 | #define kGradientTitleTopColor [UIColor colorWithRed:1.f green:1.f blue:1.f alpha:kBoxAlpha]
82 |
83 |
84 | // FONTS
85 |
86 | //normal text font
87 | #define kTextFont [UIFont fontWithName:@"HelveticaNeue" size:16.f]
88 |
89 | //normal text color
90 | #define kTextColor [UIColor colorWithRed:0.329 green:0.341 blue:0.353 alpha:1]
91 | // highlighted text color
92 | #define kTextHighlightColor [UIColor colorWithRed:0.098 green:0.102 blue:0.106 alpha:1.000]
93 |
94 | //normal text alignment
95 | #define kTextAlignment UITextAlignmentCenter
96 |
97 | //title font
98 | #define kTitleFont [UIFont fontWithName:@"HelveticaNeue-Bold" size:16.f]
99 |
100 | //title text color
101 | #define kTitleColor [UIColor colorWithRed:0.329 green:0.341 blue:0.353 alpha:1]
102 |
103 |
104 | // BORDER
105 |
106 | //bool that turns off/on the border
107 | #define kDrawBorder NO
108 |
109 | //border color
110 | #define kBorderColor [UIColor blackColor]
111 |
112 | //border width
113 | #define kBorderWidth 1.f
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/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 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/xcuserdata/lminh.xcuserdatad/xcschemes/LMGraphViewDemo.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
48 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
80 |
82 |
88 |
89 |
90 |
91 |
92 |
93 |
99 |
101 |
107 |
108 |
109 |
110 |
112 |
113 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | LMGraphView
2 | ==============
3 | LMGraphView is a simple and customizable graph view for iOS.
4 |
5 |
6 |
7 |
8 | ## Features
9 | * Display a scrollable line graph view with mutiple graph plots.
10 | * Show popover when touch on every graph points.
11 | * Allow for a large amount of customization.
12 |
13 | ## Requirements
14 | * iOS 8.0 or higher
15 | * ARC
16 |
17 | ## Installation
18 | #### From CocoaPods
19 | ```ruby
20 | pod 'LMGraphView'
21 | ```
22 | #### Manually
23 | * Drag the `LMGraphView` folder into your project.
24 | * Add `#import "LMLineGraphView.h"` to the top of classes that will use it.
25 |
26 | ## Usage
27 | You can easily integrate the LMGraphView with a few lines of code. For an example usage look at the code below.
28 | ```ObjC
29 | LMLineGraphView *graphView = [[LMLineGraphView alloc] initWithFrame:frame];
30 | graphView.xAxisValues = @[@{ @1 : @"JAN" },
31 | @{ @2 : @"FEB" },
32 | @{ @3 : @"MAR" },
33 | @{ @4 : @"APR" },
34 | @{ @5 : @"MAY" },
35 | @{ @6 : @"JUN" },
36 | @{ @7 : @"JUL" },
37 | @{ @8 : @"AUG" },
38 | @{ @9 : @"SEP" },
39 | @{ @10 : @"OCT" },
40 | @{ @11 : @"NOV" },
41 | @{ @12 : @"DEC" }];
42 | graphView.yAxisMaxValue = 90;
43 | graphView.yAxisUnit = @"(customer)";
44 | graphView.title = @"MONTHLY CUSTOMER";
45 | [self.view addSubview:graphView];
46 |
47 | LMGraphPlot *plot = [[LMGraphPlot alloc] init];
48 | plot.strokeColor = [UIColor brownColor];
49 | plot.fillColor = [UIColor clearColor];
50 | plot.graphPointColor = [UIColor brownColor];
51 | plot.graphPoints = @[LMGraphPointMake(CGPointMake(1, 47), @"1", @"47"),
52 | LMGraphPointMake(CGPointMake(2, 69), @"2", @"69"),
53 | LMGraphPointMake(CGPointMake(3, 77), @"3", @"77"),
54 | LMGraphPointMake(CGPointMake(4, 60), @"4", @"60"),
55 | LMGraphPointMake(CGPointMake(5, 59), @"5", @"59"),
56 | LMGraphPointMake(CGPointMake(6, 40), @"6", @"40"),
57 | LMGraphPointMake(CGPointMake(7, 60), @"7", @"60"),
58 | LMGraphPointMake(CGPointMake(8, 45), @"8", @"45"),
59 | LMGraphPointMake(CGPointMake(9, 50), @"9", @"50"),
60 | LMGraphPointMake(CGPointMake(10, 70), @"10", @"70"),
61 | LMGraphPointMake(CGPointMake(11, 56), @"11", @"56"),
62 | LMGraphPointMake(CGPointMake(12, 30), @"12", @"30")];
63 | graphView.graphPlots = @[plot];
64 | ```
65 |
66 | ## Customization
67 | You can customize the following properties of LMGraphView in LMGraphLayout:
68 | ```ObjC
69 | @property (nonatomic, assign) BOOL startPlotFromZero;
70 | @property (nonatomic, assign) BOOL drawMovement;
71 |
72 | @property (nonatomic, assign) BOOL xAxisScrollableOnly;
73 | @property (nonatomic, assign) BOOL xAxisGridHidden;
74 | @property (nonatomic, assign) BOOL xAxisGridDashLine;
75 | @property (nonatomic, assign) CGFloat xAxisIntervalInPx;
76 | @property (nonatomic, assign) CGFloat xAxisMargin;
77 |
78 | @property (nonatomic, assign) BOOL xAxisZeroHidden;
79 | @property (nonatomic, assign) BOOL xAxisZeroDashLine;
80 | @property (nonatomic, assign) CGFloat xAxisLinesWidth;
81 | @property (nonatomic, strong) UIColor *xAxisLinesStrokeColor;
82 | @property (nonatomic, assign) CGFloat xAxisLabelHeight;
83 | @property (nonatomic, strong) UIFont *xAxisLabelFont;
84 | @property (nonatomic, strong) UIColor *xAxisLabelColor;
85 |
86 | @property (nonatomic, assign) NSUInteger yAxisSegmentCount;
87 | @property (nonatomic, assign) BOOL yAxisZeroHidden;
88 | @property (nonatomic, assign) BOOL yAxisZeroDashLine;
89 | @property (nonatomic, assign) BOOL yAxisGridHidden;
90 | @property (nonatomic, assign) BOOL yAxisGridDashLine;
91 | @property (nonatomic, strong) UIColor *yAxisLinesStrokeColor;
92 | @property (nonatomic, assign) CGFloat yAxisLinesWidth;
93 | @property (nonatomic, assign) CGFloat yAxisLabelWidth;
94 | @property (nonatomic, assign) CGFloat yAxisLabelHeight;
95 | @property (nonatomic, strong) UIFont *yAxisLabelFont;
96 | @property (nonatomic, strong) UIColor *yAxisLabelColor;
97 |
98 | @property (nonatomic, strong) UIFont *yAxisUnitLabelFont;
99 | @property (nonatomic, strong) UIColor *yAxisUnitLabelColor;
100 |
101 | @property (nonatomic, assign) CGFloat titleLabelHeight;
102 | @property (nonatomic, strong) UIFont *titleLabelFont;
103 | @property (nonatomic, strong) UIColor *titleLabelColor;
104 |
105 | @property (nonatomic, assign) CGFloat movementLineWidth;
106 | @property (nonatomic, strong) UIColor *movementLineColor;
107 | @property (nonatomic, strong) UIColor *movementDotColor;
108 | ```
109 | (See sample Xcode project in /LMGraphViewDemo)
110 |
111 | ## License
112 | LMGraphView is licensed under the terms of the MIT License.
113 |
114 | ## Contact
115 | Minh Luong Nguyen
116 | * https://github.com/lminhtm
117 | * lminhtm@gmail.com
118 |
119 | ## Donations
120 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=J3WZJT2AD28NW&lc=VN&item_name=LMGraphView¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
121 |
--------------------------------------------------------------------------------
/LMGraphView/Vender/PopoverView.h:
--------------------------------------------------------------------------------
1 | //
2 | // PopoverView.h
3 | // Embark
4 | //
5 | // Created by Oliver Rickard on 20/08/2012.
6 | //
7 | //
8 |
9 | #import
10 | #import "PopoverViewCompatibility.h"
11 |
12 |
13 | /**************** Support both ARC and non-ARC ********************/
14 |
15 | #ifndef SUPPORT_ARC
16 | #define SUPPORT_ARC
17 |
18 | #if __has_feature(objc_arc_weak) //objc_arc_weak
19 | #define WEAK weak
20 | #define __WEAK __weak
21 | #define STRONG strong
22 |
23 | #define AUTORELEASE self
24 | #define RELEASE self
25 | #define RETAIN self
26 | #define CFTYPECAST(exp) (__bridge exp)
27 | #define TYPECAST(exp) (__bridge_transfer exp)
28 | #define CFRELEASE(exp) CFRelease(exp)
29 | #define DEALLOC self
30 |
31 | #elif __has_feature(objc_arc) //objc_arc
32 | #define WEAK unsafe_unretained
33 | #define __WEAK __unsafe_unretained
34 | #define STRONG strong
35 |
36 | #define AUTORELEASE self
37 | #define RELEASE self
38 | #define RETAIN self
39 | #define CFTYPECAST(exp) (__bridge exp)
40 | #define TYPECAST(exp) (__bridge_transfer exp)
41 | #define CFRELEASE(exp) CFRelease(exp)
42 | #define DEALLOC self
43 |
44 | #else //none
45 | #define WEAK assign
46 | #define __WEAK
47 | #define STRONG retain
48 |
49 | #define AUTORELEASE autorelease
50 | #define RELEASE release
51 | #define RETAIN retain
52 | #define CFTYPECAST(exp) (exp)
53 | #define TYPECAST(exp) (exp)
54 | #define CFRELEASE(exp) CFRelease(exp)
55 | #define DEALLOC dealloc
56 |
57 | #endif
58 | #endif
59 |
60 | /******************************************************************/
61 |
62 |
63 | @class PopoverView;
64 |
65 | @protocol PopoverViewDelegate
66 |
67 | @optional
68 |
69 | //Delegate receives this call as soon as the item has been selected
70 | - (void)popoverView:(PopoverView *)popoverView didSelectItemAtIndex:(NSInteger)index;
71 |
72 | //Delegate receives this call once the popover has begun the dismissal animation
73 | - (void)popoverViewDidDismiss:(PopoverView *)popoverView;
74 |
75 | @end
76 |
77 | @interface PopoverView : UIView {
78 | CGRect boxFrame;
79 | CGSize contentSize;
80 | CGPoint arrowPoint;
81 | BOOL above;
82 |
83 | __WEAK id delegate;
84 |
85 | UIView *parentView;
86 |
87 | UIView *topView;
88 |
89 | NSArray *subviewsArray;
90 |
91 | NSArray *dividerRects;
92 |
93 | UIView *contentView;
94 |
95 | UIView *titleView;
96 |
97 | UIActivityIndicatorView *activityIndicator;
98 |
99 | //Instance variable that can change at runtime
100 | BOOL showDividerRects;
101 | }
102 |
103 | @property (nonatomic,assign) BOOL isFitView;
104 |
105 | @property (nonatomic, STRONG) UIView *titleView;
106 |
107 | @property (nonatomic, STRONG) UIView *contentView;
108 |
109 | @property (nonatomic, STRONG) NSArray *subviewsArray;
110 |
111 | @property (nonatomic, WEAK) id delegate;
112 |
113 | #pragma mark - Class Static Showing Methods
114 |
115 | //These are the main static methods you can use to display the popover.
116 | //Simply call [PopoverView show...] with your arguments, and the popover will be generated, added to the view stack, and notify you when it's done.
117 |
118 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text delegate:(id)delegate;
119 |
120 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text delegate:(id)delegate;
121 |
122 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray delegate:(id)delegate;
123 |
124 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray delegate:(id)delegate;
125 |
126 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray delegate:(id)delegate;
127 |
128 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray delegate:(id)delegate;
129 |
130 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate;
131 |
132 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate;
133 |
134 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView delegate:(id)delegate;
135 |
136 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point
137 | inView:(UIView *)view
138 | withContentView:(UIView *)cView
139 | isFitView:(BOOL)isFitView
140 | delegate:(id)delegate;
141 |
142 | #pragma mark - Instance Showing Methods
143 |
144 | //Adds/animates in the popover to the top of the view stack with the arrow pointing at the "point"
145 | //within the specified view. The contentView will be added to the popover, and should have either
146 | //a clear color backgroundColor, or perhaps a rounded corner bg rect (radius 4.f if you're going to
147 | //round).
148 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)contentView;
149 |
150 | //Calls above method with a UILabel containing the text you deliver to this method.
151 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text;
152 |
153 | //Calls top method with an array of UIView objects. This method will stack these views vertically
154 | //with kBoxPadding padding between each view in the y-direction.
155 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray;
156 |
157 | //Does same as above, but adds a title label at top of the popover.
158 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray;
159 |
160 | //Calls the viewArray method with an array of UILabels created with the strings
161 | //in stringArray. All contents of stringArray must be NSStrings.
162 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray;
163 |
164 | //This method does same as above, but with a title label at the top of the popover.
165 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray;
166 |
167 | //Draws a vertical list of the NSString elements of stringArray with UIImages
168 | //from imageArray placed centered above them.
169 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray;
170 |
171 | //Does the same as above, but with a title
172 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray;
173 |
174 | //Lays out the PopoverView at a point once all of the views have already been setup elsewhere
175 | - (void)layoutAtPoint:(CGPoint)point inView:(UIView *)view;
176 |
177 | #pragma mark - Other Interaction
178 | //This method animates the rotation of the PopoverView to a new point
179 | - (void)animateRotationToNewPoint:(CGPoint)point inView:(UIView *)view withDuration:(NSTimeInterval)duration;
180 |
181 | #pragma mark - Dismissal
182 | //Dismisses the view, and removes it from the view stack.
183 | - (void)dismiss;
184 | - (void)dismiss:(BOOL)animated;
185 |
186 | #pragma mark - Activity Indicator Methods
187 |
188 | //Shows the activity indicator, and changes the title (if the title is available, and is a UILabel).
189 | - (void)showActivityIndicatorWithMessage:(NSString *)msg;
190 |
191 | //Hides the activity indicator, and changes the title (if the title is available) to the msg
192 | - (void)hideActivityIndicatorWithMessage:(NSString *)msg;
193 |
194 | #pragma mark - Custom Image Showing
195 |
196 | //Animate in, and display the image provided here.
197 | - (void)showImage:(UIImage *)image withMessage:(NSString *)msg;
198 |
199 | #pragma mark - Error/Success Methods
200 |
201 | //Shows (and animates in) an error X in the contentView
202 | - (void)showError;
203 |
204 | //Shows (and animates in) a success checkmark in the contentView
205 | - (void)showSuccess;
206 |
207 | @end
208 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/LMViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // LMGraphViewDemo
4 | //
5 | // Created by LMinh on 7/25/15.
6 | // Copyright (c) 2015 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMViewController.h"
10 | #import "LMLineGraphView.h"
11 |
12 | @interface LMViewController ()
13 |
14 | @property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
15 | @property (weak, nonatomic) IBOutlet LMLineGraphView *lineGraphView1;
16 | @property (weak, nonatomic) IBOutlet LMLineGraphView *lineGraphView2;
17 | @property (weak, nonatomic) IBOutlet LMLineGraphView *lineGraphView3;
18 | @property (weak, nonatomic) IBOutlet LMLineGraphView *lineGraphView4;
19 |
20 | @end
21 |
22 | @implementation LMViewController
23 |
24 | #pragma mark - VIEW LIFECYCLES
25 |
26 | - (void)viewDidLoad
27 | {
28 | [super viewDidLoad];
29 |
30 | // Generate sample data
31 | NSArray *xAxisValues = [self xAxisValues];
32 | NSArray *shortXAxisValues = [self shortXAxisValues];
33 | LMGraphPlot *plot1 = [self sampleGraphPlot1];
34 | LMGraphPlot *plot2 = [self sampleGraphPlot2];
35 | LMGraphPlot *plot3 = [self sampleGraphPlot3];
36 | LMGraphPlot *plot4 = [self sampleGraphPlot4];
37 |
38 | // Line Graph View 1
39 | self.lineGraphView1.layout.xAxisScrollableOnly = YES;
40 | self.lineGraphView1.xAxisValues = xAxisValues;
41 | self.lineGraphView1.yAxisMaxValue = 90;
42 | self.lineGraphView1.yAxisUnit = @"(customer)";
43 | self.lineGraphView1.title = @"MONTHLY CUSTOMER";
44 | self.lineGraphView1.graphPlots = @[plot1, plot2];
45 |
46 | // Line Graph View 2
47 | self.lineGraphView2.layout.xAxisScrollableOnly = NO;
48 | self.lineGraphView2.layout.drawMovement = NO;
49 | self.lineGraphView2.xAxisValues = shortXAxisValues;
50 | self.lineGraphView2.yAxisMaxValue = 90;
51 | self.lineGraphView2.yAxisUnit = @"(customer)";
52 | self.lineGraphView2.title = @"MONTHLY CUSTOMER";
53 | self.lineGraphView2.graphPlots = @[plot3];
54 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
55 | [self.lineGraphView2 animateWithDuration:2];
56 | });
57 |
58 | // Line Graph View 3
59 | self.lineGraphView3.layout.xAxisScrollableOnly = YES;
60 | self.lineGraphView3.xAxisValues = xAxisValues;
61 | self.lineGraphView3.yAxisMaxValue = 90;
62 | self.lineGraphView3.yAxisUnit = @"(customer)";
63 | self.lineGraphView3.title = @"MONTHLY CUSTOMER";
64 | self.lineGraphView3.graphPlots = @[plot3];
65 |
66 | // Line Graph View 4
67 | self.lineGraphView4.backgroundColor = [UIColor blackColor];
68 | self.lineGraphView4.layout.xAxisScrollableOnly = NO;
69 | self.lineGraphView4.xAxisStartGraphPoint = 1;
70 | self.lineGraphView4.layout.xAxisMargin = 10;
71 | self.lineGraphView4.delegate = self;
72 | self.lineGraphView4.xAxisValues = shortXAxisValues;
73 | self.lineGraphView4.yAxisMaxValue = 90;
74 | self.lineGraphView4.yAxisMinValue = 10;
75 | self.lineGraphView4.yAxisUnit = @"(customer)";
76 | self.lineGraphView4.title = @"MONTHLY CUSTOMER";
77 | self.lineGraphView4.layout.titleLabelColor = [UIColor whiteColor];
78 | self.lineGraphView4.layout.xAxisLabelColor = [UIColor whiteColor];
79 | self.lineGraphView4.layout.yAxisLabelColor = [UIColor whiteColor];
80 | self.lineGraphView4.layout.yAxisUnitLabelColor = [UIColor whiteColor];
81 | self.lineGraphView4.graphPlots = @[plot4];
82 | }
83 |
84 |
85 | #pragma mark - LINE GRAPH VIEW DELEGATE
86 |
87 | - (void)lineGraphView:(LMLineGraphView *)lineGraphView beganTouchWithGraphPoint:(LMGraphPoint *)graphPoint
88 | {
89 | self.scrollView.scrollEnabled = NO;
90 | }
91 |
92 | - (void)lineGraphView:(LMLineGraphView *)lineGraphView moveTouchWithGraphPoint:(LMGraphPoint *)graphPoint
93 | {
94 |
95 | }
96 |
97 | - (void)lineGraphView:(LMLineGraphView *)lineGraphView endTouchWithGraphPoint:(LMGraphPoint *)graphPoint
98 | {
99 | self.scrollView.scrollEnabled = YES;
100 | }
101 |
102 |
103 | #pragma mark - SUPPORT
104 |
105 | - (NSArray *)xAxisValues
106 | {
107 | return @[@{ @1 : @"JAN" },
108 | @{ @2 : @"FEB" },
109 | @{ @3 : @"MAR" },
110 | @{ @4 : @"APR" },
111 | @{ @5 : @"MAY" },
112 | @{ @6 : @"JUN" },
113 | @{ @7 : @"JUL" },
114 | @{ @8 : @"AUG" },
115 | @{ @9 : @"SEP" },
116 | @{ @10 : @"OCT" },
117 | @{ @11 : @"NOV" },
118 | @{ @12 : @"DEC" }];
119 | }
120 |
121 | - (NSArray *)shortXAxisValues
122 | {
123 | return @[@{ @1 : @"1" },
124 | @{ @2 : @"2" },
125 | @{ @3 : @"3" },
126 | @{ @4 : @"4" },
127 | @{ @5 : @"5" },
128 | @{ @6 : @"6" },
129 | @{ @7 : @"7" },
130 | @{ @8 : @"8" },
131 | @{ @9 : @"9" },
132 | @{ @10 : @"10" },
133 | @{ @11 : @"11" },
134 | @{ @12 : @"12" }];
135 | }
136 |
137 | - (LMGraphPlot *)sampleGraphPlot1
138 | {
139 | LMGraphPlot *plot1 = [[LMGraphPlot alloc] init];
140 | plot1.strokeColor = [UIColor colorWithRed:11.0/255 green:150.0/255 blue:246.0/255 alpha:0.9];
141 | plot1.fillColor = [UIColor colorWithRed:11.0/255 green:150.0/255 blue:246.0/255 alpha:0.5];
142 | plot1.graphPointColor = [UIColor whiteColor];
143 | plot1.graphPointHighlightColor = plot1.fillColor;
144 | plot1.graphPoints = @[LMGraphPointMake(CGPointMake(1, 47), @"1", @"47"),
145 | LMGraphPointMake(CGPointMake(2, 69), @"2", @"69"),
146 | LMGraphPointMake(CGPointMake(3, 77), @"3", @"77"),
147 | LMGraphPointMake(CGPointMake(4, 60), @"4", @"60"),
148 | LMGraphPointMake(CGPointMake(5, 59), @"5", @"59"),
149 | LMGraphPointMake(CGPointMake(6, 40), @"6", @"40"),
150 | LMGraphPointMake(CGPointMake(7, 60), @"7", @"60"),
151 | LMGraphPointMake(CGPointMake(8, 45), @"8", @"45"),
152 | LMGraphPointMake(CGPointMake(9, 50), @"9", @"50"),
153 | LMGraphPointMake(CGPointMake(10, 70), @"10", @"70"),
154 | LMGraphPointMake(CGPointMake(11, 56), @"11", @"56"),
155 | LMGraphPointMake(CGPointMake(12, 30), @"12", @"30")];
156 | return plot1;
157 | }
158 |
159 | - (LMGraphPlot *)sampleGraphPlot2
160 | {
161 | LMGraphPlot *plot2 = [[LMGraphPlot alloc] init];
162 | plot2.strokeColor = [UIColor colorWithRed:255/255.0 green:130/255.0 blue:166/255.0 alpha:0.9];
163 | plot2.fillColor = [UIColor colorWithRed:255/255.0 green:130/255.0 blue:166/255.0 alpha:0.5];
164 | plot2.graphPointColor = [UIColor whiteColor];
165 | plot2.graphPointHighlightColor = plot2.strokeColor;
166 | plot2.graphPoints = @[LMGraphPointMake(CGPointMake(1, 25), @"1", @"25"),
167 | LMGraphPointMake(CGPointMake(2, 20), @"2", @"20"),
168 | LMGraphPointMake(CGPointMake(3, 30), @"3", @"30"),
169 | LMGraphPointMake(CGPointMake(4, 30), @"4", @"30"),
170 | LMGraphPointMake(CGPointMake(5, 35), @"5", @"35"),
171 | LMGraphPointMake(CGPointMake(6, 30), @"6", @"30"),
172 | LMGraphPointMake(CGPointMake(7, 25), @"7", @"25"),
173 | LMGraphPointMake(CGPointMake(8, 30), @"8", @"30"),
174 | LMGraphPointMake(CGPointMake(9, 40), @"9", @"40"),
175 | LMGraphPointMake(CGPointMake(10, 15), @"10", @"15"),
176 | LMGraphPointMake(CGPointMake(11, 30), @"11", @"30"),
177 | LMGraphPointMake(CGPointMake(12, 50), @"12", @"50")];
178 | return plot2;
179 | }
180 |
181 | - (LMGraphPlot *)sampleGraphPlot3
182 | {
183 | LMGraphPlot *plot3 = [[LMGraphPlot alloc] init];
184 | plot3.strokeColor = [UIColor brownColor];
185 | plot3.fillColor = [UIColor clearColor];
186 | plot3.graphPointColor = [UIColor brownColor];
187 |
188 | NSMutableArray *graphPoints = [NSMutableArray new];
189 | for (int i = 1; i <= 12; i++) {
190 | CGFloat value = rand() % 60 + 20;
191 | NSString *titleString = [NSString stringWithFormat:@"%d", i];
192 | NSString *valueString = [NSString stringWithFormat:@"%d", (int)value];
193 | LMGraphPoint *graphPoint = LMGraphPointMake(CGPointMake(i, value), titleString, valueString);
194 | [graphPoints addObject:graphPoint];
195 | }
196 | plot3.graphPoints = graphPoints;
197 |
198 | return plot3;
199 | }
200 |
201 | - (LMGraphPlot *)sampleGraphPlot4
202 | {
203 | LMGraphPlot *plot4 = [[LMGraphPlot alloc] init];
204 | plot4.graphPointSize = 0;
205 | plot4.fillCircleAroundGraphPoint = NO;
206 | plot4.strokeCircleAroundGraphPoint = NO;
207 | plot4.curveLine = YES;
208 | plot4.strokeColor = [UIColor whiteColor];
209 | plot4.fillColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.5];
210 | plot4.graphPointColor = [UIColor whiteColor];
211 | plot4.graphPointHighlightColor = [UIColor redColor];
212 |
213 | NSMutableArray *graphPoints = [NSMutableArray new];
214 | for (CGFloat i = 1; i <= 12; i += 0.25) {
215 | CGFloat value = rand() % 50 + 30;
216 | NSString *titleString = [NSString stringWithFormat:@"%.2f", i];
217 | NSString *valueString = [NSString stringWithFormat:@"%d", (int)value];
218 | LMGraphPoint *graphPoint = LMGraphPointMake(CGPointMake(i, value), titleString, valueString);
219 | [graphPoints addObject:graphPoint];
220 | }
221 | plot4.graphPoints = graphPoints;
222 |
223 | return plot4;
224 | }
225 |
226 | @end
227 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/LMGraphViewDemo/LMGraphViewDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5CB34F042022C4F700B38732 /* LMGraphLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CB34F032022C4F700B38732 /* LMGraphLayout.m */; };
11 | 5CF204901B6378D700AF1D7B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2048F1B6378D700AF1D7B /* main.m */; };
12 | 5CF204931B6378D700AF1D7B /* LMAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204921B6378D700AF1D7B /* LMAppDelegate.m */; };
13 | 5CF204961B6378D700AF1D7B /* LMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204951B6378D700AF1D7B /* LMViewController.m */; };
14 | 5CF204991B6378D700AF1D7B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5CF204971B6378D700AF1D7B /* Main.storyboard */; };
15 | 5CF2049B1B6378D700AF1D7B /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CF2049A1B6378D700AF1D7B /* Images.xcassets */; };
16 | 5CF2049E1B6378D700AF1D7B /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5CF2049C1B6378D700AF1D7B /* LaunchScreen.xib */; };
17 | 5CF204C41B637A3800AF1D7B /* LMGraphPlot.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204B81B637A3800AF1D7B /* LMGraphPlot.m */; };
18 | 5CF204C51B637A3800AF1D7B /* LMGraphPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204BA1B637A3800AF1D7B /* LMGraphPoint.m */; };
19 | 5CF204C61B637A3800AF1D7B /* LMGraphPointView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204BC1B637A3800AF1D7B /* LMGraphPointView.m */; };
20 | 5CF204C71B637A3800AF1D7B /* LMLineGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204BE1B637A3800AF1D7B /* LMLineGraphView.m */; };
21 | 5CF204C81B637A3800AF1D7B /* PopoverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5CF204C11B637A3800AF1D7B /* PopoverView.m */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | 5CB34F022022C4F700B38732 /* LMGraphLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMGraphLayout.h; sourceTree = ""; };
26 | 5CB34F032022C4F700B38732 /* LMGraphLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMGraphLayout.m; sourceTree = ""; };
27 | 5CF2048A1B6378D700AF1D7B /* LMGraphView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LMGraphView.app; sourceTree = BUILT_PRODUCTS_DIR; };
28 | 5CF2048E1B6378D700AF1D7B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 5CF2048F1B6378D700AF1D7B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
30 | 5CF204911B6378D700AF1D7B /* LMAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMAppDelegate.h; sourceTree = ""; };
31 | 5CF204921B6378D700AF1D7B /* LMAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMAppDelegate.m; sourceTree = ""; };
32 | 5CF204941B6378D700AF1D7B /* LMViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LMViewController.h; sourceTree = ""; };
33 | 5CF204951B6378D700AF1D7B /* LMViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LMViewController.m; sourceTree = ""; };
34 | 5CF204981B6378D700AF1D7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
35 | 5CF2049A1B6378D700AF1D7B /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
36 | 5CF2049D1B6378D700AF1D7B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
37 | 5CF204B71B637A3800AF1D7B /* LMGraphPlot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMGraphPlot.h; sourceTree = ""; };
38 | 5CF204B81B637A3800AF1D7B /* LMGraphPlot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMGraphPlot.m; sourceTree = ""; };
39 | 5CF204B91B637A3800AF1D7B /* LMGraphPoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMGraphPoint.h; sourceTree = ""; };
40 | 5CF204BA1B637A3800AF1D7B /* LMGraphPoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMGraphPoint.m; sourceTree = ""; };
41 | 5CF204BB1B637A3800AF1D7B /* LMGraphPointView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMGraphPointView.h; sourceTree = ""; };
42 | 5CF204BC1B637A3800AF1D7B /* LMGraphPointView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMGraphPointView.m; sourceTree = ""; };
43 | 5CF204BD1B637A3800AF1D7B /* LMLineGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMLineGraphView.h; sourceTree = ""; };
44 | 5CF204BE1B637A3800AF1D7B /* LMLineGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMLineGraphView.m; sourceTree = ""; };
45 | 5CF204C01B637A3800AF1D7B /* PopoverView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverView.h; sourceTree = ""; };
46 | 5CF204C11B637A3800AF1D7B /* PopoverView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PopoverView.m; sourceTree = ""; };
47 | 5CF204C21B637A3800AF1D7B /* PopoverView_Configuration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverView_Configuration.h; sourceTree = ""; };
48 | 5CF204C31B637A3800AF1D7B /* PopoverViewCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverViewCompatibility.h; sourceTree = ""; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | 5CF204871B6378D700AF1D7B /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | /* End PBXFrameworksBuildPhase section */
60 |
61 | /* Begin PBXGroup section */
62 | 5CF204811B6378D700AF1D7B = {
63 | isa = PBXGroup;
64 | children = (
65 | 5CF204B31B6379AA00AF1D7B /* LMGraphView */,
66 | 5CF2048C1B6378D700AF1D7B /* LMGraphViewDemo */,
67 | 5CF2048B1B6378D700AF1D7B /* Products */,
68 | );
69 | sourceTree = "";
70 | };
71 | 5CF2048B1B6378D700AF1D7B /* Products */ = {
72 | isa = PBXGroup;
73 | children = (
74 | 5CF2048A1B6378D700AF1D7B /* LMGraphView.app */,
75 | );
76 | name = Products;
77 | sourceTree = "";
78 | };
79 | 5CF2048C1B6378D700AF1D7B /* LMGraphViewDemo */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 5CF204911B6378D700AF1D7B /* LMAppDelegate.h */,
83 | 5CF204921B6378D700AF1D7B /* LMAppDelegate.m */,
84 | 5CF204941B6378D700AF1D7B /* LMViewController.h */,
85 | 5CF204951B6378D700AF1D7B /* LMViewController.m */,
86 | 5CF204971B6378D700AF1D7B /* Main.storyboard */,
87 | 5CF2049C1B6378D700AF1D7B /* LaunchScreen.xib */,
88 | 5CF2048D1B6378D700AF1D7B /* Supporting Files */,
89 | );
90 | path = LMGraphViewDemo;
91 | sourceTree = "";
92 | };
93 | 5CF2048D1B6378D700AF1D7B /* Supporting Files */ = {
94 | isa = PBXGroup;
95 | children = (
96 | 5CF2049A1B6378D700AF1D7B /* Images.xcassets */,
97 | 5CF2048E1B6378D700AF1D7B /* Info.plist */,
98 | 5CF2048F1B6378D700AF1D7B /* main.m */,
99 | );
100 | name = "Supporting Files";
101 | sourceTree = "";
102 | };
103 | 5CF204B31B6379AA00AF1D7B /* LMGraphView */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 5CF204BD1B637A3800AF1D7B /* LMLineGraphView.h */,
107 | 5CF204BE1B637A3800AF1D7B /* LMLineGraphView.m */,
108 | 5CB34F022022C4F700B38732 /* LMGraphLayout.h */,
109 | 5CB34F032022C4F700B38732 /* LMGraphLayout.m */,
110 | 5CF204B71B637A3800AF1D7B /* LMGraphPlot.h */,
111 | 5CF204B81B637A3800AF1D7B /* LMGraphPlot.m */,
112 | 5CF204B91B637A3800AF1D7B /* LMGraphPoint.h */,
113 | 5CF204BA1B637A3800AF1D7B /* LMGraphPoint.m */,
114 | 5CF204BB1B637A3800AF1D7B /* LMGraphPointView.h */,
115 | 5CF204BC1B637A3800AF1D7B /* LMGraphPointView.m */,
116 | 5CF204BF1B637A3800AF1D7B /* Vender */,
117 | );
118 | name = LMGraphView;
119 | path = ../LMGraphView;
120 | sourceTree = "";
121 | };
122 | 5CF204BF1B637A3800AF1D7B /* Vender */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 5CF204C01B637A3800AF1D7B /* PopoverView.h */,
126 | 5CF204C11B637A3800AF1D7B /* PopoverView.m */,
127 | 5CF204C21B637A3800AF1D7B /* PopoverView_Configuration.h */,
128 | 5CF204C31B637A3800AF1D7B /* PopoverViewCompatibility.h */,
129 | );
130 | path = Vender;
131 | sourceTree = "";
132 | };
133 | /* End PBXGroup section */
134 |
135 | /* Begin PBXNativeTarget section */
136 | 5CF204891B6378D700AF1D7B /* LMGraphViewDemo */ = {
137 | isa = PBXNativeTarget;
138 | buildConfigurationList = 5CF204AD1B6378D700AF1D7B /* Build configuration list for PBXNativeTarget "LMGraphViewDemo" */;
139 | buildPhases = (
140 | 5CF204861B6378D700AF1D7B /* Sources */,
141 | 5CF204871B6378D700AF1D7B /* Frameworks */,
142 | 5CF204881B6378D700AF1D7B /* Resources */,
143 | );
144 | buildRules = (
145 | );
146 | dependencies = (
147 | );
148 | name = LMGraphViewDemo;
149 | productName = LMGraphViewDemo;
150 | productReference = 5CF2048A1B6378D700AF1D7B /* LMGraphView.app */;
151 | productType = "com.apple.product-type.application";
152 | };
153 | /* End PBXNativeTarget section */
154 |
155 | /* Begin PBXProject section */
156 | 5CF204821B6378D700AF1D7B /* Project object */ = {
157 | isa = PBXProject;
158 | attributes = {
159 | LastUpgradeCheck = 0920;
160 | ORGANIZATIONNAME = LMinh;
161 | TargetAttributes = {
162 | 5CF204891B6378D700AF1D7B = {
163 | CreatedOnToolsVersion = 6.4;
164 | DevelopmentTeam = 6HXC4UA9AV;
165 | ProvisioningStyle = Automatic;
166 | };
167 | };
168 | };
169 | buildConfigurationList = 5CF204851B6378D700AF1D7B /* Build configuration list for PBXProject "LMGraphViewDemo" */;
170 | compatibilityVersion = "Xcode 3.2";
171 | developmentRegion = English;
172 | hasScannedForEncodings = 0;
173 | knownRegions = (
174 | en,
175 | Base,
176 | );
177 | mainGroup = 5CF204811B6378D700AF1D7B;
178 | productRefGroup = 5CF2048B1B6378D700AF1D7B /* Products */;
179 | projectDirPath = "";
180 | projectRoot = "";
181 | targets = (
182 | 5CF204891B6378D700AF1D7B /* LMGraphViewDemo */,
183 | );
184 | };
185 | /* End PBXProject section */
186 |
187 | /* Begin PBXResourcesBuildPhase section */
188 | 5CF204881B6378D700AF1D7B /* Resources */ = {
189 | isa = PBXResourcesBuildPhase;
190 | buildActionMask = 2147483647;
191 | files = (
192 | 5CF204991B6378D700AF1D7B /* Main.storyboard in Resources */,
193 | 5CF2049E1B6378D700AF1D7B /* LaunchScreen.xib in Resources */,
194 | 5CF2049B1B6378D700AF1D7B /* Images.xcassets in Resources */,
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | };
198 | /* End PBXResourcesBuildPhase section */
199 |
200 | /* Begin PBXSourcesBuildPhase section */
201 | 5CF204861B6378D700AF1D7B /* Sources */ = {
202 | isa = PBXSourcesBuildPhase;
203 | buildActionMask = 2147483647;
204 | files = (
205 | 5CF204C51B637A3800AF1D7B /* LMGraphPoint.m in Sources */,
206 | 5CF204961B6378D700AF1D7B /* LMViewController.m in Sources */,
207 | 5CF204C71B637A3800AF1D7B /* LMLineGraphView.m in Sources */,
208 | 5CF204C61B637A3800AF1D7B /* LMGraphPointView.m in Sources */,
209 | 5CF204931B6378D700AF1D7B /* LMAppDelegate.m in Sources */,
210 | 5CF204C41B637A3800AF1D7B /* LMGraphPlot.m in Sources */,
211 | 5CF204C81B637A3800AF1D7B /* PopoverView.m in Sources */,
212 | 5CF204901B6378D700AF1D7B /* main.m in Sources */,
213 | 5CB34F042022C4F700B38732 /* LMGraphLayout.m in Sources */,
214 | );
215 | runOnlyForDeploymentPostprocessing = 0;
216 | };
217 | /* End PBXSourcesBuildPhase section */
218 |
219 | /* Begin PBXVariantGroup section */
220 | 5CF204971B6378D700AF1D7B /* Main.storyboard */ = {
221 | isa = PBXVariantGroup;
222 | children = (
223 | 5CF204981B6378D700AF1D7B /* Base */,
224 | );
225 | name = Main.storyboard;
226 | sourceTree = "";
227 | };
228 | 5CF2049C1B6378D700AF1D7B /* LaunchScreen.xib */ = {
229 | isa = PBXVariantGroup;
230 | children = (
231 | 5CF2049D1B6378D700AF1D7B /* Base */,
232 | );
233 | name = LaunchScreen.xib;
234 | sourceTree = "";
235 | };
236 | /* End PBXVariantGroup section */
237 |
238 | /* Begin XCBuildConfiguration section */
239 | 5CF204AB1B6378D700AF1D7B /* Debug */ = {
240 | isa = XCBuildConfiguration;
241 | buildSettings = {
242 | ALWAYS_SEARCH_USER_PATHS = NO;
243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
244 | CLANG_CXX_LIBRARY = "libc++";
245 | CLANG_ENABLE_MODULES = YES;
246 | CLANG_ENABLE_OBJC_ARC = YES;
247 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
248 | CLANG_WARN_BOOL_CONVERSION = YES;
249 | CLANG_WARN_COMMA = YES;
250 | CLANG_WARN_CONSTANT_CONVERSION = YES;
251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
252 | CLANG_WARN_EMPTY_BODY = YES;
253 | CLANG_WARN_ENUM_CONVERSION = YES;
254 | CLANG_WARN_INFINITE_RECURSION = YES;
255 | CLANG_WARN_INT_CONVERSION = YES;
256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260 | CLANG_WARN_STRICT_PROTOTYPES = YES;
261 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
262 | CLANG_WARN_UNREACHABLE_CODE = YES;
263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
265 | COPY_PHASE_STRIP = NO;
266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
267 | ENABLE_STRICT_OBJC_MSGSEND = YES;
268 | ENABLE_TESTABILITY = YES;
269 | GCC_C_LANGUAGE_STANDARD = gnu99;
270 | GCC_DYNAMIC_NO_PIC = NO;
271 | GCC_NO_COMMON_BLOCKS = YES;
272 | GCC_OPTIMIZATION_LEVEL = 0;
273 | GCC_PREPROCESSOR_DEFINITIONS = (
274 | "DEBUG=1",
275 | "$(inherited)",
276 | );
277 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280 | GCC_WARN_UNDECLARED_SELECTOR = YES;
281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282 | GCC_WARN_UNUSED_FUNCTION = YES;
283 | GCC_WARN_UNUSED_VARIABLE = YES;
284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
285 | MTL_ENABLE_DEBUG_INFO = YES;
286 | ONLY_ACTIVE_ARCH = YES;
287 | SDKROOT = iphoneos;
288 | };
289 | name = Debug;
290 | };
291 | 5CF204AC1B6378D700AF1D7B /* Release */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ALWAYS_SEARCH_USER_PATHS = NO;
295 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
296 | CLANG_CXX_LIBRARY = "libc++";
297 | CLANG_ENABLE_MODULES = YES;
298 | CLANG_ENABLE_OBJC_ARC = YES;
299 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
300 | CLANG_WARN_BOOL_CONVERSION = YES;
301 | CLANG_WARN_COMMA = YES;
302 | CLANG_WARN_CONSTANT_CONVERSION = YES;
303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
304 | CLANG_WARN_EMPTY_BODY = YES;
305 | CLANG_WARN_ENUM_CONVERSION = YES;
306 | CLANG_WARN_INFINITE_RECURSION = YES;
307 | CLANG_WARN_INT_CONVERSION = YES;
308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
309 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
311 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
312 | CLANG_WARN_STRICT_PROTOTYPES = YES;
313 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
314 | CLANG_WARN_UNREACHABLE_CODE = YES;
315 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
316 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
317 | COPY_PHASE_STRIP = NO;
318 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
319 | ENABLE_NS_ASSERTIONS = NO;
320 | ENABLE_STRICT_OBJC_MSGSEND = YES;
321 | GCC_C_LANGUAGE_STANDARD = gnu99;
322 | GCC_NO_COMMON_BLOCKS = YES;
323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
324 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
325 | GCC_WARN_UNDECLARED_SELECTOR = YES;
326 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
327 | GCC_WARN_UNUSED_FUNCTION = YES;
328 | GCC_WARN_UNUSED_VARIABLE = YES;
329 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
330 | MTL_ENABLE_DEBUG_INFO = NO;
331 | SDKROOT = iphoneos;
332 | VALIDATE_PRODUCT = YES;
333 | };
334 | name = Release;
335 | };
336 | 5CF204AE1B6378D700AF1D7B /* Debug */ = {
337 | isa = XCBuildConfiguration;
338 | buildSettings = {
339 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
341 | CODE_SIGN_STYLE = Automatic;
342 | DEVELOPMENT_TEAM = 6HXC4UA9AV;
343 | INFOPLIST_FILE = LMGraphViewDemo/Info.plist;
344 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
345 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
346 | PRODUCT_BUNDLE_IDENTIFIER = "com.lminhtm.$(PRODUCT_NAME:rfc1034identifier)";
347 | PRODUCT_NAME = LMGraphView;
348 | PROVISIONING_PROFILE_SPECIFIER = "";
349 | };
350 | name = Debug;
351 | };
352 | 5CF204AF1B6378D700AF1D7B /* Release */ = {
353 | isa = XCBuildConfiguration;
354 | buildSettings = {
355 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
357 | CODE_SIGN_STYLE = Automatic;
358 | DEVELOPMENT_TEAM = 6HXC4UA9AV;
359 | INFOPLIST_FILE = LMGraphViewDemo/Info.plist;
360 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
361 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
362 | PRODUCT_BUNDLE_IDENTIFIER = "com.lminhtm.$(PRODUCT_NAME:rfc1034identifier)";
363 | PRODUCT_NAME = LMGraphView;
364 | PROVISIONING_PROFILE_SPECIFIER = "";
365 | };
366 | name = Release;
367 | };
368 | /* End XCBuildConfiguration section */
369 |
370 | /* Begin XCConfigurationList section */
371 | 5CF204851B6378D700AF1D7B /* Build configuration list for PBXProject "LMGraphViewDemo" */ = {
372 | isa = XCConfigurationList;
373 | buildConfigurations = (
374 | 5CF204AB1B6378D700AF1D7B /* Debug */,
375 | 5CF204AC1B6378D700AF1D7B /* Release */,
376 | );
377 | defaultConfigurationIsVisible = 0;
378 | defaultConfigurationName = Release;
379 | };
380 | 5CF204AD1B6378D700AF1D7B /* Build configuration list for PBXNativeTarget "LMGraphViewDemo" */ = {
381 | isa = XCConfigurationList;
382 | buildConfigurations = (
383 | 5CF204AE1B6378D700AF1D7B /* Debug */,
384 | 5CF204AF1B6378D700AF1D7B /* Release */,
385 | );
386 | defaultConfigurationIsVisible = 0;
387 | defaultConfigurationName = Release;
388 | };
389 | /* End XCConfigurationList section */
390 | };
391 | rootObject = 5CF204821B6378D700AF1D7B /* Project object */;
392 | }
393 |
--------------------------------------------------------------------------------
/LMGraphView/LMLineGraphView.m:
--------------------------------------------------------------------------------
1 | //
2 | // LMLineGraphView.m
3 | // LMGraphView
4 | //
5 | // Created by LMinh on 17/09/2014.
6 | // Copyright (c) 2014 LMinh. All rights reserved.
7 | //
8 |
9 | #import "LMLineGraphView.h"
10 |
11 | #define DEBUG_MODE 0
12 | #define graphPointInPx(a) [self graphPointInPxFromGraphPoint:a]
13 |
14 | // RECT
15 | #define frx(a) (a.frame.origin.x)
16 | #define fry(a) (a.frame.origin.y)
17 | #define maxfry(a) (a.frame.origin.y) + (a.frame.size.height)
18 | #define maxfrx(a) (a.frame.origin.x) + (a.frame.size.width)
19 | #define midx(a) (CGRectGetMidX(a.frame))
20 | #define midy(a) (CGRectGetMidY(a.frame))
21 | #define W(a) (a.frame.size.width)
22 | #define H(a) (a.frame.size.height)
23 | #define FULL(a) (CGRectMake(0, 0, a.frame.size.width, a.frame.size.height))
24 | #define below(a) (a.frame.origin.y) + (a.frame.size.height)
25 |
26 | @interface LMLineGraphView ()
27 |
28 | @property (nonatomic, strong) LMGraphPoint *currentGraphPoint;
29 | @property (nonatomic, assign) BOOL isMovement;
30 |
31 | @property (nonatomic, assign) BOOL validGraphData;
32 |
33 | @property (nonatomic, assign) CGFloat xAxisMaxValue;
34 |
35 | @property (nonatomic, strong) NSArray *yAxisValues;
36 | @property (nonatomic, assign) CGFloat yAxisInterval;
37 | @property (nonatomic, assign) CGFloat yAxisIntervalInPx;
38 |
39 | @property (nonatomic, strong) UIScrollView *contentScrollView;
40 | @property (nonatomic, strong) UIView *graphContentView;
41 | @property (nonatomic, strong) UIView *xAxisLabelsContainerView;
42 | @property (nonatomic, strong) UIView *yAxisLabelsContainerView;
43 | @property (nonatomic, strong) UILabel *yAxisUnitLabel;
44 | @property (nonatomic, strong) UILabel *titleLabel;
45 | @property (nonatomic, strong) UILabel *noDataTextLabel;
46 |
47 | @property (nonatomic, strong) CAShapeLayer *xAxisZeroLayer;
48 | @property (nonatomic, strong) CAShapeLayer *xAxisGridLayer;
49 | @property (nonatomic, strong) CAShapeLayer *yAxisZeroLayer;
50 | @property (nonatomic, strong) CAShapeLayer *yAxisGridLayer;
51 | @property (nonatomic, strong) CAShapeLayer *touchLayer;
52 | @property (nonatomic, strong) NSMutableArray *backgroundPlotLayers;
53 | @property (nonatomic, strong) NSMutableArray *graphPlotLayers;
54 |
55 | @end
56 |
57 | @implementation LMLineGraphView
58 |
59 | #pragma mark - INIT
60 |
61 | - (id)initWithFrame:(CGRect)frame
62 | {
63 | self = [super initWithFrame:frame];
64 | if (self) {
65 | [self initialize];
66 | }
67 | return self;
68 | }
69 |
70 | - (id)initWithCoder:(NSCoder *)aDecoder
71 | {
72 | self = [super initWithCoder:aDecoder];
73 | if (self) {
74 | [self initialize];
75 | }
76 | return self;
77 | }
78 |
79 | - (void)initialize
80 | {
81 | _xAxisInterval = 1;
82 | _yAxisMinValue = 0;
83 | _xAxisStartGraphPoint = 0;
84 |
85 | _layout = [LMGraphLayout new];
86 |
87 | UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
88 | [self addGestureRecognizer:tapGesture];
89 | }
90 |
91 |
92 | #pragma mark - LAYOUT
93 |
94 | - (void)layoutSubviews
95 | {
96 | [super layoutSubviews];
97 |
98 | // Setup content views
99 | [self setupContentViews];
100 |
101 | // Redraw
102 | [self setNeedsDisplay];
103 | }
104 |
105 | - (void)setupContentViews
106 | {
107 | // Calculate frame
108 | CGFloat contentSizeWidth;
109 | if (self.layout.xAxisScrollableOnly)
110 | {
111 | contentSizeWidth = self.xAxisMaxValue * self.layout.xAxisIntervalInPx - self.layout.xAxisMargin + MAX(self.layout.xAxisMargin, self.layout.xAxisIntervalInPx/2);
112 | }
113 | else
114 | {
115 | contentSizeWidth = W(self) - self.layout.xAxisMargin;
116 | }
117 |
118 | // Title Label
119 | if (!self.titleLabel) {
120 | self.titleLabel = [[UILabel alloc] init];
121 | #if DEBUG_MODE
122 | self.titleLabel.backgroundColor = [UIColor redColor];
123 | #else
124 | self.titleLabel.backgroundColor = [UIColor clearColor];
125 | #endif
126 | self.titleLabel.textAlignment = NSTextAlignmentCenter;
127 | [self addSubview:self.titleLabel];
128 | }
129 | self.titleLabel.frame = CGRectMake(0,
130 | 5,
131 | W(self),
132 | self.title ? self.layout.titleLabelHeight : 0);
133 | self.titleLabel.font = self.layout.titleLabelFont;
134 | self.titleLabel.textColor = self.layout.titleLabelColor;
135 | self.titleLabel.text = self.title;
136 | self.titleLabel.hidden = (self.title == nil);
137 |
138 | // Y Axis Unit Label
139 | if (!self.yAxisUnitLabel) {
140 | self.yAxisUnitLabel = [[UILabel alloc] init];
141 | #if DEBUG_MODE
142 | self.yAxisUnitLabel.backgroundColor = [UIColor magentaColor];
143 | #else
144 | self.yAxisUnitLabel.backgroundColor = [UIColor clearColor];
145 | #endif
146 | self.yAxisUnitLabel.textAlignment = NSTextAlignmentLeft;
147 | [self addSubview:self.yAxisUnitLabel];
148 | }
149 | self.yAxisUnitLabel.frame = CGRectMake(self.layout.yAxisLabelWidth,
150 | maxfry(self.titleLabel),
151 | 100,
152 | self.yAxisUnit ? self.layout.yAxisLabelHeight : 0);
153 | self.yAxisUnitLabel.font = self.layout.yAxisUnitLabelFont;
154 | self.yAxisUnitLabel.textColor = self.layout.yAxisUnitLabelColor;
155 | self.yAxisUnitLabel.text = self.yAxisUnit;
156 | self.yAxisUnitLabel.hidden = (self.yAxisUnit == nil);
157 |
158 | // Content Scroll View
159 | if (!self.contentScrollView) {
160 | self.contentScrollView = [[UIScrollView alloc] init];
161 | #if DEBUG_MODE
162 | self.contentScrollView.backgroundColor = [UIColor darkTextColor];
163 | #else
164 | self.contentScrollView.backgroundColor = [UIColor clearColor];
165 | #endif
166 | self.contentScrollView.showsHorizontalScrollIndicator = NO;
167 | self.contentScrollView.showsVerticalScrollIndicator = NO;
168 | self.contentScrollView.userInteractionEnabled = self.layout.xAxisScrollableOnly;
169 | self.contentScrollView.clipsToBounds = self.layout.xAxisScrollableOnly;
170 | [self addSubview:self.contentScrollView];
171 | }
172 | CGFloat contentX = self.layout.xAxisScrollableOnly ? self.layout.yAxisLabelWidth : 0;
173 | CGFloat contentY = maxfry(self.yAxisUnitLabel);
174 | self.contentScrollView.frame = CGRectMake(contentX,
175 | contentY,
176 | W(self) - contentX - self.layout.xAxisMargin,
177 | H(self) - contentY);
178 | self.contentScrollView.contentSize = CGSizeMake(contentSizeWidth, H(self.contentScrollView));
179 |
180 | // Graph Content View
181 | if (!self.graphContentView) {
182 | self.graphContentView = [[UIView alloc] init];
183 | #if DEBUG_MODE
184 | self.graphContentView.backgroundColor = [UIColor brownColor];
185 | #else
186 | self.graphContentView.backgroundColor = [UIColor clearColor];
187 | #endif
188 | [self.contentScrollView addSubview:self.graphContentView];
189 | }
190 | contentX = self.layout.xAxisScrollableOnly ? 0 : self.layout.yAxisLabelWidth;
191 | self.graphContentView.frame = CGRectMake(contentX,
192 | 0,
193 | self.contentScrollView.contentSize.width - contentX,
194 | H(self.contentScrollView) - self.layout.xAxisLabelHeight);
195 |
196 | // noDataText Label
197 | if (!self.noDataTextLabel) {
198 | self.noDataTextLabel = [[UILabel alloc] init];
199 | #if DEBUG_MODE
200 | self.noDataTextLabel.backgroundColor = [UIColor magentaColor];
201 | #else
202 | self.noDataTextLabel.backgroundColor = [UIColor clearColor];
203 | #endif
204 | self.noDataTextLabel.textAlignment = NSTextAlignmentCenter;
205 | [self addSubview:self.noDataTextLabel];
206 | }
207 | self.noDataTextLabel.frame = CGRectMake(0,
208 | fry(self.contentScrollView),
209 | W(self),
210 | H(self) - fry(self.contentScrollView));
211 | self.noDataTextLabel.font = self.layout.yAxisUnitLabelFont;
212 | self.noDataTextLabel.textColor = self.layout.yAxisUnitLabelColor;
213 | self.noDataTextLabel.text = self.noDataText;
214 | self.noDataTextLabel.hidden = YES;
215 | }
216 |
217 |
218 | #pragma mark - DRAWING
219 |
220 | - (void)drawRect:(CGRect)rect
221 | {
222 | // Calculate graph data
223 | [self calculateGraphData];
224 |
225 | // Check valid data
226 | if (self.validGraphData)
227 | {
228 | self.noDataTextLabel.hidden = YES;
229 | self.yAxisUnitLabel.hidden = (self.yAxisUnit == nil);
230 |
231 | // Draw XY Axis
232 | [self drawXYAxis];
233 |
234 | // Draw plots
235 | [self drawPlots];
236 | }
237 | else
238 | {
239 | self.noDataTextLabel.hidden = NO;
240 | self.yAxisUnitLabel.hidden = YES;
241 | }
242 | }
243 |
244 | - (void)drawXYAxis
245 | {
246 | // X Axis Labels
247 | [self drawXAxisLabels];
248 |
249 | // X Axis Line
250 | if (!self.layout.xAxisZeroHidden) {
251 | [self drawXAsisZero];
252 | }
253 |
254 | // X Axis Grid
255 | if (!self.layout.xAxisGridHidden) {
256 | [self drawXAxisGrid];
257 | }
258 |
259 | // Y Axis Labels
260 | [self drawYAxisLabels];
261 |
262 | // Y Axis Line
263 | if (!self.layout.yAxisZeroHidden) {
264 | [self drawYAsisZero];
265 | }
266 |
267 | // Y Axis Grid
268 | if (!self.layout.yAxisGridHidden) {
269 | [self drawYAxisGrid];
270 | }
271 | }
272 |
273 | - (void)drawXAxisLabels
274 | {
275 | // xAxisLabels Container View
276 | if (!self.xAxisLabelsContainerView) {
277 | self.xAxisLabelsContainerView = [[UIView alloc] init];
278 | #if DEBUG_MODE
279 | self.xAxisLabelsContainerView.backgroundColor = [UIColor blueColor];
280 | #else
281 | self.xAxisLabelsContainerView.backgroundColor = [UIColor clearColor];
282 | #endif
283 | [self.contentScrollView addSubview:self.xAxisLabelsContainerView];
284 | }
285 | CGFloat contentX = self.layout.xAxisScrollableOnly ? 0 : self.layout.yAxisLabelWidth;
286 | self.xAxisLabelsContainerView.frame = CGRectMake(contentX,
287 | H(self.contentScrollView) - self.layout.xAxisLabelHeight,
288 | self.contentScrollView.contentSize.width - contentX,
289 | self.layout.xAxisLabelHeight);
290 | [[self.xAxisLabelsContainerView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
291 |
292 | // xAxisLabels
293 | if (!self.isMovement)
294 | {
295 | for (NSDictionary *xAxisValueDict in self.xAxisValues)
296 | {
297 | __block NSNumber *xAxisValue = nil;
298 | __block NSString *xAxisTitle = nil;
299 | [xAxisValueDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
300 | xAxisValue = (NSNumber *)key;
301 | xAxisTitle = (NSString *)obj;
302 | }];
303 |
304 | // Create xAxisLabel
305 | CGPoint graphPointInPx = graphPointInPx(CGPointMake([xAxisValue floatValue], 0));
306 | graphPointInPx = [self.xAxisLabelsContainerView convertPoint:graphPointInPx fromView:self.graphContentView];
307 | CGFloat labelWidth = [xAxisTitle boundingRectWithSize:CGSizeMake(MAXFLOAT, self.layout.xAxisLabelHeight)
308 | options:NSStringDrawingUsesLineFragmentOrigin
309 | attributes:@{NSFontAttributeName:self.layout.xAxisLabelFont}
310 | context:nil].size.width;
311 | labelWidth = MAX(MIN(labelWidth, 150), self.layout.xAxisIntervalInPx);
312 | UILabel *xAxisLabel = [[UILabel alloc] initWithFrame:CGRectMake(graphPointInPx.x - labelWidth/2,
313 | 0,
314 | labelWidth,
315 | self.layout.xAxisLabelHeight)];
316 | xAxisLabel.backgroundColor = [UIColor clearColor];
317 | xAxisLabel.font = self.layout.xAxisLabelFont;
318 | xAxisLabel.textColor = self.layout.xAxisLabelColor;
319 | xAxisLabel.textAlignment = NSTextAlignmentCenter;
320 | xAxisLabel.text = xAxisTitle;
321 | xAxisLabel.numberOfLines = 2;
322 | [self.xAxisLabelsContainerView addSubview:xAxisLabel];
323 | }
324 | }
325 | else
326 | {
327 | NSNumber *xAxisValue = @(self.currentGraphPoint.point.x);
328 | NSString *xAxisTitle = self.currentGraphPoint.title;
329 |
330 | // Create xAxisLabel
331 | CGPoint graphPointInPx = graphPointInPx(CGPointMake([xAxisValue floatValue], 0));
332 | graphPointInPx = [self.xAxisLabelsContainerView convertPoint:graphPointInPx fromView:self.graphContentView];
333 | CGFloat labelWidth = [xAxisTitle boundingRectWithSize:CGSizeMake(MAXFLOAT, self.layout.xAxisLabelHeight)
334 | options:NSStringDrawingUsesLineFragmentOrigin
335 | attributes:@{NSFontAttributeName:self.layout.xAxisLabelFont}
336 | context:nil].size.width;
337 | labelWidth = MAX(MIN(labelWidth, 150), self.layout.xAxisIntervalInPx);
338 |
339 | CGFloat originX = graphPointInPx.x - labelWidth/2;
340 | originX = MIN(MAX(originX, 0), W(self.xAxisLabelsContainerView) - labelWidth);
341 | UILabel *xAxisLabel = [[UILabel alloc] initWithFrame:CGRectMake(originX,
342 | 0,
343 | labelWidth,
344 | self.layout.xAxisLabelHeight)];
345 | xAxisLabel.backgroundColor = [UIColor clearColor];
346 | xAxisLabel.font = self.layout.xAxisLabelFont;
347 | xAxisLabel.textColor = self.layout.xAxisLabelColor;
348 | xAxisLabel.textAlignment = NSTextAlignmentCenter;
349 | xAxisLabel.text = xAxisTitle;
350 | xAxisLabel.numberOfLines = 2;
351 | [self.xAxisLabelsContainerView addSubview:xAxisLabel];
352 | }
353 | }
354 |
355 | - (void)drawYAxisLabels
356 | {
357 | // yAxisLabels Container View
358 | if (!self.yAxisLabelsContainerView) {
359 | self.yAxisLabelsContainerView = [[UIView alloc] init];
360 | #if DEBUG_MODE
361 | self.yAxisLabelsContainerView.backgroundColor = [UIColor greenColor];
362 | #else
363 | self.yAxisLabelsContainerView.backgroundColor = [UIColor clearColor];
364 | #endif
365 | if (self.layout.xAxisScrollableOnly) {
366 | [self addSubview:self.yAxisLabelsContainerView];
367 | }
368 | else {
369 | [self.contentScrollView insertSubview:self.yAxisLabelsContainerView belowSubview:self.graphContentView];
370 | }
371 | }
372 | CGFloat positionY = self.layout.xAxisScrollableOnly ? maxfry(self.yAxisUnitLabel) : 0;
373 | self.yAxisLabelsContainerView.frame = CGRectMake(0,
374 | positionY,
375 | self.layout.yAxisLabelWidth,
376 | H(self.yAxisLabelsContainerView.superview) - positionY);
377 | [[self.yAxisLabelsContainerView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
378 |
379 | // yAxisLabels
380 | for (NSDictionary *yAxisValueDict in self.yAxisValues)
381 | {
382 | __block NSNumber *yAxisValue = nil;
383 | __block NSString *yAxisTitle = nil;
384 | [yAxisValueDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
385 | yAxisValue = (NSNumber *)key;
386 | yAxisTitle = (NSString *)obj;
387 | }];
388 |
389 | // Create yAxisLabel
390 | CGPoint graphPointInPx = graphPointInPx(CGPointMake(0, [yAxisValue floatValue]));
391 | graphPointInPx = [self.yAxisLabelsContainerView convertPoint:graphPointInPx fromView:self.graphContentView];
392 | UILabel *yAxisLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,
393 | graphPointInPx.y - self.layout.yAxisLabelHeight/2,
394 | self.layout.yAxisLabelWidth - 5,
395 | self.layout.yAxisLabelHeight)];
396 | yAxisLabel.backgroundColor = [UIColor clearColor];
397 | yAxisLabel.font = self.layout.yAxisLabelFont;
398 | yAxisLabel.textColor = self.layout.yAxisLabelColor;
399 | yAxisLabel.textAlignment = NSTextAlignmentRight;
400 | yAxisLabel.text = yAxisTitle;
401 | yAxisLabel.adjustsFontSizeToFitWidth = YES;
402 | yAxisLabel.minimumScaleFactor = 0.5;
403 | [self.yAxisLabelsContainerView addSubview:yAxisLabel];
404 | }
405 | }
406 |
407 | - (void)drawXAsisZero
408 | {
409 | // Create xAxisZeroPath
410 | CGMutablePathRef xAxisZeroPath = CGPathCreateMutable();
411 |
412 | CGPoint zeroPoint = graphPointInPx(CGPointMake(0, self.yAxisMinValue));
413 | CGPathMoveToPoint(xAxisZeroPath, NULL, 0, zeroPoint.y);
414 | CGPathAddLineToPoint(xAxisZeroPath, NULL, W(self.graphContentView), zeroPoint.y);
415 |
416 | // Create xAxisZeroLayer
417 | if (!self.xAxisZeroLayer) {
418 | self.xAxisZeroLayer = [CAShapeLayer layer];
419 | self.xAxisZeroLayer.fillColor = [UIColor clearColor].CGColor;
420 | self.xAxisZeroLayer.backgroundColor = [UIColor clearColor].CGColor;
421 | [self.graphContentView.layer addSublayer:self.xAxisZeroLayer];
422 | }
423 | self.xAxisZeroLayer.frame = self.xAxisZeroLayer.superlayer.bounds;
424 | self.xAxisZeroLayer.lineWidth = self.layout.xAxisLinesWidth;
425 | self.xAxisZeroLayer.strokeColor = self.layout.xAxisLinesStrokeColor.CGColor;
426 | self.xAxisZeroLayer.lineDashPattern = self.layout.xAxisZeroDashLine ? @[@(2), @(1)] : nil;
427 | self.xAxisZeroLayer.path = xAxisZeroPath;
428 | CGPathRelease(xAxisZeroPath);
429 | }
430 |
431 | - (void)drawYAsisZero
432 | {
433 | // Create yAxisZeroPath
434 | CGMutablePathRef yAxisZeroPath = CGPathCreateMutable();
435 |
436 | UIView *superView = self.layout.xAxisScrollableOnly ? self : self.graphContentView;
437 | CGPoint zeroPoint = [superView convertPoint:CGPointZero fromView:self.graphContentView];
438 | CGPathMoveToPoint(yAxisZeroPath, NULL, zeroPoint.x + self.layout.xAxisLinesWidth/2, zeroPoint.y);
439 | CGPathAddLineToPoint(yAxisZeroPath, NULL, zeroPoint.x + self.layout.xAxisLinesWidth/2, zeroPoint.y + H(self.graphContentView));
440 |
441 | // Create yAxisZeroLayer
442 | if (!self.yAxisZeroLayer) {
443 | self.yAxisZeroLayer = [CAShapeLayer layer];
444 | self.yAxisZeroLayer.fillColor = [UIColor clearColor].CGColor;
445 | self.yAxisZeroLayer.backgroundColor = [UIColor clearColor].CGColor;
446 | [superView.layer addSublayer:self.yAxisZeroLayer];
447 | }
448 | self.yAxisZeroLayer.frame = self.yAxisZeroLayer.superlayer.bounds;
449 | self.yAxisZeroLayer.lineWidth = self.layout.yAxisLinesWidth;
450 | self.yAxisZeroLayer.strokeColor = self.layout.yAxisLinesStrokeColor.CGColor;
451 | self.yAxisZeroLayer.lineDashPattern = self.layout.yAxisZeroDashLine ? @[@(2), @(1)] : nil;
452 | self.yAxisZeroLayer.path = yAxisZeroPath;
453 | CGPathRelease(yAxisZeroPath);
454 | }
455 |
456 | - (void)drawXAxisGrid
457 | {
458 | // Create xAxisGridPath
459 | CGMutablePathRef xAxisGridPath = CGPathCreateMutable();
460 |
461 | for (NSDictionary *xAxisValueDict in self.yAxisValues)
462 | {
463 | __block NSNumber *xAxisValue = nil;
464 | [xAxisValueDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
465 | xAxisValue = (NSNumber *)key;
466 | }];
467 |
468 | CGPoint convertedPoint = graphPointInPx(CGPointMake(0, [xAxisValue floatValue]));
469 | CGPathMoveToPoint(xAxisGridPath, NULL, 0, convertedPoint.y);
470 | CGPathAddLineToPoint(xAxisGridPath, NULL, W(self.graphContentView), convertedPoint.y);
471 | }
472 |
473 | // Create xAxisGridLayer
474 | if (!self.xAxisGridLayer) {
475 | self.xAxisGridLayer = [CAShapeLayer layer];
476 | self.xAxisGridLayer.fillColor = [UIColor clearColor].CGColor;
477 | self.xAxisGridLayer.backgroundColor = [UIColor clearColor].CGColor;
478 | [self.graphContentView.layer addSublayer:self.xAxisGridLayer];
479 | }
480 | self.xAxisGridLayer.frame = self.graphContentView.bounds;
481 | self.xAxisGridLayer.lineWidth = self.layout.xAxisLinesWidth;
482 | self.xAxisGridLayer.strokeColor = self.layout.xAxisLinesStrokeColor.CGColor;
483 | self.xAxisGridLayer.lineDashPattern = self.layout.xAxisGridDashLine ? @[@(2), @(1)] : nil;
484 | self.xAxisGridLayer.path = xAxisGridPath;
485 | CGPathRelease(xAxisGridPath);
486 | }
487 |
488 | - (void)drawYAxisGrid
489 | {
490 | // Create xAxisGridPath
491 | CGMutablePathRef yAxisGridPath = CGPathCreateMutable();
492 |
493 | CGPoint leftPoint = CGPointZero;
494 | CGPathMoveToPoint(yAxisGridPath, NULL, leftPoint.x + self.layout.xAxisLinesWidth/2, 0);
495 | CGPathAddLineToPoint(yAxisGridPath, NULL, leftPoint.x + self.layout.xAxisLinesWidth/2, H(self.graphContentView));
496 |
497 | for (NSDictionary *yAxisValueDict in self.xAxisValues)
498 | {
499 | __block NSNumber *yAxisValue = nil;
500 | [yAxisValueDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
501 | yAxisValue = (NSNumber *)key;
502 | }];
503 |
504 | CGPoint convertedPoint = graphPointInPx(CGPointMake([yAxisValue floatValue], 0));
505 | CGPathMoveToPoint(yAxisGridPath, NULL, convertedPoint.x, 0);
506 | CGPathAddLineToPoint(yAxisGridPath, NULL, convertedPoint.x, H(self.graphContentView));
507 | }
508 |
509 | if (self.layout.xAxisScrollableOnly) {
510 | CGPoint rightPoint = CGPointMake(W(self.graphContentView) - self.layout.xAxisLinesWidth, 0);
511 | CGPathMoveToPoint(yAxisGridPath, NULL, rightPoint.x + self.layout.xAxisLinesWidth/2, 0);
512 | CGPathAddLineToPoint(yAxisGridPath, NULL, rightPoint.x + self.layout.xAxisLinesWidth/2, H(self.graphContentView));
513 | }
514 |
515 | // Create yAxisGridLayer
516 | if (!self.yAxisGridLayer) {
517 | self.yAxisGridLayer = [CAShapeLayer layer];
518 | self.yAxisGridLayer.fillColor = [UIColor clearColor].CGColor;
519 | self.yAxisGridLayer.backgroundColor = [UIColor clearColor].CGColor;
520 | [self.graphContentView.layer addSublayer:self.yAxisGridLayer];
521 | }
522 | self.yAxisGridLayer.frame = self.graphContentView.bounds;
523 | self.yAxisGridLayer.lineWidth = self.layout.yAxisLinesWidth;
524 | self.yAxisGridLayer.strokeColor = self.layout.yAxisLinesStrokeColor.CGColor;
525 | self.yAxisGridLayer.lineDashPattern = self.layout.yAxisGridDashLine ? @[@(2), @(1)] : nil;
526 | self.yAxisGridLayer.path = yAxisGridPath;
527 | CGPathRelease(yAxisGridPath);
528 | }
529 |
530 | - (void)drawPlots
531 | {
532 | // Remove previous plot content views and layers
533 | [[self.graphContentView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
534 | NSMutableArray *layersToRemove = [NSMutableArray new];
535 | for (CALayer *layer in self.graphContentView.layer.sublayers)
536 | {
537 | BOOL canRemove = [layer.name isEqualToString:@"backgroundPlotLayer"] || [layer.name isEqualToString:@"linePlotLayer"];
538 | if (canRemove) {
539 | [layersToRemove addObject:layer];
540 | }
541 | }
542 | for (CALayer *layer in layersToRemove) {
543 | [layer removeFromSuperlayer];
544 | }
545 |
546 | // Start to draw
547 | for (LMGraphPlot *graphPlot in self.graphPlots)
548 | {
549 | // Create Background Plot & Line Plot Path
550 | CGMutablePathRef backgroundPlotPath = CGPathCreateMutable();
551 | CGMutablePathRef linePlotPath = CGPathCreateMutable();
552 | for (int i = 0; i < graphPlot.graphPoints.count; i++)
553 | {
554 | LMGraphPoint *graphPoint = [graphPlot.graphPoints objectAtIndex:i];
555 | CGPoint convertedPoint = graphPointInPx(graphPoint.point);
556 | LMGraphPoint *preGraphPoint = graphPoint;
557 | CGPoint convertedPrePoint = convertedPoint;
558 | if (i > 0) {
559 | preGraphPoint = [graphPlot.graphPoints objectAtIndex:i-1];
560 | convertedPrePoint = graphPointInPx(preGraphPoint.point);
561 | }
562 |
563 | // Move to zero start point
564 | if (i == 0) {
565 | CGPathMoveToPoint(backgroundPlotPath, NULL, 0, self.layout.startPlotFromZero ? H(self.graphContentView) : convertedPoint.y);
566 | CGPathMoveToPoint(linePlotPath, NULL, 0, self.layout.startPlotFromZero ? H(self.graphContentView) : convertedPoint.y);
567 | }
568 |
569 | if (graphPlot.curveLine)
570 | {
571 | // Draw curve line
572 | CGPoint midPoint = midPointForPoints(convertedPrePoint, convertedPoint);
573 | CGPoint cp1 = controlPointForPoints(midPoint, convertedPrePoint);
574 | CGPoint cp2 = controlPointForPoints(midPoint, convertedPoint);
575 | CGPathAddQuadCurveToPoint(backgroundPlotPath, NULL, cp1.x, cp1.y, midPoint.x, midPoint.y);
576 | CGPathAddQuadCurveToPoint(backgroundPlotPath, NULL, cp2.x, cp2.y, convertedPoint.x, convertedPoint.y);
577 | CGPathAddQuadCurveToPoint(linePlotPath, NULL, cp1.x, cp1.y, midPoint.x, midPoint.y);
578 | CGPathAddQuadCurveToPoint(linePlotPath, NULL, cp2.x, cp2.y, convertedPoint.x, convertedPoint.y);
579 | }
580 | else
581 | {
582 | // Draw line to graph point
583 | CGPathAddLineToPoint(backgroundPlotPath, NULL, convertedPoint.x, convertedPoint.y);
584 | CGPathAddLineToPoint(linePlotPath, NULL, convertedPoint.x, convertedPoint.y);
585 | }
586 |
587 | // Draw last line
588 | if (i == graphPlot.graphPoints.count - 1) {
589 | CGPathAddLineToPoint(backgroundPlotPath, NULL, W(self.graphContentView), convertedPoint.y);
590 | CGPathAddLineToPoint(linePlotPath, NULL, W(self.graphContentView), convertedPoint.y);
591 |
592 | // Close subpath of backgroundPlotPath
593 | CGPathAddLineToPoint(backgroundPlotPath, NULL, W(self.graphContentView), H(self.graphContentView));
594 | CGPathAddLineToPoint(backgroundPlotPath, NULL, 0, H(self.graphContentView));
595 | CGPathCloseSubpath(backgroundPlotPath);
596 | }
597 | }
598 |
599 | // Create Background Plot Layer
600 | CAShapeLayer *backgroundPlotLayer = [CAShapeLayer layer];
601 | backgroundPlotLayer.name = @"backgroundPlotLayer";
602 | backgroundPlotLayer.frame = self.graphContentView.bounds;
603 | backgroundPlotLayer.fillColor = graphPlot.fillColor.CGColor;
604 | backgroundPlotLayer.backgroundColor = [UIColor clearColor].CGColor;
605 | backgroundPlotLayer.strokeColor = [UIColor clearColor].CGColor;
606 | [self.graphContentView.layer addSublayer:backgroundPlotLayer];
607 |
608 | // Create Line Plot Layer
609 | CAShapeLayer *linePlotLayer = [CAShapeLayer layer];
610 | linePlotLayer.name = @"linePlotLayer";
611 | linePlotLayer.frame = self.graphContentView.bounds;
612 | linePlotLayer.fillColor = [UIColor clearColor].CGColor;
613 | linePlotLayer.backgroundColor = [UIColor clearColor].CGColor;
614 | linePlotLayer.strokeColor = graphPlot.strokeColor.CGColor;
615 | linePlotLayer.lineWidth = graphPlot.lineWidth;
616 | [self.graphContentView.layer addSublayer:linePlotLayer];
617 |
618 | // Draw graph points
619 | if (graphPlot.graphPointSize > 0)
620 | {
621 | for (int i = 0; i < graphPlot.graphPoints.count; i++)
622 | {
623 | LMGraphPoint *graphPoint = [graphPlot.graphPoints objectAtIndex:i];
624 | CGPoint convertedPoint = graphPointInPx(graphPoint.point);
625 | CGRect frame = CGRectMake(convertedPoint.x - graphPlot.graphPointSize/2.0,
626 | convertedPoint.y - graphPlot.graphPointSize/2.0,
627 | graphPlot.graphPointSize,
628 | graphPlot.graphPointSize);
629 |
630 | // Draw circle around graph point
631 | if (graphPlot.fillCircleAroundGraphPoint) {
632 | CGPathAddEllipseInRect(backgroundPlotPath, NULL, CGRectInset(frame, -graphPlot.lineWidth, -graphPlot.lineWidth));
633 | CGPathMoveToPoint(backgroundPlotPath, NULL, convertedPoint.x, convertedPoint.y);
634 | }
635 | if (graphPlot.strokeCircleAroundGraphPoint) {
636 | CGPathAddEllipseInRect(linePlotPath, NULL, CGRectInset(frame, -graphPlot.lineWidth/2, -graphPlot.lineWidth/2));
637 | CGPathMoveToPoint(linePlotPath, NULL, convertedPoint.x, convertedPoint.y);
638 | }
639 |
640 | // Create Graph Point View
641 | LMGraphPointView *graphPointView = [[LMGraphPointView alloc] initWithFrame:frame
642 | graphPoint:graphPoint
643 | color:graphPlot.graphPointColor
644 | highlightColor:graphPlot.graphPointHighlightColor];
645 | [self.graphContentView addSubview:graphPointView];
646 | }
647 | }
648 |
649 | // Update Background Plot & Line Plot Path
650 | backgroundPlotLayer.path = backgroundPlotPath;
651 | linePlotLayer.path = linePlotPath;
652 | CGPathRelease(backgroundPlotPath);
653 | CGPathRelease(linePlotPath);
654 | }
655 | }
656 |
657 | - (void)handleDrawMovement
658 | {
659 | if (self.isMovement && self.currentGraphPoint)
660 | {
661 | CGPoint point = graphPointInPx(self.currentGraphPoint.point);
662 |
663 | // Create touchPath
664 | CGMutablePathRef touchPath = CGPathCreateMutable();
665 | CGPathMoveToPoint(touchPath, NULL, point.x, 0);
666 | CGPathAddLineToPoint(touchPath, NULL, point.x, point.y - 5);
667 | CGPathMoveToPoint(touchPath, NULL, point.x, point.y);
668 | CGPathAddEllipseInRect(touchPath, NULL, CGRectMake(point.x - 5, point.y - 5, 10, 10));
669 | CGPathMoveToPoint(touchPath, NULL, point.x, point.y + 5);
670 | CGPathAddLineToPoint(touchPath, NULL, point.x, H(self.graphContentView));
671 |
672 | // Create touchLayer
673 | if (!self.touchLayer) {
674 | self.touchLayer = [CAShapeLayer layer];
675 | self.touchLayer.backgroundColor = [UIColor clearColor].CGColor;
676 | [self.graphContentView.layer addSublayer:self.touchLayer];
677 | }
678 | CGFloat maxZPosition = 0;
679 | for (CALayer *layer in self.graphContentView.layer.sublayers) {
680 | maxZPosition = (layer.zPosition > maxZPosition) ? layer.zPosition : maxZPosition;
681 | }
682 | self.touchLayer.zPosition = maxZPosition + 1;
683 | self.touchLayer.frame = self.graphContentView.bounds;
684 | self.touchLayer.lineWidth = self.layout.movementLineWidth;
685 | self.touchLayer.fillColor = self.layout.movementDotColor.CGColor;
686 | self.touchLayer.strokeColor = self.layout.movementLineColor.CGColor;
687 | self.touchLayer.path = touchPath;
688 | CGPathRelease(touchPath);
689 | }
690 | else
691 | {
692 | self.touchLayer.path = nil;
693 | }
694 | }
695 |
696 |
697 | #pragma mark - TOUCH EVENTS
698 |
699 | - (void)handleTapGesture:(UITapGestureRecognizer *)gestureRecognizer
700 | {
701 | if (!self.layout.xAxisScrollableOnly && self.layout.drawMovement) {
702 | return;
703 | }
704 |
705 | CGPoint tapPoint = [gestureRecognizer locationInView:gestureRecognizer.view];
706 | CGPoint convertedTapPoint = [self.graphContentView convertPoint:tapPoint fromView:self];
707 |
708 | for (UIView *view in self.graphContentView.subviews) {
709 | if ([view isKindOfClass:[LMGraphPointView class]]) {
710 | [((LMGraphPointView *)view) setIsSelected:NO];
711 | }
712 | }
713 |
714 | LMGraphPointView *selectedGraphPointView = [self graphPointViewWithTouchPoint:convertedTapPoint];
715 | if (selectedGraphPointView)
716 | {
717 | [selectedGraphPointView setIsSelected:YES];
718 |
719 | if (selectedGraphPointView.graphPoint)
720 | {
721 | UIButton *popoverButton = [UIButton buttonWithType:UIButtonTypeSystem];
722 | popoverButton.frame = CGRectMake(0, 0, 80, 30);
723 | [popoverButton.titleLabel setNumberOfLines:2];
724 | [popoverButton setBackgroundColor:[UIColor clearColor]];
725 | [popoverButton setTitle:selectedGraphPointView.graphPoint.value forState:UIControlStateNormal];
726 | [popoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
727 | [popoverButton.titleLabel setFont:[UIFont systemFontOfSize:12]];
728 | [popoverButton sizeToFit];
729 |
730 | CGPoint point = [self convertPoint:selectedGraphPointView.center fromView:self.graphContentView];
731 | [PopoverView showPopoverAtPoint:CGPointMake(point.x, point.y)
732 | inView:self
733 | withContentView:popoverButton
734 | isFitView:NO
735 | delegate:nil];
736 | }
737 | }
738 | }
739 |
740 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
741 | {
742 | if (self.layout.xAxisScrollableOnly || !self.layout.drawMovement) {
743 | return;
744 | }
745 |
746 | self.isMovement = YES;
747 | CGPoint point = [[touches anyObject] locationInView:self.graphContentView];
748 | point.x = MAX(0, MIN(point.x, W(self.graphContentView)));
749 | self.currentGraphPoint = [self graphPointFromTouchPoint:point];
750 |
751 | // Draw movement
752 | [self handleDrawMovement];
753 |
754 | // Update xAxisLabels
755 | [self drawXAxisLabels];
756 |
757 | // Delegate
758 | if (self.delegate && [self.delegate respondsToSelector:@selector(lineGraphView:beganTouchWithGraphPoint:)]) {
759 | [self.delegate lineGraphView:self beganTouchWithGraphPoint:self.currentGraphPoint];
760 | }
761 | }
762 |
763 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
764 | {
765 | if (self.layout.xAxisScrollableOnly || !self.layout.drawMovement) {
766 | return;
767 | }
768 |
769 | CGPoint point = [[touches anyObject] locationInView:self.graphContentView];
770 | point.x = MAX(0, MIN(point.x, W(self.graphContentView)));
771 | self.currentGraphPoint = [self graphPointFromTouchPoint:point];
772 |
773 | // Draw movement
774 | [self handleDrawMovement];
775 |
776 | // Update xAxisLabels
777 | [self drawXAxisLabels];
778 |
779 | // Delegate
780 | if (self.delegate && [self.delegate respondsToSelector:@selector(lineGraphView:moveTouchWithGraphPoint:)]) {
781 | [self.delegate lineGraphView:self moveTouchWithGraphPoint:self.currentGraphPoint];
782 | }
783 | }
784 |
785 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
786 | {
787 | if (self.layout.xAxisScrollableOnly || !self.layout.drawMovement) {
788 | return;
789 | }
790 |
791 | self.isMovement = NO;
792 | CGPoint point = [[touches anyObject] locationInView:self.graphContentView];
793 | point = CGPointMake(MAX(0, MIN(point.x, W(self.graphContentView))), -1);
794 | self.currentGraphPoint = [self graphPointFromTouchPoint:point];
795 |
796 | // Draw movement
797 | [self handleDrawMovement];
798 |
799 | // Update xAxisLabels
800 | [self drawXAxisLabels];
801 |
802 | // Delegate
803 | if (self.delegate && [self.delegate respondsToSelector:@selector(lineGraphView:endTouchWithGraphPoint:)]) {
804 | [self.delegate lineGraphView:self endTouchWithGraphPoint:self.currentGraphPoint];
805 | }
806 | }
807 |
808 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
809 | {
810 | [self touchesEnded:touches withEvent:event];
811 | }
812 |
813 |
814 | #pragma mark - SUPPORT
815 |
816 | - (void)calculateGraphData
817 | {
818 | // Calculate yAxisMaxValue if needed
819 | if (self.yAxisMaxValue == 0) {
820 | CGFloat yAxisMaxValue = -MAXFLOAT;
821 | for (LMGraphPlot *graphPlot in self.graphPlots) {
822 | for (LMGraphPoint *graphPoint in graphPlot.graphPoints) {
823 | if (yAxisMaxValue < graphPoint.point.y) {
824 | yAxisMaxValue = graphPoint.point.y;
825 | }
826 | }
827 | }
828 | self.yAxisMaxValue = yAxisMaxValue;
829 | }
830 |
831 | // Calculate xAxisMaxValue
832 | CGFloat xAxisMaxValue = -MAXFLOAT;
833 | for (NSDictionary *xAxisValueDict in self.xAxisValues)
834 | {
835 | __block NSNumber *xAxisValue = nil;
836 | [xAxisValueDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
837 | xAxisValue = (NSNumber *)key;
838 | }];
839 |
840 | if (xAxisMaxValue < [xAxisValue doubleValue]) {
841 | xAxisMaxValue = [xAxisValue doubleValue];
842 | }
843 | }
844 | self.xAxisMaxValue = xAxisMaxValue;
845 |
846 | // Check validDataToDraw
847 | if (self.layout.yAxisSegmentCount <= 0
848 | || self.yAxisMaxValue <= 0
849 | || (H(self.graphContentView) - self.layout.yAxisLinesWidth <= 0)
850 | || self.xAxisValues.count == 0) {
851 | self.validGraphData = NO;
852 | return;
853 | }
854 | else {
855 | self.validGraphData = YES;
856 | }
857 |
858 | // Calculate xAxisIntervalInPx
859 | if (!self.layout.xAxisScrollableOnly && self.xAxisValues.count) {
860 | self.layout.xAxisIntervalInPx = MAX(W(self.graphContentView)/(self.xAxisValues.count - 1), 5);
861 | }
862 |
863 | // Calculate yAxisInterval
864 | self.yAxisInterval = (self.yAxisMaxValue - self.yAxisMinValue) / self.layout.yAxisSegmentCount;
865 |
866 | // Calculate yAxisIntervalInPx
867 | self.yAxisIntervalInPx = (H(self.graphContentView) - self.layout.yAxisLinesWidth)/self.layout.yAxisSegmentCount;
868 |
869 | // Calculate yAxisValues
870 | NSMutableArray *yAxisValues = [NSMutableArray array];
871 | for (int i = 0; i < self.layout.yAxisSegmentCount + 1; i++)
872 | {
873 | double yAxisValue = (self.yAxisInterval * i) + self.yAxisMinValue;
874 |
875 | NSString *yAxisValueTitle;
876 | if (self.delegate && [self.delegate respondsToSelector:@selector(lineGraphView:yAxisLabelTitleFromValue:index:)]) {
877 | yAxisValueTitle = [self.delegate lineGraphView:self yAxisLabelTitleFromValue:yAxisValue index:i];
878 | }
879 | else {
880 | if (yAxisValue < 1 && yAxisValue != 0) {
881 | yAxisValueTitle = [NSString stringWithFormat:@"%f", yAxisValue];
882 | }
883 | else {
884 | static NSNumberFormatter *numberFormatter = nil;
885 | static dispatch_once_t onceToken;
886 | dispatch_once(&onceToken, ^{
887 | numberFormatter = [[NSNumberFormatter alloc] init];
888 | numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
889 | numberFormatter.maximumFractionDigits = 0;
890 | numberFormatter.locale = [NSLocale currentLocale];
891 | });
892 | yAxisValueTitle = [numberFormatter stringFromNumber:@(yAxisValue)];
893 | }
894 | }
895 |
896 | if (self.yAxisSuffix) {
897 | yAxisValueTitle = [yAxisValueTitle stringByAppendingString:self.yAxisSuffix];
898 | }
899 |
900 | NSDictionary *finalYAxisValue = @{@(yAxisValue) : yAxisValueTitle ? yAxisValueTitle : @""};
901 | [yAxisValues addObject:finalYAxisValue];
902 | }
903 | self.yAxisValues = yAxisValues;
904 | }
905 |
906 | - (CGPoint)graphPointInPxFromGraphPoint:(CGPoint)graphPoint
907 | {
908 | CGFloat convertedX = 0;
909 | CGFloat convertedY = 0;
910 |
911 | CGFloat realGraphPointX = graphPoint.x - self.xAxisStartGraphPoint;
912 | convertedX = (realGraphPointX/self.xAxisInterval) * self.layout.xAxisIntervalInPx + self.layout.xAxisLinesWidth/2;
913 |
914 | convertedY = ((graphPoint.y - self.yAxisMinValue)/self.yAxisInterval) * self.yAxisIntervalInPx + self.layout.yAxisLinesWidth/2;
915 | convertedY = H(self.graphContentView) - convertedY;
916 |
917 | return CGPointMake(convertedX, convertedY);
918 | }
919 |
920 | - (LMGraphPoint *)graphPointFromTouchPoint:(CGPoint)touchPoint
921 | {
922 | LMGraphPlot *graphPlot = [self.graphPlots firstObject];
923 |
924 | CGFloat interval = MAX(W(self.graphContentView)/(graphPlot.graphPoints.count - 1), 1);
925 | NSInteger index = round(touchPoint.x/interval);
926 | index = MIN(MAX(index, 0), graphPlot.graphPoints.count - 1);
927 |
928 | LMGraphPoint *graphPoint = [graphPlot.graphPoints objectAtIndex:index];
929 | return graphPoint;
930 | }
931 |
932 | - (LMGraphPointView *)graphPointViewWithTouchPoint:(CGPoint)touchPoint
933 | {
934 | for (UIView *view in self.graphContentView.subviews)
935 | {
936 | if ([view isKindOfClass:[LMGraphPointView class]])
937 | {
938 | LMGraphPointView *graphPointView = (LMGraphPointView *)view;
939 | if (CGRectContainsPoint(CGRectInset(graphPointView.frame, -5, -5), touchPoint)) {
940 | return graphPointView;
941 | }
942 | }
943 | }
944 | return nil;
945 | }
946 |
947 | static CGPoint midPointForPoints(CGPoint p1, CGPoint p2)
948 | {
949 | return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
950 | }
951 |
952 | static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2)
953 | {
954 | CGPoint controlPoint = midPointForPoints(p1, p2);
955 | CGFloat diffY = fabs(p2.y - controlPoint.y);
956 |
957 | if (p1.y < p2.y)
958 | controlPoint.y += diffY;
959 | else if (p1.y > p2.y)
960 | controlPoint.y -= diffY;
961 |
962 | return controlPoint;
963 | }
964 |
965 |
966 | #pragma mark - PUBLIC
967 |
968 | - (void)animateWithDuration:(NSTimeInterval)duration
969 | {
970 | [self.graphContentView.layer removeAllAnimations];
971 |
972 | for (CAShapeLayer *layer in self.graphContentView.layer.sublayers) {
973 | if ([layer.name isEqualToString:@"linePlotLayer"]) {
974 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
975 | animation.duration = duration;
976 | animation.fromValue = @(0.0);
977 | animation.toValue = @(1.0);
978 | [layer addAnimation:animation forKey:@"strokeEnd"];
979 | }
980 | }
981 | }
982 |
983 | - (UIImage *)graphImage
984 | {
985 | CGFloat scale = 1;
986 | if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
987 | scale = [[UIScreen mainScreen] scale];
988 | }
989 |
990 | if (scale > 1) {
991 | UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, scale);
992 | }
993 | else {
994 | UIGraphicsBeginImageContext(self.frame.size);
995 | }
996 |
997 | CGContextRef context = UIGraphicsGetCurrentContext();
998 | [self.layer renderInContext: context];
999 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
1000 | UIGraphicsEndImageContext();
1001 |
1002 | return image;
1003 | }
1004 |
1005 | @end
1006 |
1007 |
--------------------------------------------------------------------------------
/LMGraphView/Vender/PopoverView.m:
--------------------------------------------------------------------------------
1 | //
2 | // PopoverView.m
3 | // Embark
4 | //
5 | // Created by Oliver Rickard on 20/08/2012.
6 | //
7 | //
8 |
9 | #import "PopoverView.h"
10 | #import "PopoverView_Configuration.h"
11 | #import
12 |
13 | #pragma mark - Implementation
14 |
15 | @implementation PopoverView
16 |
17 | @synthesize subviewsArray;
18 | @synthesize contentView;
19 | @synthesize titleView;
20 | @synthesize delegate;
21 |
22 | #pragma mark - Static Methods
23 |
24 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text delegate:(id)delegate {
25 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
26 | [popoverView showAtPoint:point inView:view withText:text];
27 | popoverView.delegate = delegate;
28 | [popoverView RELEASE];
29 | return popoverView;
30 | }
31 |
32 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text delegate:(id)delegate {
33 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
34 | [popoverView showAtPoint:point inView:view withTitle:title withText:text];
35 | popoverView.delegate = delegate;
36 | [popoverView RELEASE];
37 | return popoverView;
38 | }
39 |
40 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray delegate:(id)delegate {
41 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
42 | [popoverView showAtPoint:point inView:view withViewArray:viewArray];
43 | popoverView.delegate = delegate;
44 | [popoverView RELEASE];
45 | return popoverView;
46 | }
47 |
48 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray delegate:(id)delegate {
49 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
50 | [popoverView showAtPoint:point inView:view withTitle:title withViewArray:viewArray];
51 | popoverView.delegate = delegate;
52 | [popoverView RELEASE];
53 | return popoverView;
54 | }
55 |
56 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray delegate:(id)delegate {
57 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
58 | [popoverView showAtPoint:point inView:view withStringArray:stringArray];
59 | popoverView.delegate = delegate;
60 | [popoverView RELEASE];
61 | return popoverView;
62 | }
63 |
64 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray delegate:(id)delegate {
65 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
66 | [popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray];
67 | popoverView.delegate = delegate;
68 | [popoverView RELEASE];
69 | return popoverView;
70 | }
71 |
72 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate {
73 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
74 | [popoverView showAtPoint:point inView:view withStringArray:stringArray withImageArray:imageArray];
75 | popoverView.delegate = delegate;
76 | [popoverView RELEASE];
77 | return popoverView;
78 | }
79 |
80 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate {
81 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
82 | [popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray withImageArray:imageArray];
83 | popoverView.delegate = delegate;
84 | [popoverView RELEASE];
85 | return popoverView;
86 | }
87 |
88 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView delegate:(id)delegate {
89 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
90 | [popoverView showAtPoint:point inView:view withTitle:title withContentView:cView];
91 | popoverView.delegate = delegate;
92 | [popoverView RELEASE];
93 | return popoverView;
94 | }
95 |
96 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView isFitView:(BOOL)isFitView delegate:(id)delegate {
97 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero];
98 | popoverView.isFitView = isFitView;
99 | [popoverView showAtPoint:point inView:view withContentView:cView];
100 | popoverView.delegate = delegate;
101 | [popoverView RELEASE];
102 | return popoverView;
103 | }
104 |
105 | #pragma mark - View Lifecycle
106 |
107 | - (id)initWithFrame:(CGRect)frame
108 | {
109 | self = [super initWithFrame:frame];
110 | if (self) {
111 | // Initialization code
112 |
113 | self.backgroundColor = [UIColor clearColor];
114 |
115 | self.titleView = nil;
116 | self.contentView = nil;
117 |
118 | showDividerRects = kShowDividersBetweenViews;
119 | }
120 | return self;
121 | }
122 |
123 | - (void)dealloc
124 | {
125 | self.subviewsArray = nil;
126 |
127 | if (dividerRects) {
128 | [dividerRects RELEASE];
129 | dividerRects = nil;
130 | }
131 |
132 | self.contentView = nil;
133 | self.titleView = nil;
134 |
135 | [super DEALLOC];
136 | }
137 |
138 |
139 |
140 | #pragma mark - Display methods
141 |
142 | // get the screen size, adjusted for orientation and status bar display
143 | // see http://stackoverflow.com/questions/7905432/how-to-get-orientation-dependent-height-and-width-of-the-screen/7905540#7905540
144 | - (CGSize) screenSize
145 | {
146 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
147 | CGSize size = [UIScreen mainScreen].bounds.size;
148 | UIApplication *application = [UIApplication sharedApplication];
149 | if (UIInterfaceOrientationIsLandscape(orientation))
150 | {
151 | size = CGSizeMake(size.height, size.width);
152 | }
153 | if (application.statusBarHidden == NO)
154 | {
155 | size.height -= MIN(application.statusBarFrame.size.width, application.statusBarFrame.size.height);
156 | }
157 | return size;
158 | }
159 |
160 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text
161 | {
162 | UIFont *font = kTextFont;
163 |
164 | CGSize screenSize = [self screenSize];
165 | CGSize textSize = [text boundingRectWithSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f)
166 | options:NSStringDrawingUsesLineFragmentOrigin
167 | attributes:@{NSFontAttributeName:font}
168 | context:nil].size;
169 |
170 | UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
171 | textView.backgroundColor = [UIColor clearColor];
172 | textView.userInteractionEnabled = NO;
173 | [textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text
174 | textView.font = font;
175 | textView.textAlignment = kTextAlignment;
176 | textView.textColor = kTextColor;
177 | textView.text = text;
178 |
179 | [self showAtPoint:point inView:view withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]];
180 | }
181 |
182 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text
183 | {
184 | UIFont *font = kTextFont;
185 |
186 | CGSize screenSize = [self screenSize];
187 | CGSize textSize = [text boundingRectWithSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f)
188 | options:NSStringDrawingUsesLineFragmentOrigin
189 | attributes:@{NSFontAttributeName:font}
190 | context:nil].size;
191 |
192 | UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
193 | textView.backgroundColor = [UIColor clearColor];
194 | textView.userInteractionEnabled = NO;
195 | [textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text
196 | textView.font = font;
197 | textView.textAlignment = kTextAlignment;
198 | textView.textColor = kTextColor;
199 | textView.text = text;
200 |
201 | [self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]];
202 | }
203 |
204 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray
205 | {
206 | UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
207 |
208 | float totalHeight = 0.f;
209 | float totalWidth = 0.f;
210 |
211 | int i = 0;
212 |
213 | //Position each view the first time, and identify which view has the largest width that controls
214 | //the sizing of the popover.
215 | for (UIView *view in viewArray) {
216 |
217 | view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height);
218 | //Only add padding below the view if it's not the last item
219 | float padding = (i == viewArray.count-1) ? 0 : kBoxPadding;
220 |
221 | totalHeight += view.frame.size.height + padding;
222 |
223 | if (view.frame.size.width > totalWidth) {
224 | totalWidth = view.frame.size.width;
225 | }
226 |
227 | [container addSubview:view];
228 |
229 | i++;
230 | }
231 |
232 | //If dividers are enabled, then we allocate the divider rect array. This will hold NSValues
233 | if (kShowDividersBetweenViews) {
234 | dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1];
235 | }
236 |
237 | container.frame = CGRectMake(0, 0, totalWidth, totalHeight);
238 |
239 | i = 0;
240 |
241 | totalHeight = 0;
242 |
243 | //Now we actually change the frame element for each subview, and center the views horizontally.
244 | for (UIView *view in viewArray) {
245 | if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
246 | //Now make sure all flexible views are the full width
247 | view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height);
248 | } else {
249 | //If the view is not flexible width, then we position it centered in the view
250 | //without stretching it.
251 | view.frame = CGRectMake(floorf(CGRectGetMinX(boxFrame) + totalWidth*0.5f - view.frame.size.width*0.5f), view.frame.origin.y, view.frame.size.width, view.frame.size.height);
252 | }
253 |
254 | //and if dividers are enabled, we record their position for the drawing methods
255 | if (kShowDividersBetweenViews && i != viewArray.count-1) {
256 | CGRect dividerRect = CGRectMake(view.frame.origin.x, floorf(view.frame.origin.y + view.frame.size.height + kBoxPadding*0.5f), view.frame.size.width, 0.5f);
257 |
258 | [((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]];
259 | }
260 |
261 | //Only add padding below the view if it's not the last item
262 | float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding;
263 |
264 | totalHeight += view.frame.size.height + padding;
265 |
266 | i++;
267 | }
268 |
269 | self.subviewsArray = viewArray;
270 |
271 | [self showAtPoint:point inView:view withContentView:[container AUTORELEASE]];
272 | }
273 |
274 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray
275 | {
276 | UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
277 |
278 | //Create a label for the title text.
279 | // CGSize titleSize = [title sizeWithFont:kTitleFont];
280 | CGSize titleSize = [title boundingRectWithSize:CGSizeZero
281 | options:NSStringDrawingUsesLineFragmentOrigin
282 | attributes:@{NSFontAttributeName:kTitleFont}
283 | context:nil].size;
284 |
285 | UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.f, 0.f, titleSize.width, titleSize.height)];
286 | titleLabel.backgroundColor = [UIColor clearColor];
287 | titleLabel.font = kTitleFont;
288 | titleLabel.textAlignment = UITextAlignmentCenter;
289 | titleLabel.textColor = kTitleColor;
290 | titleLabel.text = title;
291 |
292 | //Make sure that the title's label will have non-zero height. If it has zero height, then we don't allocate any space
293 | //for it in the positioning of the views.
294 | float titleHeightOffset = (titleSize.height > 0.f ? kBoxPadding : 0.f);
295 |
296 | float totalHeight = titleSize.height + titleHeightOffset + kBoxPadding;
297 | float totalWidth = titleSize.width;
298 |
299 | int i = 0;
300 |
301 | //Position each view the first time, and identify which view has the largest width that controls
302 | //the sizing of the popover.
303 | for (UIView *view in viewArray) {
304 |
305 | view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height);
306 |
307 | //Only add padding below the view if it's not the last item.
308 | float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding;
309 |
310 | totalHeight += view.frame.size.height + padding;
311 |
312 | if (view.frame.size.width > totalWidth) {
313 | totalWidth = view.frame.size.width;
314 | }
315 |
316 | [container addSubview:view];
317 |
318 | i++;
319 | }
320 |
321 | //If dividers are enabled, then we allocate the divider rect array. This will hold NSValues
322 | if (kShowDividersBetweenViews) {
323 | dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1];
324 | }
325 |
326 | i = 0;
327 |
328 | for (UIView *view in viewArray) {
329 | if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) {
330 | //Now make sure all flexible views are the full width
331 | view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height);
332 | } else {
333 | //If the view is not flexible width, then we position it centered in the view
334 | //without stretching it.
335 | view.frame = CGRectMake(floorf(CGRectGetMinX(boxFrame) + totalWidth*0.5f - view.frame.size.width*0.5f), view.frame.origin.y, view.frame.size.width, view.frame.size.height);
336 | }
337 |
338 | //and if dividers are enabled, we record their position for the drawing methods
339 | if (kShowDividersBetweenViews && i != viewArray.count-1) {
340 | CGRect dividerRect = CGRectMake(view.frame.origin.x, floorf(view.frame.origin.y + view.frame.size.height + kBoxPadding*0.5f), view.frame.size.width, 0.5f);
341 |
342 | [((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]];
343 | }
344 |
345 | i++;
346 | }
347 |
348 | titleLabel.frame = CGRectMake(floorf(totalWidth*0.5f - titleSize.width*0.5f), 0, titleSize.width, titleSize.height);
349 |
350 | //Store the titleView as an instance variable if it is larger than 0 height (not an empty string)
351 | if (titleSize.height > 0) {
352 | self.titleView = titleLabel;
353 | }
354 |
355 | [container addSubview:[titleLabel AUTORELEASE]];
356 |
357 | container.frame = CGRectMake(0, 0, totalWidth, totalHeight);
358 |
359 | self.subviewsArray = viewArray;
360 |
361 | [self showAtPoint:point inView:view withContentView:[container AUTORELEASE]];
362 | }
363 |
364 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray
365 | {
366 | NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
367 |
368 | UIFont *font = kTextFont;
369 |
370 | for (NSString *string in stringArray) {
371 | // CGSize textSize = [string sizeWithFont:font];
372 | CGSize textSize = [string boundingRectWithSize:CGSizeZero
373 | options:NSStringDrawingUsesLineFragmentOrigin
374 | attributes:@{NSFontAttributeName:kTitleFont}
375 | context:nil].size;
376 |
377 | UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
378 | textButton.backgroundColor = [UIColor clearColor];
379 | textButton.titleLabel.font = font;
380 | textButton.titleLabel.textAlignment = kTextAlignment;
381 | textButton.titleLabel.textColor = kTextColor;
382 | [textButton setTitle:string forState:UIControlStateNormal];
383 | textButton.layer.cornerRadius = 4.f;
384 | [textButton setTitleColor:kTextColor forState:UIControlStateNormal];
385 | [textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted];
386 | [textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
387 |
388 | [labelArray addObject:[textButton AUTORELEASE]];
389 | }
390 |
391 | [self showAtPoint:point inView:view withViewArray:[labelArray AUTORELEASE]];
392 | }
393 |
394 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray
395 | {
396 | NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
397 |
398 | UIFont *font = kTextFont;
399 |
400 | for (NSString *string in stringArray) {
401 | // CGSize textSize = [string sizeWithFont:font];
402 | CGSize textSize = [string boundingRectWithSize:CGSizeZero
403 | options:NSStringDrawingUsesLineFragmentOrigin
404 | attributes:@{NSFontAttributeName:kTitleFont}
405 | context:nil].size;
406 |
407 | UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
408 | textButton.backgroundColor = [UIColor clearColor];
409 | textButton.titleLabel.font = font;
410 | textButton.titleLabel.textAlignment = kTextAlignment;
411 | textButton.titleLabel.textColor = kTextColor;
412 | [textButton setTitle:string forState:UIControlStateNormal];
413 | textButton.layer.cornerRadius = 4.f;
414 | [textButton setTitleColor:kTextColor forState:UIControlStateNormal];
415 | [textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted];
416 | [textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];
417 |
418 | [labelArray addObject:[textButton AUTORELEASE]];
419 | }
420 |
421 | [self showAtPoint:point inView:view withTitle:title withViewArray:[labelArray AUTORELEASE]];
422 | }
423 |
424 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray
425 | {
426 | //Here we do something pretty similar to the stringArray method above.
427 | //We create an array of subviews that contains the strings and images centered above a label.
428 |
429 | NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count");
430 | NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray];
431 |
432 | [self showAtPoint:point inView:view withViewArray:tempViewArray];
433 | }
434 |
435 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray
436 | {
437 | NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count");
438 | NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray];
439 |
440 | [self showAtPoint:point inView:view withTitle:title withViewArray:tempViewArray];
441 | }
442 |
443 | - (NSMutableArray*) makeTempViewsWithStrings:(NSArray *)stringArray andImages:(NSArray *)imageArray
444 | {
445 | NSMutableArray *tempViewArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count];
446 |
447 | UIFont *font = kTextFont;
448 |
449 | for (int i = 0; i < stringArray.count; i++) {
450 | NSString *string = [stringArray objectAtIndex:i];
451 |
452 | //First we build a label for the text to set in.
453 | // CGSize textSize = [string sizeWithFont:font];
454 | CGSize textSize = [string boundingRectWithSize:CGSizeZero
455 | options:NSStringDrawingUsesLineFragmentOrigin
456 | attributes:@{NSFontAttributeName:kTitleFont}
457 | context:nil].size;
458 |
459 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)];
460 | label.backgroundColor = [UIColor clearColor];
461 | label.font = font;
462 | label.textAlignment = kTextAlignment;
463 | label.textColor = kTextColor;
464 | label.text = string;
465 | label.layer.cornerRadius = 4.f;
466 |
467 | //Now we grab the image at the same index in the imageArray, and create
468 | //a UIImageView for it.
469 | UIImage *image = [imageArray objectAtIndex:i];
470 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
471 |
472 | //Take the larger of the two widths as the width for the container
473 | float containerWidth = MAX(imageView.frame.size.width, label.frame.size.width);
474 | float containerHeight = label.frame.size.height + kImageTopPadding + kImageBottomPadding + imageView.frame.size.height;
475 |
476 | //This container will hold both the image and the label
477 | UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerWidth, containerHeight)];
478 |
479 | //Now we do the frame manipulations to put the imageView on top of the label, both centered
480 | imageView.frame = CGRectMake(floorf(containerWidth*0.5f - imageView.frame.size.width*0.5f), kImageTopPadding, imageView.frame.size.width, imageView.frame.size.height);
481 | label.frame = CGRectMake(floorf(containerWidth*0.5f - label.frame.size.width*0.5f), imageView.frame.size.height + kImageBottomPadding + kImageTopPadding, label.frame.size.width, label.frame.size.height);
482 |
483 | [containerView addSubview:imageView];
484 | [containerView addSubview:label];
485 |
486 | [label RELEASE];
487 | [imageView RELEASE];
488 |
489 | [tempViewArray addObject:containerView];
490 | [containerView RELEASE];
491 | }
492 |
493 | return [tempViewArray AUTORELEASE];
494 | }
495 |
496 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView
497 | {
498 | [self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:cView]];
499 | }
500 |
501 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView {
502 |
503 | //NSLog(@"point:%f,%f", point.x, point.y);
504 |
505 | self.contentView = cView;
506 | parentView = view;
507 |
508 | // get the top view
509 | // http://stackoverflow.com/questions/3843411/getting-reference-to-the-top-most-view-window-in-ios-application/8045804#8045804
510 | topView = [PopoverView topMostController].view;
511 |
512 | [self setupLayout:point inView:view];
513 |
514 | // Make the view small and transparent before animation
515 | self.alpha = 0.f;
516 | self.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
517 |
518 | // animate into full size
519 | // First stage animates to 1.05x normal size, then second stage animates back down to 1x size.
520 | // This two-stage animation creates a little "pop" on open.
521 | [UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
522 | self.alpha = 1.f;
523 | self.transform = CGAffineTransformMakeScale(1.05f, 1.05f);
524 | } completion:^(BOOL finished) {
525 | [UIView animateWithDuration:0.08f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
526 | self.transform = CGAffineTransformIdentity;
527 | } completion:nil];
528 | }];
529 | }
530 |
531 | + (UIViewController*) topMostController
532 | {
533 | UIViewController *topController = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
534 | while (topController.presentedViewController) {
535 | topController = topController.presentedViewController;
536 | }
537 | return topController;
538 | }
539 |
540 | - (void)layoutAtPoint:(CGPoint)point inView:(UIView *)view
541 | {
542 | // make transparent
543 | self.alpha = 0.f;
544 |
545 | [self setupLayout:point inView:view];
546 |
547 | // animate back to full opacity
548 | [UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{
549 | self.alpha = 1.f;
550 | } completion:nil];
551 | }
552 |
553 | -(void)setupLayout:(CGPoint)point inView:(UIView*)view
554 | {
555 | CGPoint topPoint = [topView convertPoint:point fromView:view];
556 |
557 | arrowPoint = topPoint;
558 |
559 | //NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y);
560 |
561 | CGRect topViewBounds = topView.bounds;
562 | //NSLog(@"topViewBounds %@", NSStringFromCGRect(topViewBounds));
563 |
564 | float contentHeight = contentView.frame.size.height;
565 | float contentWidth = contentView.frame.size.width;
566 |
567 | float padding = kBoxPadding;
568 |
569 | float boxHeight = contentHeight + 2.f*padding;
570 | float boxWidth = contentWidth + 2.f*padding;
571 |
572 | float xOrigin = 0.f;
573 |
574 | //Make sure the arrow point is within the drawable bounds for the popover.
575 | if (arrowPoint.x + kArrowHeight > topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding) {//Too far to the right
576 | arrowPoint.x = topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding - kArrowHeight;
577 | //NSLog(@"Correcting Arrow Point because it's too far to the right");
578 | } else if (arrowPoint.x - kArrowHeight < kHorizontalMargin + kBoxRadius + kArrowHorizontalPadding) {//Too far to the left
579 | arrowPoint.x = kHorizontalMargin + kArrowHeight + kBoxRadius + kArrowHorizontalPadding;
580 | //NSLog(@"Correcting Arrow Point because it's too far to the left");
581 | }
582 |
583 | //NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y);
584 |
585 | xOrigin = floorf(arrowPoint.x - boxWidth*0.5f);
586 |
587 | //Check to see if the centered xOrigin value puts the box outside of the normal range.
588 | if (xOrigin < CGRectGetMinX(topViewBounds) + kHorizontalMargin) {
589 | xOrigin = CGRectGetMinX(topViewBounds) + kHorizontalMargin;
590 | } else if (xOrigin + boxWidth > CGRectGetMaxX(topViewBounds) - kHorizontalMargin) {
591 | //Check to see if the positioning puts the box out of the window towards the left
592 | xOrigin = CGRectGetMaxX(topViewBounds) - kHorizontalMargin - boxWidth;
593 | }
594 |
595 | float arrowHeight = kArrowHeight;
596 |
597 | float topPadding = kTopMargin;
598 |
599 | above = YES;
600 |
601 | if (topPoint.y - contentHeight - arrowHeight - topPadding < CGRectGetMinY(topViewBounds)) {
602 | //Position below because it won't fit above.
603 | above = NO;
604 |
605 | boxFrame = CGRectMake(xOrigin, arrowPoint.y + arrowHeight, boxWidth, boxHeight);
606 | } else {
607 | //Position above.
608 | above = YES;
609 |
610 | boxFrame = CGRectMake(xOrigin, arrowPoint.y - arrowHeight - boxHeight, boxWidth, boxHeight);
611 | }
612 |
613 | //NSLog(@"boxFrame:(%f,%f,%f,%f)", boxFrame.origin.x, boxFrame.origin.y, boxFrame.size.width, boxFrame.size.height);
614 |
615 | CGRect contentFrame = CGRectMake(boxFrame.origin.x + padding, boxFrame.origin.y + padding, contentWidth, contentHeight);
616 | contentView.frame = contentFrame;
617 |
618 | //We set the anchorPoint here so the popover will "grow" out of the arrowPoint specified by the user.
619 | //You have to set the anchorPoint before setting the frame, because the anchorPoint property will
620 | //implicitly set the frame for the view, which we do not want.
621 | self.layer.anchorPoint = CGPointMake(arrowPoint.x / topViewBounds.size.width, arrowPoint.y / topViewBounds.size.height);
622 | self.frame = topViewBounds;
623 | [self setNeedsDisplay];
624 |
625 | [self addSubview:contentView];
626 | [topView addSubview:self];
627 |
628 | //Add a tap gesture recognizer to the large invisible view (self), which will detect taps anywhere on the screen.
629 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
630 | tap.cancelsTouchesInView = NO; // Allow touches through to a UITableView or other touchable view, as suggested by Dimajp.
631 | [self addGestureRecognizer:tap];
632 | [tap RELEASE];
633 |
634 | self.userInteractionEnabled = YES;
635 | }
636 |
637 |
638 | #pragma mark - Activity Indicator
639 |
640 | //Animates in a progress indicator, and removes
641 | - (void)showActivityIndicatorWithMessage:(NSString *)msg
642 | {
643 | if ([titleView isKindOfClass:[UILabel class]]) {
644 | ((UILabel *)titleView).text = msg;
645 | }
646 |
647 | if (subviewsArray && (subviewsArray.count > 0)) {
648 | [UIView animateWithDuration:0.2f animations:^{
649 | for (UIView *view in subviewsArray) {
650 | view.alpha = 0.f;
651 | }
652 | }];
653 |
654 | if (showDividerRects) {
655 | showDividerRects = NO;
656 | [self setNeedsDisplay];
657 | }
658 | }
659 |
660 | if (activityIndicator) {
661 | [activityIndicator RELEASE];
662 | [activityIndicator removeFromSuperview];
663 | activityIndicator = nil;
664 | }
665 |
666 | activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
667 | activityIndicator.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 10.f, CGRectGetMidY(contentView.bounds) - 10.f + 20.f, 20.f, 20.f);
668 | [contentView addSubview:activityIndicator];
669 |
670 | [activityIndicator startAnimating];
671 | }
672 |
673 | - (void)hideActivityIndicatorWithMessage:(NSString *)msg
674 | {
675 | if ([titleView isKindOfClass:[UILabel class]]) {
676 | ((UILabel *)titleView).text = msg;
677 | }
678 |
679 | [activityIndicator stopAnimating];
680 | [UIView animateWithDuration:0.1f animations:^{
681 | activityIndicator.alpha = 0.f;
682 | } completion:^(BOOL finished) {
683 | [activityIndicator RELEASE];
684 | [activityIndicator removeFromSuperview];
685 | activityIndicator = nil;
686 | }];
687 | }
688 |
689 | - (void)showImage:(UIImage *)image withMessage:(NSString *)msg
690 | {
691 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
692 | imageView.alpha = 0.f;
693 | imageView.frame = CGRectMake(floorf(CGRectGetMidX(contentView.bounds) - image.size.width*0.5f), floorf(CGRectGetMidY(contentView.bounds) - image.size.height*0.5f + ((self.titleView) ? 20 : 0.f)), image.size.width, image.size.height);
694 | imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
695 |
696 | [contentView addSubview:[imageView AUTORELEASE]];
697 |
698 | if (subviewsArray && (subviewsArray.count > 0)) {
699 | [UIView animateWithDuration:0.2f animations:^{
700 | for (UIView *view in subviewsArray) {
701 | view.alpha = 0.f;
702 | }
703 | }];
704 |
705 | if (showDividerRects) {
706 | showDividerRects = NO;
707 | [self setNeedsDisplay];
708 | }
709 | }
710 |
711 | if (msg) {
712 | if ([titleView isKindOfClass:[UILabel class]]) {
713 | ((UILabel *)titleView).text = msg;
714 | }
715 | }
716 |
717 | [UIView animateWithDuration:0.2f delay:0.2f options:UIViewAnimationOptionCurveEaseOut animations:^{
718 | imageView.alpha = 1.f;
719 | imageView.transform = CGAffineTransformIdentity;
720 | } completion:^(BOOL finished) {
721 | //[imageView removeFromSuperview];
722 | }];
723 | }
724 |
725 | - (void)showError
726 | {
727 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"error"]];
728 | imageView.alpha = 0.f;
729 | imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f);
730 | imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
731 |
732 | [contentView addSubview:[imageView AUTORELEASE]];
733 |
734 | if (subviewsArray && (subviewsArray.count > 0)) {
735 | [UIView animateWithDuration:0.1f animations:^{
736 | for (UIView *view in subviewsArray) {
737 | view.alpha = 0.f;
738 | }
739 | }];
740 |
741 | if (showDividerRects) {
742 | showDividerRects = NO;
743 | [self setNeedsDisplay];
744 | }
745 | }
746 |
747 | [UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{
748 | imageView.alpha = 1.f;
749 | imageView.transform = CGAffineTransformIdentity;
750 | } completion:^(BOOL finished) {
751 | //[imageView removeFromSuperview];
752 | }];
753 |
754 | }
755 |
756 | - (void)showSuccess
757 | {
758 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"success"]];
759 | imageView.alpha = 0.f;
760 | imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f);
761 | imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
762 |
763 | [contentView addSubview:[imageView AUTORELEASE]];
764 |
765 | if (subviewsArray && (subviewsArray.count > 0)) {
766 | [UIView animateWithDuration:0.1f animations:^{
767 | for (UIView *view in subviewsArray) {
768 | view.alpha = 0.f;
769 | }
770 | }];
771 |
772 | if (showDividerRects) {
773 | showDividerRects = NO;
774 | [self setNeedsDisplay];
775 | }
776 | }
777 |
778 | [UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{
779 | imageView.alpha = 1.f;
780 | imageView.transform = CGAffineTransformIdentity;
781 | } completion:^(BOOL finished) {
782 | //[imageView removeFromSuperview];
783 | }];
784 |
785 | }
786 |
787 | #pragma mark - User Interaction
788 |
789 | - (void)tapped:(UITapGestureRecognizer *)tap
790 | {
791 | CGPoint point = [tap locationInView:contentView];
792 |
793 | //NSLog(@"point:(%f,%f)", point.x, point.y);
794 |
795 | BOOL found = NO;
796 |
797 | //NSLog(@"subviewsArray:%@", subviewsArray);
798 |
799 | for (int i = 0; i < subviewsArray.count && !found; i++) {
800 | UIView *view = [subviewsArray objectAtIndex:i];
801 |
802 | //NSLog(@"Rect:(%f,%f,%f,%f)", view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
803 |
804 | if (CGRectContainsPoint(view.frame, point)) {
805 | //The tap was within this view, so we notify the delegate, and break the loop.
806 |
807 | found = YES;
808 |
809 | //NSLog(@"Tapped subview:%d", i);
810 |
811 | if ([view isKindOfClass:[UIButton class]]) {
812 | return;
813 | }
814 |
815 | if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) {
816 | [delegate popoverView:self didSelectItemAtIndex:i];
817 | }
818 |
819 | break;
820 | }
821 | }
822 |
823 | if (!found && CGRectContainsPoint(contentView.bounds, point)) {
824 | found = YES;
825 | //NSLog(@"popover box contains point, ignoring user input");
826 | }
827 |
828 | if (!found) {
829 | [self dismiss:YES];
830 | }
831 |
832 | }
833 |
834 | - (void)didTapButton:(UIButton *)sender
835 | {
836 | NSUInteger index = [subviewsArray indexOfObject:sender];
837 |
838 | if (index == NSNotFound) {
839 | return;
840 | }
841 |
842 | if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) {
843 | [delegate popoverView:self didSelectItemAtIndex:index];
844 | }
845 | }
846 |
847 | - (void)dismiss
848 | {
849 | [self dismiss:YES];
850 | }
851 |
852 | - (void)dismiss:(BOOL)animated
853 | {
854 | if (!animated)
855 | {
856 | [self dismissComplete];
857 | }
858 | else
859 | {
860 | [UIView animateWithDuration:0.3f animations:^{
861 | self.alpha = 0.1f;
862 | self.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
863 | } completion:^(BOOL finished) {
864 | [self dismissComplete];
865 | }];
866 | }
867 | }
868 |
869 | - (void)dismissComplete
870 | {
871 | [self removeFromSuperview];
872 |
873 | if (self.delegate && [self.delegate respondsToSelector:@selector(popoverViewDidDismiss:)]) {
874 | [delegate popoverViewDidDismiss:self];
875 | }
876 | }
877 |
878 | - (void)animateRotationToNewPoint:(CGPoint)point inView:(UIView *)view withDuration:(NSTimeInterval)duration
879 | {
880 | [self layoutAtPoint:point inView:view];
881 | }
882 |
883 | #pragma mark - Drawing Routines
884 |
885 | // Only override drawRect: if you perform custom drawing.
886 | // An empty implementation adversely affects performance during animation.
887 | - (void)drawRect:(CGRect)rect
888 | {
889 | if (self.isFitView) {
890 | return;
891 | }
892 |
893 |
894 | // Drawing code
895 |
896 | // Build the popover path
897 | CGRect frame = boxFrame;
898 |
899 | float xMin = CGRectGetMinX(frame);
900 | float yMin = CGRectGetMinY(frame);
901 |
902 | float xMax = CGRectGetMaxX(frame);
903 | float yMax = CGRectGetMaxY(frame);
904 |
905 | float radius = kBoxRadius; //Radius of the curvature.
906 |
907 | float cpOffset = kCPOffset; //Control Point Offset. Modifies how "curved" the corners are.
908 |
909 |
910 | /*
911 | LT2 RT1
912 | LT1⌜⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⌝RT2
913 | | |
914 | | popover |
915 | | |
916 | LB2⌞_______________⌟RB1
917 | LB1 RB2
918 |
919 | Traverse rectangle in clockwise order, starting at LT1
920 | L = Left
921 | R = Right
922 | T = Top
923 | B = Bottom
924 | 1,2 = order of traversal for any given corner
925 |
926 | */
927 |
928 | UIBezierPath *popoverPath = [UIBezierPath bezierPath];
929 | [popoverPath moveToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1
930 | [popoverPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2
931 |
932 | //If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover.
933 | //In this case, the arrow is located between LT2 and RT1
934 | if (!above) {
935 | [popoverPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side
936 | [popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point
937 | [popoverPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side
938 | }
939 |
940 | [popoverPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1
941 | [popoverPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2
942 | [popoverPath addLineToPoint:CGPointMake(xMax, yMax - radius)];//RB1
943 | [popoverPath addCurveToPoint:CGPointMake(xMax - radius, yMax) controlPoint1:CGPointMake(xMax, yMax - radius + cpOffset) controlPoint2:CGPointMake(xMax - radius + cpOffset, yMax)];//RB2
944 |
945 | //If the popover is positioned above the arrowPoint, then we know that the arrow must be on the bottom of the popover.
946 | //In this case, the arrow is located somewhere between LB1 and RB2
947 | if (above) {
948 | [popoverPath addLineToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMax)];//right side
949 | [popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMax) controlPoint2:arrowPoint];//arrow point
950 | [popoverPath addCurveToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMax) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMax)];
951 | }
952 |
953 | [popoverPath addLineToPoint:CGPointMake(xMin + radius, yMax)];//LB1
954 | [popoverPath addCurveToPoint:CGPointMake(xMin, yMax - radius) controlPoint1:CGPointMake(xMin + radius - cpOffset, yMax) controlPoint2:CGPointMake(xMin, yMax - radius + cpOffset)];//LB2
955 | [popoverPath closePath];
956 |
957 | //// General Declarations
958 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
959 | CGContextRef context = UIGraphicsGetCurrentContext();
960 |
961 | //// Shadow Declarations
962 | UIColor* shadow = [UIColor colorWithWhite:0.0f alpha:kShadowAlpha];
963 | CGSize shadowOffset = CGSizeMake(0, 1);
964 | CGFloat shadowBlurRadius = kShadowBlur;
965 |
966 | //// Gradient Declarations
967 | NSArray* gradientColors = [NSArray arrayWithObjects:
968 | (id)kGradientTopColor.CGColor,
969 | (id)kGradientBottomColor.CGColor, nil];
970 | CGFloat gradientLocations[] = {0, 1};
971 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations);
972 |
973 |
974 | //These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows.
975 | float bottomOffset = (above ? kArrowHeight : 0.f);
976 | float topOffset = (!above ? kArrowHeight : 0.f);
977 |
978 | //Draw the actual gradient and shadow.
979 | CGContextSaveGState(context);
980 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor);
981 | CGContextBeginTransparencyLayer(context, NULL);
982 | [popoverPath addClip];
983 | CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMaxY(frame) + bottomOffset), 0);
984 | CGContextEndTransparencyLayer(context);
985 | CGContextRestoreGState(context);
986 |
987 | //// Cleanup
988 | CGGradientRelease(gradient);
989 | CGColorSpaceRelease(colorSpace);
990 |
991 |
992 | //Draw the title background
993 | if (kDrawTitleGradient) {
994 | //Calculate the height of the title bg
995 | float titleBGHeight = -1;
996 |
997 | //NSLog(@"titleView:%@", titleView);
998 |
999 | if (titleView != nil) {
1000 | titleBGHeight = kBoxPadding*2.f + titleView.frame.size.height;
1001 | }
1002 |
1003 |
1004 | //Draw the title bg height, but only if we need to.
1005 | if (titleBGHeight > 0.f) {
1006 | CGPoint startingPoint = CGPointMake(xMin, yMin + titleBGHeight);
1007 | CGPoint endingPoint = CGPointMake(xMax, yMin + titleBGHeight);
1008 |
1009 | UIBezierPath *titleBGPath = [UIBezierPath bezierPath];
1010 | [titleBGPath moveToPoint:startingPoint];
1011 | [titleBGPath addLineToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1
1012 | [titleBGPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2
1013 |
1014 | //If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover.
1015 | //In this case, the arrow is located between LT2 and RT1
1016 | if (!above) {
1017 | [titleBGPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side
1018 | [titleBGPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point
1019 | [titleBGPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side
1020 | }
1021 |
1022 | [titleBGPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1
1023 | [titleBGPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2
1024 | [titleBGPath addLineToPoint:endingPoint];
1025 | [titleBGPath addLineToPoint:startingPoint];
1026 | [titleBGPath closePath];
1027 |
1028 | //// General Declarations
1029 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
1030 | CGContextRef context = UIGraphicsGetCurrentContext();
1031 |
1032 | //// Gradient Declarations
1033 | NSArray* gradientColors = [NSArray arrayWithObjects:
1034 | (id)kGradientTitleTopColor.CGColor,
1035 | (id)kGradientTitleBottomColor.CGColor, nil];
1036 | CGFloat gradientLocations[] = {0, 1};
1037 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations);
1038 |
1039 |
1040 | //These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows.
1041 | float topOffset = (!above ? kArrowHeight : 0.f);
1042 |
1043 | //Draw the actual gradient and shadow.
1044 | CGContextSaveGState(context);
1045 | CGContextBeginTransparencyLayer(context, NULL);
1046 | [titleBGPath addClip];
1047 | CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) + titleBGHeight), 0);
1048 | CGContextEndTransparencyLayer(context);
1049 | CGContextRestoreGState(context);
1050 |
1051 | UIBezierPath *dividerLine = [UIBezierPath bezierPathWithRect:CGRectMake(startingPoint.x, startingPoint.y, (endingPoint.x - startingPoint.x), 0.5f)];
1052 | [[UIColor colorWithRed:0.741 green:0.741 blue:0.741 alpha:0.5f] setFill];
1053 | [dividerLine fill];
1054 |
1055 | //// Cleanup
1056 | CGGradientRelease(gradient);
1057 | CGColorSpaceRelease(colorSpace);
1058 | }
1059 | }
1060 |
1061 |
1062 |
1063 | //Draw the divider rects if we need to
1064 | {
1065 | if (kShowDividersBetweenViews && showDividerRects) {
1066 | if (dividerRects && dividerRects.count > 0) {
1067 | for (NSValue *value in dividerRects) {
1068 | CGRect rect = value.CGRectValue;
1069 | rect.origin.x += contentView.frame.origin.x;
1070 | rect.origin.y += contentView.frame.origin.y;
1071 |
1072 | UIBezierPath *dividerPath = [UIBezierPath bezierPathWithRect:rect];
1073 | [kDividerColor setFill];
1074 | [dividerPath fill];
1075 | }
1076 | }
1077 | }
1078 | }
1079 |
1080 | //Draw border if we need to
1081 | //The border is done last because it needs to be drawn on top of everything else
1082 | if (kDrawBorder) {
1083 | [kBorderColor setStroke];
1084 | popoverPath.lineWidth = kBorderWidth;
1085 | [popoverPath stroke];
1086 | }
1087 | }
1088 |
1089 | @end
1090 |
--------------------------------------------------------------------------------