├── .gitignore ├── 001.png ├── 002.png ├── 003.png ├── README.md ├── WZCChartLineView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ ├── lafong.xcuserdatad │ └── UserInterfaceState.xcuserstate │ └── wuzhicheng.xcuserdatad │ └── UserInterfaceState.xcuserstate └── WZCChartLineView ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m ├── WZCChartLineView ├── ChartView.h ├── ChartView.m ├── PopoverView │ ├── PopoverView.h │ ├── PopoverView.m │ ├── PopoverViewCompatibility.h │ └── PopoverView_Configuration.h ├── UIBezierPath+LxThroughPointsBezier.h ├── UIBezierPath+LxThroughPointsBezier.m ├── UIView+WZCViewCategory.h ├── UIView+WZCViewCategory.m ├── WZCChartLine.h └── WZCChartLine.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voisen/WZCChartLineView/f47398d878eac3edcbae85314ffb9749a7bd40a2/001.png -------------------------------------------------------------------------------- /002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voisen/WZCChartLineView/f47398d878eac3edcbae85314ffb9749a7bd40a2/002.png -------------------------------------------------------------------------------- /003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voisen/WZCChartLineView/f47398d878eac3edcbae85314ffb9749a7bd40a2/003.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WZCChartLineView 2 | 一款可最大化自定义的折线图与曲线图 3 | 4 | 感谢大家对这款曲线/折线图的厚爱, 由于这款折线图目前无法满足实际开发中的效果与用户体验, 5 | 新的折线图 XCharts 已经出现, XCharts支持实时回放,去除pop数值提示,提供最新的提示方式, 6 | 同时支持柱状图. 7 | `XCharts` 体验地址: [https://github.com/voisen/XCharts](https://github.com/voisen/XCharts) 8 | 9 | 老司机们 快快[上车](https://github.com/voisen/XCharts)吧 10 | 11 | ----- 12 | 13 | ### 建议使用 [XCharts](https://github.com/voisen/XCharts) 14 | 15 | ----- 16 | 17 | ## 特性 18 | 19 | * 支持设置 Y 轴刻度数量 20 | * 支持设置 X 轴的位置 21 | * 支持对称显示坐标轴 22 | * 支持设置最小显示的 Y 值 (也支持自动设置 Y 值) 23 | * 支持图例显示 24 | * 支持图例自由拖动 25 | * 点击点支持pop提示类型显示(微信的+号效果显示) 26 | * 支持折线/曲线绘制 27 | * 支持图标区域滚动,Y坐标固定显示 28 | * 支持多条走势线在一个表上显示 29 | * 其他... 30 | 31 | ## 更新日志 32 | 33 | 2017.05.27 修复部分bug 34 | 35 | 2016.10.14 修复添加新功能后的重大 bug, 添加新功能(支持设置 x 轴的位置,支持对称于 X 轴显示坐标, 刻度的数量智能化) 36 | 37 | 2016.10.13 添加支持设置最小显示的 Y 值 (也支持自动设置 Y 值) 38 | 39 | 2016.8.12 添加可控参数,控制是否显示辅助线条;增加垂直于 Y 轴的辅助线 40 | 41 | ## 效果图 42 | 43 | * 不要纠结,图例可以任意拖动,坐标轴可以自由设定 44 | 45 | ![折线图](001.png) 46 | 47 | * 不要纠结,图例可以任意拖动 48 | 49 | ![曲线图](002.png) 50 | 51 | * 不要纠结, 图例可以任意拖动, X坐标轴自定义在0点 52 | ![曲线图](003.png) 53 | 54 | 55 | ## WZCChartLineView 56 | 57 | ~~~~objc 58 | 59 | #pragma mark - 必须设置 60 | /** X 坐标轴上的值 (字符串)*/ 61 | @property (nonnull,strong,nonatomic) NSArray *x_values; 62 | /* Y坐标上的值 支持多组值 (字符串)*/ 63 | @property (nonnull,strong,nonatomic) NSArray * > *y_values; 64 | /* 折线的名称/类别 (有几条直线就有一个名字,默认无) */ 65 | @property (nonnull,strong,nonatomic) NSArray *y_titles; 66 | /* 折线的颜色数组(默认随机方法) */ 67 | @property (nonnull,strong,nonatomic) NSArray *colorsArray; 68 | 69 | /* 以上 设置完毕后才调用绘图方法 */ 70 | -(void)startDrawWithLineType:(WZCChartLineType)lineType; 71 | 72 | #pragma mark - 可选设置 73 | /** 74 | * 设置最小的 Y 值(默认为0) ,设置时需注意: 如果设置的值大于最大的 Y 值, 则设置为无效; 75 | * 76 | * @param minValue 最小的 Y 值 (设置负数可以自动设置 Y 值,从最小的 Y 值起步) 77 | */ 78 | - (void)setMinY:(CGFloat)minValue; 79 | 80 | /** 81 | * 设置 X 坐标轴的位置 82 | * 83 | * @param minValue 对应的 Y 值(默认为最小值) 84 | */ 85 | - (void)setXCoordinatesLocationInYValue:(CGFloat)yValue; 86 | 87 | /** 88 | * 设置 Y 轴刻度的个数 89 | * 90 | * @param tipCont 默认为自动 91 | */ 92 | - (void)setCoords_Y_Tips:(NSInteger)tipCont; 93 | 94 | /** 95 | * 如果坐标轴中存在负数,调用此函数功能是对称显示坐标 96 | * 97 | * @param show 是否显示0点刻度 98 | */ 99 | - (void)setCoordPlusAndMinusSymmetryShowZeroPoint:(BOOL)show; 100 | 101 | ~~~~ 102 | 103 | ## 自定义配置 104 | 105 | ~~~~objc 106 | 107 | #define Arrows_Size 3 //箭头半径 108 | #define Arrows_Height 6 //箭头的高度 109 | #define Coords_lineColor [UIColor blackColor].CGColor //坐标线的颜色 110 | //#define Coords_Y_Tip 5 //刻度个数 -->改动 111 | #define Coords_Y_Tip_Width 6 //刻度宽度 112 | #define Coords_Y_LableFont_Size 12 //Y轴标签的字体大小 113 | #define Coords_X_LableFont_Size 10 //Y轴标签的字体大小 114 | #define Coords_X_Lable_Space 10 //X轴标签间距 115 | #define Coords_X_Verticlal_Line_Color [UIColor lightGrayColor].CGColor //垂直于X轴的线条颜色 116 | #define Coords_X_Verticlal_Line_Width 0.8 //垂直于X轴的线条宽度 117 | #define Coords_Values_Line_Width 1.8 //折线的线条宽度 118 | #define Coords_Legend_Font_Size 15 //图例的字体大小 119 | #define Coords_Y_Verticlal_Line_Color [UIColor lightGrayColor].CGColor //垂直于Y轴的线条颜色 120 | #define Coords_Y_Verticlal_Line_Width 0.8 //垂直于Y轴的线条宽度 121 | 122 | //新增---->2016.8.12 123 | #define Show_Coords_X_Verticlal_Line YES // 显示垂直于X轴的线条 124 | #define Show_Coords_Y_Verticlal_Line YES //显示垂直于Y轴的线条 125 | 126 | ~~~~ 127 | 128 | ## 使用 129 | 130 | ~~~~objc 131 | 132 | WZCChartLine *v = [[WZCChartLine alloc]initWithFrame:self.view.bounds]; 133 | 134 | v.x_values = @[@"一月",@"二月",@"三月",@"四月",@"五月",@"六月",@"七月",@"八月",@"九月",@"十月",@"十一月",@"十二月"]; 135 | 136 | NSArray *arr1 = @[@"5000",@"5500",@"6200",@"4000",@"3850",@"6489",@"7200",@"5345",@"6740",@"4980",@"4600",@"5390"]; 137 | NSArray *arr2 = @[@"500",@"3500",@"610",@"1200",@"3670",@"3320",@"4532",@"1210",@"2100",@"992",@"667",@"873"]; 138 | v.y_values = @[arr1,arr2]; 139 | v.y_titles = @[@"收入",@"支出"]; 140 | v.colorsArray = @[[UIColor greenColor],[UIColor redColor]]; 141 | 142 | [v startDrawWithLineType:WZCChartLineTypeBroken]; 143 | 144 | [self.view addSubview:v]; 145 | 146 | ~~~~ 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /WZCChartLineView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C4B4896A1D45DEC500F77591 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B489521D45DEC500F77591 /* AppDelegate.m */; }; 11 | C4B4896B1D45DEC500F77591 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C4B489531D45DEC500F77591 /* Assets.xcassets */; }; 12 | C4B4896C1D45DEC500F77591 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C4B489541D45DEC500F77591 /* LaunchScreen.storyboard */; }; 13 | C4B4896D1D45DEC500F77591 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C4B489561D45DEC500F77591 /* Main.storyboard */; }; 14 | C4B4896E1D45DEC500F77591 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4B489581D45DEC500F77591 /* Info.plist */; }; 15 | C4B4896F1D45DEC500F77591 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B489591D45DEC500F77591 /* main.m */; }; 16 | C4B489701D45DEC500F77591 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B4895B1D45DEC500F77591 /* ViewController.m */; }; 17 | C4B489711D45DEC500F77591 /* ChartView.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B4895E1D45DEC500F77591 /* ChartView.m */; }; 18 | C4B489721D45DEC500F77591 /* PopoverView.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B489611D45DEC500F77591 /* PopoverView.m */; }; 19 | C4B489731D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B489651D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.m */; }; 20 | C4B489741D45DEC500F77591 /* UIView+WZCViewCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B489671D45DEC500F77591 /* UIView+WZCViewCategory.m */; }; 21 | C4B489751D45DEC500F77591 /* WZCChartLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C4B489691D45DEC500F77591 /* WZCChartLine.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | C49010311D3F0B4B0080B525 /* WZCChartLineView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WZCChartLineView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | C4B489511D45DEC500F77591 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 27 | C4B489521D45DEC500F77591 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 28 | C4B489531D45DEC500F77591 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | C4B489551D45DEC500F77591 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 30 | C4B489571D45DEC500F77591 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | C4B489581D45DEC500F77591 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | C4B489591D45DEC500F77591 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | C4B4895A1D45DEC500F77591 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 34 | C4B4895B1D45DEC500F77591 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 35 | C4B4895D1D45DEC500F77591 /* ChartView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChartView.h; sourceTree = ""; }; 36 | C4B4895E1D45DEC500F77591 /* ChartView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChartView.m; sourceTree = ""; }; 37 | C4B489601D45DEC500F77591 /* PopoverView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverView.h; sourceTree = ""; }; 38 | C4B489611D45DEC500F77591 /* PopoverView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PopoverView.m; sourceTree = ""; }; 39 | C4B489621D45DEC500F77591 /* PopoverView_Configuration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverView_Configuration.h; sourceTree = ""; }; 40 | C4B489631D45DEC500F77591 /* PopoverViewCompatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopoverViewCompatibility.h; sourceTree = ""; }; 41 | C4B489641D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBezierPath+LxThroughPointsBezier.h"; sourceTree = ""; }; 42 | C4B489651D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBezierPath+LxThroughPointsBezier.m"; sourceTree = ""; }; 43 | C4B489661D45DEC500F77591 /* UIView+WZCViewCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+WZCViewCategory.h"; sourceTree = ""; }; 44 | C4B489671D45DEC500F77591 /* UIView+WZCViewCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+WZCViewCategory.m"; sourceTree = ""; }; 45 | C4B489681D45DEC500F77591 /* WZCChartLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WZCChartLine.h; sourceTree = ""; }; 46 | C4B489691D45DEC500F77591 /* WZCChartLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WZCChartLine.m; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | C490102E1D3F0B4B0080B525 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | C49010281D3F0B4B0080B525 = { 61 | isa = PBXGroup; 62 | children = ( 63 | C4B489501D45DEC500F77591 /* WZCChartLineView */, 64 | C49010321D3F0B4B0080B525 /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | C49010321D3F0B4B0080B525 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | C49010311D3F0B4B0080B525 /* WZCChartLineView.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | C4B489501D45DEC500F77591 /* WZCChartLineView */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | C4B4895C1D45DEC500F77591 /* WZCChartLineView */, 80 | C4B489511D45DEC500F77591 /* AppDelegate.h */, 81 | C4B489521D45DEC500F77591 /* AppDelegate.m */, 82 | C4B489531D45DEC500F77591 /* Assets.xcassets */, 83 | C4B489541D45DEC500F77591 /* LaunchScreen.storyboard */, 84 | C4B489561D45DEC500F77591 /* Main.storyboard */, 85 | C4B489581D45DEC500F77591 /* Info.plist */, 86 | C4B489591D45DEC500F77591 /* main.m */, 87 | C4B4895A1D45DEC500F77591 /* ViewController.h */, 88 | C4B4895B1D45DEC500F77591 /* ViewController.m */, 89 | ); 90 | path = WZCChartLineView; 91 | sourceTree = ""; 92 | }; 93 | C4B4895C1D45DEC500F77591 /* WZCChartLineView */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | C4B4895F1D45DEC500F77591 /* PopoverView */, 97 | C4B4895D1D45DEC500F77591 /* ChartView.h */, 98 | C4B4895E1D45DEC500F77591 /* ChartView.m */, 99 | C4B489641D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.h */, 100 | C4B489651D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.m */, 101 | C4B489661D45DEC500F77591 /* UIView+WZCViewCategory.h */, 102 | C4B489671D45DEC500F77591 /* UIView+WZCViewCategory.m */, 103 | C4B489681D45DEC500F77591 /* WZCChartLine.h */, 104 | C4B489691D45DEC500F77591 /* WZCChartLine.m */, 105 | ); 106 | path = WZCChartLineView; 107 | sourceTree = ""; 108 | }; 109 | C4B4895F1D45DEC500F77591 /* PopoverView */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | C4B489601D45DEC500F77591 /* PopoverView.h */, 113 | C4B489611D45DEC500F77591 /* PopoverView.m */, 114 | C4B489621D45DEC500F77591 /* PopoverView_Configuration.h */, 115 | C4B489631D45DEC500F77591 /* PopoverViewCompatibility.h */, 116 | ); 117 | path = PopoverView; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | C49010301D3F0B4B0080B525 /* WZCChartLineView */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = C49010481D3F0B4B0080B525 /* Build configuration list for PBXNativeTarget "WZCChartLineView" */; 126 | buildPhases = ( 127 | C490102D1D3F0B4B0080B525 /* Sources */, 128 | C490102E1D3F0B4B0080B525 /* Frameworks */, 129 | C490102F1D3F0B4B0080B525 /* Resources */, 130 | ); 131 | buildRules = ( 132 | ); 133 | dependencies = ( 134 | ); 135 | name = WZCChartLineView; 136 | productName = "曲线-新算法"; 137 | productReference = C49010311D3F0B4B0080B525 /* WZCChartLineView.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | C49010291D3F0B4B0080B525 /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | LastUpgradeCheck = 0710; 147 | ORGANIZATIONNAME = "邬志成"; 148 | TargetAttributes = { 149 | C49010301D3F0B4B0080B525 = { 150 | CreatedOnToolsVersion = 7.1; 151 | DevelopmentTeam = U4M9ZNVDZ2; 152 | }; 153 | }; 154 | }; 155 | buildConfigurationList = C490102C1D3F0B4B0080B525 /* Build configuration list for PBXProject "WZCChartLineView" */; 156 | compatibilityVersion = "Xcode 3.2"; 157 | developmentRegion = English; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | en, 161 | Base, 162 | ); 163 | mainGroup = C49010281D3F0B4B0080B525; 164 | productRefGroup = C49010321D3F0B4B0080B525 /* Products */; 165 | projectDirPath = ""; 166 | projectRoot = ""; 167 | targets = ( 168 | C49010301D3F0B4B0080B525 /* WZCChartLineView */, 169 | ); 170 | }; 171 | /* End PBXProject section */ 172 | 173 | /* Begin PBXResourcesBuildPhase section */ 174 | C490102F1D3F0B4B0080B525 /* Resources */ = { 175 | isa = PBXResourcesBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | C4B4896E1D45DEC500F77591 /* Info.plist in Resources */, 179 | C4B4896D1D45DEC500F77591 /* Main.storyboard in Resources */, 180 | C4B4896B1D45DEC500F77591 /* Assets.xcassets in Resources */, 181 | C4B4896C1D45DEC500F77591 /* LaunchScreen.storyboard in Resources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | C490102D1D3F0B4B0080B525 /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | C4B489701D45DEC500F77591 /* ViewController.m in Sources */, 193 | C4B489721D45DEC500F77591 /* PopoverView.m in Sources */, 194 | C4B4896F1D45DEC500F77591 /* main.m in Sources */, 195 | C4B489731D45DEC500F77591 /* UIBezierPath+LxThroughPointsBezier.m in Sources */, 196 | C4B4896A1D45DEC500F77591 /* AppDelegate.m in Sources */, 197 | C4B489711D45DEC500F77591 /* ChartView.m in Sources */, 198 | C4B489741D45DEC500F77591 /* UIView+WZCViewCategory.m in Sources */, 199 | C4B489751D45DEC500F77591 /* WZCChartLine.m in Sources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXSourcesBuildPhase section */ 204 | 205 | /* Begin PBXVariantGroup section */ 206 | C4B489541D45DEC500F77591 /* LaunchScreen.storyboard */ = { 207 | isa = PBXVariantGroup; 208 | children = ( 209 | C4B489551D45DEC500F77591 /* Base */, 210 | ); 211 | name = LaunchScreen.storyboard; 212 | sourceTree = ""; 213 | }; 214 | C4B489561D45DEC500F77591 /* Main.storyboard */ = { 215 | isa = PBXVariantGroup; 216 | children = ( 217 | C4B489571D45DEC500F77591 /* Base */, 218 | ); 219 | name = Main.storyboard; 220 | sourceTree = ""; 221 | }; 222 | /* End PBXVariantGroup section */ 223 | 224 | /* Begin XCBuildConfiguration section */ 225 | C49010461D3F0B4B0080B525 /* Debug */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | ALWAYS_SEARCH_USER_PATHS = NO; 229 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 230 | CLANG_CXX_LIBRARY = "libc++"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 236 | CLANG_WARN_EMPTY_BODY = YES; 237 | CLANG_WARN_ENUM_CONVERSION = YES; 238 | CLANG_WARN_INT_CONVERSION = YES; 239 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 240 | CLANG_WARN_UNREACHABLE_CODE = YES; 241 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 242 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 243 | COPY_PHASE_STRIP = NO; 244 | DEBUG_INFORMATION_FORMAT = dwarf; 245 | ENABLE_STRICT_OBJC_MSGSEND = YES; 246 | ENABLE_TESTABILITY = YES; 247 | GCC_C_LANGUAGE_STANDARD = gnu99; 248 | GCC_DYNAMIC_NO_PIC = NO; 249 | GCC_NO_COMMON_BLOCKS = YES; 250 | GCC_OPTIMIZATION_LEVEL = 0; 251 | GCC_PREPROCESSOR_DEFINITIONS = ( 252 | "DEBUG=1", 253 | "$(inherited)", 254 | ); 255 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 256 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 257 | GCC_WARN_UNDECLARED_SELECTOR = YES; 258 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 259 | GCC_WARN_UNUSED_FUNCTION = YES; 260 | GCC_WARN_UNUSED_VARIABLE = YES; 261 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 262 | MTL_ENABLE_DEBUG_INFO = YES; 263 | ONLY_ACTIVE_ARCH = YES; 264 | SDKROOT = iphoneos; 265 | TARGETED_DEVICE_FAMILY = "1,2"; 266 | }; 267 | name = Debug; 268 | }; 269 | C49010471D3F0B4B0080B525 /* Release */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ALWAYS_SEARCH_USER_PATHS = NO; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_WARN_BOOL_CONVERSION = YES; 278 | CLANG_WARN_CONSTANT_CONVERSION = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_EMPTY_BODY = YES; 281 | CLANG_WARN_ENUM_CONVERSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_UNREACHABLE_CODE = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 287 | COPY_PHASE_STRIP = NO; 288 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 289 | ENABLE_NS_ASSERTIONS = NO; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | GCC_C_LANGUAGE_STANDARD = gnu99; 292 | GCC_NO_COMMON_BLOCKS = YES; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 300 | MTL_ENABLE_DEBUG_INFO = NO; 301 | SDKROOT = iphoneos; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | VALIDATE_PRODUCT = YES; 304 | }; 305 | name = Release; 306 | }; 307 | C49010491D3F0B4B0080B525 /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | INFOPLIST_FILE = "$(SRCROOT)/WZCChartLineView/Info.plist"; 312 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 313 | PRODUCT_BUNDLE_IDENTIFIER = zhicheng.wu.; 314 | PRODUCT_NAME = WZCChartLineView; 315 | }; 316 | name = Debug; 317 | }; 318 | C490104A1D3F0B4B0080B525 /* Release */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 322 | INFOPLIST_FILE = "$(SRCROOT)/WZCChartLineView/Info.plist"; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | PRODUCT_BUNDLE_IDENTIFIER = zhicheng.wu.; 325 | PRODUCT_NAME = WZCChartLineView; 326 | }; 327 | name = Release; 328 | }; 329 | /* End XCBuildConfiguration section */ 330 | 331 | /* Begin XCConfigurationList section */ 332 | C490102C1D3F0B4B0080B525 /* Build configuration list for PBXProject "WZCChartLineView" */ = { 333 | isa = XCConfigurationList; 334 | buildConfigurations = ( 335 | C49010461D3F0B4B0080B525 /* Debug */, 336 | C49010471D3F0B4B0080B525 /* Release */, 337 | ); 338 | defaultConfigurationIsVisible = 0; 339 | defaultConfigurationName = Release; 340 | }; 341 | C49010481D3F0B4B0080B525 /* Build configuration list for PBXNativeTarget "WZCChartLineView" */ = { 342 | isa = XCConfigurationList; 343 | buildConfigurations = ( 344 | C49010491D3F0B4B0080B525 /* Debug */, 345 | C490104A1D3F0B4B0080B525 /* Release */, 346 | ); 347 | defaultConfigurationIsVisible = 0; 348 | defaultConfigurationName = Release; 349 | }; 350 | /* End XCConfigurationList section */ 351 | }; 352 | rootObject = C49010291D3F0B4B0080B525 /* Project object */; 353 | } 354 | -------------------------------------------------------------------------------- /WZCChartLineView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WZCChartLineView.xcodeproj/project.xcworkspace/xcuserdata/lafong.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voisen/WZCChartLineView/f47398d878eac3edcbae85314ffb9749a7bd40a2/WZCChartLineView.xcodeproj/project.xcworkspace/xcuserdata/lafong.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /WZCChartLineView.xcodeproj/project.xcworkspace/xcuserdata/wuzhicheng.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voisen/WZCChartLineView/f47398d878eac3edcbae85314ffb9749a7bd40a2/WZCChartLineView.xcodeproj/project.xcworkspace/xcuserdata/wuzhicheng.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /WZCChartLineView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /WZCChartLineView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | [self.window makeKeyWindow]; 21 | return YES; 22 | } 23 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 27 | } 28 | 29 | - (void)applicationDidEnterBackground:(UIApplication *)application { 30 | // 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. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | - (void)applicationWillEnterForeground:(UIApplication *)application { 35 | // 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. 36 | } 37 | 38 | - (void)applicationDidBecomeActive:(UIApplication *)application { 39 | // 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. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /WZCChartLineView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /WZCChartLineView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /WZCChartLineView/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 | -------------------------------------------------------------------------------- /WZCChartLineView/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 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /WZCChartLineView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /WZCChartLineView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "WZCChartLine.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController{ 17 | 18 | WZCChartLine *v; 19 | } 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | //创建折线图 24 | v = [[WZCChartLine alloc]initWithFrame:self.view.bounds]; 25 | 26 | NSMutableArray *x_vls = [[NSMutableArray alloc]init]; 27 | for (int i = 0; i < 20; i ++) { 28 | NSString *str = [NSString stringWithFormat:@"第%d天",i+1]; 29 | [x_vls addObject:str]; 30 | } 31 | // x轴坐标数组 32 | v.x_values = x_vls; 33 | NSMutableArray *y_array = [NSMutableArray array]; 34 | for (int i = 0; i < 8; i ++) { 35 | NSMutableArray *arr = [NSMutableArray array]; 36 | for (int j = 0; j < v.x_values.count; j ++) { 37 | CGFloat value = - arc4random_uniform(10); 38 | [arr addObject:[NSString stringWithFormat:@"%0.2f",value]]; 39 | } 40 | [y_array addObject:arr]; 41 | } 42 | // y轴坐标数组 43 | v.y_values = @[@[@"-100",@"-153",@"-72",@"47",@"-33",@"0",@"-23",@"98",@"66",@"99",@"-10",@"-53",@"32",@"-27",@"-93",@"50",@"17",@"8",@"-35",@"-14"]];//y_array; 44 | //折线名称 45 | v.y_titles = @[@"测试"];//@[@"黄金",@"黄金珠宝",@"项链首饰",@"项链",@"首饰",@"金币",@"销售量",@"单价浮动"]; 46 | [v setXCoordinatesLocationInYValue:0]; 47 | [v setCoordPlusAndMinusSymmetryShowZeroPoint:YES]; 48 | [self.view addSubview:v]; 49 | } 50 | 51 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 52 | static dispatch_once_t onceToken; 53 | dispatch_once(&onceToken, ^{ 54 | //点击屏幕 绘制曲线 55 | [v startDrawWithLineType:WZCChartLineTypeCurve]; 56 | }); 57 | } 58 | 59 | -(UIInterfaceOrientationMask)supportedInterfaceOrientations{ 60 | return UIInterfaceOrientationMaskLandscapeRight; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/ChartView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChartView.h 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/22. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import 10 | @protocol ChartViewDelegate 11 | 12 | -(void)chartViewTouchPoint:(CGPoint)point; 13 | 14 | @end 15 | 16 | @interface ChartView : UIScrollView 17 | 18 | @property (nonatomic,strong) id touch_delegate; 19 | 20 | @end 21 | 22 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/ChartView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChartView.m 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/22. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import "ChartView.h" 10 | 11 | 12 | @implementation ChartView 13 | 14 | 15 | - (instancetype)initWithFrame:(CGRect)frame{ 16 | self = [super initWithFrame:frame]; 17 | 18 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; 19 | longPress.minimumPressDuration = 0.2; 20 | [self addGestureRecognizer:longPress]; 21 | 22 | return self; 23 | } 24 | 25 | - (void)longPress:(UIGestureRecognizer*)press{ 26 | 27 | if (press.state == UIGestureRecognizerStateBegan) { 28 | 29 | CGPoint point = [press locationInView:self]; 30 | 31 | [self.touch_delegate chartViewTouchPoint:point]; 32 | } 33 | 34 | } 35 | 36 | //-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 37 | // UITouch *touch = [touches anyObject]; 38 | // CGPoint touch_point = [touch locationInView:self]; 39 | // [self.touch_delegate chartViewTouchPoint:touch_point]; 40 | //} 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/PopoverView/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 | 82 | BOOL above; 83 | 84 | __WEAK id delegate; 85 | 86 | UIView *parentView; 87 | 88 | UIView *topView; 89 | 90 | NSArray *subviewsArray; 91 | 92 | NSArray *dividerRects; 93 | 94 | UIView *contentView; 95 | 96 | UIView *titleView; 97 | 98 | UIActivityIndicatorView *activityIndicator; 99 | 100 | //Instance variable that can change at runtime 101 | BOOL showDividerRects; 102 | } 103 | 104 | @property (nonatomic, STRONG) UIView *titleView; 105 | 106 | @property (nonatomic, STRONG) UIView *contentView; 107 | 108 | @property (nonatomic, STRONG) NSArray *subviewsArray; 109 | 110 | @property (nonatomic, WEAK) id delegate; 111 | 112 | #pragma mark - Class Static Showing Methods 113 | 114 | //These are the main static methods you can use to display the popover. 115 | //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. 116 | 117 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text delegate:(id)delegate; 118 | 119 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text delegate:(id)delegate; 120 | 121 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray delegate:(id)delegate; 122 | 123 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray delegate:(id)delegate; 124 | 125 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray delegate:(id)delegate; 126 | 127 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray delegate:(id)delegate; 128 | 129 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate; 130 | 131 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate; 132 | 133 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView delegate:(id)delegate; 134 | 135 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView delegate:(id)delegate; 136 | 137 | #pragma mark - Instance Showing Methods 138 | 139 | //Adds/animates in the popover to the top of the view stack with the arrow pointing at the "point" 140 | //within the specified view. The contentView will be added to the popover, and should have either 141 | //a clear color backgroundColor, or perhaps a rounded corner bg rect (radius 4.f if you're going to 142 | //round). 143 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)contentView; 144 | 145 | //Calls above method with a UILabel containing the text you deliver to this method. 146 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text; 147 | 148 | //Calls top method with an array of UIView objects. This method will stack these views vertically 149 | //with kBoxPadding padding between each view in the y-direction. 150 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray; 151 | 152 | //Does same as above, but adds a title label at top of the popover. 153 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray; 154 | 155 | //Calls the viewArray method with an array of UILabels created with the strings 156 | //in stringArray. All contents of stringArray must be NSStrings. 157 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray; 158 | 159 | //This method does same as above, but with a title label at the top of the popover. 160 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray; 161 | 162 | //Draws a vertical list of the NSString elements of stringArray with UIImages 163 | //from imageArray placed centered above them. 164 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray; 165 | 166 | //Does the same as above, but with a title 167 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray; 168 | 169 | //Lays out the PopoverView at a point once all of the views have already been setup elsewhere 170 | - (void)layoutAtPoint:(CGPoint)point inView:(UIView *)view; 171 | 172 | #pragma mark - Other Interaction 173 | //This method animates the rotation of the PopoverView to a new point 174 | - (void)animateRotationToNewPoint:(CGPoint)point inView:(UIView *)view withDuration:(NSTimeInterval)duration; 175 | 176 | #pragma mark - Dismissal 177 | //Dismisses the view, and removes it from the view stack. 178 | - (void)dismiss; 179 | - (void)dismiss:(BOOL)animated; 180 | 181 | #pragma mark - Activity Indicator Methods 182 | 183 | //Shows the activity indicator, and changes the title (if the title is available, and is a UILabel). 184 | - (void)showActivityIndicatorWithMessage:(NSString *)msg; 185 | 186 | //Hides the activity indicator, and changes the title (if the title is available) to the msg 187 | - (void)hideActivityIndicatorWithMessage:(NSString *)msg; 188 | 189 | #pragma mark - Custom Image Showing 190 | 191 | //Animate in, and display the image provided here. 192 | - (void)showImage:(UIImage *)image withMessage:(NSString *)msg; 193 | 194 | #pragma mark - Error/Success Methods 195 | 196 | //Shows (and animates in) an error X in the contentView 197 | - (void)showError; 198 | 199 | //Shows (and animates in) a success checkmark in the contentView 200 | - (void)showSuccess; 201 | 202 | @end 203 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/PopoverView/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 | #pragma mark - Static Methods 22 | 23 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text delegate:(id)delegate { 24 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 25 | [popoverView showAtPoint:point inView:view withText:text]; 26 | popoverView.delegate = delegate; 27 | [popoverView RELEASE]; 28 | return popoverView; 29 | } 30 | 31 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text delegate:(id)delegate { 32 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 33 | [popoverView showAtPoint:point inView:view withTitle:title withText:text]; 34 | popoverView.delegate = delegate; 35 | [popoverView RELEASE]; 36 | return popoverView; 37 | } 38 | 39 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray delegate:(id)delegate { 40 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 41 | [popoverView showAtPoint:point inView:view withViewArray:viewArray]; 42 | popoverView.delegate = delegate; 43 | [popoverView RELEASE]; 44 | return popoverView; 45 | } 46 | 47 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray delegate:(id)delegate { 48 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 49 | [popoverView showAtPoint:point inView:view withTitle:title withViewArray:viewArray]; 50 | popoverView.delegate = delegate; 51 | [popoverView RELEASE]; 52 | return popoverView; 53 | } 54 | 55 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray delegate:(id)delegate { 56 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 57 | [popoverView showAtPoint:point inView:view withStringArray:stringArray]; 58 | popoverView.delegate = delegate; 59 | [popoverView RELEASE]; 60 | return popoverView; 61 | } 62 | 63 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray delegate:(id)delegate { 64 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 65 | [popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray]; 66 | popoverView.delegate = delegate; 67 | [popoverView RELEASE]; 68 | return popoverView; 69 | } 70 | 71 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate { 72 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 73 | [popoverView showAtPoint:point inView:view withStringArray:stringArray withImageArray:imageArray]; 74 | popoverView.delegate = delegate; 75 | [popoverView RELEASE]; 76 | return popoverView; 77 | } 78 | 79 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray delegate:(id)delegate { 80 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 81 | [popoverView showAtPoint:point inView:view withTitle:title withStringArray:stringArray withImageArray:imageArray]; 82 | popoverView.delegate = delegate; 83 | [popoverView RELEASE]; 84 | return popoverView; 85 | } 86 | 87 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView delegate:(id)delegate { 88 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 89 | [popoverView showAtPoint:point inView:view withTitle:title withContentView:cView]; 90 | popoverView.delegate = delegate; 91 | [popoverView RELEASE]; 92 | return popoverView; 93 | } 94 | 95 | + (PopoverView *)showPopoverAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView delegate:(id)delegate { 96 | PopoverView *popoverView = [[PopoverView alloc] initWithFrame:CGRectZero]; 97 | [popoverView showAtPoint:point inView:view withContentView:cView]; 98 | popoverView.delegate = delegate; 99 | [popoverView RELEASE]; 100 | return popoverView; 101 | } 102 | 103 | #pragma mark - View Lifecycle 104 | 105 | - (id)initWithFrame:(CGRect)frame 106 | { 107 | self = [super initWithFrame:frame]; 108 | if (self) { 109 | // Initialization code 110 | 111 | self.backgroundColor = [UIColor clearColor]; 112 | 113 | self.titleView = nil; 114 | self.contentView = nil; 115 | 116 | showDividerRects = kShowDividersBetweenViews; 117 | } 118 | return self; 119 | } 120 | 121 | - (void)dealloc 122 | { 123 | self.subviewsArray = nil; 124 | 125 | if (dividerRects) { 126 | [dividerRects RELEASE]; 127 | dividerRects = nil; 128 | } 129 | 130 | self.contentView = nil; 131 | self.titleView = nil; 132 | 133 | [super DEALLOC]; 134 | } 135 | 136 | 137 | 138 | #pragma mark - Display methods 139 | 140 | // get the screen size, adjusted for orientation and status bar display 141 | // see http://stackoverflow.com/questions/7905432/how-to-get-orientation-dependent-height-and-width-of-the-screen/7905540#7905540 142 | - (CGSize) screenSize 143 | { 144 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 145 | CGSize size = [UIScreen mainScreen].bounds.size; 146 | UIApplication *application = [UIApplication sharedApplication]; 147 | if (UIInterfaceOrientationIsLandscape(orientation)) 148 | { 149 | size = CGSizeMake(size.height, size.width); 150 | } 151 | if (application.statusBarHidden == NO) 152 | { 153 | size.height -= MIN(application.statusBarFrame.size.width, application.statusBarFrame.size.height); 154 | } 155 | return size; 156 | } 157 | 158 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withText:(NSString *)text 159 | { 160 | UIFont *font = kTextFont; 161 | 162 | CGSize screenSize = [self screenSize]; 163 | CGSize textSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f) lineBreakMode:UILineBreakModeWordWrap]; 164 | 165 | UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)]; 166 | textView.backgroundColor = [UIColor clearColor]; 167 | textView.userInteractionEnabled = NO; 168 | [textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text 169 | textView.font = font; 170 | textView.textAlignment = kTextAlignment; 171 | textView.textColor = kTextColor; 172 | textView.text = text; 173 | 174 | [self showAtPoint:point inView:view withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]]; 175 | } 176 | 177 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withText:(NSString *)text 178 | { 179 | UIFont *font = kTextFont; 180 | 181 | CGSize screenSize = [self screenSize]; 182 | CGSize textSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(screenSize.width - kHorizontalMargin*4.f, 1000.f) lineBreakMode:UILineBreakModeWordWrap]; 183 | 184 | UILabel *textView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)]; 185 | textView.backgroundColor = [UIColor clearColor]; 186 | textView.userInteractionEnabled = NO; 187 | [textView setNumberOfLines:0]; //This is so the label word wraps instead of cutting off the text 188 | textView.font = font; 189 | textView.textAlignment = kTextAlignment; 190 | textView.textColor = kTextColor; 191 | textView.text = text; 192 | 193 | [self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:[textView AUTORELEASE]]]; 194 | } 195 | 196 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withViewArray:(NSArray *)viewArray 197 | { 198 | UIView *container = [[UIView alloc] initWithFrame:CGRectZero]; 199 | 200 | float totalHeight = 0.f; 201 | float totalWidth = 0.f; 202 | 203 | int i = 0; 204 | 205 | //Position each view the first time, and identify which view has the largest width that controls 206 | //the sizing of the popover. 207 | for (UIView *view in viewArray) { 208 | 209 | view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height); 210 | //Only add padding below the view if it's not the last item 211 | float padding = (i == viewArray.count-1) ? 0 : kBoxPadding; 212 | 213 | totalHeight += view.frame.size.height + padding; 214 | 215 | if (view.frame.size.width > totalWidth) { 216 | totalWidth = view.frame.size.width; 217 | } 218 | 219 | [container addSubview:view]; 220 | 221 | i++; 222 | } 223 | 224 | //If dividers are enabled, then we allocate the divider rect array. This will hold NSValues 225 | if (kShowDividersBetweenViews) { 226 | dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1]; 227 | } 228 | 229 | container.frame = CGRectMake(0, 0, totalWidth, totalHeight); 230 | 231 | i = 0; 232 | 233 | totalHeight = 0; 234 | 235 | //Now we actually change the frame element for each subview, and center the views horizontally. 236 | for (UIView *view in viewArray) { 237 | if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) { 238 | //Now make sure all flexible views are the full width 239 | view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height); 240 | } else { 241 | //If the view is not flexible width, then we position it centered in the view 242 | //without stretching it. 243 | 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); 244 | } 245 | 246 | //and if dividers are enabled, we record their position for the drawing methods 247 | if (kShowDividersBetweenViews && i != viewArray.count-1) { 248 | 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); 249 | 250 | [((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]]; 251 | } 252 | 253 | //Only add padding below the view if it's not the last item 254 | float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding; 255 | 256 | totalHeight += view.frame.size.height + padding; 257 | 258 | i++; 259 | } 260 | 261 | self.subviewsArray = viewArray; 262 | 263 | [self showAtPoint:point inView:view withContentView:[container AUTORELEASE]]; 264 | } 265 | 266 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withViewArray:(NSArray *)viewArray 267 | { 268 | UIView *container = [[UIView alloc] initWithFrame:CGRectZero]; 269 | 270 | //Create a label for the title text. 271 | CGSize titleSize = [title sizeWithFont:kTitleFont]; 272 | UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.f, 0.f, titleSize.width, titleSize.height)]; 273 | titleLabel.backgroundColor = [UIColor clearColor]; 274 | titleLabel.font = kTitleFont; 275 | titleLabel.textAlignment = UITextAlignmentCenter; 276 | titleLabel.textColor = kTitleColor; 277 | titleLabel.text = title; 278 | 279 | //Make sure that the title's label will have non-zero height. If it has zero height, then we don't allocate any space 280 | //for it in the positioning of the views. 281 | float titleHeightOffset = (titleSize.height > 0.f ? kBoxPadding : 0.f); 282 | 283 | float totalHeight = titleSize.height + titleHeightOffset + kBoxPadding; 284 | float totalWidth = titleSize.width; 285 | 286 | int i = 0; 287 | 288 | //Position each view the first time, and identify which view has the largest width that controls 289 | //the sizing of the popover. 290 | for (UIView *view in viewArray) { 291 | 292 | view.frame = CGRectMake(0, totalHeight, view.frame.size.width, view.frame.size.height); 293 | 294 | //Only add padding below the view if it's not the last item. 295 | float padding = (i == viewArray.count-1) ? 0.f : kBoxPadding; 296 | 297 | totalHeight += view.frame.size.height + padding; 298 | 299 | if (view.frame.size.width > totalWidth) { 300 | totalWidth = view.frame.size.width; 301 | } 302 | 303 | [container addSubview:view]; 304 | 305 | i++; 306 | } 307 | 308 | //If dividers are enabled, then we allocate the divider rect array. This will hold NSValues 309 | if (kShowDividersBetweenViews) { 310 | dividerRects = [[NSMutableArray alloc] initWithCapacity:viewArray.count-1]; 311 | } 312 | 313 | i = 0; 314 | 315 | for (UIView *view in viewArray) { 316 | if ([view autoresizingMask] == UIViewAutoresizingFlexibleWidth) { 317 | //Now make sure all flexible views are the full width 318 | view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, totalWidth, view.frame.size.height); 319 | } else { 320 | //If the view is not flexible width, then we position it centered in the view 321 | //without stretching it. 322 | 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); 323 | } 324 | 325 | //and if dividers are enabled, we record their position for the drawing methods 326 | if (kShowDividersBetweenViews && i != viewArray.count-1) { 327 | 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); 328 | 329 | [((NSMutableArray *)dividerRects) addObject:[NSValue valueWithCGRect:dividerRect]]; 330 | } 331 | 332 | i++; 333 | } 334 | 335 | titleLabel.frame = CGRectMake(floorf(totalWidth*0.5f - titleSize.width*0.5f), 0, titleSize.width, titleSize.height); 336 | 337 | //Store the titleView as an instance variable if it is larger than 0 height (not an empty string) 338 | if (titleSize.height > 0) { 339 | self.titleView = titleLabel; 340 | } 341 | 342 | [container addSubview:[titleLabel AUTORELEASE]]; 343 | 344 | container.frame = CGRectMake(0, 0, totalWidth, totalHeight); 345 | 346 | self.subviewsArray = viewArray; 347 | 348 | [self showAtPoint:point inView:view withContentView:[container AUTORELEASE]]; 349 | } 350 | 351 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray 352 | { 353 | NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count]; 354 | 355 | UIFont *font = kTextFont; 356 | 357 | for (NSString *string in stringArray) { 358 | CGSize textSize = [string sizeWithFont:font]; 359 | UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)]; 360 | textButton.backgroundColor = [UIColor clearColor]; 361 | textButton.titleLabel.font = font; 362 | textButton.titleLabel.textAlignment = kTextAlignment; 363 | textButton.titleLabel.textColor = kTextColor; 364 | [textButton setTitle:string forState:UIControlStateNormal]; 365 | textButton.layer.cornerRadius = 4.f; 366 | [textButton setTitleColor:kTextColor forState:UIControlStateNormal]; 367 | [textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted]; 368 | [textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside]; 369 | 370 | [labelArray addObject:[textButton AUTORELEASE]]; 371 | } 372 | 373 | [self showAtPoint:point inView:view withViewArray:[labelArray AUTORELEASE]]; 374 | } 375 | 376 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray 377 | { 378 | NSMutableArray *labelArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count]; 379 | 380 | UIFont *font = kTextFont; 381 | 382 | for (NSString *string in stringArray) { 383 | CGSize textSize = [string sizeWithFont:font]; 384 | UIButton *textButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)]; 385 | textButton.backgroundColor = [UIColor clearColor]; 386 | textButton.titleLabel.font = font; 387 | textButton.titleLabel.textAlignment = kTextAlignment; 388 | textButton.titleLabel.textColor = kTextColor; 389 | [textButton setTitle:string forState:UIControlStateNormal]; 390 | textButton.layer.cornerRadius = 4.f; 391 | [textButton setTitleColor:kTextColor forState:UIControlStateNormal]; 392 | [textButton setTitleColor:kTextHighlightColor forState:UIControlStateHighlighted]; 393 | [textButton addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside]; 394 | 395 | [labelArray addObject:[textButton AUTORELEASE]]; 396 | } 397 | 398 | [self showAtPoint:point inView:view withTitle:title withViewArray:[labelArray AUTORELEASE]]; 399 | } 400 | 401 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray 402 | { 403 | //Here we do something pretty similar to the stringArray method above. 404 | //We create an array of subviews that contains the strings and images centered above a label. 405 | 406 | NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count"); 407 | NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray]; 408 | 409 | [self showAtPoint:point inView:view withViewArray:tempViewArray]; 410 | } 411 | 412 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withStringArray:(NSArray *)stringArray withImageArray:(NSArray *)imageArray 413 | { 414 | NSAssert((stringArray.count == imageArray.count), @"stringArray.count should equal imageArray.count"); 415 | NSMutableArray* tempViewArray = [self makeTempViewsWithStrings:stringArray andImages:imageArray]; 416 | 417 | [self showAtPoint:point inView:view withTitle:title withViewArray:tempViewArray]; 418 | } 419 | 420 | - (NSMutableArray*) makeTempViewsWithStrings:(NSArray *)stringArray andImages:(NSArray *)imageArray 421 | { 422 | NSMutableArray *tempViewArray = [[NSMutableArray alloc] initWithCapacity:stringArray.count]; 423 | 424 | UIFont *font = kTextFont; 425 | 426 | for (int i = 0; i < stringArray.count; i++) { 427 | NSString *string = [stringArray objectAtIndex:i]; 428 | 429 | //First we build a label for the text to set in. 430 | CGSize textSize = [string sizeWithFont:font]; 431 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, textSize.width, textSize.height)]; 432 | label.backgroundColor = [UIColor clearColor]; 433 | label.font = font; 434 | label.textAlignment = kTextAlignment; 435 | label.textColor = kTextColor; 436 | label.text = string; 437 | label.layer.cornerRadius = 4.f; 438 | 439 | //Now we grab the image at the same index in the imageArray, and create 440 | //a UIImageView for it. 441 | UIImage *image = [imageArray objectAtIndex:i]; 442 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 443 | 444 | //Take the larger of the two widths as the width for the container 445 | float containerWidth = MAX(imageView.frame.size.width, label.frame.size.width); 446 | float containerHeight = label.frame.size.height + kImageTopPadding + kImageBottomPadding + imageView.frame.size.height; 447 | 448 | //This container will hold both the image and the label 449 | UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, containerWidth, containerHeight)]; 450 | 451 | //Now we do the frame manipulations to put the imageView on top of the label, both centered 452 | imageView.frame = CGRectMake(floorf(containerWidth*0.5f - imageView.frame.size.width*0.5f), kImageTopPadding, imageView.frame.size.width, imageView.frame.size.height); 453 | 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); 454 | 455 | [containerView addSubview:imageView]; 456 | [containerView addSubview:label]; 457 | 458 | [label RELEASE]; 459 | [imageView RELEASE]; 460 | 461 | [tempViewArray addObject:containerView]; 462 | [containerView RELEASE]; 463 | } 464 | 465 | return [tempViewArray AUTORELEASE]; 466 | } 467 | 468 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withTitle:(NSString *)title withContentView:(UIView *)cView 469 | { 470 | [self showAtPoint:point inView:view withTitle:title withViewArray:[NSArray arrayWithObject:cView]]; 471 | } 472 | 473 | - (void)showAtPoint:(CGPoint)point inView:(UIView *)view withContentView:(UIView *)cView { 474 | 475 | //NSLog(@"point:%f,%f", point.x, point.y); 476 | 477 | self.contentView = cView; 478 | parentView = view; 479 | 480 | // get the top view 481 | // http://stackoverflow.com/questions/3843411/getting-reference-to-the-top-most-view-window-in-ios-application/8045804#8045804 482 | topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject]; 483 | 484 | [self setupLayout:point inView:view]; 485 | 486 | // Make the view small and transparent before animation 487 | self.alpha = 0.f; 488 | self.transform = CGAffineTransformMakeScale(0.1f, 0.1f); 489 | 490 | // animate into full size 491 | // First stage animates to 1.05x normal size, then second stage animates back down to 1x size. 492 | // This two-stage animation creates a little "pop" on open. 493 | [UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 494 | self.alpha = 1.f; 495 | self.transform = CGAffineTransformMakeScale(1.05f, 1.05f); 496 | } completion:^(BOOL finished) { 497 | [UIView animateWithDuration:0.08f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 498 | self.transform = CGAffineTransformIdentity; 499 | } completion:nil]; 500 | }]; 501 | } 502 | 503 | - (void)layoutAtPoint:(CGPoint)point inView:(UIView *)view 504 | { 505 | // make transparent 506 | self.alpha = 0.f; 507 | 508 | [self setupLayout:point inView:view]; 509 | 510 | // animate back to full opacity 511 | [UIView animateWithDuration:0.2f delay:0.f options:UIViewAnimationOptionCurveEaseInOut animations:^{ 512 | self.alpha = 1.f; 513 | } completion:nil]; 514 | } 515 | 516 | -(void)setupLayout:(CGPoint)point inView:(UIView*)view 517 | { 518 | CGPoint topPoint = [topView convertPoint:point fromView:view]; 519 | 520 | arrowPoint = topPoint; 521 | 522 | //NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y); 523 | 524 | CGRect topViewBounds = topView.bounds; 525 | //NSLog(@"topViewBounds %@", NSStringFromCGRect(topViewBounds)); 526 | 527 | float contentHeight = contentView.frame.size.height; 528 | float contentWidth = contentView.frame.size.width; 529 | 530 | float padding = kBoxPadding; 531 | 532 | float boxHeight = contentHeight + 2.f*padding; 533 | float boxWidth = contentWidth + 2.f*padding; 534 | 535 | float xOrigin = 0.f; 536 | 537 | //Make sure the arrow point is within the drawable bounds for the popover. 538 | if (arrowPoint.x + kArrowHeight > topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding) {//Too far to the right 539 | arrowPoint.x = topViewBounds.size.width - kHorizontalMargin - kBoxRadius - kArrowHorizontalPadding - kArrowHeight; 540 | //NSLog(@"Correcting Arrow Point because it's too far to the right"); 541 | } else if (arrowPoint.x - kArrowHeight < kHorizontalMargin + kBoxRadius + kArrowHorizontalPadding) {//Too far to the left 542 | arrowPoint.x = kHorizontalMargin + kArrowHeight + kBoxRadius + kArrowHorizontalPadding; 543 | //NSLog(@"Correcting Arrow Point because it's too far to the left"); 544 | } 545 | 546 | //NSLog(@"arrowPoint:%f,%f", arrowPoint.x, arrowPoint.y); 547 | 548 | xOrigin = floorf(arrowPoint.x - boxWidth*0.5f); 549 | 550 | //Check to see if the centered xOrigin value puts the box outside of the normal range. 551 | if (xOrigin < CGRectGetMinX(topViewBounds) + kHorizontalMargin) { 552 | xOrigin = CGRectGetMinX(topViewBounds) + kHorizontalMargin; 553 | } else if (xOrigin + boxWidth > CGRectGetMaxX(topViewBounds) - kHorizontalMargin) { 554 | //Check to see if the positioning puts the box out of the window towards the left 555 | xOrigin = CGRectGetMaxX(topViewBounds) - kHorizontalMargin - boxWidth; 556 | } 557 | 558 | float arrowHeight = kArrowHeight; 559 | 560 | float topPadding = kTopMargin; 561 | 562 | above = YES; 563 | 564 | if (topPoint.y - contentHeight - arrowHeight - topPadding < CGRectGetMinY(topViewBounds)) { 565 | //Position below because it won't fit above. 566 | above = NO; 567 | 568 | boxFrame = CGRectMake(xOrigin, arrowPoint.y + arrowHeight, boxWidth, boxHeight); 569 | } else { 570 | //Position above. 571 | above = YES; 572 | 573 | boxFrame = CGRectMake(xOrigin, arrowPoint.y - arrowHeight - boxHeight, boxWidth, boxHeight); 574 | } 575 | 576 | //NSLog(@"boxFrame:(%f,%f,%f,%f)", boxFrame.origin.x, boxFrame.origin.y, boxFrame.size.width, boxFrame.size.height); 577 | 578 | CGRect contentFrame = CGRectMake(boxFrame.origin.x + padding, boxFrame.origin.y + padding, contentWidth, contentHeight); 579 | contentView.frame = contentFrame; 580 | 581 | //We set the anchorPoint here so the popover will "grow" out of the arrowPoint specified by the user. 582 | //You have to set the anchorPoint before setting the frame, because the anchorPoint property will 583 | //implicitly set the frame for the view, which we do not want. 584 | self.layer.anchorPoint = CGPointMake(arrowPoint.x / topViewBounds.size.width, arrowPoint.y / topViewBounds.size.height); 585 | self.frame = topViewBounds; 586 | [self setNeedsDisplay]; 587 | 588 | [self addSubview:contentView]; 589 | [topView addSubview:self]; 590 | 591 | //Add a tap gesture recognizer to the large invisible view (self), which will detect taps anywhere on the screen. 592 | UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)]; 593 | tap.cancelsTouchesInView = NO; // Allow touches through to a UITableView or other touchable view, as suggested by Dimajp. 594 | [self addGestureRecognizer:tap]; 595 | [tap RELEASE]; 596 | 597 | self.userInteractionEnabled = YES; 598 | } 599 | 600 | 601 | #pragma mark - Activity Indicator 602 | 603 | //Animates in a progress indicator, and removes 604 | - (void)showActivityIndicatorWithMessage:(NSString *)msg 605 | { 606 | if ([titleView isKindOfClass:[UILabel class]]) { 607 | ((UILabel *)titleView).text = msg; 608 | } 609 | 610 | if (subviewsArray && (subviewsArray.count > 0)) { 611 | [UIView animateWithDuration:0.2f animations:^{ 612 | for (UIView *view in subviewsArray) { 613 | view.alpha = 0.f; 614 | } 615 | }]; 616 | 617 | if (showDividerRects) { 618 | showDividerRects = NO; 619 | [self setNeedsDisplay]; 620 | } 621 | } 622 | 623 | if (activityIndicator) { 624 | [activityIndicator RELEASE]; 625 | [activityIndicator removeFromSuperview]; 626 | activityIndicator = nil; 627 | } 628 | 629 | activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 630 | activityIndicator.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 10.f, CGRectGetMidY(contentView.bounds) - 10.f + 20.f, 20.f, 20.f); 631 | [contentView addSubview:activityIndicator]; 632 | 633 | [activityIndicator startAnimating]; 634 | } 635 | 636 | - (void)hideActivityIndicatorWithMessage:(NSString *)msg 637 | { 638 | if ([titleView isKindOfClass:[UILabel class]]) { 639 | ((UILabel *)titleView).text = msg; 640 | } 641 | 642 | [activityIndicator stopAnimating]; 643 | [UIView animateWithDuration:0.1f animations:^{ 644 | activityIndicator.alpha = 0.f; 645 | } completion:^(BOOL finished) { 646 | [activityIndicator RELEASE]; 647 | [activityIndicator removeFromSuperview]; 648 | activityIndicator = nil; 649 | }]; 650 | } 651 | 652 | - (void)showImage:(UIImage *)image withMessage:(NSString *)msg 653 | { 654 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 655 | imageView.alpha = 0.f; 656 | 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); 657 | imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f); 658 | 659 | [contentView addSubview:[imageView AUTORELEASE]]; 660 | 661 | if (subviewsArray && (subviewsArray.count > 0)) { 662 | [UIView animateWithDuration:0.2f animations:^{ 663 | for (UIView *view in subviewsArray) { 664 | view.alpha = 0.f; 665 | } 666 | }]; 667 | 668 | if (showDividerRects) { 669 | showDividerRects = NO; 670 | [self setNeedsDisplay]; 671 | } 672 | } 673 | 674 | if (msg) { 675 | if ([titleView isKindOfClass:[UILabel class]]) { 676 | ((UILabel *)titleView).text = msg; 677 | } 678 | } 679 | 680 | [UIView animateWithDuration:0.2f delay:0.2f options:UIViewAnimationOptionCurveEaseOut animations:^{ 681 | imageView.alpha = 1.f; 682 | imageView.transform = CGAffineTransformIdentity; 683 | } completion:^(BOOL finished) { 684 | //[imageView removeFromSuperview]; 685 | }]; 686 | } 687 | 688 | - (void)showError 689 | { 690 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"error"]]; 691 | imageView.alpha = 0.f; 692 | imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f); 693 | imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f); 694 | 695 | [contentView addSubview:[imageView AUTORELEASE]]; 696 | 697 | if (subviewsArray && (subviewsArray.count > 0)) { 698 | [UIView animateWithDuration:0.1f animations:^{ 699 | for (UIView *view in subviewsArray) { 700 | view.alpha = 0.f; 701 | } 702 | }]; 703 | 704 | if (showDividerRects) { 705 | showDividerRects = NO; 706 | [self setNeedsDisplay]; 707 | } 708 | } 709 | 710 | [UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{ 711 | imageView.alpha = 1.f; 712 | imageView.transform = CGAffineTransformIdentity; 713 | } completion:^(BOOL finished) { 714 | //[imageView removeFromSuperview]; 715 | }]; 716 | 717 | } 718 | 719 | - (void)showSuccess 720 | { 721 | UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"success"]]; 722 | imageView.alpha = 0.f; 723 | imageView.frame = CGRectMake(CGRectGetMidX(contentView.bounds) - 20.f, CGRectGetMidY(contentView.bounds) - 20.f + ((self.titleView) ? 20 : 0.f), 40.f, 40.f); 724 | imageView.transform = CGAffineTransformMakeScale(0.1f, 0.1f); 725 | 726 | [contentView addSubview:[imageView AUTORELEASE]]; 727 | 728 | if (subviewsArray && (subviewsArray.count > 0)) { 729 | [UIView animateWithDuration:0.1f animations:^{ 730 | for (UIView *view in subviewsArray) { 731 | view.alpha = 0.f; 732 | } 733 | }]; 734 | 735 | if (showDividerRects) { 736 | showDividerRects = NO; 737 | [self setNeedsDisplay]; 738 | } 739 | } 740 | 741 | [UIView animateWithDuration:0.1f delay:0.1f options:UIViewAnimationOptionCurveEaseOut animations:^{ 742 | imageView.alpha = 1.f; 743 | imageView.transform = CGAffineTransformIdentity; 744 | } completion:^(BOOL finished) { 745 | //[imageView removeFromSuperview]; 746 | }]; 747 | 748 | } 749 | 750 | #pragma mark - User Interaction 751 | 752 | - (void)tapped:(UITapGestureRecognizer *)tap 753 | { 754 | CGPoint point = [tap locationInView:contentView]; 755 | 756 | //NSLog(@"point:(%f,%f)", point.x, point.y); 757 | 758 | BOOL found = NO; 759 | 760 | //NSLog(@"subviewsArray:%@", subviewsArray); 761 | 762 | for (int i = 0; i < subviewsArray.count && !found; i++) { 763 | UIView *view = [subviewsArray objectAtIndex:i]; 764 | 765 | //NSLog(@"Rect:(%f,%f,%f,%f)", view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height); 766 | 767 | if (CGRectContainsPoint(view.frame, point)) { 768 | //The tap was within this view, so we notify the delegate, and break the loop. 769 | 770 | found = YES; 771 | 772 | //NSLog(@"Tapped subview:%d", i); 773 | 774 | if ([view isKindOfClass:[UIButton class]]) { 775 | return; 776 | } 777 | 778 | if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) { 779 | [delegate popoverView:self didSelectItemAtIndex:i]; 780 | } 781 | 782 | break; 783 | } 784 | } 785 | 786 | if (!found && CGRectContainsPoint(contentView.bounds, point)) { 787 | found = YES; 788 | //NSLog(@"popover box contains point, ignoring user input"); 789 | } 790 | 791 | if (!found) { 792 | [self dismiss:YES]; 793 | } 794 | 795 | } 796 | 797 | - (void)didTapButton:(UIButton *)sender 798 | { 799 | NSUInteger index = [subviewsArray indexOfObject:sender]; 800 | 801 | if (index == NSNotFound) { 802 | return; 803 | } 804 | 805 | if (delegate && [delegate respondsToSelector:@selector(popoverView:didSelectItemAtIndex:)]) { 806 | [delegate popoverView:self didSelectItemAtIndex:index]; 807 | } 808 | } 809 | 810 | - (void)dismiss 811 | { 812 | [self dismiss:YES]; 813 | } 814 | 815 | - (void)dismiss:(BOOL)animated 816 | { 817 | if (!animated) 818 | { 819 | [self dismissComplete]; 820 | } 821 | else 822 | { 823 | [UIView animateWithDuration:0.3f animations:^{ 824 | self.alpha = 0.1f; 825 | self.transform = CGAffineTransformMakeScale(0.1f, 0.1f); 826 | } completion:^(BOOL finished) { 827 | self.transform = CGAffineTransformIdentity; 828 | [self dismissComplete]; 829 | }]; 830 | } 831 | } 832 | 833 | - (void)dismissComplete 834 | { 835 | if (self.delegate && [self.delegate respondsToSelector:@selector(popoverViewDidDismiss:)]) { 836 | [delegate popoverViewDidDismiss:self]; 837 | } 838 | 839 | [self removeFromSuperview]; 840 | } 841 | 842 | - (void)animateRotationToNewPoint:(CGPoint)point inView:(UIView *)view withDuration:(NSTimeInterval)duration 843 | { 844 | [self layoutAtPoint:point inView:view]; 845 | } 846 | 847 | #pragma mark - Drawing Routines 848 | 849 | // Only override drawRect: if you perform custom drawing. 850 | // An empty implementation adversely affects performance during animation. 851 | - (void)drawRect:(CGRect)rect 852 | { 853 | // Drawing code 854 | 855 | // Build the popover path 856 | CGRect frame = boxFrame; 857 | 858 | float xMin = CGRectGetMinX(frame); 859 | float yMin = CGRectGetMinY(frame); 860 | 861 | float xMax = CGRectGetMaxX(frame); 862 | float yMax = CGRectGetMaxY(frame); 863 | 864 | float radius = kBoxRadius; //Radius of the curvature. 865 | 866 | float cpOffset = kCPOffset; //Control Point Offset. Modifies how "curved" the corners are. 867 | 868 | 869 | /* 870 | LT2 RT1 871 | LT1⌜⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⌝RT2 872 | | | 873 | | popover | 874 | | | 875 | LB2⌞_______________⌟RB1 876 | LB1 RB2 877 | 878 | Traverse rectangle in clockwise order, starting at LT1 879 | L = Left 880 | R = Right 881 | T = Top 882 | B = Bottom 883 | 1,2 = order of traversal for any given corner 884 | 885 | */ 886 | 887 | UIBezierPath *popoverPath = [UIBezierPath bezierPath]; 888 | [popoverPath moveToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1 889 | [popoverPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2 890 | 891 | //If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover. 892 | //In this case, the arrow is located between LT2 and RT1 893 | if (!above) { 894 | [popoverPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side 895 | [popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point 896 | [popoverPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side 897 | } 898 | 899 | [popoverPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1 900 | [popoverPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2 901 | [popoverPath addLineToPoint:CGPointMake(xMax, yMax - radius)];//RB1 902 | [popoverPath addCurveToPoint:CGPointMake(xMax - radius, yMax) controlPoint1:CGPointMake(xMax, yMax - radius + cpOffset) controlPoint2:CGPointMake(xMax - radius + cpOffset, yMax)];//RB2 903 | 904 | //If the popover is positioned above the arrowPoint, then we know that the arrow must be on the bottom of the popover. 905 | //In this case, the arrow is located somewhere between LB1 and RB2 906 | if (above) { 907 | [popoverPath addLineToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMax)];//right side 908 | [popoverPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMax) controlPoint2:arrowPoint];//arrow point 909 | [popoverPath addCurveToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMax) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMax)]; 910 | } 911 | 912 | [popoverPath addLineToPoint:CGPointMake(xMin + radius, yMax)];//LB1 913 | [popoverPath addCurveToPoint:CGPointMake(xMin, yMax - radius) controlPoint1:CGPointMake(xMin + radius - cpOffset, yMax) controlPoint2:CGPointMake(xMin, yMax - radius + cpOffset)];//LB2 914 | [popoverPath closePath]; 915 | 916 | //// General Declarations 917 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 918 | CGContextRef context = UIGraphicsGetCurrentContext(); 919 | 920 | //// Shadow Declarations 921 | UIColor* shadow = [UIColor colorWithWhite:0.0f alpha:kShadowAlpha]; 922 | CGSize shadowOffset = CGSizeMake(0, 1); 923 | CGFloat shadowBlurRadius = kShadowBlur; 924 | 925 | //// Gradient Declarations 926 | NSArray* gradientColors = [NSArray arrayWithObjects: 927 | (id)kGradientTopColor.CGColor, 928 | (id)kGradientBottomColor.CGColor, nil]; 929 | CGFloat gradientLocations[] = {0, 1}; 930 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations); 931 | 932 | 933 | //These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows. 934 | float bottomOffset = (above ? kArrowHeight : 0.f); 935 | float topOffset = (!above ? kArrowHeight : 0.f); 936 | 937 | //Draw the actual gradient and shadow. 938 | CGContextSaveGState(context); 939 | CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, shadow.CGColor); 940 | CGContextBeginTransparencyLayer(context, NULL); 941 | [popoverPath addClip]; 942 | CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMaxY(frame) + bottomOffset), 0); 943 | CGContextEndTransparencyLayer(context); 944 | CGContextRestoreGState(context); 945 | 946 | //// Cleanup 947 | CGGradientRelease(gradient); 948 | CGColorSpaceRelease(colorSpace); 949 | 950 | 951 | //Draw the title background 952 | if (kDrawTitleGradient) { 953 | //Calculate the height of the title bg 954 | float titleBGHeight = -1; 955 | 956 | //NSLog(@"titleView:%@", titleView); 957 | 958 | if (titleView != nil) { 959 | titleBGHeight = kBoxPadding*2.f + titleView.frame.size.height; 960 | } 961 | 962 | 963 | //Draw the title bg height, but only if we need to. 964 | if (titleBGHeight > 0.f) { 965 | CGPoint startingPoint = CGPointMake(xMin, yMin + titleBGHeight); 966 | CGPoint endingPoint = CGPointMake(xMax, yMin + titleBGHeight); 967 | 968 | UIBezierPath *titleBGPath = [UIBezierPath bezierPath]; 969 | [titleBGPath moveToPoint:startingPoint]; 970 | [titleBGPath addLineToPoint:CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + radius)];//LT1 971 | [titleBGPath addCurveToPoint:CGPointMake(xMin + radius, yMin) controlPoint1:CGPointMake(xMin, yMin + radius - cpOffset) controlPoint2:CGPointMake(xMin + radius - cpOffset, yMin)];//LT2 972 | 973 | //If the popover is positioned below (!above) the arrowPoint, then we know that the arrow must be on the top of the popover. 974 | //In this case, the arrow is located between LT2 and RT1 975 | if (!above) { 976 | [titleBGPath addLineToPoint:CGPointMake(arrowPoint.x - kArrowHeight, yMin)];//left side 977 | [titleBGPath addCurveToPoint:arrowPoint controlPoint1:CGPointMake(arrowPoint.x - kArrowHeight + kArrowCurvature, yMin) controlPoint2:arrowPoint];//actual arrow point 978 | [titleBGPath addCurveToPoint:CGPointMake(arrowPoint.x + kArrowHeight, yMin) controlPoint1:arrowPoint controlPoint2:CGPointMake(arrowPoint.x + kArrowHeight - kArrowCurvature, yMin)];//right side 979 | } 980 | 981 | [titleBGPath addLineToPoint:CGPointMake(xMax - radius, yMin)];//RT1 982 | [titleBGPath addCurveToPoint:CGPointMake(xMax, yMin + radius) controlPoint1:CGPointMake(xMax - radius + cpOffset, yMin) controlPoint2:CGPointMake(xMax, yMin + radius - cpOffset)];//RT2 983 | [titleBGPath addLineToPoint:endingPoint]; 984 | [titleBGPath addLineToPoint:startingPoint]; 985 | [titleBGPath closePath]; 986 | 987 | //// General Declarations 988 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 989 | CGContextRef context = UIGraphicsGetCurrentContext(); 990 | 991 | //// Gradient Declarations 992 | NSArray* gradientColors = [NSArray arrayWithObjects: 993 | (id)kGradientTitleTopColor.CGColor, 994 | (id)kGradientTitleBottomColor.CGColor, nil]; 995 | CGFloat gradientLocations[] = {0, 1}; 996 | CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFTYPECAST(CFArrayRef)gradientColors), gradientLocations); 997 | 998 | 999 | //These floats are the top and bottom offsets for the gradient drawing so the drawing includes the arrows. 1000 | float topOffset = (!above ? kArrowHeight : 0.f); 1001 | 1002 | //Draw the actual gradient and shadow. 1003 | CGContextSaveGState(context); 1004 | CGContextBeginTransparencyLayer(context, NULL); 1005 | [titleBGPath addClip]; 1006 | CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) - topOffset), CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame) + titleBGHeight), 0); 1007 | CGContextEndTransparencyLayer(context); 1008 | CGContextRestoreGState(context); 1009 | 1010 | UIBezierPath *dividerLine = [UIBezierPath bezierPathWithRect:CGRectMake(startingPoint.x, startingPoint.y, (endingPoint.x - startingPoint.x), 0.5f)]; 1011 | [[UIColor colorWithRed:0.741 green:0.741 blue:0.741 alpha:0.5f] setFill]; 1012 | [dividerLine fill]; 1013 | 1014 | //// Cleanup 1015 | CGGradientRelease(gradient); 1016 | CGColorSpaceRelease(colorSpace); 1017 | } 1018 | } 1019 | 1020 | 1021 | 1022 | //Draw the divider rects if we need to 1023 | { 1024 | if (kShowDividersBetweenViews && showDividerRects) { 1025 | if (dividerRects && dividerRects.count > 0) { 1026 | for (NSValue *value in dividerRects) { 1027 | CGRect rect = value.CGRectValue; 1028 | rect.origin.x += contentView.frame.origin.x; 1029 | rect.origin.y += contentView.frame.origin.y; 1030 | 1031 | UIBezierPath *dividerPath = [UIBezierPath bezierPathWithRect:rect]; 1032 | [kDividerColor setFill]; 1033 | [dividerPath fill]; 1034 | } 1035 | } 1036 | } 1037 | } 1038 | 1039 | //Draw border if we need to 1040 | //The border is done last because it needs to be drawn on top of everything else 1041 | if (kDrawBorder) { 1042 | [kBorderColor setStroke]; 1043 | popoverPath.lineWidth = kBorderWidth; 1044 | [popoverPath stroke]; 1045 | } 1046 | 1047 | } 1048 | 1049 | @end 1050 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/PopoverView/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 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/PopoverView/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 12.f 15 | 16 | //padding within the box for the contentView 17 | #define kBoxPadding 10.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 6.f 27 | 28 | //Minimum distance from the side of the arrow to the beginning of curvature for the box 29 | #define kArrowHorizontalPadding 5.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 50.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.400 green:0.855 blue:0.761 alpha:1.00]//[UIColor colorWithRed:0.98f green:0.98f blue:0.98f alpha:kBoxAlpha] 66 | 67 | //top color white value in gradient bg 68 | #define kGradientTopColor [UIColor colorWithRed:0.400 green:0.855 blue:0.761 alpha:1.00]//[UIColor colorWithRed:1.f green:1.f blue:1.f alpha:kBoxAlpha] 69 | 70 | 71 | // TITLE GRADIENT 72 | 73 | //bool that turns off/on title gradient 74 | #define kDrawTitleGradient YES 75 | 76 | //bottom color white value in title gradient bg 77 | #define kGradientTitleBottomColor [UIColor colorWithRed:0.93f green:0.93f blue:0.93f alpha:kBoxAlpha] 78 | 79 | //top color white value in title gradient bg 80 | #define kGradientTitleTopColor [UIColor colorWithRed:1.f green:1.f blue:1.f alpha:kBoxAlpha] 81 | 82 | 83 | // FONTS 84 | 85 | //normal text font 86 | #warning 此处修改提示字体的大小 this could set font 87 | #define kTextFont [UIFont fontWithName:@"HelveticaNeue" size:12.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 114 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/UIBezierPath+LxThroughPointsBezier.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+LxThroughPointsBezier.h 3 | // LxThroughPointsBezierDemo 4 | // 5 | 6 | #import 7 | 8 | @interface UIBezierPath (LxThroughPointsBezier) 9 | 10 | /** 11 | * The curve‘s bend level. The good value is about 0.6 ~ 0.8. The default and recommended value is 0.7. 12 | */ 13 | @property (nonatomic) CGFloat contractionFactor; 14 | 15 | /** 16 | * You must wrap CGPoint struct to NSValue object. 17 | * 18 | * @param pointArray Points you want to through. You must give at least 1 point for drawing curve. 19 | */ 20 | - (void)addBezierThroughPoints:(NSArray *)pointArray; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/UIBezierPath+LxThroughPointsBezier.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPath+LxThroughPointsBezier.m 3 | // LxThroughPointsBezierDemo 4 | // 5 | 6 | #import "UIBezierPath+LxThroughPointsBezier.h" 7 | #import 8 | 9 | @implementation UIBezierPath (LxThroughPointsBezier) 10 | 11 | - (void)setContractionFactor:(CGFloat)contractionFactor 12 | { 13 | objc_setAssociatedObject(self, @selector(contractionFactor), @(contractionFactor), OBJC_ASSOCIATION_RETAIN_NONATOMIC); 14 | } 15 | 16 | - (CGFloat)contractionFactor 17 | { 18 | id contractionFactorAssociatedObject = objc_getAssociatedObject(self, @selector(contractionFactor)); 19 | if (contractionFactorAssociatedObject == nil) { 20 | return 0.7; 21 | } 22 | return [contractionFactorAssociatedObject floatValue]; 23 | } 24 | 25 | - (void)addBezierThroughPoints:(NSArray *)pointArray 26 | { 27 | NSAssert(pointArray.count > 0, @"You must give at least 1 point for drawing the curve."); 28 | 29 | if (pointArray.count < 3) { 30 | switch (pointArray.count) { 31 | case 1: 32 | { 33 | NSValue * point0Value = pointArray[0]; 34 | CGPoint point0 = [point0Value CGPointValue]; 35 | [self addLineToPoint:point0]; 36 | } 37 | break; 38 | case 2: 39 | { 40 | NSValue * point0Value = pointArray[0]; 41 | CGPoint point0 = [point0Value CGPointValue]; 42 | NSValue * point1Value = pointArray[1]; 43 | CGPoint point1 = [point1Value CGPointValue]; 44 | [self addQuadCurveToPoint:point1 controlPoint:ControlPointForTheBezierCanThrough3Point(self.currentPoint, point0, point1)]; 45 | } 46 | break; 47 | default: 48 | break; 49 | } 50 | } 51 | 52 | CGPoint previousPoint = CGPointZero; 53 | 54 | CGPoint previousCenterPoint = CGPointZero; 55 | CGPoint centerPoint = CGPointZero; 56 | CGFloat centerPointDistance = 0; 57 | 58 | CGFloat obliqueAngle = 0; 59 | 60 | CGPoint previousControlPoint1 = CGPointZero; 61 | CGPoint previousControlPoint2 = CGPointZero; 62 | CGPoint controlPoint1 = CGPointZero; 63 | 64 | previousPoint = self.currentPoint; 65 | 66 | for (int i = 0; i < pointArray.count; i++) { 67 | 68 | NSValue * pointIValue = pointArray[i]; 69 | CGPoint pointI = [pointIValue CGPointValue]; 70 | 71 | if (i > 0) { 72 | 73 | previousCenterPoint = CenterPointOf(self.currentPoint, previousPoint); 74 | centerPoint = CenterPointOf(previousPoint, pointI); 75 | 76 | centerPointDistance = DistanceBetweenPoint(previousCenterPoint, centerPoint); 77 | 78 | obliqueAngle = ObliqueAngleOfStraightThrough(centerPoint, previousCenterPoint); 79 | 80 | previousControlPoint2 = CGPointMake(previousPoint.x - 0.5 * self.contractionFactor * centerPointDistance * cos(obliqueAngle), previousPoint.y - 0.5 * self.contractionFactor * centerPointDistance * sin(obliqueAngle)); 81 | controlPoint1 = CGPointMake(previousPoint.x + 0.5 * self.contractionFactor * centerPointDistance * cos(obliqueAngle), previousPoint.y + 0.5 * self.contractionFactor * centerPointDistance * sin(obliqueAngle)); 82 | } 83 | 84 | if (i == 1) { 85 | 86 | [self addQuadCurveToPoint:previousPoint controlPoint:previousControlPoint2]; 87 | } 88 | else if (i > 1 && i < pointArray.count - 1) { 89 | 90 | [self addCurveToPoint:previousPoint controlPoint1:previousControlPoint1 controlPoint2:previousControlPoint2]; 91 | } 92 | else if (i == pointArray.count - 1) { 93 | 94 | [self addCurveToPoint:previousPoint controlPoint1:previousControlPoint1 controlPoint2:previousControlPoint2]; 95 | [self addQuadCurveToPoint:pointI controlPoint:controlPoint1]; 96 | } 97 | else { 98 | 99 | } 100 | 101 | previousControlPoint1 = controlPoint1; 102 | previousPoint = pointI; 103 | } 104 | } 105 | 106 | CGFloat ObliqueAngleOfStraightThrough(CGPoint point1, CGPoint point2) // [-π/2, 3π/2) 107 | { 108 | CGFloat obliqueRatio = 0; 109 | CGFloat obliqueAngle = 0; 110 | 111 | if (point1.x > point2.x) { 112 | 113 | obliqueRatio = (point2.y - point1.y) / (point2.x - point1.x); 114 | obliqueAngle = atan(obliqueRatio); 115 | } 116 | else if (point1.x < point2.x) { 117 | 118 | obliqueRatio = (point2.y - point1.y) / (point2.x - point1.x); 119 | obliqueAngle = M_PI + atan(obliqueRatio); 120 | } 121 | else if (point2.y - point1.y >= 0) { 122 | 123 | obliqueAngle = M_PI/2; 124 | } 125 | else { 126 | obliqueAngle = -M_PI/2; 127 | } 128 | 129 | return obliqueAngle; 130 | } 131 | 132 | CGPoint ControlPointForTheBezierCanThrough3Point(CGPoint point1, CGPoint point2, CGPoint point3) 133 | { 134 | return CGPointMake(2 * point2.x - (point1.x + point3.x) / 2, 2 * point2.y - (point1.y + point3.y) / 2); 135 | } 136 | 137 | CGFloat DistanceBetweenPoint(CGPoint point1, CGPoint point2) 138 | { 139 | return sqrt((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y)); 140 | } 141 | 142 | CGPoint CenterPointOf(CGPoint point1, CGPoint point2) 143 | { 144 | return CGPointMake((point1.x + point2.x) / 2, (point1.y + point2.y) / 2); 145 | } 146 | 147 | @end 148 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/UIView+WZCViewCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+WZCViewCategory.h 3 | // bsbdj 4 | // 5 | // Created by 邬志成 on 16/6/14. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIView (WZCViewCategory) 12 | 13 | @property (nonatomic,assign) CGFloat y; 14 | @property (nonatomic,assign) CGFloat x; 15 | @property (nonatomic,assign) CGFloat width; 16 | @property (nonatomic,assign) CGFloat height; 17 | @property (nonatomic,assign) CGSize size; 18 | 19 | @property (nonatomic,assign) CGFloat centerX; 20 | 21 | @property (nonatomic,assign) CGFloat centerY; 22 | 23 | -(void)setX:(CGFloat)x; 24 | -(CGFloat)x; 25 | -(void)setY:(CGFloat)y; 26 | -(CGFloat)y; 27 | -(void)setWidth:(CGFloat)width; 28 | -(CGFloat)width; 29 | -(void)setHeight:(CGFloat)height; 30 | -(CGFloat)height; 31 | -(void)setSize:(CGSize)size; 32 | -(CGSize)size; 33 | -(CGFloat)centerX; 34 | -(void)setCenterX:(CGFloat)centerX; 35 | -(CGFloat)centerY; 36 | -(void)setCenterY:(CGFloat)centerY; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/UIView+WZCViewCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+WZCViewCategory.m 3 | // bsbdj 4 | // 5 | // Created by 邬志成 on 16/6/14. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import "UIView+WZCViewCategory.h" 10 | 11 | @implementation UIView (WZCViewCategory) 12 | 13 | -(void)setX:(CGFloat)x{ 14 | CGRect frame = self.frame; 15 | frame.origin.x = x; 16 | self.frame = frame; 17 | } 18 | 19 | -(void)setY:(CGFloat)y{ 20 | CGRect frame = self.frame; 21 | frame.origin.y = y; 22 | self.frame = frame; 23 | } 24 | 25 | -(void)setWidth:(CGFloat)width{ 26 | CGRect frame = self.frame; 27 | frame.size.width = width; 28 | self.frame = frame; 29 | } 30 | -(void)setHeight:(CGFloat)height{ 31 | CGRect frame = self.frame; 32 | frame.size.height = height; 33 | self.frame = frame; 34 | } 35 | 36 | -(CGFloat)x{ 37 | return self.frame.origin.x; 38 | } 39 | -(CGFloat)y{ 40 | return self.frame.origin.y; 41 | } 42 | 43 | -(CGFloat)width{ 44 | return self.frame.size.width; 45 | } 46 | 47 | -(CGFloat)height{ 48 | return self.frame.size.height; 49 | } 50 | 51 | -(void)setSize:(CGSize)size{ 52 | CGRect frame = self.frame; 53 | frame.size = size; 54 | self.frame = frame; 55 | } 56 | 57 | -(CGSize)size{ 58 | return self.frame.size; 59 | } 60 | 61 | -(CGFloat)centerX{ 62 | 63 | return self.center.x; 64 | 65 | } 66 | -(void)setCenterX:(CGFloat)centerX{ 67 | 68 | CGPoint center = self.center; 69 | center.x = centerX; 70 | self.center = center; 71 | 72 | } 73 | -(CGFloat)centerY{ 74 | return self.center.y; 75 | } 76 | -(void)setCenterY:(CGFloat)centerY{ 77 | 78 | CGPoint center = self.center; 79 | center.y = centerY; 80 | self.center = center; 81 | 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/WZCChartLine.h: -------------------------------------------------------------------------------- 1 | // 2 | // WZCChartLine.h 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ChartView.h" 11 | 12 | typedef enum : NSInteger { 13 | WZCChartLineTypeBroken = 0,//broken line 折线 14 | WZCChartLineTypeCurve //曲线 15 | } WZCChartLineType; 16 | 17 | @interface WZCChartLine : UIView 18 | 19 | #pragma mark - 必须设置 20 | /** X 坐标轴上的值 (字符串)*/ 21 | @property (nonnull,strong,nonatomic) NSArray *x_values; 22 | /* Y坐标上的值 支持多组值 (字符串)*/ 23 | @property (nonnull,strong,nonatomic) NSArray * > *y_values; 24 | /* 以上 设置完毕后才调用绘图方法 */ 25 | -(void)startDrawWithLineType:(WZCChartLineType)lineType; 26 | 27 | #pragma mark - 可选设置 28 | 29 | /** 是否显示图例(默认 NO) 需要y_titles不为空*/ 30 | @property (nonatomic,assign) BOOL showLegend; 31 | 32 | /** Y轴显示的单位 */ 33 | @property (nullable,nonatomic,copy) NSString *yUnit; 34 | 35 | /** 36 | X轴的单位(用于pop提示) 37 | */ 38 | @property (nullable,nonatomic,copy) NSString *xUnit; 39 | 40 | /* 折线的名称/类别 (有几条直线就有几个名字,默认无,图例/pop提示用) */ 41 | @property (nonnull,strong,nonatomic) NSArray *y_titles; 42 | 43 | /* 折线的颜色数组(默认随机方法) */ 44 | @property (nonnull,strong,nonatomic) NSArray *colorsArray; 45 | 46 | 47 | /** 48 | * 设置最小的 Y 值(默认为0) ,设置时需注意: 如果设置的值大于最大的 Y 值, 则设置为无效; 49 | * 50 | * @param minValue 最小的 Y 值 (设置负数可以自动设置 Y 值,从最小的 Y 值起步) 51 | */ 52 | - (void)setMinY:(CGFloat)minValue; 53 | 54 | /** 55 | * 设置 X 坐标轴的位置 56 | * 57 | * @param minValue 对应的 Y 值(默认为最小值) 58 | */ 59 | - (void)setXCoordinatesLocationInYValue:(CGFloat)yValue; 60 | 61 | /** 62 | * 设置 Y 轴刻度的个数 63 | * 64 | * @param tipCont 默认为自动 65 | */ 66 | - (void)setCoords_Y_Tips:(NSInteger)tipCont; 67 | 68 | /** 69 | * 如果坐标轴中存在负数,调用此函数功能是对称显示坐标 70 | * 71 | * @param show 是否显示0点刻度 72 | */ 73 | - (void)setCoordPlusAndMinusSymmetryShowZeroPoint:(BOOL)show; 74 | @end 75 | 76 | -------------------------------------------------------------------------------- /WZCChartLineView/WZCChartLineView/WZCChartLine.m: -------------------------------------------------------------------------------- 1 | // 2 | // WZCChartLine.m 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import "WZCChartLine.h" 10 | #import "UIView+WZCViewCategory.h" 11 | #import "UIBezierPath+LxThroughPointsBezier.h" 12 | #import "PopoverView.h" 13 | /*计算的宏*/ 14 | #define ABS_VALUE(x,y) (MAX(x,y)-MIN(x,y)) 15 | /*配置线条的宏*/ 16 | #define Arrows_Size 3 //箭头半径 17 | #define Arrows_Height 6 //箭头的高度 18 | #define Coords_lineColor [UIColor colorWithRed:0.000 green:0.565 blue:0.459 alpha:1.00].CGColor //坐标线的颜色 19 | #define Coords_lineWidth 2.0f 20 | #define Coords_Y_Tip_Width 4 //刻度宽度 21 | #define Coords_Y_LableFont_Size 10 //Y轴标签的字体大小 22 | #define Coords_X_LableFont_Size 10 //X轴标签的字体大小 23 | #define Coords_X_Lable_Space 6 //X轴标签间距 24 | #define Coords_X_Verticlal_Line_Color [UIColor lightGrayColor].CGColor //垂直于X轴的线条颜色 25 | #define Coords_X_Verticlal_Line_Width 0.2 //垂直于X轴的线条宽度 26 | #define Coords_Values_Line_Width 1.8 //折线的线条宽度 27 | #define Coords_Legend_Font_Size 15 //图例的字体大小 28 | #define Coords_Y_Verticlal_Line_Color [UIColor lightGrayColor].CGColor //垂直于Y轴的线条颜色 29 | #define Coords_Y_Verticlal_Line_Width 0.2 //垂直于Y轴的线条宽度 30 | #define Show_Coords_X_Verticlal_Line YES // 显示垂直于X轴的线条 31 | #define Show_Coords_Y_Verticlal_Line YES //显示垂直于Y轴的线条 32 | 33 | @interface WZCChartLine() 34 | //转换后的坐标点 35 | @property (strong,nonatomic) NSArray *coords_y_values; 36 | 37 | @property (strong,nonatomic) NSArray *coords_x_values; 38 | 39 | //固定Y坐标的区域 40 | @property (assign,nonatomic) UIView *y_coord_View; 41 | 42 | //绘图的区域 43 | @property (assign,nonatomic) ChartView *draw_view; 44 | 45 | //缩放因子 46 | @property (assign,nonatomic) CGFloat scale_Value; 47 | 48 | //X轴label的宽度 49 | @property (assign,nonatomic) CGFloat coords_x_label_width; 50 | 51 | 52 | /** 53 | * 各种距离 54 | */ 55 | @property (assign,nonatomic) CGFloat offset_top; 56 | 57 | @property (assign,nonatomic) CGFloat offset_bottom; 58 | 59 | @property (assign,nonatomic) CGFloat offset_left; 60 | 61 | @property (assign,nonatomic) CGFloat offset_right; 62 | 63 | @property (assign,nonatomic) CGFloat minY; 64 | 65 | @property (assign,nonatomic) CGFloat x_coord_location; 66 | 67 | @end 68 | 69 | @implementation WZCChartLine{ 70 | 71 | UIView *legendView; //图例视图 72 | NSArray *coords_y_tips; 73 | BOOL isCustemY; 74 | NSInteger Coords_Y_Tip; 75 | BOOL showZeroPoint; //是否显示0刻度点 76 | CGFloat maxY; 77 | } 78 | 79 | -(void)startDrawWithLineType:(WZCChartLineType)lineType{ 80 | [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 81 | [self.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)]; 82 | // if (![self PASS_checkErrors]) { 83 | // return; 84 | // } 85 | /******/ 86 | 87 | /*默认值*/ 88 | [self setDefaultValue]; 89 | //转换坐标系 90 | [self drawCoordindined_Y]; 91 | [self drawCoordindined_X]; 92 | [self swithYValues]; 93 | [self swithXValuesWithLabelWidth:_coords_x_label_width]; 94 | if (lineType == WZCChartLineTypeBroken) { 95 | [self makePolyLineWithLabelWidth:_coords_x_label_width]; 96 | }else{ 97 | [self makecurveWithLabelWidth:_coords_x_label_width]; 98 | } 99 | [self addLegend]; 100 | } 101 | 102 | - (void)setDefaultValue{ 103 | 104 | #warning 这里修改到视图边距的值 105 | self.offset_top = 20; 106 | // self.offset_left = [WZCTool getTextSizeWithText:[NSString stringWithFormat:@"%0.1f",[self getMaxYValue]] Font:WZC_FONT(Coords_Y_LableFont_Size) MaxSize:CGSizeZero].width + 18; 107 | self.offset_left = [[NSString stringWithFormat:@"%0.1f",[self getMaxYValue]] boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:Coords_Y_LableFont_Size]} context:nil].size.width + 18; 108 | self.offset_right = 20; 109 | self.offset_bottom = 16; 110 | if (!_x_values.count) { 111 | _x_values = @[@"",@"",@"",@"",@"",@"",@"",@"",@"",@""]; 112 | } 113 | if (!_y_values.count) { 114 | _y_values = @[@[]]; 115 | } 116 | /**************以下设置请勿更改****************/ 117 | if (Coords_Y_Tip <= 0) { 118 | Coords_Y_Tip = [self getTipsWithValue:[self getMaxYValue]]; 119 | } 120 | 121 | if (self.minY == 0) { 122 | CGFloat min_yValue = [self getMinYValue]; 123 | if (min_yValue < 0) { 124 | self.minY = min_yValue; 125 | }else{ 126 | self.minY = 0; 127 | } 128 | } 129 | 130 | if (!isCustemY) { 131 | if (self.minY != 0) { 132 | self.x_coord_location = self.minY; 133 | }else{ 134 | self.x_coord_location = 0; 135 | } 136 | } 137 | 138 | } 139 | #pragma mark -这些都是坐标系相关的 140 | //画坐标系 141 | -(void)drawCoordindined_X{ 142 | 143 | ChartView *chtView = [[ChartView alloc]initWithFrame:CGRectMake(self.offset_left - Arrows_Size, 0, self.width + Arrows_Size - self.offset_left, self.height)]; 144 | chtView.touch_delegate = self; 145 | //不允许弹跳效果 146 | chtView.bounces = NO; 147 | chtView.showsHorizontalScrollIndicator = NO; 148 | _draw_view = chtView; 149 | //获取最大的label的尺寸 150 | CGSize max_label_size = [self getX_MaxLabelSize]; 151 | _coords_x_label_width = max_label_size.width; 152 | //横坐标总长度 153 | CGFloat coord_x_lenth = (max_label_size.width + Coords_X_Lable_Space) * _x_values.count; 154 | //设置contentSize 155 | _draw_view.contentSize = CGSizeMake(coord_x_lenth + self.offset_right, 0); 156 | 157 | UIBezierPath *coords_path = [UIBezierPath bezierPath]; 158 | 159 | //画一条横坐标的线 160 | //x坐标最右端顶点x值 161 | CGFloat coord_x_right_value = coord_x_lenth ; 162 | //x坐标轴的Y值 163 | CGFloat tmp_value = self.x_coord_location - self.minY; 164 | tmp_value = self.frame.size.height - tmp_value * _scale_Value - self.offset_bottom; 165 | CGFloat coord_x_custem_y_value = tmp_value; 166 | 167 | 168 | CGFloat coord_x_yValue = _draw_view.height - self.offset_bottom; 169 | //画横坐标坐标轴 170 | UIBezierPath *coord_x_path = [UIBezierPath bezierPath]; 171 | 172 | [coord_x_path moveToPoint:CGPointMake(0, coord_x_custem_y_value)]; 173 | [coord_x_path addLineToPoint:CGPointMake(coord_x_right_value, coord_x_custem_y_value)]; 174 | 175 | //画横坐标的箭头 176 | UIBezierPath *coordsArrow_path = [UIBezierPath bezierPath]; 177 | [coordsArrow_path moveToPoint:CGPointMake(coord_x_right_value - Arrows_Height, coord_x_custem_y_value - Arrows_Size)]; 178 | [coordsArrow_path addLineToPoint:CGPointMake(coord_x_right_value, coord_x_custem_y_value)]; 179 | [coordsArrow_path addLineToPoint:CGPointMake(coord_x_right_value - Arrows_Height, coord_x_custem_y_value + Arrows_Size)]; 180 | //绘制垂直于 Y 轴的线 181 | if(Show_Coords_Y_Verticlal_Line){ 182 | UIBezierPath *Coords_Y_Verticlal_Line_path = [UIBezierPath bezierPath]; 183 | for (NSNumber *value in coords_y_tips) { 184 | @autoreleasepool { 185 | CGFloat y_tip = [value floatValue]; 186 | UIBezierPath *tip_path = [UIBezierPath bezierPath]; 187 | [tip_path moveToPoint:CGPointMake(0, y_tip)]; 188 | [tip_path addLineToPoint:CGPointMake(coord_x_right_value - (max_label_size.width + Coords_X_Lable_Space) / 2.0f, y_tip)]; 189 | [Coords_Y_Verticlal_Line_path appendPath:tip_path]; 190 | } 191 | } 192 | 193 | //添加y坐标竖线的图层 194 | CAShapeLayer *coords_y_Verticlal_Line_layer = [[CAShapeLayer alloc]init]; 195 | coords_y_Verticlal_Line_layer.frame = _draw_view.bounds; 196 | coords_y_Verticlal_Line_layer.path = Coords_Y_Verticlal_Line_path.CGPath; 197 | coords_y_Verticlal_Line_layer.lineWidth = Coords_Y_Verticlal_Line_Width; 198 | coords_y_Verticlal_Line_layer.strokeColor = Coords_Y_Verticlal_Line_Color; 199 | coords_y_Verticlal_Line_layer.fillColor = [UIColor clearColor].CGColor; 200 | [_draw_view.layer addSublayer:coords_y_Verticlal_Line_layer]; 201 | 202 | } 203 | 204 | //绘制X轴的竖线 205 | 206 | UIBezierPath *Coords_X_Verticlal_Line_path = [UIBezierPath bezierPath]; 207 | CGFloat coords_max_y = coord_x_yValue; 208 | CGFloat coords_min_y = coords_max_y - ([self getMaxYValue] - self.minY) * self.scale_Value; 209 | 210 | CGFloat label_CenterY = coord_x_custem_y_value + [self getLabelWidthWithStr:@"字符串" font:[UIFont systemFontOfSize:Coords_X_LableFont_Size]].height / 1.5;//标签的中心Y 211 | [_x_values enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 212 | @autoreleasepool { 213 | CGFloat label_CenterX = ((float)idx + 0.5f) * (max_label_size.width + Coords_X_Lable_Space); 214 | UILabel *x_label_tmp = [[UILabel alloc]init]; 215 | x_label_tmp.width = max_label_size.width; 216 | x_label_tmp.height = self.offset_bottom; 217 | x_label_tmp.text = obj; 218 | x_label_tmp.center = CGPointMake(label_CenterX, label_CenterY); 219 | x_label_tmp.textAlignment = NSTextAlignmentCenter; 220 | x_label_tmp.font = [UIFont systemFontOfSize:Coords_X_LableFont_Size]; 221 | [_draw_view addSubview:x_label_tmp]; 222 | if(Show_Coords_X_Verticlal_Line){ 223 | UIBezierPath *coords_x_V_tmp = [UIBezierPath bezierPath]; 224 | [coords_x_V_tmp moveToPoint:CGPointMake(label_CenterX, coords_min_y)]; 225 | [coords_x_V_tmp addLineToPoint:CGPointMake(label_CenterX, coords_max_y)]; 226 | [Coords_X_Verticlal_Line_path appendPath:coords_x_V_tmp]; 227 | } 228 | } 229 | }]; 230 | if(Show_Coords_X_Verticlal_Line){ 231 | //添加x坐标竖线的图层 232 | CAShapeLayer *coords_x_Verticlal_Line_layer = [[CAShapeLayer alloc]init]; 233 | coords_x_Verticlal_Line_layer.frame = _draw_view.bounds; 234 | coords_x_Verticlal_Line_layer.path = Coords_X_Verticlal_Line_path.CGPath; 235 | coords_x_Verticlal_Line_layer.lineWidth = Coords_X_Verticlal_Line_Width; 236 | coords_x_Verticlal_Line_layer.strokeColor = Coords_X_Verticlal_Line_Color; 237 | coords_x_Verticlal_Line_layer.fillColor = [UIColor clearColor].CGColor; 238 | [_draw_view.layer addSublayer:coords_x_Verticlal_Line_layer]; 239 | } 240 | 241 | //汇总path 242 | [coords_path appendPath:coord_x_path]; 243 | [coords_path appendPath:coordsArrow_path]; 244 | 245 | 246 | 247 | //添加坐标的图层 248 | CAShapeLayer *coords_layer = [[CAShapeLayer alloc]init]; 249 | coords_layer.frame = _draw_view.bounds; 250 | coords_layer.path = coords_path.CGPath; 251 | coords_layer.lineWidth = Coords_lineWidth; 252 | coords_layer.strokeColor = Coords_lineColor; 253 | coords_layer.fillColor = [UIColor clearColor].CGColor; 254 | [_draw_view.layer addSublayer:coords_layer]; 255 | [self addSubview:_draw_view]; 256 | 257 | } 258 | 259 | 260 | #pragma mark - 自动获取刻度个数 261 | - (NSInteger)getTipsWithValue:(CGFloat)value{ 262 | NSInteger Multiple = 1; 263 | if ((int)value != value) { 264 | if (value * 10 == (int)(value * 10)) { 265 | Multiple = 10; 266 | }else{ 267 | Multiple = 100; 268 | } 269 | } 270 | NSInteger maxTips = (self.height - self.offset_top - self.offset_bottom) / [self getLabelWidthWithStr:@"测试" font:[UIFont systemFontOfSize:Coords_Y_LableFont_Size]].height; 271 | if (maxTips < 5) { 272 | return maxTips; 273 | } 274 | for (int i = 5; i < maxTips; i ++) { 275 | if ((int)(value * Multiple) % (i * Multiple) == 0) { 276 | return i - 1; 277 | break; 278 | } 279 | } 280 | return 5; 281 | } 282 | 283 | #pragma mark 设置坐标轴对称 284 | - (void)setCoordPlusAndMinusSymmetryShowZeroPoint:(BOOL)show{ 285 | showZeroPoint = show; 286 | if ([self getMinYValue] < 0) { 287 | 288 | if (- [self getMinYValue] > [self getMaxYValue]) { 289 | maxY = - [self getMinYValue]; 290 | }else{ 291 | 292 | _minY = - [self getMaxYValue]; 293 | 294 | } 295 | } 296 | } 297 | 298 | /** 299 | * 画纵坐标 300 | */ 301 | -(void)drawCoordindined_Y{ 302 | //最大坐标(到顶) 303 | CGFloat maxY_value = [self getMaxYValue] * 1.0f - self.minY; 304 | if (maxY_value == 0) { 305 | maxY_value = 1; 306 | } 307 | //步进值 308 | CGFloat step_value = (maxY_value / Coords_Y_Tip); 309 | 310 | UIView *y_CoordView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.offset_left, self.height)]; 311 | _y_coord_View = y_CoordView; 312 | 313 | _scale_Value = (_y_coord_View.height - self.offset_bottom - self.offset_top) * 1.0f / maxY_value; 314 | 315 | //绘制竖线 316 | 317 | CGFloat coord_y_top = self.offset_top / 3.0f; 318 | UIBezierPath *y_coord_path = [UIBezierPath bezierPath]; 319 | [y_coord_path moveToPoint:CGPointMake(_y_coord_View.width - Arrows_Size, coord_y_top)]; 320 | [y_coord_path addLineToPoint:CGPointMake(_y_coord_View.width - Arrows_Size, _y_coord_View.height - self.offset_bottom + 1)]; 321 | //坐标空隙补偿 322 | // [y_coord_path addLineToPoint:CGPointMake(_y_coord_View.width, _y_coord_View.height - self.offset_bottom)]; 323 | 324 | /** 325 | * 绘制箭头 326 | */ 327 | UIBezierPath *arrows_path = [UIBezierPath bezierPath]; 328 | [arrows_path moveToPoint:CGPointMake(_y_coord_View.width - 2 * Arrows_Size, coord_y_top + Arrows_Height)]; 329 | [arrows_path addLineToPoint:CGPointMake(_y_coord_View.width - Arrows_Size, coord_y_top)]; 330 | [arrows_path addLineToPoint:CGPointMake(_y_coord_View.width, coord_y_top + Arrows_Height)]; 331 | 332 | //绘制刻度 333 | 334 | UIBezierPath *coords_steps = [UIBezierPath bezierPath]; 335 | 336 | UIFont *font = [UIFont systemFontOfSize:Coords_Y_LableFont_Size]; 337 | NSMutableArray *tips_array = [NSMutableArray array]; 338 | BOOL isShowZeroPoint = NO; //是否包含零点 339 | for (int i = 0; i <= Coords_Y_Tip; i ++) { 340 | @autoreleasepool { 341 | 342 | UILabel *y_label_tmp = [[UILabel alloc]init]; 343 | CGFloat y_value; 344 | 345 | if (step_value < Coords_Y_Tip) { 346 | y_label_tmp.text = [NSString stringWithFormat:@"%.1f",(step_value * i + self.minY)]; 347 | y_value = _y_coord_View.height - ((step_value * i) * _scale_Value + self.offset_bottom); 348 | }else{ 349 | 350 | y_label_tmp.text = [NSString stringWithFormat:@"%0.0f",((step_value) * i + self.minY)]; 351 | y_value = _y_coord_View.height - (step_value * i * _scale_Value + self.offset_bottom); 352 | } 353 | 354 | if (!isShowZeroPoint) { 355 | isShowZeroPoint = step_value * i == 0 ? YES:NO; 356 | } 357 | y_label_tmp.font = font; 358 | y_label_tmp.size = [self getLabelWidthWithStr:y_label_tmp.text font:font]; 359 | y_label_tmp.height = Coords_Y_LableFont_Size; 360 | y_label_tmp.center = CGPointMake(_y_coord_View.width - Arrows_Size * 2 - Coords_Y_Tip_Width - y_label_tmp.width / 2.0f, y_value); 361 | [_y_coord_View addSubview:y_label_tmp]; 362 | 363 | 364 | if (i == Coords_Y_Tip && !isShowZeroPoint && showZeroPoint) { 365 | //绘制0刻度点 366 | UILabel *y_label_zero = [[UILabel alloc]init]; 367 | CGFloat zero_value; 368 | y_label_zero.text = [NSString stringWithFormat:@"%0.1f",0.0]; 369 | 370 | CGFloat tmp_value = 0 - self.minY; 371 | zero_value = self.frame.size.height - tmp_value * _scale_Value - self.offset_bottom; 372 | y_label_zero.font = font; 373 | y_label_zero.size = [self getLabelWidthWithStr:y_label_zero.text font:font]; 374 | y_label_zero.height = Coords_Y_LableFont_Size; 375 | y_label_zero.center = CGPointMake(_y_coord_View.width - Arrows_Size * 2 - Coords_Y_Tip_Width - y_label_zero.width / 2.0f, zero_value); 376 | [_y_coord_View addSubview:y_label_zero]; 377 | 378 | 379 | UIBezierPath *tmp_path = [UIBezierPath bezierPath]; 380 | [tmp_path moveToPoint:CGPointMake(_y_coord_View.width - Arrows_Size - Coords_Y_Tip_Width, zero_value)]; 381 | [tmp_path addLineToPoint:CGPointMake(_y_coord_View.width - Arrows_Size, zero_value)]; 382 | [coords_steps appendPath:tmp_path]; 383 | } 384 | 385 | if (i == 0) { 386 | continue; 387 | } 388 | 389 | //单位设置 390 | if (i == Coords_Y_Tip) { 391 | if (self.yUnit) { 392 | UILabel *unitLab = [[UILabel alloc] init]; 393 | unitLab.text = self.yUnit; 394 | unitLab.textColor = [UIColor colorWithRed:0.400 green:0.855 blue:0.761 alpha:1.00]; 395 | unitLab.textAlignment = NSTextAlignmentCenter; 396 | unitLab.font = [UIFont systemFontOfSize:11]; 397 | [unitLab sizeToFit]; 398 | unitLab.y = y_label_tmp.y - unitLab.height - 3; 399 | unitLab.centerX = _y_coord_View.centerX; 400 | [_y_coord_View addSubview:unitLab]; 401 | } 402 | } 403 | UIBezierPath *tmp_path = [UIBezierPath bezierPath]; 404 | [tmp_path moveToPoint:CGPointMake(_y_coord_View.width - Arrows_Size - Coords_Y_Tip_Width, y_value)]; 405 | [tmp_path addLineToPoint:CGPointMake(_y_coord_View.width - Arrows_Size, y_value)]; 406 | [tips_array addObject:@(y_value)]; 407 | [coords_steps appendPath:tmp_path]; 408 | 409 | } 410 | } 411 | coords_y_tips = tips_array; 412 | //汇总路径 413 | [y_coord_path appendPath:arrows_path]; 414 | [y_coord_path appendPath:coords_steps]; 415 | //添加到图层 416 | CAShapeLayer *coordsLayer = [[CAShapeLayer alloc]init]; 417 | coordsLayer.frame = _y_coord_View.bounds; 418 | coordsLayer.path = y_coord_path.CGPath; 419 | coordsLayer.lineWidth = Coords_lineWidth; 420 | coordsLayer.strokeColor = Coords_lineColor; 421 | coordsLayer.fillColor = [UIColor clearColor].CGColor; 422 | [_y_coord_View.layer addSublayer:coordsLayer]; 423 | [self addSubview:_y_coord_View]; 424 | } 425 | 426 | 427 | #pragma mark - 曲线与折线的算法 428 | //画曲线 429 | -(void)makecurveWithLabelWidth:(CGFloat)label_width{ 430 | for (int i = 0; i < _coords_y_values.count; i++) { 431 | NSArray *values_arr = [_coords_y_values objectAtIndex:i]; 432 | UIBezierPath *value_path = [UIBezierPath bezierPath]; 433 | UIBezierPath *dots = [UIBezierPath bezierPath]; 434 | NSMutableArray *points = [NSMutableArray array]; 435 | [values_arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 436 | @autoreleasepool { 437 | 438 | CGFloat path_x = [_coords_x_values[idx] floatValue];//((float)idx + 0.5f) * (label_width + Coords_X_Lable_Space); 439 | 440 | CGFloat path_y = [obj floatValue]; 441 | 442 | [points addObject:[NSValue valueWithCGPoint:CGPointMake(path_x, path_y)]]; 443 | //画点 444 | UIBezierPath *dot = [UIBezierPath bezierPath]; 445 | [dot addArcWithCenter:CGPointMake(path_x, path_y) radius:Coords_Values_Line_Width/1.7f startAngle:0 endAngle:M_PI * 2 clockwise:NO]; 446 | [dots appendPath:dot]; 447 | 448 | } 449 | }]; 450 | 451 | [value_path moveToPoint: [[points firstObject] CGPointValue]]; 452 | if (points.count>0) { 453 | [value_path addBezierThroughPoints:points]; 454 | } 455 | [value_path appendPath:dots]; 456 | 457 | CAShapeLayer *value_layer = [[CAShapeLayer alloc]init]; 458 | value_layer.frame = _draw_view.bounds; 459 | value_layer.path = value_path.CGPath; 460 | value_layer.lineWidth = Coords_Values_Line_Width; 461 | value_layer.strokeColor = [self.colorsArray objectAtIndex:i].CGColor; 462 | value_layer.fillColor = [UIColor clearColor].CGColor; 463 | //动画 464 | [self addAnimationToLayer:value_layer]; 465 | [_draw_view.layer addSublayer:value_layer]; 466 | } 467 | } 468 | 469 | 470 | //绘制折线图 471 | -(void)makePolyLineWithLabelWidth:(CGFloat)label_width{ 472 | for (int i = 0; i < _coords_y_values.count; i++) { 473 | @autoreleasepool { 474 | 475 | NSArray *values_arr = [_coords_y_values objectAtIndex:i]; 476 | UIBezierPath *value_path = [UIBezierPath bezierPath]; 477 | UIBezierPath *dots = [UIBezierPath bezierPath]; 478 | [values_arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 479 | @autoreleasepool { 480 | CGFloat path_x = [_coords_x_values[idx] floatValue];; 481 | CGFloat path_y = [obj floatValue]; 482 | //画点 483 | UIBezierPath *dot = [UIBezierPath bezierPath]; 484 | [dot addArcWithCenter:CGPointMake(path_x, path_y) radius:Coords_Values_Line_Width/1.7f startAngle:0 endAngle:M_PI * 2 clockwise:NO]; 485 | if (idx == 0) { 486 | [value_path moveToPoint:CGPointMake(path_x, path_y)]; 487 | }else{ 488 | [value_path addLineToPoint:CGPointMake(path_x, path_y)]; 489 | } 490 | [dots appendPath:dot]; 491 | } 492 | }]; 493 | 494 | [value_path appendPath:dots]; 495 | 496 | CAShapeLayer *value_layer = [[CAShapeLayer alloc]init]; 497 | value_layer.frame = _draw_view.bounds; 498 | value_layer.path = value_path.CGPath; 499 | value_layer.lineWidth = Coords_Values_Line_Width; 500 | value_layer.strokeColor = [self.colorsArray objectAtIndex:i].CGColor; 501 | value_layer.fillColor = [UIColor clearColor].CGColor; 502 | value_layer.lineJoin = kCALineCapRound; 503 | [self addAnimationToLayer:value_layer]; 504 | [_draw_view.layer addSublayer:value_layer]; 505 | 506 | } 507 | } 508 | } 509 | #pragma mark - 曲线图的辅助视图(图例) 510 | 511 | /** 512 | * 添加图例的方法 513 | */ 514 | -(void)addLegend{ 515 | 516 | if (_y_titles.count == 0 || _y_titles == nil || _showLegend == NO) { 517 | return; 518 | } 519 | 520 | CGSize maxTitleSize = [self getSizeFromeMaxTitleWidth]; 521 | CGFloat offset_space = 3; //label的间距 522 | maxTitleSize.height += offset_space; 523 | CGFloat legend_with = 30.0f; //图例的宽度 524 | CGFloat minLegendViewHeight = self.offset_top;//最小视图的高度 525 | CGFloat legend_W = maxTitleSize.width + legend_with + offset_space * 2; 526 | CGFloat legend_H = maxTitleSize.height * _y_titles.count; 527 | CGFloat legend_X = self.width - legend_W; 528 | CGFloat legend_Y = 0; 529 | CGFloat path_width = 2; 530 | if (legend_H < minLegendViewHeight) { 531 | legend_H = minLegendViewHeight; 532 | maxTitleSize.height = minLegendViewHeight * 1.0f / _y_titles.count; 533 | } 534 | //创建视图 535 | legendView = [[UIView alloc]initWithFrame:CGRectMake(legend_X, legend_Y, legend_W, legend_H)]; 536 | legendView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8]; 537 | UIBezierPath *legendPath = [UIBezierPath bezierPath]; 538 | for (int i = 0; i < _y_titles.count; i ++) { 539 | @autoreleasepool { 540 | UILabel *lab_tmp = [[UILabel alloc]init]; 541 | lab_tmp.size = maxTitleSize; 542 | lab_tmp.x = offset_space; 543 | lab_tmp.y = i * maxTitleSize.height; 544 | lab_tmp.textAlignment = NSTextAlignmentCenter; 545 | #warning 设置图例文本的颜色 set legend text colors 546 | lab_tmp.textColor = [UIColor grayColor];//self.colorsArray[i]; 547 | lab_tmp.text = _y_titles[i]; 548 | lab_tmp.font = [UIFont systemFontOfSize:Coords_Legend_Font_Size weight:bold]; 549 | UIBezierPath *tmp_path = [UIBezierPath bezierPath]; 550 | UIBezierPath *dot_path = [UIBezierPath bezierPath]; 551 | CGFloat path_minX = legend_W - 3; 552 | CGFloat path_maxX = maxTitleSize.width + 12; 553 | CGFloat path_Y = lab_tmp.centerY; 554 | CGFloat path_center = (path_maxX + path_minX)/2.0f; 555 | [tmp_path moveToPoint:CGPointMake(path_minX, path_Y)]; 556 | [tmp_path addLineToPoint:CGPointMake(path_maxX, path_Y)]; 557 | [dot_path addArcWithCenter:CGPointMake(path_center, path_Y) radius:path_width startAngle:0 endAngle:M_PI*2 clockwise:YES]; 558 | [tmp_path appendPath:dot_path]; 559 | [legendPath appendPath:tmp_path]; 560 | [legendView addSubview:lab_tmp]; 561 | //绘制线条 562 | CAShapeLayer *legendLayer = [[CAShapeLayer alloc]init]; 563 | legendLayer.frame = CGRectMake(0, maxTitleSize.height * i, legendView.width - maxTitleSize.width, lab_tmp.height); 564 | legendLayer.path = legendPath.CGPath; 565 | legendLayer.lineCap = kCALineCapRound; 566 | legendLayer.lineWidth = path_width; 567 | legendLayer.strokeColor = self.colorsArray[i].CGColor; 568 | [legendView.layer addSublayer:legendLayer]; 569 | } 570 | } 571 | legendView.layer.cornerRadius = 2; 572 | legendView.layer.borderWidth = 0.5; 573 | legendView.layer.borderColor = [UIColor grayColor].CGColor; 574 | legendView.layer.masksToBounds = YES; 575 | [self addSubview:legendView]; 576 | } 577 | 578 | 579 | #pragma mark - 一些视图动画 580 | /** 581 | * 为线条添加动画 582 | * 583 | * @param layer CAShapeLayer 584 | */ 585 | -(void)addAnimationToLayer:(CAShapeLayer *)layer{ 586 | CABasicAnimation *baseAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; 587 | baseAnimation.fromValue = @0; 588 | baseAnimation.byValue = @0.3; 589 | baseAnimation.toValue = @1; 590 | baseAnimation.duration = 1.6; 591 | [layer addAnimation:baseAnimation forKey:nil]; 592 | } 593 | #pragma mark - 一些必要的工具 594 | /** 595 | * 返回最小的Y值 596 | * 597 | * @return 返回最小的Y 598 | */ 599 | -(CGFloat)getMinYValue{ 600 | __block CGFloat min_tmp = MAXFLOAT; 601 | BOOL isZero = YES; 602 | //遍历获取 603 | for (NSArray *array_tmp in _y_values) { 604 | if (array_tmp.count) { 605 | isZero = NO; 606 | } 607 | [array_tmp enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 608 | @autoreleasepool { 609 | CGFloat value_tmp = [obj floatValue]; 610 | min_tmp = min_tmp < value_tmp ? min_tmp:value_tmp; 611 | } 612 | }]; 613 | 614 | } 615 | if (isZero) { 616 | return 0; 617 | } 618 | return min_tmp; 619 | } 620 | 621 | /** 622 | * 返回最大的Y值 623 | * 624 | * @return 返回最大的Y 625 | */ 626 | -(CGFloat)getMaxYValue{ 627 | if (maxY == 0) { 628 | maxY = - MAXFLOAT; 629 | } 630 | BOOL isZero = YES; 631 | __block CGFloat max_tmp = maxY; 632 | //遍历获取 633 | for (NSArray *array_tmp in _y_values) { 634 | if (array_tmp.count) { 635 | isZero = NO; 636 | } 637 | [array_tmp enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 638 | @autoreleasepool { 639 | CGFloat value_tmp = [obj floatValue]; 640 | max_tmp = max_tmp > value_tmp ? max_tmp:value_tmp; 641 | } 642 | }]; 643 | } 644 | if (isZero) { 645 | return 1; 646 | } 647 | return max_tmp * 1.1; 648 | } 649 | /** 650 | * 根据Y轴的标题宽度获取最大值获取Size 651 | * 652 | * @return size 653 | */ 654 | -(CGSize)getSizeFromeMaxTitleWidth{ 655 | CGSize maxTitleSize = CGSizeZero; 656 | UIFont *font = [UIFont systemFontOfSize:Coords_Legend_Font_Size weight:bold]; 657 | for (NSString *str in _y_titles) { 658 | @autoreleasepool { 659 | CGSize tmpSize = [str sizeWithAttributes:@{NSFontAttributeName:font}]; 660 | if (tmpSize.width > maxTitleSize.width) { 661 | maxTitleSize = tmpSize; 662 | } 663 | } 664 | } 665 | return maxTitleSize; 666 | } 667 | 668 | /** 669 | * 获取x轴中最大的字符串size 670 | * 671 | * @return size 672 | */ 673 | -(CGSize)getX_MaxLabelSize{ 674 | 675 | CGSize label_size = CGSizeZero; 676 | 677 | for (NSString *str in _x_values) { 678 | CGSize size_tmp = [self getLabelWidthWithStr:str font:[UIFont systemFontOfSize:Coords_X_LableFont_Size]]; 679 | if (size_tmp.width > label_size.width) { 680 | label_size = size_tmp; 681 | } 682 | } 683 | if (label_size.width < (_draw_view.width - self.offset_right - (_x_values.count * Coords_X_Lable_Space)) / (float)_x_values.count) { 684 | label_size.width = (_draw_view.width - self.offset_right - (_x_values.count * Coords_X_Lable_Space)) / (float)_x_values.count; 685 | } 686 | return label_size; 687 | } 688 | /** 689 | * 转换X值,符合视图坐标 690 | * 691 | * @param label_width X轴的标签宽度 692 | */ 693 | -(void)swithXValuesWithLabelWidth:(CGFloat)label_width{ 694 | NSMutableArray *x_vls = [NSMutableArray array]; 695 | [_x_values enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 696 | CGFloat tmp = ((float)idx + 0.5f) * (label_width + Coords_X_Lable_Space); 697 | [x_vls addObject:@(tmp)]; 698 | }]; 699 | _coords_x_values = x_vls; 700 | } 701 | /** 702 | * 转换y值,符合视图坐标 703 | */ 704 | -(void)swithYValues{ 705 | NSMutableArray *arrays = [NSMutableArray array]; 706 | for (NSArray *y_values_tmp in _y_values) { 707 | NSMutableArray *tmp_arr = [NSMutableArray array]; 708 | [y_values_tmp enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 709 | @autoreleasepool { 710 | CGFloat tmp_value = [obj floatValue] - self.minY; 711 | tmp_value = self.frame.size.height - tmp_value * _scale_Value - self.offset_bottom; 712 | [tmp_arr addObject:@(tmp_value)]; 713 | } 714 | }]; 715 | [arrays addObject:tmp_arr]; 716 | } 717 | _coords_y_values = arrays; 718 | } 719 | 720 | /** 721 | * 是否能通过检查 722 | * 723 | * @return 返回YES表示通过检查 724 | */ 725 | -(BOOL)PASS_checkErrors{ 726 | //|| || self.y_values.count == 0 727 | if (CGRectEqualToRect(self.frame, CGRectZero)) { 728 | 729 | NSLog(@"frame 尺寸为空 : %@",NSStringFromCGRect(self.frame)); 730 | return NO; 731 | } 732 | if (self.x_values.count == 0) { 733 | // NSLog(@"x 坐标值为空 : %@",self.x_values); 734 | return NO; 735 | } 736 | if (self.y_values.count == 0) { 737 | 738 | // NSLog(@"y 坐标值为空 : %@",self.y_values); 739 | return NO; 740 | } 741 | 742 | return YES; 743 | } 744 | 745 | /** 746 | * 根据字体大小获取字符串的尺寸 747 | * 748 | * @param str 目标字符串 749 | * @param font 字体 750 | * 751 | * @return size 752 | */ 753 | -(CGSize)getLabelWidthWithStr:(NSString *)str font:(UIFont*)font{ 754 | 755 | return [str sizeWithAttributes:@{NSFontAttributeName:font}]; 756 | 757 | } 758 | 759 | #pragma mark -GET&SET方法重写 760 | -(NSArray *)colorsArray{ 761 | 762 | if (!_colorsArray) { 763 | NSMutableArray *color_array = [[NSMutableArray alloc]init]; 764 | for (int i = 0; i < _y_values.count; i ++) { 765 | UIColor *color = [UIColor colorWithRed:(arc4random_uniform(255)/255.0f) green:(arc4random_uniform(255)/255.0f) blue:(arc4random_uniform(255)/255.0f) alpha:1]; 766 | [color_array addObject:color]; 767 | } 768 | _colorsArray = color_array; 769 | } 770 | return _colorsArray; 771 | } 772 | 773 | #pragma mark - 设置刻度的个数 774 | - (void)setCoords_Y_Tips:(NSInteger)tipCont{ 775 | 776 | Coords_Y_Tip = tipCont; 777 | } 778 | 779 | #pragma mark - 设置最小的 Y 值 780 | - (void)setMinY:(CGFloat)minValue{ 781 | if (minValue < 0) { 782 | minValue = [self getMinYValue]; 783 | } 784 | if (minValue > [self getMaxYValue]) { 785 | minValue = [self getMaxYValue] - (Coords_Y_Tip + 1) / 10.0f; 786 | } 787 | _minY = minValue; 788 | if (isCustemY) { 789 | [self setXCoordinatesLocationInYValue:_x_coord_location]; 790 | } 791 | } 792 | 793 | - (void)setXCoordinatesLocationInYValue:(CGFloat)yValue{ 794 | 795 | isCustemY = YES; 796 | 797 | if (yValue < self.minY) { 798 | 799 | yValue = self.minY; 800 | 801 | } 802 | if (yValue > [self getMaxYValue]) { 803 | yValue = [self getMaxYValue]; 804 | } 805 | 806 | _x_coord_location = yValue; 807 | 808 | } 809 | 810 | #pragma mark - 点击事件的代理方法(数值显示) 811 | -(void)chartViewTouchPoint:(CGPoint)point{ 812 | __block BOOL canTouch = NO; 813 | [_y_values enumerateObjectsUsingBlock:^(NSArray * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 814 | if (obj.count) { 815 | canTouch = YES; 816 | } 817 | }]; 818 | if (!canTouch) { 819 | return; 820 | } 821 | CGFloat x_touch_space = _coords_x_label_width / 2.0f; 822 | __block NSInteger x_idx = -1; 823 | __block NSInteger y_idx = -1; 824 | [_coords_x_values enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 825 | 826 | CGFloat x_vls = [obj floatValue]; 827 | CGFloat distance = MAX(x_vls, point.x) - MIN(x_vls, point.x);//ABS(x_vls - point.x); 828 | if (distance < x_touch_space) { 829 | x_idx = idx; 830 | } 831 | }]; 832 | 833 | __block CGFloat distance = MAXFLOAT; 834 | for (int i = 0; i < _coords_y_values.count; i ++) { 835 | 836 | NSArray *y_vls = _coords_y_values[i]; 837 | 838 | if (y_vls.count - 1 >= x_idx && x_idx != -1) { 839 | CGFloat dis = ABS_VALUE(point.y,[y_vls[x_idx] floatValue]); 840 | if (distance > dis) { 841 | y_idx = i; 842 | distance = dis; 843 | } 844 | } 845 | } 846 | if (y_idx < 0 || x_idx<0) { 847 | return; 848 | } 849 | NSMutableString *info = [NSMutableString string]; 850 | NSString *valueStr = _y_values[y_idx][x_idx]; 851 | for (int i = 0 ; i < _y_values.count; i ++) { 852 | if (_y_values[i].count>x_idx&&[_y_values[i][x_idx] isEqualToString:valueStr]) { 853 | if (![info isEqualToString:@""]) { 854 | [info appendString:@"\n"]; 855 | } 856 | if (_y_titles.count > i) { 857 | [info appendString:_y_titles[i]]; 858 | // [info appendString:@"\n"]; 859 | } 860 | 861 | NSString *xStr = @""; 862 | NSString *yStr = @""; 863 | if (self.yUnit.length>0) { 864 | yStr = [NSString stringWithFormat:@"%@",self.yUnit]; 865 | } 866 | [info appendString:[NSString stringWithFormat:@"(%@%@, %@ %@)",_x_values[x_idx],xStr,_y_values[i][x_idx],yStr]]; 867 | } 868 | } 869 | 870 | CGPoint showPoint = CGPointMake([_coords_x_values[x_idx] floatValue], [_coords_y_values[y_idx][x_idx] floatValue]); 871 | PopoverView *popView = [PopoverView new]; 872 | [popView showAtPoint:showPoint inView:_draw_view withText:info]; 873 | 874 | } 875 | 876 | #pragma mark - 触摸可移动图例 877 | BOOL isMove; 878 | CGPoint legend_point; 879 | -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 880 | if (isMove) { 881 | return; 882 | } 883 | [super touchesBegan:touches withEvent:event]; 884 | isMove = NO; 885 | UITouch *touch = [touches anyObject]; 886 | CGPoint point = [touch locationInView:self]; 887 | if (CGRectContainsPoint(legendView.frame, point)) { 888 | legend_point = [touch locationInView:legendView]; 889 | // isMove = YES; 890 | } 891 | } 892 | 893 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ 894 | isMove = NO; 895 | [super touchesEnded:touches withEvent:event]; 896 | } 897 | 898 | -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 899 | if (!isMove) { 900 | [super touchesMoved:touches withEvent:event]; 901 | return; 902 | } 903 | @autoreleasepool { 904 | UITouch *touch = [touches anyObject]; 905 | CGPoint point = [touch locationInView:self]; 906 | //转化成相对的中心 907 | point.x += legendView.width/2.0f - legend_point.x; 908 | point.y += legendView.height/2.0f - legend_point.y; 909 | 910 | if (point.x < legendView.width / 2.0f) { 911 | point.x = legendView.width / 2.0f; 912 | } 913 | if (point.y < legendView.height / 2.0f) { 914 | point.y = legendView.height / 2.0f; 915 | } 916 | 917 | if (point.x > self.width - legendView.width / 2.0f) { 918 | point.x = self.width - legendView.width / 2.0f; 919 | } 920 | if (point.y > self.height - legendView.height / 2.0f) { 921 | point.y = self.height - legendView.height / 2.0f; 922 | } 923 | 924 | 925 | legendView.center = point; 926 | } 927 | } 928 | 929 | 930 | @end 931 | 932 | 933 | -------------------------------------------------------------------------------- /WZCChartLineView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // 曲线-新算法 4 | // 5 | // Created by 邬志成 on 16/7/20. 6 | // Copyright © 2016年 邬志成. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------