├── _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 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](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 | --------------------------------------------------------------------------------