├── Classes ├── LMRGrid.h ├── LMRGrid.m ├── LMRLabel.h ├── LMRLabel.m ├── LMRStyle.h ├── LMRStyle.m ├── LMReport.h ├── LMReportView.h ├── LMReportView.m ├── NSIndexPath+LMReport.h └── NSIndexPath+LMReport.m ├── Demo ├── .gitignore ├── LMReport │ ├── LMRGrid.h │ ├── LMRGrid.m │ ├── LMRLabel.h │ ├── LMRLabel.m │ ├── LMRStyle.h │ ├── LMRStyle.m │ ├── LMReport.h │ ├── LMReportView.h │ ├── LMReportView.m │ ├── NSIndexPath+LMReport.h │ └── NSIndexPath+LMReport.m ├── LMReportDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── LMReportDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── image-1.imageset │ │ ├── Contents.json │ │ └── image-1.jpg │ ├── image-2.imageset │ │ ├── Contents.json │ │ └── image-2.jpg │ └── image-3.imageset │ │ ├── Contents.json │ │ └── image-0.jpg │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── ComplexViewController.h │ ├── ComplexViewController.m │ ├── DetailingViewController.h │ ├── DetailingViewController.m │ ├── FitViewController.h │ ├── FitViewController.m │ ├── HandleEventViewController.h │ ├── HandleEventViewController.m │ ├── Info.plist │ ├── LMRStyle+Demo.h │ ├── LMRStyle+Demo.m │ ├── SimpleViewController.h │ ├── SimpleViewController.m │ ├── StylesViewController.h │ ├── StylesViewController.m │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── DemoDisplay ├── complex.png ├── detailing.png ├── fit.png ├── handleEvent.png ├── simple.png ├── sort.png └── styles.png ├── LICENSE ├── LMReport.podspec └── README.md /Classes/LMRGrid.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRGrid.h 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LMRGrid : NSObject 12 | 13 | @property (nonatomic, copy) NSString *text; 14 | @property (nonatomic, strong) UIImage *image; 15 | 16 | @property (nonatomic, strong) UIColor *backgroundColor; 17 | @property (nonatomic, strong) UIColor *textColor; 18 | @property (nonatomic, strong) UIFont *font; 19 | @property (nonatomic, assign) NSTextAlignment textAlignment; 20 | @property (nonatomic, assign) NSInteger rowspan; 21 | @property (nonatomic, assign) NSInteger colspan; 22 | @property (nonatomic, assign) BOOL underline; 23 | @property (nonatomic, readonly) BOOL isTextAlignmentOriginal; 24 | 25 | @property (nonatomic, strong) NSIndexPath *indexPath; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Classes/LMRGrid.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRGrid.m 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "LMRGrid.h" 10 | 11 | @implementation LMRGrid 12 | 13 | - (instancetype)init { 14 | if (self = [super init]) { 15 | _rowspan = 1; 16 | _colspan = 1; 17 | _isTextAlignmentOriginal = YES; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)setTextAlignment:(NSTextAlignment)textAlignment { 23 | _textAlignment = textAlignment; 24 | _isTextAlignmentOriginal = NO; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Classes/LMRLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRLabel.h 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LMRLabel : UILabel 12 | 13 | @property (nonatomic, strong) NSIndexPath *indexPath; 14 | @property (nonatomic, assign) NSInteger rowspan; 15 | @property (nonatomic, assign) NSInteger colspan; 16 | @property (nonatomic, assign) BOOL underline; 17 | @property (nonatomic, retain) UIImage *image; 18 | 19 | - (void)heightToFit; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Classes/LMRLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRLabel.m 3 | // 4 | // Created by Chenly on 15/2/27. 5 | // Copyright (c) 2015年 Chenly. All rights reserved. 6 | // 7 | 8 | #import "LMRLabel.h" 9 | 10 | @implementation LMRLabel 11 | 12 | - (void)drawRect:(CGRect)rect { 13 | 14 | if (self.image) { 15 | self.clipsToBounds = YES; 16 | [self.image drawInRect:rect]; 17 | return; 18 | } 19 | 20 | if (self.text.length > 0) { 21 | NSMutableAttributedString *multableAttributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; 22 | NSRange range = NSMakeRange(0, multableAttributedText.length); 23 | [multableAttributedText addAttribute:NSUnderlineStyleAttributeName 24 | value:(self.underline ? @(NSUnderlineStyleSingle) : @(NSUnderlineStyleNone)) 25 | range:range]; 26 | [multableAttributedText addAttribute:NSUnderlineColorAttributeName 27 | value:self.textColor 28 | range:range]; 29 | self.attributedText = [multableAttributedText copy]; 30 | } 31 | [super drawRect:rect]; 32 | } 33 | 34 | - (void)heightToFit { 35 | if (self.text.length == 0) { 36 | return; 37 | } 38 | else { 39 | CGFloat height = [self.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.bounds), 0) 40 | options:NSStringDrawingUsesLineFragmentOrigin 41 | attributes:@{ 42 | NSFontAttributeName: self.font, 43 | NSUnderlineColorAttributeName: @(self.underline) 44 | } 45 | context:nil].size.height; 46 | if (height > CGRectGetHeight(self.frame)) { 47 | CGRect rect = self.frame; 48 | rect.size.height = roundf(height + 0.5f); 49 | self.frame = rect; 50 | } 51 | } 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Classes/LMRStyle.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRStyle.h 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // LMRStyleSettings 12 | extern NSString *const LMRHeightOfHeaderRowSettingName; 13 | extern NSString *const LMRHeightOfRowSettingName; 14 | extern NSString *const LMRWidthOfFirstColSettingName; 15 | extern NSString *const LMRWidthOfColSettingName; 16 | extern NSString *const LMRAutoFitHeightSettingName; 17 | extern NSString *const LMRBackgroundColorSettingName; 18 | extern NSString *const LMRBackgroundColorOfHeaderSettingName; 19 | extern NSString *const LMRTextColorSettingName; 20 | extern NSString *const LMRTextColorOfHeaderSettingName; 21 | extern NSString *const LMRFontSettingName; 22 | extern NSString *const LMRFontOfHeaderSettingName; 23 | extern NSString *const LMRTextAlignmentSettingName; 24 | extern NSString *const LMRTextAlignmentOfHeaderSettingName; 25 | extern NSString *const LMRSpacingSettingName; 26 | extern NSString *const LMRBorderColorSettingName; 27 | extern NSString *const LMRBorderInsetsSettingName; 28 | extern NSString *const LMRTrimWidthSpaceSettingName; 29 | extern NSString *const LMRTrimHeightSpaceSettingName; 30 | extern NSString *const LMRStripeBackgroundColorSettingName; 31 | extern NSString *const LMRStripeTextColorSettingName; 32 | 33 | 34 | /** 35 | * LMRStyle 对应报表整体的风格样式 36 | */ 37 | @interface LMRStyle : NSObject 38 | /** 39 | * 表头行高,为0时返回heightOfRow,默认为0。 40 | */ 41 | @property (nonatomic, assign) CGFloat heightOfHeaderRow; 42 | /** 43 | * 行高,默认为0。 44 | */ 45 | @property (nonatomic, assign) CGFloat heightOfRow; 46 | /** 47 | * 第一列宽度,为0时返回widthOfCol,默认为0。 48 | */ 49 | @property (nonatomic, assign) CGFloat widthOfFirstCol; 50 | /** 51 | * 列宽,默认为0。 52 | */ 53 | @property (nonatomic, assign) CGFloat widthOfCol; 54 | /** 55 | * 表头是否自适应行高,当一行中的某个单元格文字较多超出所设置的行高时,是否自动调整行高使文字全部显示,默认为No。(在datasource实现heightOfRow方法时无效) 56 | */ 57 | @property (nonatomic, assign, getter=isAutoFitHeight) BOOL autoFitHeight; 58 | /** 59 | * 表头背景颜色,为空时返回下面属性background的值,默认为nil。 60 | */ 61 | @property (nonatomic, strong) UIColor *backgroundColorOfHeader; 62 | /** 63 | * 背景颜色,默认为[UIColor whiteColor] 64 | */ 65 | @property (nonatomic, strong) UIColor *backgroundColor; 66 | /** 67 | * 表头字体颜色,为空时返回下面属性textColor的值,默认为nil。 68 | */ 69 | @property (nonatomic, strong) UIColor *textColorOfHeader; 70 | /** 71 | * 字体颜色,默认为[UIColor blackColor]。 72 | */ 73 | @property (nonatomic, strong) UIColor *textColor; 74 | /** 75 | * 表头字体,为空时返回下面属性font的值,默认为nil。 76 | */ 77 | @property (nonatomic, strong) UIFont *fontOfHeader; 78 | /** 79 | * 字体,默认为[UIFont systemFontOfSize:17.f]。 80 | */ 81 | @property (nonatomic, strong) UIFont *font; 82 | /** 83 | * 表头对齐方式,未设置时返回textAlignment。 84 | */ 85 | @property(nonatomic, assign) NSTextAlignment textAlignmentOfHeader; 86 | /** 87 | * 对齐方式,默认为NSTextAlignmentCenter。 88 | */ 89 | @property(nonatomic, assign) NSTextAlignment textAlignment; 90 | /** 91 | * 单元格间距(表格线宽),默认为1。 92 | */ 93 | @property (nonatomic, assign) CGFloat spacing; 94 | /** 95 | * 边界线,默认为{1,1,1,1}。 96 | */ 97 | @property (nonatomic, assign) UIEdgeInsets borderInsets; 98 | /** 99 | * 边界线颜色,默认为[UIColor grayColor]。 100 | */ 101 | @property (nonatomic, strong) UIColor *borderColor; 102 | /** 103 | * 切除多余,默认为YES。 104 | */ 105 | @property (nonatomic, assign, getter=isTrimWidthSpace) BOOL trimWidthSpace; 106 | /** 107 | * 当实际表格高度小于设置的Frame时候调整单元格高度 108 | */ 109 | @property (nonatomic, assign, getter=isTrimHeightSpace) BOOL trimHeightSpace; 110 | /** 111 | * 条纹背景色(设置表格奇偶行不一致背景色增加对比度),默认为nil。 112 | */ 113 | @property (nonatomic, strong) UIColor *stripeBackgroundColor; 114 | /** 115 | * 条纹字体颜色,默认为nil。 116 | */ 117 | @property (nonatomic, strong) UIColor *stripeTextColor; 118 | 119 | + (instancetype)defaultStyle; 120 | + (instancetype)styleWithSettings:(NSDictionary *)settings; 121 | - (instancetype)initWithSettings:(NSDictionary *)settings; 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Classes/LMRStyle.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRStyle.m 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "LMRStyle.h" 10 | 11 | NSString *const LMRHeightOfHeaderRowSettingName = @"LMRHeightOfHeaderRow"; 12 | NSString *const LMRHeightOfRowSettingName = @"LMRHeightOfRow"; 13 | NSString *const LMRWidthOfFirstColSettingName = @"LMRWidthOfFirstCol"; 14 | NSString *const LMRWidthOfColSettingName = @"LMRWidthOfCol"; 15 | NSString *const LMRAutoFitHeightSettingName = @"LMRAutoFitHeight"; 16 | NSString *const LMRBackgroundColorSettingName = @"LMRBackgroundColor"; 17 | NSString *const LMRBackgroundColorOfHeaderSettingName = @"LMRBackgroundColorOfHeader"; 18 | NSString *const LMRTextColorSettingName = @"LMRTextColor"; 19 | NSString *const LMRTextColorOfHeaderSettingName = @"LMRTextColorOfHeader"; 20 | NSString *const LMRFontSettingName = @"LMRFont"; 21 | NSString *const LMRFontOfHeaderSettingName = @"LMRFontOfHeader"; 22 | NSString *const LMRTextAlignmentSettingName = @"LMRTextAlignment"; 23 | NSString *const LMRTextAlignmentOfHeaderSettingName = @"LMRTextAlignmentOfHeader"; 24 | NSString *const LMRSpacingSettingName = @"LMRSpacing"; 25 | NSString *const LMRBorderColorSettingName = @"LMRBorderColor"; 26 | NSString *const LMRBorderInsetsSettingName = @"LMRBorderInsets"; 27 | NSString *const LMRTrimWidthSpaceSettingName = @"LMRTrimWidthSpace"; 28 | NSString *const LMRTrimHeightSpaceSettingName = @"LMRTrimHeightSpace"; 29 | NSString *const LMRStripeBackgroundColorSettingName = @"LMRStripeBackgroundColor"; 30 | NSString *const LMRStripeTextColorSettingName = @"LMRStripeTextColor"; 31 | 32 | @implementation LMRStyle 33 | { 34 | BOOL _isTextAlignmentOfHeaderOrigin; 35 | } 36 | 37 | @synthesize textAlignmentOfHeader = _textAlignmentOfHeader; 38 | 39 | - (instancetype)init { 40 | return [self initWithSettings:nil]; 41 | } 42 | 43 | - (instancetype)initWithSettings:(NSDictionary *)settings { 44 | if (self = [super init]) { 45 | _heightOfHeaderRow = 0.f; 46 | _heightOfRow = 0.f; 47 | _widthOfFirstCol = 0.f; 48 | _widthOfCol = 0.f; 49 | _backgroundColor = [UIColor whiteColor]; 50 | _backgroundColorOfHeader = nil; 51 | _textColor = [UIColor blackColor]; 52 | _textColorOfHeader = nil; 53 | _font = [UIFont systemFontOfSize:17.f]; 54 | _fontOfHeader = nil; 55 | _textAlignment = NSTextAlignmentCenter; 56 | _isTextAlignmentOfHeaderOrigin = YES; 57 | _spacing = 1.f; 58 | _borderColor = [UIColor grayColor]; 59 | _borderInsets = UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f); 60 | _trimWidthSpace = YES; 61 | _trimHeightSpace = YES; 62 | _stripeBackgroundColor = nil; 63 | _stripeTextColor = nil; 64 | 65 | if (settings) { 66 | id obj; 67 | if ((obj = [settings objectForKey:LMRHeightOfHeaderRowSettingName])) _heightOfHeaderRow = [obj floatValue]; 68 | if ((obj = [settings objectForKey:LMRHeightOfRowSettingName])) _heightOfRow = [obj floatValue]; 69 | if ((obj = [settings objectForKey:LMRWidthOfFirstColSettingName])) _widthOfFirstCol = [obj floatValue]; 70 | if ((obj = [settings objectForKey:LMRWidthOfColSettingName])) _widthOfCol = [obj floatValue]; 71 | if ((obj = [settings objectForKey:LMRAutoFitHeightSettingName])) _autoFitHeight = [obj boolValue]; 72 | if ((obj = [settings objectForKey:LMRBackgroundColorSettingName])) _backgroundColor = obj; 73 | if ((obj = [settings objectForKey:LMRBackgroundColorOfHeaderSettingName])) _backgroundColorOfHeader = obj; 74 | if ((obj = [settings objectForKey:LMRTextColorSettingName])) _textColor = obj; 75 | if ((obj = [settings objectForKey:LMRTextColorOfHeaderSettingName])) _textColorOfHeader = obj; 76 | if ((obj = [settings objectForKey:LMRFontSettingName])) _font = obj; 77 | if ((obj = [settings objectForKey:LMRFontOfHeaderSettingName])) _fontOfHeader = obj; 78 | if ((obj = [settings objectForKey:LMRTextAlignmentSettingName])) _textAlignment = [obj integerValue]; 79 | if ((obj = [settings objectForKey:LMRTextAlignmentOfHeaderSettingName])) self.textAlignmentOfHeader = [obj floatValue]; 80 | if ((obj = [settings objectForKey:LMRSpacingSettingName])) _spacing = [obj floatValue]; 81 | if ((obj = [settings objectForKey:LMRBorderColorSettingName])) _borderColor = obj; 82 | if ((obj = [settings objectForKey:LMRBorderInsetsSettingName])) _borderInsets = [obj UIEdgeInsetsValue]; 83 | if ((obj = [settings objectForKey:LMRTrimWidthSpaceSettingName])) _trimWidthSpace = [obj boolValue]; 84 | if ((obj = [settings objectForKey:LMRTrimHeightSpaceSettingName])) _trimHeightSpace = [obj boolValue]; 85 | if ((obj = [settings objectForKey:LMRStripeBackgroundColorSettingName])) _stripeBackgroundColor = obj; 86 | if ((obj = [settings objectForKey:LMRStripeTextColorSettingName])) _stripeTextColor = obj; 87 | } 88 | } 89 | return self; 90 | } 91 | 92 | + (instancetype)defaultStyle { 93 | NSDictionary *settings = @{ 94 | LMRBackgroundColorOfHeaderSettingName: [UIColor colorWithRed:104/255.f green:173/255.f blue:213/255.f alpha:1.f], 95 | LMRBorderColorSettingName: [UIColor colorWithRed:170/255.f green:200/255.f blue:230/255.f alpha:1.f], 96 | LMRBackgroundColorSettingName: [UIColor whiteColor], 97 | LMRHeightOfHeaderRowSettingName: @36.f, 98 | LMRHeightOfRowSettingName: @36.f, 99 | LMRWidthOfFirstColSettingName: @74.f, 100 | LMRWidthOfColSettingName: @75.f, 101 | LMRFontOfHeaderSettingName: [UIFont systemFontOfSize:13.f], 102 | LMRFontSettingName: [UIFont systemFontOfSize:13.f], 103 | LMRTextColorSettingName: [UIColor blackColor], 104 | LMRTextColorOfHeaderSettingName: [UIColor whiteColor], 105 | LMRBorderInsetsSettingName: [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f)] 106 | }; 107 | return [LMRStyle styleWithSettings:settings]; 108 | } 109 | 110 | + (instancetype)styleWithSettings:(NSDictionary *)settings { 111 | return [[LMRStyle alloc] initWithSettings:settings]; 112 | } 113 | 114 | - (UIColor *)backgroundOfHeader { 115 | return _backgroundColorOfHeader ?: _backgroundColor; 116 | } 117 | 118 | - (void)setTextAlignmentOfHeader:(NSTextAlignment)textAlignmentOfHeader { 119 | _textAlignmentOfHeader = textAlignmentOfHeader; 120 | _isTextAlignmentOfHeaderOrigin = NO; 121 | } 122 | 123 | - (NSTextAlignment)textAlignmentOfHeader { 124 | return _isTextAlignmentOfHeaderOrigin ? _textAlignment : _textAlignmentOfHeader; 125 | } 126 | 127 | - (UIFont *)fontOfHeader { 128 | return _fontOfHeader ?: _font; 129 | } 130 | 131 | - (UIColor *)textColorOfHeader { 132 | return _textColorOfHeader ?: _textColor; 133 | } 134 | 135 | - (CGFloat)widthOfFirstCol { 136 | return _widthOfFirstCol > 0 ? _widthOfFirstCol : _widthOfCol; 137 | } 138 | 139 | - (CGFloat)heightOfHeaderRow { 140 | return _heightOfHeaderRow > 0 ? _heightOfHeaderRow : _heightOfRow; 141 | } 142 | 143 | @end -------------------------------------------------------------------------------- /Classes/LMReport.h: -------------------------------------------------------------------------------- 1 | 2 | #import "LMRGrid.h" 3 | #import "LMRLabel.h" 4 | #import "LMRStyle.h" 5 | #import "LMReportView.h" 6 | #import "NSIndexPath+LMReport.h" -------------------------------------------------------------------------------- /Classes/LMReportView.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @class LMReportView; 5 | @class LMRGrid; 6 | @class LMRLabel; 7 | @class LMRStyle; 8 | 9 | typedef NS_ENUM(NSInteger, LMROrder) { 10 | LMROrderedAscending = -1, 11 | LMROrderedNature, 12 | LMROrderedDescending 13 | }; 14 | 15 | @protocol LMReportViewDatasource 16 | 17 | @required 18 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView; 19 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView; 20 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath; 21 | 22 | @optional 23 | - (NSInteger)numberOfHeadRowsInReportView:(LMReportView *)reportView; // 表头行数,默认为1 24 | - (CGFloat)reportView:(LMReportView *)reportView heightOfRow:(NSInteger)row; // 某一行的高度,如果未实现则取style中的heightOfHeaderRow/heightOfRow的值 25 | - (CGFloat)reportView:(LMReportView *)reportView widthOfCol:(NSInteger)col; // 某一列的宽度,如果未实现则取style中的widthOfFirstCol/widthOfCol的值 26 | 27 | @end 28 | 29 | @protocol LMReportViewDelegate 30 | 31 | @optional 32 | - (void)reportView:(LMReportView *)reportView didTapLabel:(LMRLabel *)label; 33 | - (void)reportView:(LMReportView *)reportView didLongPressLabel:(LMRLabel *)label; 34 | - (NSOrderedSet *)reportView:(LMReportView *)reportView indexesSortedByCol:(NSInteger)col order:(LMROrder)order; 35 | 36 | @end 37 | 38 | @interface LMReportView : UIView 39 | 40 | @property (nonatomic, weak) id datasource; 41 | @property (nonatomic, weak) id delegate; 42 | 43 | @property (nonatomic, readonly) UITapGestureRecognizer *tapGestureRecognizer; 44 | @property (nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; // 默认minimumPressDuration = 1.0。 45 | 46 | @property (nonatomic, strong) LMRStyle *style; // 报表整体风格样式,在报表已加载过的情况下(didMoveToSuperview),需要调用手动reloadData来刷新样式。 47 | 48 | @property (nonatomic, assign) NSInteger sortedCol; 49 | @property (nonatomic, assign) LMROrder sortedOrder; 50 | 51 | - (instancetype)initWithFrame:(CGRect)frame; // 在frame.size.width/height == 0 时候,会自动拉伸至内容所需宽度/长度 52 | - (void)reloadData; 53 | - (void)sortByCol:(NSInteger)col order:(LMROrder)order; 54 | 55 | - (NSInteger)numberOfRows; 56 | - (NSInteger)numberOfCols; 57 | - (NSInteger)numberOfHeadRows; 58 | - (LMRGrid *)gridAtIndexPath:(NSIndexPath *)indexPath; 59 | - (CGFloat)heightOfRow:(NSInteger)row; 60 | - (CGFloat)widthOfCol:(NSInteger)col; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Classes/LMReportView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMReportView.m 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "LMReportView.h" 10 | #import "LMRGrid.h" 11 | #import "LMRLabel.h" 12 | #import "LMRStyle.h" 13 | #import "NSIndexPath+LMReport.h" 14 | 15 | typedef NS_ENUM(NSUInteger, LMRPart) { 16 | LMRPartTopLeft, 17 | LMRPartTopRight, 18 | LMRPartBottomLeft, 19 | LMRPartBottomRight, 20 | }; 21 | 22 | @interface LMReportView () 23 | { 24 | UIScrollView *_topRightScroll; 25 | UIScrollView *_bottomRightScroll; 26 | UIScrollView *_bottomLeftScroll; 27 | UIView *_topLeftView; 28 | UIView *_topRightView; 29 | UIView *_bottomLeftView; 30 | UIView *_bottomRightView; 31 | 32 | NSInteger _numberOfRows; 33 | NSInteger _numberOfCols; 34 | NSInteger _numberOfHeadRows; 35 | 36 | struct { 37 | unsigned int numberOfHeadRows : 1; 38 | unsigned int heightOfRow : 1; 39 | unsigned int widthOfCol : 1; 40 | } _datasourceFlags; 41 | 42 | struct { 43 | unsigned int didTapLabel : 1; 44 | unsigned int didLongPressLabel : 1; 45 | unsigned int indexesSorted : 1; 46 | } _delegateFlags; 47 | } 48 | @end 49 | 50 | @implementation LMReportView 51 | 52 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 53 | if (self = [super initWithCoder:aDecoder]) { 54 | [self p_setupViews]; 55 | [self p_setupGestureRecognizers]; 56 | _style = [LMRStyle defaultStyle]; 57 | } 58 | return self; 59 | } 60 | 61 | - (instancetype)initWithFrame:(CGRect)frame { 62 | if (self = [super initWithFrame:frame]) { 63 | [self p_setupViews]; 64 | [self p_setupGestureRecognizers]; 65 | _style = [LMRStyle defaultStyle]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)p_setupViews { 71 | _topRightScroll = [[UIScrollView alloc] init]; 72 | _topRightScroll.backgroundColor = [UIColor clearColor]; 73 | _topRightScroll.scrollEnabled = NO; 74 | 75 | _bottomLeftScroll = [[UIScrollView alloc] init]; 76 | _bottomLeftScroll.backgroundColor = [UIColor clearColor]; 77 | _bottomLeftScroll.scrollEnabled = NO; 78 | 79 | _bottomRightScroll = [[UIScrollView alloc] init]; 80 | _bottomRightScroll.backgroundColor = [UIColor clearColor]; 81 | _bottomRightScroll.bounces = NO; 82 | _bottomRightScroll.directionalLockEnabled = YES; 83 | _bottomRightScroll.delegate = self; 84 | 85 | UIView *(^createContentView)(void) = ^{ 86 | UIView *contentView = [[UIView alloc] init]; 87 | contentView.backgroundColor = [UIColor clearColor]; 88 | contentView.clipsToBounds = YES; 89 | return contentView; 90 | }; 91 | _topLeftView = createContentView(); 92 | _topRightView = createContentView(); 93 | _bottomLeftView = createContentView(); 94 | _bottomRightView = createContentView(); 95 | 96 | [_topRightScroll addSubview:_topRightView]; 97 | [_bottomLeftScroll addSubview:_bottomLeftView]; 98 | [_bottomRightScroll addSubview:_bottomRightView]; 99 | 100 | [self addSubview:_topLeftView]; 101 | [self addSubview:_topRightScroll]; 102 | [self addSubview:_bottomLeftScroll]; 103 | [self addSubview:_bottomRightScroll]; 104 | } 105 | 106 | - (void)p_setupGestureRecognizers { 107 | _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(p_handleLongPressGR:)]; 108 | _longPressGestureRecognizer.minimumPressDuration = 1.0; 109 | _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(p_handleTapGR:)]; 110 | [_tapGestureRecognizer requireGestureRecognizerToFail:_longPressGestureRecognizer]; 111 | [self addGestureRecognizer:_longPressGestureRecognizer]; 112 | [self addGestureRecognizer:_tapGestureRecognizer]; 113 | } 114 | 115 | - (void)didMoveToSuperview { 116 | if (!self.superview) { 117 | return; 118 | } 119 | [super didMoveToSuperview]; 120 | [self reloadData]; 121 | } 122 | 123 | #pragma mark - load 124 | 125 | - (void)reloadData { 126 | @autoreleasepool { 127 | [_topLeftView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 128 | [_topRightView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 129 | [_bottomLeftView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 130 | [_bottomRightView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 131 | } 132 | if (!_datasource) { 133 | return; 134 | } 135 | 136 | self.backgroundColor = _style.borderColor; 137 | 138 | _numberOfRows = [_datasource numberOfRowsInReportView:self]; 139 | _numberOfCols = [_datasource numberOfColsInReportView:self]; 140 | if (_numberOfRows * _numberOfCols == 0) { 141 | return; 142 | } 143 | _numberOfHeadRows = [_datasource respondsToSelector:@selector(numberOfHeadRowsInReportView:)] ? [_datasource numberOfHeadRowsInReportView:self] : 1; 144 | 145 | _datasourceFlags.numberOfHeadRows = [_datasource respondsToSelector:@selector(numberOfHeadRowsInReportView:)]; 146 | _datasourceFlags.heightOfRow = [_datasource respondsToSelector:@selector(reportView:heightOfRow:)]; 147 | _datasourceFlags.widthOfCol = [_datasource respondsToSelector:@selector(reportView:widthOfCol:)]; 148 | 149 | _delegateFlags.didTapLabel = [_delegate respondsToSelector:@selector(reportView:didTapLabel:)]; 150 | _delegateFlags.didLongPressLabel = [_delegate respondsToSelector:@selector(reportView:didLongPressLabel:)]; 151 | _delegateFlags.indexesSorted = [_delegate respondsToSelector:@selector(reportView:indexesSortedByCol:order:)]; 152 | _sortedCol = 0; 153 | _sortedOrder = LMROrderedNature; 154 | 155 | [self p_loadTopLeft]; 156 | [self p_loadTopRight]; 157 | [self p_loadBottomLeft]; 158 | [self p_loadBottomRight]; 159 | [self setNeedsLayout]; 160 | } 161 | 162 | - (void)p_loadTopLeft { 163 | CGRect rect = CGRectZero; 164 | rect.size.width = _datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:0] : _style.widthOfFirstCol; 165 | 166 | NSInteger row = 0; 167 | while (row < _numberOfHeadRows) { 168 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:0 inRow:row]; 169 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 170 | if (!grid) { 171 | row ++; 172 | continue; 173 | } 174 | grid.indexPath = indexPath; 175 | LMRLabel *label; 176 | [self p_getLabel:&label forGrid:grid inPart:LMRPartTopLeft]; 177 | label.indexPath = indexPath; 178 | 179 | if (_datasourceFlags.heightOfRow) { 180 | CGFloat height = 0; 181 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 182 | height += [_datasource reportView:self heightOfRow:i]; 183 | } 184 | height += _style.spacing * (grid.rowspan - 1); 185 | rect.size.height = height; 186 | } 187 | else { 188 | rect.size.height = (_style.heightOfHeaderRow + _style.spacing) * grid.rowspan - _style.spacing; 189 | } 190 | label.frame = rect; 191 | if (_style.isAutoFitHeight && label.rowspan == 1) { 192 | [label heightToFit]; 193 | } 194 | rect = label.frame; 195 | 196 | [_topLeftView addSubview:label]; 197 | row += grid.rowspan; 198 | rect.origin.y = CGRectGetMaxY(rect) + _style.spacing; 199 | } 200 | rect.size.height = rect.origin.y - _style.spacing; 201 | rect.origin = CGPointMake(_style.borderInsets.left, _style.borderInsets.top); 202 | _topLeftView.frame = rect; 203 | } 204 | 205 | - (void)p_loadTopRight { 206 | CGRect rect = CGRectZero; 207 | NSInteger row = 0; 208 | while (row < _numberOfHeadRows) { 209 | rect.origin = CGPointZero; 210 | rect.origin.y = 0; 211 | for (NSInteger i = 0; i < row; i ++) { 212 | if (_datasourceFlags.heightOfRow) { 213 | rect.origin.y += [_datasource reportView:self heightOfRow:i] + _style.spacing; 214 | } 215 | else { 216 | rect.origin.y += _style.heightOfHeaderRow + _style.spacing; 217 | } 218 | } 219 | NSInteger col = 1; 220 | while (col < _numberOfCols) { 221 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:col inRow:row]; 222 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 223 | if (!grid) { 224 | rect.origin.x += (_datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:col] : _style.widthOfCol) + _style.spacing; 225 | col ++; 226 | continue; 227 | } 228 | grid.indexPath = indexPath; 229 | LMRLabel *label; 230 | [self p_getLabel:&label forGrid:grid inPart:LMRPartTopRight]; 231 | label.indexPath = indexPath; 232 | 233 | if (_datasourceFlags.widthOfCol) { 234 | CGFloat width = 0; 235 | for (NSInteger i = col; i < col + grid.colspan; i ++) { 236 | width += [_datasource reportView:self widthOfCol:i]; 237 | } 238 | width += _style.spacing * (grid.colspan - 1); 239 | rect.size.width = width; 240 | } 241 | else { 242 | rect.size.width = (_style.widthOfCol + _style.spacing) * grid.colspan - _style.spacing; 243 | } 244 | if (_datasourceFlags.heightOfRow) { 245 | CGFloat height = 0; 246 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 247 | height += [_datasource reportView:self heightOfRow:i]; 248 | } 249 | height += _style.spacing * (grid.rowspan - 1); 250 | rect.size.height = height; 251 | } 252 | else { 253 | rect.size.height = (_style.heightOfHeaderRow + _style.spacing) * grid.rowspan - _style.spacing; 254 | } 255 | label.frame = rect; 256 | if (_style.isAutoFitHeight && label.rowspan == 1) { 257 | [label heightToFit]; 258 | } 259 | rect = label.frame; 260 | 261 | [_topRightView addSubview:label]; 262 | col += grid.colspan; 263 | rect.origin.x = CGRectGetMaxX(rect) + _style.spacing; 264 | } 265 | row ++; 266 | } 267 | rect.size.width = rect.origin.x - _style.spacing; 268 | rect.size.height = CGRectGetMaxY(rect); 269 | rect.origin = CGPointZero; 270 | _topRightView.frame = rect; 271 | _topRightScroll.contentSize = rect.size; 272 | } 273 | 274 | - (void)p_loadBottomLeft { 275 | CGRect rect = CGRectZero; 276 | rect.size.width = _datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:0] : _style.widthOfFirstCol; 277 | 278 | NSInteger row = _numberOfHeadRows; 279 | while (row < _numberOfRows) { 280 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:0 inRow:row]; 281 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 282 | if (!grid) { 283 | rect.origin.x += _style.widthOfCol + _style.spacing; 284 | row ++; 285 | continue; 286 | } 287 | grid.indexPath = indexPath; 288 | LMRLabel *label; 289 | [self p_getLabel:&label forGrid:grid inPart:LMRPartBottomLeft]; 290 | label.indexPath = indexPath; 291 | 292 | if (_datasourceFlags.heightOfRow) { 293 | CGFloat height = 0; 294 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 295 | height += [_datasource reportView:self heightOfRow:i]; 296 | } 297 | height += _style.spacing * (grid.rowspan - 1); 298 | rect.size.height = height; 299 | } 300 | else { 301 | rect.size.height = (_style.heightOfRow + _style.spacing) * grid.rowspan - _style.spacing; 302 | } 303 | label.frame = rect; 304 | if (_style.isAutoFitHeight && label.rowspan == 1) { 305 | [label heightToFit]; 306 | } 307 | rect = label.frame; 308 | 309 | [_bottomLeftView addSubview:label]; 310 | row += grid.rowspan; 311 | rect.origin.y = CGRectGetMaxY(rect) + _style.spacing; 312 | } 313 | rect.size.height = rect.origin.y - _style.spacing; 314 | rect.origin = CGPointZero; 315 | _bottomLeftView.frame = rect; 316 | _bottomLeftScroll.contentSize = rect.size; 317 | } 318 | 319 | - (void)p_loadBottomRight { 320 | CGRect rect = CGRectZero; 321 | NSInteger row = _numberOfHeadRows; 322 | while (row < _numberOfRows) { 323 | rect.origin = CGPointZero; 324 | rect.origin.y = 0; 325 | for (NSInteger i = _numberOfHeadRows; i < row; i ++) { 326 | if (_datasourceFlags.heightOfRow) { 327 | rect.origin.y += [_datasource reportView:self heightOfRow:i] + _style.spacing; 328 | } 329 | else { 330 | rect.origin.y += _style.heightOfRow + _style.spacing; 331 | } 332 | } 333 | NSInteger col = 1; 334 | while (col < _numberOfCols) { 335 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:col inRow:row]; 336 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 337 | if (!grid) { 338 | rect.origin.x += (_datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:col] : _style.widthOfCol) + _style.spacing; 339 | col ++; 340 | continue; 341 | } 342 | grid.indexPath = indexPath; 343 | LMRLabel *label; 344 | [self p_getLabel:&label forGrid:grid inPart:LMRPartBottomRight]; 345 | label.indexPath = indexPath; 346 | 347 | if (_datasourceFlags.widthOfCol) { 348 | CGFloat width = 0; 349 | for (NSInteger i = col; i < col + grid.colspan; i ++) { 350 | width += [_datasource reportView:self widthOfCol:i]; 351 | } 352 | width += _style.spacing * (grid.colspan - 1); 353 | rect.size.width = width; 354 | } 355 | else { 356 | rect.size.width = (_style.widthOfCol + _style.spacing) * grid.colspan - _style.spacing; 357 | } 358 | if (_datasourceFlags.heightOfRow) { 359 | CGFloat height = 0; 360 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 361 | height += [_datasource reportView:self heightOfRow:i]; 362 | } 363 | height += _style.spacing * (grid.rowspan - 1); 364 | rect.size.height = height; 365 | } 366 | else { 367 | rect.size.height = (_style.heightOfRow + _style.spacing) * grid.rowspan - _style.spacing; 368 | } 369 | label.frame = rect; 370 | if (_style.isAutoFitHeight && label.rowspan == 1) { 371 | [label heightToFit]; 372 | } 373 | rect = label.frame; 374 | 375 | [_bottomRightView addSubview:label]; 376 | col += grid.colspan; 377 | rect.origin.x = CGRectGetMaxX(rect) + _style.spacing; 378 | } 379 | row ++; 380 | } 381 | rect.size.width = rect.origin.x - _style.spacing; 382 | rect.size.height = CGRectGetMaxY(rect); 383 | rect.origin = CGPointZero; 384 | _bottomRightView.frame = rect; 385 | _bottomRightScroll.contentSize = rect.size; 386 | } 387 | 388 | - (void)p_getLabel:(LMRLabel **)label forGrid:(LMRGrid *)grid inPart:(LMRPart)part { 389 | LMRLabel *theLabel = [LMRLabel new]; 390 | switch (part) { 391 | case LMRPartTopLeft: 392 | case LMRPartTopRight: 393 | { 394 | theLabel.font = grid.font ? : _style.fontOfHeader; 395 | theLabel.textColor = grid.textColor ? : _style.textColorOfHeader; 396 | theLabel.backgroundColor = grid.backgroundColor ? : _style.backgroundColorOfHeader; 397 | theLabel.textAlignment = grid.isTextAlignmentOriginal ? _style.textAlignmentOfHeader : grid.textAlignment; 398 | break; 399 | } 400 | case LMRPartBottomLeft: 401 | case LMRPartBottomRight: 402 | { 403 | theLabel.font = grid.font ? : _style.font; 404 | UIColor *textColor = grid.textColor; 405 | if (!textColor) { 406 | textColor = _style.textColor; 407 | if (_style.stripeTextColor && (grid.indexPath.row - _numberOfHeadRows) % 2 == 1) { 408 | textColor = _style.stripeTextColor; 409 | } 410 | } 411 | theLabel.textColor = textColor; 412 | UIColor *backgroundColor = grid.backgroundColor; 413 | if (!backgroundColor) { 414 | backgroundColor = _style.backgroundColor; 415 | if (_style.stripeBackgroundColor && (grid.indexPath.row - _numberOfHeadRows) % 2 == 1) { 416 | backgroundColor = _style.stripeBackgroundColor; 417 | } 418 | } 419 | theLabel.backgroundColor = backgroundColor; 420 | theLabel.textAlignment = grid.isTextAlignmentOriginal ? _style.textAlignment : grid.textAlignment; 421 | break; 422 | } 423 | } 424 | theLabel.rowspan = grid.rowspan; 425 | theLabel.colspan = grid.colspan; 426 | theLabel.image = grid.image; 427 | theLabel.text = grid.text; 428 | theLabel.lineBreakMode = NSLineBreakByCharWrapping; 429 | theLabel.numberOfLines = 0; 430 | theLabel.underline = grid.underline; 431 | *label = theLabel; 432 | } 433 | 434 | #pragma mark - layout 435 | 436 | - (void)layoutSubviews { 437 | if (!_datasource) { 438 | return; 439 | } 440 | 441 | if (_style.isAutoFitHeight) { 442 | [self p_fitHeight]; 443 | } 444 | 445 | CGFloat frameWidth = CGRectGetWidth(self.frame); 446 | CGFloat frameHeight = CGRectGetHeight(self.frame); 447 | CGFloat contentWidth = CGRectGetMaxX(_topLeftView.frame) + _style.spacing + _bottomRightScroll.contentSize.width + _style.borderInsets.right; 448 | CGFloat contentHeight = CGRectGetMaxY(_topLeftView.frame) + _style.spacing + _bottomRightScroll.contentSize.height + _style.borderInsets.bottom; 449 | if (frameWidth == contentWidth && frameHeight == contentHeight) { 450 | return; 451 | } 452 | 453 | //frameWidth设为0的时候自动拉伸到内容实际宽度 454 | if (frameWidth == 0) { 455 | frameWidth = contentWidth; 456 | } 457 | //frameHeight设为0的时候自动拉伸到内容实际高度 458 | if (frameHeight == 0) { 459 | frameHeight = contentHeight; 460 | } 461 | 462 | if (contentWidth < frameWidth && _style.isTrimWidthSpace) { 463 | frameWidth = contentWidth; 464 | } 465 | if (contentHeight < frameHeight && _style.isTrimHeightSpace) { 466 | frameHeight = contentHeight; 467 | } 468 | 469 | CGRect rect = self.frame; 470 | rect.size.width = frameWidth; 471 | rect.size.height = frameHeight; 472 | self.frame = rect; 473 | 474 | rect = _topLeftView.frame; 475 | rect.origin.x = CGRectGetMaxX(rect) + _style.spacing; 476 | rect.size.width = CGRectGetWidth(self.frame) - _style.borderInsets.right - rect.origin.x; 477 | _topRightScroll.frame = rect; 478 | 479 | rect = _topLeftView.frame; 480 | rect.origin.y = CGRectGetMaxY(rect) + _style.spacing; 481 | rect.size.height = CGRectGetHeight(self.frame) - _style.borderInsets.bottom - rect.origin.y; 482 | _bottomLeftScroll.frame = rect; 483 | 484 | rect.origin.x = CGRectGetMinX(_topRightScroll.frame); 485 | rect.origin.y = CGRectGetMinY(_bottomLeftScroll.frame); 486 | rect.size.width = CGRectGetWidth(_topRightScroll.frame); 487 | rect.size.height = CGRectGetHeight(_bottomLeftScroll.frame); 488 | _bottomRightScroll.frame = rect; 489 | } 490 | 491 | - (NSArray *)p_suitableHeights { 492 | NSMutableArray *heights = [NSMutableArray arrayWithCapacity:_numberOfRows]; 493 | for (int i = 0; i <_numberOfRows; i ++) { 494 | [heights addObject:@(0)]; 495 | } 496 | for (UIView *contentView in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) { 497 | for (LMRLabel *label in contentView.subviews) { 498 | if (label.rowspan > 1) { 499 | continue; 500 | } 501 | CGFloat maxHeight = [heights[label.indexPath.row] floatValue]; 502 | CGFloat currentHeight = CGRectGetHeight(label.frame); 503 | if (currentHeight > maxHeight) { 504 | heights[label.indexPath.row] = @(currentHeight); 505 | } 506 | } 507 | } 508 | return [heights copy]; 509 | } 510 | 511 | - (void)p_fitHeight { 512 | NSArray *heights = [self p_suitableHeights]; 513 | for (UIView *contentView in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) { 514 | for (LMRLabel *label in contentView.subviews) { 515 | CGRect rect = label.frame; 516 | rect.origin.y = 0; 517 | 518 | NSInteger i = (contentView == _topLeftView || contentView == _topRightView) ? 0 : _numberOfHeadRows; 519 | while (i < label.indexPath.row) { 520 | rect.origin.y += [heights[i] floatValue] + _style.spacing; 521 | i ++; 522 | } 523 | rect.size.height = 0; 524 | for (int i = 0; i < label.rowspan; i ++) { 525 | rect.size.height += [heights[label.indexPath.row + i] floatValue]; 526 | } 527 | label.frame = rect; 528 | } 529 | } 530 | 531 | void (^frameSet)(UIView *) = ^(UIView *view){ 532 | CGRect rect = view.frame; 533 | rect.size.height = CGRectGetMaxY([[view.subviews lastObject] frame]); 534 | view.frame = rect; 535 | }; 536 | frameSet(_topLeftView); 537 | frameSet(_topRightView); 538 | frameSet(_bottomLeftView); 539 | frameSet(_bottomRightView); 540 | 541 | CGRect rect; 542 | _topRightScroll.contentSize = _topRightView.frame.size; 543 | rect = _topRightScroll.frame; 544 | rect.size.height = CGRectGetHeight(_topLeftView.frame); 545 | _topRightScroll.frame = rect; 546 | 547 | _bottomLeftScroll.contentSize = _bottomLeftView.frame.size; 548 | rect = _bottomLeftScroll.frame; 549 | rect.origin.y = CGRectGetMaxY(_topLeftView.frame) + _style.spacing; 550 | rect.size.height = CGRectGetHeight(_bottomLeftView.frame); 551 | _bottomLeftScroll.frame = rect; 552 | 553 | _bottomRightScroll.contentSize = _bottomRightView.frame.size; 554 | rect = _bottomRightScroll.frame; 555 | rect.origin.x = CGRectGetMinX(_topRightScroll.frame); 556 | rect.origin.y = CGRectGetMinY(_bottomLeftScroll.frame); 557 | rect.size.width = CGRectGetWidth(_topRightScroll.frame); 558 | rect.size.height = CGRectGetHeight(_bottomLeftScroll.frame); 559 | _bottomRightScroll.frame = rect; 560 | } 561 | 562 | #pragma mark - ScrollView delegate 563 | 564 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 565 | if (scrollView == _bottomRightScroll) { 566 | _topRightScroll.contentOffset = CGPointMake(_bottomRightScroll.contentOffset.x, 0); 567 | _bottomLeftScroll.contentOffset = CGPointMake(0, _bottomRightScroll.contentOffset.y); 568 | } 569 | } 570 | 571 | #pragma mark - handle gesture recognizer 572 | 573 | - (LMRLabel *)p_touchedLabelWithGesture:(UIGestureRecognizer *)gestureRecognizer { 574 | CGPoint point = [gestureRecognizer locationInView:self]; 575 | UIView *inView; 576 | for (UIView *view in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) { 577 | CGRect rect = [view convertRect:view.bounds toView:self]; 578 | if (CGRectContainsPoint(rect, point)) { 579 | inView = view; 580 | break; 581 | } 582 | } 583 | if (!inView) { 584 | return nil; 585 | } 586 | LMRLabel *label; 587 | for (UIView *subview in inView.subviews) { 588 | if ([subview isKindOfClass:[LMRLabel class]]) { 589 | CGRect rect = [subview convertRect:subview.bounds toView:self]; 590 | if (CGRectContainsPoint(rect, point)) { 591 | label = (LMRLabel *)subview; 592 | break; 593 | } 594 | } 595 | } 596 | return label; 597 | } 598 | 599 | - (void)p_handleLongPressGR:(UILongPressGestureRecognizer *)longPressGR { 600 | if (longPressGR.state != UIGestureRecognizerStateBegan) { 601 | return; 602 | } 603 | if (_delegateFlags.didLongPressLabel) { 604 | LMRLabel *label = [self p_touchedLabelWithGesture:longPressGR]; 605 | if (label) { 606 | [_delegate reportView:self didLongPressLabel:[self p_touchedLabelWithGesture:longPressGR]]; 607 | } 608 | } 609 | } 610 | 611 | - (void)p_handleTapGR:(UILongPressGestureRecognizer *)tapGR { 612 | if (_delegateFlags.didTapLabel) { 613 | LMRLabel *label = [self p_touchedLabelWithGesture:tapGR]; 614 | if (label) { 615 | [_delegate reportView:self didTapLabel:label]; 616 | } 617 | } 618 | } 619 | 620 | #pragma mark - 621 | 622 | - (NSInteger)numberOfRows { 623 | return _numberOfRows; 624 | } 625 | 626 | - (NSInteger)numberOfCols { 627 | return _numberOfCols; 628 | } 629 | 630 | - (NSInteger)numberOfHeadRows { 631 | return _numberOfHeadRows; 632 | } 633 | 634 | - (LMRGrid *)gridAtIndexPath:(NSIndexPath *)indexPath { 635 | return [_datasource reportView:self gridAtIndexPath:indexPath]; 636 | } 637 | 638 | - (CGFloat)heightOfRow:(NSInteger)row { 639 | if (_datasourceFlags.heightOfRow) { 640 | return [_datasource reportView:self heightOfRow:row]; 641 | } 642 | else { 643 | return row < _numberOfHeadRows ? _style.heightOfHeaderRow : _style.heightOfRow; 644 | } 645 | } 646 | 647 | - (CGFloat)widthOfCol:(NSInteger)col { 648 | if (_datasourceFlags.widthOfCol) { 649 | return [_datasource reportView:self widthOfCol:col]; 650 | } 651 | else { 652 | return col == 0 ? _style.widthOfFirstCol : _style.widthOfCol; 653 | } 654 | } 655 | 656 | #pragma mark - sort 657 | 658 | - (void)sortByCol:(NSInteger)col order:(LMROrder)order { 659 | if (!_delegateFlags.indexesSorted) { 660 | return; 661 | } 662 | 663 | if (_sortedOrder != LMROrderedNature) { 664 | LMRLabel *label; 665 | if (_sortedCol == 0) { 666 | label = _topLeftView.subviews[0]; 667 | } 668 | else { 669 | label = _topRightView.subviews[_sortedCol - 1]; 670 | } 671 | label.text = [label.text substringToIndex:label.text.length - 1]; 672 | } 673 | _sortedCol = col; 674 | _sortedOrder = order; 675 | LMRLabel *label; 676 | if (col == 0) { 677 | label = _topLeftView.subviews[0]; 678 | } 679 | else { 680 | label = _topRightView.subviews[_sortedCol - 1]; 681 | } 682 | NSOrderedSet *indexes; 683 | switch (order) { 684 | case LMROrderedAscending: 685 | { 686 | label.text = [label.text stringByAppendingString:@"↑"]; 687 | indexes = [self.delegate reportView:self indexesSortedByCol:col order:order]; 688 | break; 689 | } 690 | case LMROrderedNature: 691 | { 692 | NSMutableOrderedSet *tempIndexes = [[NSMutableOrderedSet alloc] initWithCapacity:_numberOfRows - 1]; 693 | NSInteger i = 1; 694 | while (i < _numberOfRows) { 695 | [tempIndexes addObject:@(i)]; 696 | i ++; 697 | } 698 | indexes = [tempIndexes copy]; 699 | break; 700 | } 701 | case LMROrderedDescending: 702 | { 703 | label.text = [label.text stringByAppendingString:@"↓"]; 704 | indexes = [self.delegate reportView:self indexesSortedByCol:col order:order]; 705 | break; 706 | } 707 | } 708 | NSMutableArray *yOrigins = [NSMutableArray arrayWithCapacity:_numberOfRows - 1]; 709 | CGFloat y = 0; 710 | for (NSNumber *index in indexes) { 711 | [yOrigins addObject:@(y)]; 712 | LMRLabel *label = _bottomLeftView.subviews[index.integerValue - 1]; 713 | CGRect rect = label.frame; 714 | rect.origin.y = y; 715 | label.frame = rect; 716 | y += CGRectGetHeight(rect) + _style.spacing; 717 | } 718 | for (LMRLabel *label in _bottomRightView.subviews) { 719 | CGRect rect = label.frame; 720 | __block NSInteger index; 721 | [indexes enumerateObjectsUsingBlock:^(NSNumber *obj, NSUInteger idx, BOOL *stop) { 722 | if ([obj isEqualToNumber:@(label.indexPath.row)]) { 723 | index = idx; 724 | *stop = YES; 725 | } 726 | }]; 727 | rect.origin.y = [yOrigins[index] floatValue]; 728 | label.frame = rect; 729 | } 730 | } 731 | 732 | @end -------------------------------------------------------------------------------- /Classes/NSIndexPath+LMReport.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+LMReport.h 3 | // LMReportViewDemo 4 | // 5 | // Created by Chenly on 15/3/17. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSIndexPath (LMReport) 12 | 13 | @property (nonatomic, readonly) NSInteger row; 14 | @property (nonatomic, readonly) NSInteger col; 15 | + (NSIndexPath *)indexPathForCol:(NSInteger)col inRow:(NSInteger)row; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Classes/NSIndexPath+LMReport.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+LMR.m 3 | // LMReportViewDemo 4 | // 5 | // Created by Chenly on 15/3/17. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "NSIndexPath+LMReport.h" 10 | 11 | @implementation NSIndexPath (LMReport) 12 | 13 | + (NSIndexPath *)indexPathForCol:(NSInteger)col inRow:(NSInteger)row { 14 | return [NSIndexPath indexPathForRow:row inSection:col]; 15 | } 16 | 17 | - (NSInteger)col { 18 | return self.section; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Demo/.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | 5 | ## Various settings 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | 16 | ## Other 17 | *.moved-aside 18 | *.xcuserstate 19 | 20 | ## Obj-C/Swift specific 21 | *.hmap 22 | *.ipa 23 | 24 | # CocoaPods 25 | Pods/ -------------------------------------------------------------------------------- /Demo/LMReport/LMRGrid.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRGrid.h 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LMRGrid : NSObject 12 | 13 | @property (nonatomic, copy) NSString *text; 14 | @property (nonatomic, strong) UIImage *image; 15 | 16 | @property (nonatomic, strong) UIColor *backgroundColor; 17 | @property (nonatomic, strong) UIColor *textColor; 18 | @property (nonatomic, strong) UIFont *font; 19 | @property (nonatomic, assign) NSTextAlignment textAlignment; 20 | @property (nonatomic, assign) NSInteger rowspan; 21 | @property (nonatomic, assign) NSInteger colspan; 22 | @property (nonatomic, assign) BOOL underline; 23 | @property (nonatomic, readonly) BOOL isTextAlignmentOriginal; 24 | 25 | @property (nonatomic, strong) NSIndexPath *indexPath; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Demo/LMReport/LMRGrid.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRGrid.m 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "LMRGrid.h" 10 | 11 | @implementation LMRGrid 12 | 13 | - (instancetype)init { 14 | if (self = [super init]) { 15 | _rowspan = 1; 16 | _colspan = 1; 17 | _isTextAlignmentOriginal = YES; 18 | } 19 | return self; 20 | } 21 | 22 | - (void)setTextAlignment:(NSTextAlignment)textAlignment { 23 | _textAlignment = textAlignment; 24 | _isTextAlignmentOriginal = NO; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Demo/LMReport/LMRLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRLabel.h 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LMRLabel : UILabel 12 | 13 | @property (nonatomic, strong) NSIndexPath *indexPath; 14 | @property (nonatomic, assign) NSInteger rowspan; 15 | @property (nonatomic, assign) NSInteger colspan; 16 | @property (nonatomic, assign) BOOL underline; 17 | @property (nonatomic, retain) UIImage *image; 18 | 19 | - (void)heightToFit; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Demo/LMReport/LMRLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRLabel.m 3 | // 4 | // Created by Chenly on 15/2/27. 5 | // Copyright (c) 2015年 Chenly. All rights reserved. 6 | // 7 | 8 | #import "LMRLabel.h" 9 | 10 | @implementation LMRLabel 11 | 12 | - (void)drawRect:(CGRect)rect { 13 | 14 | if (self.image) { 15 | self.clipsToBounds = YES; 16 | [self.image drawInRect:rect]; 17 | return; 18 | } 19 | 20 | if (self.text.length > 0) { 21 | NSMutableAttributedString *multableAttributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText]; 22 | NSRange range = NSMakeRange(0, multableAttributedText.length); 23 | [multableAttributedText addAttribute:NSUnderlineStyleAttributeName 24 | value:(self.underline ? @(NSUnderlineStyleSingle) : @(NSUnderlineStyleNone)) 25 | range:range]; 26 | [multableAttributedText addAttribute:NSUnderlineColorAttributeName 27 | value:self.textColor 28 | range:range]; 29 | self.attributedText = [multableAttributedText copy]; 30 | } 31 | [super drawRect:rect]; 32 | } 33 | 34 | - (void)heightToFit { 35 | if (self.text.length == 0) { 36 | return; 37 | } 38 | else { 39 | CGFloat height = [self.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.bounds), 0) 40 | options:NSStringDrawingUsesLineFragmentOrigin 41 | attributes:@{ 42 | NSFontAttributeName: self.font, 43 | NSUnderlineColorAttributeName: @(self.underline) 44 | } 45 | context:nil].size.height; 46 | if (height > CGRectGetHeight(self.frame)) { 47 | CGRect rect = self.frame; 48 | rect.size.height = roundf(height + 0.5f); 49 | self.frame = rect; 50 | } 51 | } 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Demo/LMReport/LMRStyle.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRStyle.h 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | // LMRStyleSettings 12 | extern NSString *const LMRHeightOfHeaderRowSettingName; 13 | extern NSString *const LMRHeightOfRowSettingName; 14 | extern NSString *const LMRWidthOfFirstColSettingName; 15 | extern NSString *const LMRWidthOfColSettingName; 16 | extern NSString *const LMRAutoFitHeightSettingName; 17 | extern NSString *const LMRBackgroundColorSettingName; 18 | extern NSString *const LMRBackgroundColorOfHeaderSettingName; 19 | extern NSString *const LMRTextColorSettingName; 20 | extern NSString *const LMRTextColorOfHeaderSettingName; 21 | extern NSString *const LMRFontSettingName; 22 | extern NSString *const LMRFontOfHeaderSettingName; 23 | extern NSString *const LMRTextAlignmentSettingName; 24 | extern NSString *const LMRTextAlignmentOfHeaderSettingName; 25 | extern NSString *const LMRSpacingSettingName; 26 | extern NSString *const LMRBorderColorSettingName; 27 | extern NSString *const LMRBorderInsetsSettingName; 28 | extern NSString *const LMRTrimWidthSpaceSettingName; 29 | extern NSString *const LMRTrimHeightSpaceSettingName; 30 | extern NSString *const LMRStripeBackgroundColorSettingName; 31 | extern NSString *const LMRStripeTextColorSettingName; 32 | 33 | 34 | /** 35 | * LMRStyle 对应报表整体的风格样式 36 | */ 37 | @interface LMRStyle : NSObject 38 | /** 39 | * 表头行高,为0时返回heightOfRow,默认为0。 40 | */ 41 | @property (nonatomic, assign) CGFloat heightOfHeaderRow; 42 | /** 43 | * 行高,默认为0。 44 | */ 45 | @property (nonatomic, assign) CGFloat heightOfRow; 46 | /** 47 | * 第一列宽度,为0时返回widthOfCol,默认为0。 48 | */ 49 | @property (nonatomic, assign) CGFloat widthOfFirstCol; 50 | /** 51 | * 列宽,默认为0。 52 | */ 53 | @property (nonatomic, assign) CGFloat widthOfCol; 54 | /** 55 | * 表头是否自适应行高,当一行中的某个单元格文字较多超出所设置的行高时,是否自动调整行高使文字全部显示,默认为No。(在datasource实现heightOfRow方法时无效) 56 | */ 57 | @property (nonatomic, assign, getter=isAutoFitHeight) BOOL autoFitHeight; 58 | /** 59 | * 表头背景颜色,为空时返回下面属性background的值,默认为nil。 60 | */ 61 | @property (nonatomic, strong) UIColor *backgroundColorOfHeader; 62 | /** 63 | * 背景颜色,默认为[UIColor whiteColor] 64 | */ 65 | @property (nonatomic, strong) UIColor *backgroundColor; 66 | /** 67 | * 表头字体颜色,为空时返回下面属性textColor的值,默认为nil。 68 | */ 69 | @property (nonatomic, strong) UIColor *textColorOfHeader; 70 | /** 71 | * 字体颜色,默认为[UIColor blackColor]。 72 | */ 73 | @property (nonatomic, strong) UIColor *textColor; 74 | /** 75 | * 表头字体,为空时返回下面属性font的值,默认为nil。 76 | */ 77 | @property (nonatomic, strong) UIFont *fontOfHeader; 78 | /** 79 | * 字体,默认为[UIFont systemFontOfSize:17.f]。 80 | */ 81 | @property (nonatomic, strong) UIFont *font; 82 | /** 83 | * 表头对齐方式,未设置时返回textAlignment。 84 | */ 85 | @property(nonatomic, assign) NSTextAlignment textAlignmentOfHeader; 86 | /** 87 | * 对齐方式,默认为NSTextAlignmentCenter。 88 | */ 89 | @property(nonatomic, assign) NSTextAlignment textAlignment; 90 | /** 91 | * 单元格间距(表格线宽),默认为1。 92 | */ 93 | @property (nonatomic, assign) CGFloat spacing; 94 | /** 95 | * 边界线,默认为{1,1,1,1}。 96 | */ 97 | @property (nonatomic, assign) UIEdgeInsets borderInsets; 98 | /** 99 | * 边界线颜色,默认为[UIColor grayColor]。 100 | */ 101 | @property (nonatomic, strong) UIColor *borderColor; 102 | /** 103 | * 切除多余,默认为YES。 104 | */ 105 | @property (nonatomic, assign, getter=isTrimWidthSpace) BOOL trimWidthSpace; 106 | /** 107 | * 当实际表格高度小于设置的Frame时候调整单元格高度 108 | */ 109 | @property (nonatomic, assign, getter=isTrimHeightSpace) BOOL trimHeightSpace; 110 | /** 111 | * 条纹背景色(设置表格奇偶行不一致背景色增加对比度),默认为nil。 112 | */ 113 | @property (nonatomic, strong) UIColor *stripeBackgroundColor; 114 | /** 115 | * 条纹字体颜色,默认为nil。 116 | */ 117 | @property (nonatomic, strong) UIColor *stripeTextColor; 118 | 119 | + (instancetype)defaultStyle; 120 | + (instancetype)styleWithSettings:(NSDictionary *)settings; 121 | - (instancetype)initWithSettings:(NSDictionary *)settings; 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /Demo/LMReport/LMRStyle.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRStyle.m 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "LMRStyle.h" 10 | 11 | NSString *const LMRHeightOfHeaderRowSettingName = @"LMRHeightOfHeaderRow"; 12 | NSString *const LMRHeightOfRowSettingName = @"LMRHeightOfRow"; 13 | NSString *const LMRWidthOfFirstColSettingName = @"LMRWidthOfFirstCol"; 14 | NSString *const LMRWidthOfColSettingName = @"LMRWidthOfCol"; 15 | NSString *const LMRAutoFitHeightSettingName = @"LMRAutoFitHeight"; 16 | NSString *const LMRBackgroundColorSettingName = @"LMRBackgroundColor"; 17 | NSString *const LMRBackgroundColorOfHeaderSettingName = @"LMRBackgroundColorOfHeader"; 18 | NSString *const LMRTextColorSettingName = @"LMRTextColor"; 19 | NSString *const LMRTextColorOfHeaderSettingName = @"LMRTextColorOfHeader"; 20 | NSString *const LMRFontSettingName = @"LMRFont"; 21 | NSString *const LMRFontOfHeaderSettingName = @"LMRFontOfHeader"; 22 | NSString *const LMRTextAlignmentSettingName = @"LMRTextAlignment"; 23 | NSString *const LMRTextAlignmentOfHeaderSettingName = @"LMRTextAlignmentOfHeader"; 24 | NSString *const LMRSpacingSettingName = @"LMRSpacing"; 25 | NSString *const LMRBorderColorSettingName = @"LMRBorderColor"; 26 | NSString *const LMRBorderInsetsSettingName = @"LMRBorderInsets"; 27 | NSString *const LMRTrimWidthSpaceSettingName = @"LMRTrimWidthSpace"; 28 | NSString *const LMRTrimHeightSpaceSettingName = @"LMRTrimHeightSpace"; 29 | NSString *const LMRStripeBackgroundColorSettingName = @"LMRStripeBackgroundColor"; 30 | NSString *const LMRStripeTextColorSettingName = @"LMRStripeTextColor"; 31 | 32 | @implementation LMRStyle 33 | { 34 | BOOL _isTextAlignmentOfHeaderOrigin; 35 | } 36 | 37 | @synthesize textAlignmentOfHeader = _textAlignmentOfHeader; 38 | 39 | - (instancetype)init { 40 | return [self initWithSettings:nil]; 41 | } 42 | 43 | - (instancetype)initWithSettings:(NSDictionary *)settings { 44 | if (self = [super init]) { 45 | _heightOfHeaderRow = 0.f; 46 | _heightOfRow = 0.f; 47 | _widthOfFirstCol = 0.f; 48 | _widthOfCol = 0.f; 49 | _backgroundColor = [UIColor whiteColor]; 50 | _backgroundColorOfHeader = nil; 51 | _textColor = [UIColor blackColor]; 52 | _textColorOfHeader = nil; 53 | _font = [UIFont systemFontOfSize:17.f]; 54 | _fontOfHeader = nil; 55 | _textAlignment = NSTextAlignmentCenter; 56 | _isTextAlignmentOfHeaderOrigin = YES; 57 | _spacing = 1.f; 58 | _borderColor = [UIColor grayColor]; 59 | _borderInsets = UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f); 60 | _trimWidthSpace = YES; 61 | _trimHeightSpace = YES; 62 | _stripeBackgroundColor = nil; 63 | _stripeTextColor = nil; 64 | 65 | if (settings) { 66 | id obj; 67 | if ((obj = [settings objectForKey:LMRHeightOfHeaderRowSettingName])) _heightOfHeaderRow = [obj floatValue]; 68 | if ((obj = [settings objectForKey:LMRHeightOfRowSettingName])) _heightOfRow = [obj floatValue]; 69 | if ((obj = [settings objectForKey:LMRWidthOfFirstColSettingName])) _widthOfFirstCol = [obj floatValue]; 70 | if ((obj = [settings objectForKey:LMRWidthOfColSettingName])) _widthOfCol = [obj floatValue]; 71 | if ((obj = [settings objectForKey:LMRAutoFitHeightSettingName])) _autoFitHeight = [obj boolValue]; 72 | if ((obj = [settings objectForKey:LMRBackgroundColorSettingName])) _backgroundColor = obj; 73 | if ((obj = [settings objectForKey:LMRBackgroundColorOfHeaderSettingName])) _backgroundColorOfHeader = obj; 74 | if ((obj = [settings objectForKey:LMRTextColorSettingName])) _textColor = obj; 75 | if ((obj = [settings objectForKey:LMRTextColorOfHeaderSettingName])) _textColorOfHeader = obj; 76 | if ((obj = [settings objectForKey:LMRFontSettingName])) _font = obj; 77 | if ((obj = [settings objectForKey:LMRFontOfHeaderSettingName])) _fontOfHeader = obj; 78 | if ((obj = [settings objectForKey:LMRTextAlignmentSettingName])) _textAlignment = [obj integerValue]; 79 | if ((obj = [settings objectForKey:LMRTextAlignmentOfHeaderSettingName])) self.textAlignmentOfHeader = [obj floatValue]; 80 | if ((obj = [settings objectForKey:LMRSpacingSettingName])) _spacing = [obj floatValue]; 81 | if ((obj = [settings objectForKey:LMRBorderColorSettingName])) _borderColor = obj; 82 | if ((obj = [settings objectForKey:LMRBorderInsetsSettingName])) _borderInsets = [obj UIEdgeInsetsValue]; 83 | if ((obj = [settings objectForKey:LMRTrimWidthSpaceSettingName])) _trimWidthSpace = [obj boolValue]; 84 | if ((obj = [settings objectForKey:LMRTrimHeightSpaceSettingName])) _trimHeightSpace = [obj boolValue]; 85 | if ((obj = [settings objectForKey:LMRStripeBackgroundColorSettingName])) _stripeBackgroundColor = obj; 86 | if ((obj = [settings objectForKey:LMRStripeTextColorSettingName])) _stripeTextColor = obj; 87 | } 88 | } 89 | return self; 90 | } 91 | 92 | + (instancetype)defaultStyle { 93 | NSDictionary *settings = @{ 94 | LMRBackgroundColorOfHeaderSettingName: [UIColor colorWithRed:104/255.f green:173/255.f blue:213/255.f alpha:1.f], 95 | LMRBorderColorSettingName: [UIColor colorWithRed:170/255.f green:200/255.f blue:230/255.f alpha:1.f], 96 | LMRBackgroundColorSettingName: [UIColor whiteColor], 97 | LMRHeightOfHeaderRowSettingName: @36.f, 98 | LMRHeightOfRowSettingName: @36.f, 99 | LMRWidthOfFirstColSettingName: @74.f, 100 | LMRWidthOfColSettingName: @75.f, 101 | LMRFontOfHeaderSettingName: [UIFont systemFontOfSize:13.f], 102 | LMRFontSettingName: [UIFont systemFontOfSize:13.f], 103 | LMRTextColorSettingName: [UIColor blackColor], 104 | LMRTextColorOfHeaderSettingName: [UIColor whiteColor], 105 | LMRBorderInsetsSettingName: [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f)] 106 | }; 107 | return [LMRStyle styleWithSettings:settings]; 108 | } 109 | 110 | + (instancetype)styleWithSettings:(NSDictionary *)settings { 111 | return [[LMRStyle alloc] initWithSettings:settings]; 112 | } 113 | 114 | - (UIColor *)backgroundOfHeader { 115 | return _backgroundColorOfHeader ?: _backgroundColor; 116 | } 117 | 118 | - (void)setTextAlignmentOfHeader:(NSTextAlignment)textAlignmentOfHeader { 119 | _textAlignmentOfHeader = textAlignmentOfHeader; 120 | _isTextAlignmentOfHeaderOrigin = NO; 121 | } 122 | 123 | - (NSTextAlignment)textAlignmentOfHeader { 124 | return _isTextAlignmentOfHeaderOrigin ? _textAlignment : _textAlignmentOfHeader; 125 | } 126 | 127 | - (UIFont *)fontOfHeader { 128 | return _fontOfHeader ?: _font; 129 | } 130 | 131 | - (UIColor *)textColorOfHeader { 132 | return _textColorOfHeader ?: _textColor; 133 | } 134 | 135 | - (CGFloat)widthOfFirstCol { 136 | return _widthOfFirstCol > 0 ? _widthOfFirstCol : _widthOfCol; 137 | } 138 | 139 | - (CGFloat)heightOfHeaderRow { 140 | return _heightOfHeaderRow > 0 ? _heightOfHeaderRow : _heightOfRow; 141 | } 142 | 143 | @end -------------------------------------------------------------------------------- /Demo/LMReport/LMReport.h: -------------------------------------------------------------------------------- 1 | 2 | #import "LMRGrid.h" 3 | #import "LMRLabel.h" 4 | #import "LMRStyle.h" 5 | #import "LMReportView.h" 6 | #import "NSIndexPath+LMReport.h" -------------------------------------------------------------------------------- /Demo/LMReport/LMReportView.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @class LMReportView; 5 | @class LMRGrid; 6 | @class LMRLabel; 7 | @class LMRStyle; 8 | 9 | typedef NS_ENUM(NSInteger, LMROrder) { 10 | LMROrderedAscending = -1, 11 | LMROrderedNature, 12 | LMROrderedDescending 13 | }; 14 | 15 | @protocol LMReportViewDatasource 16 | 17 | @required 18 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView; 19 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView; 20 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath; 21 | 22 | @optional 23 | - (NSInteger)numberOfHeadRowsInReportView:(LMReportView *)reportView; // 表头行数,默认为1 24 | - (CGFloat)reportView:(LMReportView *)reportView heightOfRow:(NSInteger)row; // 某一行的高度,如果未实现则取style中的heightOfHeaderRow/heightOfRow的值 25 | - (CGFloat)reportView:(LMReportView *)reportView widthOfCol:(NSInteger)col; // 某一列的宽度,如果未实现则取style中的widthOfFirstCol/widthOfCol的值 26 | 27 | @end 28 | 29 | @protocol LMReportViewDelegate 30 | 31 | @optional 32 | - (void)reportView:(LMReportView *)reportView didTapLabel:(LMRLabel *)label; 33 | - (void)reportView:(LMReportView *)reportView didLongPressLabel:(LMRLabel *)label; 34 | - (NSOrderedSet *)reportView:(LMReportView *)reportView indexesSortedByCol:(NSInteger)col order:(LMROrder)order; 35 | 36 | @end 37 | 38 | @interface LMReportView : UIView 39 | 40 | @property (nonatomic, weak) id datasource; 41 | @property (nonatomic, weak) id delegate; 42 | 43 | @property (nonatomic, readonly) UITapGestureRecognizer *tapGestureRecognizer; 44 | @property (nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer; // 默认minimumPressDuration = 1.0。 45 | 46 | @property (nonatomic, strong) LMRStyle *style; // 报表整体风格样式,在报表已加载过的情况下(didMoveToSuperview),需要调用手动reloadData来刷新样式。 47 | 48 | @property (nonatomic, assign) NSInteger sortedCol; 49 | @property (nonatomic, assign) LMROrder sortedOrder; 50 | 51 | - (instancetype)initWithFrame:(CGRect)frame; // 在frame.size.width/height == 0 时候,会自动拉伸至内容所需宽度/长度 52 | - (void)reloadData; 53 | - (void)sortByCol:(NSInteger)col order:(LMROrder)order; 54 | 55 | - (NSInteger)numberOfRows; 56 | - (NSInteger)numberOfCols; 57 | - (NSInteger)numberOfHeadRows; 58 | - (LMRGrid *)gridAtIndexPath:(NSIndexPath *)indexPath; 59 | - (CGFloat)heightOfRow:(NSInteger)row; 60 | - (CGFloat)widthOfCol:(NSInteger)col; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Demo/LMReport/LMReportView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMReportView.m 3 | // LMReportView 4 | // 5 | // Created by Chenly on 15/2/27. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "LMReportView.h" 10 | #import "LMRGrid.h" 11 | #import "LMRLabel.h" 12 | #import "LMRStyle.h" 13 | #import "NSIndexPath+LMReport.h" 14 | 15 | typedef NS_ENUM(NSUInteger, LMRPart) { 16 | LMRPartTopLeft, 17 | LMRPartTopRight, 18 | LMRPartBottomLeft, 19 | LMRPartBottomRight, 20 | }; 21 | 22 | @interface LMReportView () 23 | { 24 | UIScrollView *_topRightScroll; 25 | UIScrollView *_bottomRightScroll; 26 | UIScrollView *_bottomLeftScroll; 27 | UIView *_topLeftView; 28 | UIView *_topRightView; 29 | UIView *_bottomLeftView; 30 | UIView *_bottomRightView; 31 | 32 | NSInteger _numberOfRows; 33 | NSInteger _numberOfCols; 34 | NSInteger _numberOfHeadRows; 35 | 36 | struct { 37 | unsigned int numberOfHeadRows : 1; 38 | unsigned int heightOfRow : 1; 39 | unsigned int widthOfCol : 1; 40 | } _datasourceFlags; 41 | 42 | struct { 43 | unsigned int didTapLabel : 1; 44 | unsigned int didLongPressLabel : 1; 45 | unsigned int indexesSorted : 1; 46 | } _delegateFlags; 47 | } 48 | @end 49 | 50 | @implementation LMReportView 51 | 52 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 53 | if (self = [super initWithCoder:aDecoder]) { 54 | [self p_setupViews]; 55 | [self p_setupGestureRecognizers]; 56 | _style = [LMRStyle defaultStyle]; 57 | } 58 | return self; 59 | } 60 | 61 | - (instancetype)initWithFrame:(CGRect)frame { 62 | if (self = [super initWithFrame:frame]) { 63 | [self p_setupViews]; 64 | [self p_setupGestureRecognizers]; 65 | _style = [LMRStyle defaultStyle]; 66 | } 67 | return self; 68 | } 69 | 70 | - (void)p_setupViews { 71 | _topRightScroll = [[UIScrollView alloc] init]; 72 | _topRightScroll.backgroundColor = [UIColor clearColor]; 73 | _topRightScroll.scrollEnabled = NO; 74 | 75 | _bottomLeftScroll = [[UIScrollView alloc] init]; 76 | _bottomLeftScroll.backgroundColor = [UIColor clearColor]; 77 | _bottomLeftScroll.scrollEnabled = NO; 78 | 79 | _bottomRightScroll = [[UIScrollView alloc] init]; 80 | _bottomRightScroll.backgroundColor = [UIColor clearColor]; 81 | _bottomRightScroll.bounces = NO; 82 | _bottomRightScroll.directionalLockEnabled = YES; 83 | _bottomRightScroll.delegate = self; 84 | 85 | UIView *(^createContentView)(void) = ^{ 86 | UIView *contentView = [[UIView alloc] init]; 87 | contentView.backgroundColor = [UIColor clearColor]; 88 | contentView.clipsToBounds = YES; 89 | return contentView; 90 | }; 91 | _topLeftView = createContentView(); 92 | _topRightView = createContentView(); 93 | _bottomLeftView = createContentView(); 94 | _bottomRightView = createContentView(); 95 | 96 | [_topRightScroll addSubview:_topRightView]; 97 | [_bottomLeftScroll addSubview:_bottomLeftView]; 98 | [_bottomRightScroll addSubview:_bottomRightView]; 99 | 100 | [self addSubview:_topLeftView]; 101 | [self addSubview:_topRightScroll]; 102 | [self addSubview:_bottomLeftScroll]; 103 | [self addSubview:_bottomRightScroll]; 104 | } 105 | 106 | - (void)p_setupGestureRecognizers { 107 | _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(p_handleLongPressGR:)]; 108 | _longPressGestureRecognizer.minimumPressDuration = 1.0; 109 | _tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(p_handleTapGR:)]; 110 | [_tapGestureRecognizer requireGestureRecognizerToFail:_longPressGestureRecognizer]; 111 | [self addGestureRecognizer:_longPressGestureRecognizer]; 112 | [self addGestureRecognizer:_tapGestureRecognizer]; 113 | } 114 | 115 | - (void)didMoveToSuperview { 116 | if (!self.superview) { 117 | return; 118 | } 119 | [super didMoveToSuperview]; 120 | [self reloadData]; 121 | } 122 | 123 | #pragma mark - load 124 | 125 | - (void)reloadData { 126 | @autoreleasepool { 127 | [_topLeftView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 128 | [_topRightView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 129 | [_bottomLeftView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 130 | [_bottomRightView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 131 | } 132 | if (!_datasource) { 133 | return; 134 | } 135 | 136 | self.backgroundColor = _style.borderColor; 137 | 138 | _numberOfRows = [_datasource numberOfRowsInReportView:self]; 139 | _numberOfCols = [_datasource numberOfColsInReportView:self]; 140 | if (_numberOfRows * _numberOfCols == 0) { 141 | return; 142 | } 143 | _numberOfHeadRows = [_datasource respondsToSelector:@selector(numberOfHeadRowsInReportView:)] ? [_datasource numberOfHeadRowsInReportView:self] : 1; 144 | 145 | _datasourceFlags.numberOfHeadRows = [_datasource respondsToSelector:@selector(numberOfHeadRowsInReportView:)]; 146 | _datasourceFlags.heightOfRow = [_datasource respondsToSelector:@selector(reportView:heightOfRow:)]; 147 | _datasourceFlags.widthOfCol = [_datasource respondsToSelector:@selector(reportView:widthOfCol:)]; 148 | 149 | _delegateFlags.didTapLabel = [_delegate respondsToSelector:@selector(reportView:didTapLabel:)]; 150 | _delegateFlags.didLongPressLabel = [_delegate respondsToSelector:@selector(reportView:didLongPressLabel:)]; 151 | _delegateFlags.indexesSorted = [_delegate respondsToSelector:@selector(reportView:indexesSortedByCol:order:)]; 152 | _sortedCol = 0; 153 | _sortedOrder = LMROrderedNature; 154 | 155 | [self p_loadTopLeft]; 156 | [self p_loadTopRight]; 157 | [self p_loadBottomLeft]; 158 | [self p_loadBottomRight]; 159 | [self setNeedsLayout]; 160 | } 161 | 162 | - (void)p_loadTopLeft { 163 | CGRect rect = CGRectZero; 164 | rect.size.width = _datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:0] : _style.widthOfFirstCol; 165 | 166 | NSInteger row = 0; 167 | while (row < _numberOfHeadRows) { 168 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:0 inRow:row]; 169 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 170 | if (!grid) { 171 | row ++; 172 | continue; 173 | } 174 | grid.indexPath = indexPath; 175 | LMRLabel *label; 176 | [self p_getLabel:&label forGrid:grid inPart:LMRPartTopLeft]; 177 | label.indexPath = indexPath; 178 | 179 | if (_datasourceFlags.heightOfRow) { 180 | CGFloat height = 0; 181 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 182 | height += [_datasource reportView:self heightOfRow:i]; 183 | } 184 | height += _style.spacing * (grid.rowspan - 1); 185 | rect.size.height = height; 186 | } 187 | else { 188 | rect.size.height = (_style.heightOfHeaderRow + _style.spacing) * grid.rowspan - _style.spacing; 189 | } 190 | label.frame = rect; 191 | if (_style.isAutoFitHeight && label.rowspan == 1) { 192 | [label heightToFit]; 193 | } 194 | rect = label.frame; 195 | 196 | [_topLeftView addSubview:label]; 197 | row += grid.rowspan; 198 | rect.origin.y = CGRectGetMaxY(rect) + _style.spacing; 199 | } 200 | rect.size.height = rect.origin.y - _style.spacing; 201 | rect.origin = CGPointMake(_style.borderInsets.left, _style.borderInsets.top); 202 | _topLeftView.frame = rect; 203 | } 204 | 205 | - (void)p_loadTopRight { 206 | CGRect rect = CGRectZero; 207 | NSInteger row = 0; 208 | while (row < _numberOfHeadRows) { 209 | rect.origin = CGPointZero; 210 | rect.origin.y = 0; 211 | for (NSInteger i = 0; i < row; i ++) { 212 | if (_datasourceFlags.heightOfRow) { 213 | rect.origin.y += [_datasource reportView:self heightOfRow:i] + _style.spacing; 214 | } 215 | else { 216 | rect.origin.y += _style.heightOfHeaderRow + _style.spacing; 217 | } 218 | } 219 | NSInteger col = 1; 220 | while (col < _numberOfCols) { 221 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:col inRow:row]; 222 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 223 | if (!grid) { 224 | rect.origin.x += (_datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:col] : _style.widthOfCol) + _style.spacing; 225 | col ++; 226 | continue; 227 | } 228 | grid.indexPath = indexPath; 229 | LMRLabel *label; 230 | [self p_getLabel:&label forGrid:grid inPart:LMRPartTopRight]; 231 | label.indexPath = indexPath; 232 | 233 | if (_datasourceFlags.widthOfCol) { 234 | CGFloat width = 0; 235 | for (NSInteger i = col; i < col + grid.colspan; i ++) { 236 | width += [_datasource reportView:self widthOfCol:i]; 237 | } 238 | width += _style.spacing * (grid.colspan - 1); 239 | rect.size.width = width; 240 | } 241 | else { 242 | rect.size.width = (_style.widthOfCol + _style.spacing) * grid.colspan - _style.spacing; 243 | } 244 | if (_datasourceFlags.heightOfRow) { 245 | CGFloat height = 0; 246 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 247 | height += [_datasource reportView:self heightOfRow:i]; 248 | } 249 | height += _style.spacing * (grid.rowspan - 1); 250 | rect.size.height = height; 251 | } 252 | else { 253 | rect.size.height = (_style.heightOfHeaderRow + _style.spacing) * grid.rowspan - _style.spacing; 254 | } 255 | label.frame = rect; 256 | if (_style.isAutoFitHeight && label.rowspan == 1) { 257 | [label heightToFit]; 258 | } 259 | rect = label.frame; 260 | 261 | [_topRightView addSubview:label]; 262 | col += grid.colspan; 263 | rect.origin.x = CGRectGetMaxX(rect) + _style.spacing; 264 | } 265 | row ++; 266 | } 267 | rect.size.width = rect.origin.x - _style.spacing; 268 | rect.size.height = CGRectGetMaxY(rect); 269 | rect.origin = CGPointZero; 270 | _topRightView.frame = rect; 271 | _topRightScroll.contentSize = rect.size; 272 | } 273 | 274 | - (void)p_loadBottomLeft { 275 | CGRect rect = CGRectZero; 276 | rect.size.width = _datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:0] : _style.widthOfFirstCol; 277 | 278 | NSInteger row = _numberOfHeadRows; 279 | while (row < _numberOfRows) { 280 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:0 inRow:row]; 281 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 282 | if (!grid) { 283 | rect.origin.x += _style.widthOfCol + _style.spacing; 284 | row ++; 285 | continue; 286 | } 287 | grid.indexPath = indexPath; 288 | LMRLabel *label; 289 | [self p_getLabel:&label forGrid:grid inPart:LMRPartBottomLeft]; 290 | label.indexPath = indexPath; 291 | 292 | if (_datasourceFlags.heightOfRow) { 293 | CGFloat height = 0; 294 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 295 | height += [_datasource reportView:self heightOfRow:i]; 296 | } 297 | height += _style.spacing * (grid.rowspan - 1); 298 | rect.size.height = height; 299 | } 300 | else { 301 | rect.size.height = (_style.heightOfRow + _style.spacing) * grid.rowspan - _style.spacing; 302 | } 303 | label.frame = rect; 304 | if (_style.isAutoFitHeight && label.rowspan == 1) { 305 | [label heightToFit]; 306 | } 307 | rect = label.frame; 308 | 309 | [_bottomLeftView addSubview:label]; 310 | row += grid.rowspan; 311 | rect.origin.y = CGRectGetMaxY(rect) + _style.spacing; 312 | } 313 | rect.size.height = rect.origin.y - _style.spacing; 314 | rect.origin = CGPointZero; 315 | _bottomLeftView.frame = rect; 316 | _bottomLeftScroll.contentSize = rect.size; 317 | } 318 | 319 | - (void)p_loadBottomRight { 320 | CGRect rect = CGRectZero; 321 | NSInteger row = _numberOfHeadRows; 322 | while (row < _numberOfRows) { 323 | rect.origin = CGPointZero; 324 | rect.origin.y = 0; 325 | for (NSInteger i = _numberOfHeadRows; i < row; i ++) { 326 | if (_datasourceFlags.heightOfRow) { 327 | rect.origin.y += [_datasource reportView:self heightOfRow:i] + _style.spacing; 328 | } 329 | else { 330 | rect.origin.y += _style.heightOfRow + _style.spacing; 331 | } 332 | } 333 | NSInteger col = 1; 334 | while (col < _numberOfCols) { 335 | NSIndexPath *indexPath = [NSIndexPath indexPathForCol:col inRow:row]; 336 | LMRGrid *grid = [_datasource reportView:self gridAtIndexPath:indexPath]; 337 | if (!grid) { 338 | rect.origin.x += (_datasourceFlags.widthOfCol ? [_datasource reportView:self widthOfCol:col] : _style.widthOfCol) + _style.spacing; 339 | col ++; 340 | continue; 341 | } 342 | grid.indexPath = indexPath; 343 | LMRLabel *label; 344 | [self p_getLabel:&label forGrid:grid inPart:LMRPartBottomRight]; 345 | label.indexPath = indexPath; 346 | 347 | if (_datasourceFlags.widthOfCol) { 348 | CGFloat width = 0; 349 | for (NSInteger i = col; i < col + grid.colspan; i ++) { 350 | width += [_datasource reportView:self widthOfCol:i]; 351 | } 352 | width += _style.spacing * (grid.colspan - 1); 353 | rect.size.width = width; 354 | } 355 | else { 356 | rect.size.width = (_style.widthOfCol + _style.spacing) * grid.colspan - _style.spacing; 357 | } 358 | if (_datasourceFlags.heightOfRow) { 359 | CGFloat height = 0; 360 | for (NSInteger i = row; i < row + grid.rowspan; i ++) { 361 | height += [_datasource reportView:self heightOfRow:i]; 362 | } 363 | height += _style.spacing * (grid.rowspan - 1); 364 | rect.size.height = height; 365 | } 366 | else { 367 | rect.size.height = (_style.heightOfRow + _style.spacing) * grid.rowspan - _style.spacing; 368 | } 369 | label.frame = rect; 370 | if (_style.isAutoFitHeight && label.rowspan == 1) { 371 | [label heightToFit]; 372 | } 373 | rect = label.frame; 374 | 375 | [_bottomRightView addSubview:label]; 376 | col += grid.colspan; 377 | rect.origin.x = CGRectGetMaxX(rect) + _style.spacing; 378 | } 379 | row ++; 380 | } 381 | rect.size.width = rect.origin.x - _style.spacing; 382 | rect.size.height = CGRectGetMaxY(rect); 383 | rect.origin = CGPointZero; 384 | _bottomRightView.frame = rect; 385 | _bottomRightScroll.contentSize = rect.size; 386 | } 387 | 388 | - (void)p_getLabel:(LMRLabel **)label forGrid:(LMRGrid *)grid inPart:(LMRPart)part { 389 | LMRLabel *theLabel = [LMRLabel new]; 390 | switch (part) { 391 | case LMRPartTopLeft: 392 | case LMRPartTopRight: 393 | { 394 | theLabel.font = grid.font ? : _style.fontOfHeader; 395 | theLabel.textColor = grid.textColor ? : _style.textColorOfHeader; 396 | theLabel.backgroundColor = grid.backgroundColor ? : _style.backgroundColorOfHeader; 397 | theLabel.textAlignment = grid.isTextAlignmentOriginal ? _style.textAlignmentOfHeader : grid.textAlignment; 398 | break; 399 | } 400 | case LMRPartBottomLeft: 401 | case LMRPartBottomRight: 402 | { 403 | theLabel.font = grid.font ? : _style.font; 404 | UIColor *textColor = grid.textColor; 405 | if (!textColor) { 406 | textColor = _style.textColor; 407 | if (_style.stripeTextColor && (grid.indexPath.row - _numberOfHeadRows) % 2 == 1) { 408 | textColor = _style.stripeTextColor; 409 | } 410 | } 411 | theLabel.textColor = textColor; 412 | UIColor *backgroundColor = grid.backgroundColor; 413 | if (!backgroundColor) { 414 | backgroundColor = _style.backgroundColor; 415 | if (_style.stripeBackgroundColor && (grid.indexPath.row - _numberOfHeadRows) % 2 == 1) { 416 | backgroundColor = _style.stripeBackgroundColor; 417 | } 418 | } 419 | theLabel.backgroundColor = backgroundColor; 420 | theLabel.textAlignment = grid.isTextAlignmentOriginal ? _style.textAlignment : grid.textAlignment; 421 | break; 422 | } 423 | } 424 | theLabel.rowspan = grid.rowspan; 425 | theLabel.colspan = grid.colspan; 426 | theLabel.image = grid.image; 427 | theLabel.text = grid.text; 428 | theLabel.lineBreakMode = NSLineBreakByCharWrapping; 429 | theLabel.numberOfLines = 0; 430 | theLabel.underline = grid.underline; 431 | *label = theLabel; 432 | } 433 | 434 | #pragma mark - layout 435 | 436 | - (void)layoutSubviews { 437 | if (!_datasource) { 438 | return; 439 | } 440 | 441 | if (_style.isAutoFitHeight) { 442 | [self p_fitHeight]; 443 | } 444 | 445 | CGFloat frameWidth = CGRectGetWidth(self.frame); 446 | CGFloat frameHeight = CGRectGetHeight(self.frame); 447 | CGFloat contentWidth = CGRectGetMaxX(_topLeftView.frame) + _style.spacing + _bottomRightScroll.contentSize.width + _style.borderInsets.right; 448 | CGFloat contentHeight = CGRectGetMaxY(_topLeftView.frame) + _style.spacing + _bottomRightScroll.contentSize.height + _style.borderInsets.bottom; 449 | if (frameWidth == contentWidth && frameHeight == contentHeight) { 450 | return; 451 | } 452 | 453 | //frameWidth设为0的时候自动拉伸到内容实际宽度 454 | if (frameWidth == 0) { 455 | frameWidth = contentWidth; 456 | } 457 | //frameHeight设为0的时候自动拉伸到内容实际高度 458 | if (frameHeight == 0) { 459 | frameHeight = contentHeight; 460 | } 461 | 462 | if (contentWidth < frameWidth && _style.isTrimWidthSpace) { 463 | frameWidth = contentWidth; 464 | } 465 | if (contentHeight < frameHeight && _style.isTrimHeightSpace) { 466 | frameHeight = contentHeight; 467 | } 468 | 469 | CGRect rect = self.frame; 470 | rect.size.width = frameWidth; 471 | rect.size.height = frameHeight; 472 | self.frame = rect; 473 | 474 | rect = _topLeftView.frame; 475 | rect.origin.x = CGRectGetMaxX(rect) + _style.spacing; 476 | rect.size.width = CGRectGetWidth(self.frame) - _style.borderInsets.right - rect.origin.x; 477 | _topRightScroll.frame = rect; 478 | 479 | rect = _topLeftView.frame; 480 | rect.origin.y = CGRectGetMaxY(rect) + _style.spacing; 481 | rect.size.height = CGRectGetHeight(self.frame) - _style.borderInsets.bottom - rect.origin.y; 482 | _bottomLeftScroll.frame = rect; 483 | 484 | rect.origin.x = CGRectGetMinX(_topRightScroll.frame); 485 | rect.origin.y = CGRectGetMinY(_bottomLeftScroll.frame); 486 | rect.size.width = CGRectGetWidth(_topRightScroll.frame); 487 | rect.size.height = CGRectGetHeight(_bottomLeftScroll.frame); 488 | _bottomRightScroll.frame = rect; 489 | } 490 | 491 | - (NSArray *)p_suitableHeights { 492 | NSMutableArray *heights = [NSMutableArray arrayWithCapacity:_numberOfRows]; 493 | for (int i = 0; i <_numberOfRows; i ++) { 494 | [heights addObject:@(0)]; 495 | } 496 | for (UIView *contentView in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) { 497 | for (LMRLabel *label in contentView.subviews) { 498 | if (label.rowspan > 1) { 499 | continue; 500 | } 501 | CGFloat maxHeight = [heights[label.indexPath.row] floatValue]; 502 | CGFloat currentHeight = CGRectGetHeight(label.frame); 503 | if (currentHeight > maxHeight) { 504 | heights[label.indexPath.row] = @(currentHeight); 505 | } 506 | } 507 | } 508 | return [heights copy]; 509 | } 510 | 511 | - (void)p_fitHeight { 512 | NSArray *heights = [self p_suitableHeights]; 513 | for (UIView *contentView in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) { 514 | for (LMRLabel *label in contentView.subviews) { 515 | CGRect rect = label.frame; 516 | rect.origin.y = 0; 517 | 518 | NSInteger i = (contentView == _topLeftView || contentView == _topRightView) ? 0 : _numberOfHeadRows; 519 | while (i < label.indexPath.row) { 520 | rect.origin.y += [heights[i] floatValue] + _style.spacing; 521 | i ++; 522 | } 523 | rect.size.height = 0; 524 | for (int i = 0; i < label.rowspan; i ++) { 525 | rect.size.height += [heights[label.indexPath.row + i] floatValue]; 526 | } 527 | label.frame = rect; 528 | } 529 | } 530 | 531 | void (^frameSet)(UIView *) = ^(UIView *view){ 532 | CGRect rect = view.frame; 533 | rect.size.height = CGRectGetMaxY([[view.subviews lastObject] frame]); 534 | view.frame = rect; 535 | }; 536 | frameSet(_topLeftView); 537 | frameSet(_topRightView); 538 | frameSet(_bottomLeftView); 539 | frameSet(_bottomRightView); 540 | 541 | CGRect rect; 542 | _topRightScroll.contentSize = _topRightView.frame.size; 543 | rect = _topRightScroll.frame; 544 | rect.size.height = CGRectGetHeight(_topLeftView.frame); 545 | _topRightScroll.frame = rect; 546 | 547 | _bottomLeftScroll.contentSize = _bottomLeftView.frame.size; 548 | rect = _bottomLeftScroll.frame; 549 | rect.origin.y = CGRectGetMaxY(_topLeftView.frame) + _style.spacing; 550 | rect.size.height = CGRectGetHeight(_bottomLeftView.frame); 551 | _bottomLeftScroll.frame = rect; 552 | 553 | _bottomRightScroll.contentSize = _bottomRightView.frame.size; 554 | rect = _bottomRightScroll.frame; 555 | rect.origin.x = CGRectGetMinX(_topRightScroll.frame); 556 | rect.origin.y = CGRectGetMinY(_bottomLeftScroll.frame); 557 | rect.size.width = CGRectGetWidth(_topRightScroll.frame); 558 | rect.size.height = CGRectGetHeight(_bottomLeftScroll.frame); 559 | _bottomRightScroll.frame = rect; 560 | } 561 | 562 | #pragma mark - ScrollView delegate 563 | 564 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 565 | if (scrollView == _bottomRightScroll) { 566 | _topRightScroll.contentOffset = CGPointMake(_bottomRightScroll.contentOffset.x, 0); 567 | _bottomLeftScroll.contentOffset = CGPointMake(0, _bottomRightScroll.contentOffset.y); 568 | } 569 | } 570 | 571 | #pragma mark - handle gesture recognizer 572 | 573 | - (LMRLabel *)p_touchedLabelWithGesture:(UIGestureRecognizer *)gestureRecognizer { 574 | CGPoint point = [gestureRecognizer locationInView:self]; 575 | UIView *inView; 576 | for (UIView *view in @[_topLeftView, _topRightView, _bottomLeftView, _bottomRightView]) { 577 | CGRect rect = [view convertRect:view.bounds toView:self]; 578 | if (CGRectContainsPoint(rect, point)) { 579 | inView = view; 580 | break; 581 | } 582 | } 583 | if (!inView) { 584 | return nil; 585 | } 586 | LMRLabel *label; 587 | for (UIView *subview in inView.subviews) { 588 | if ([subview isKindOfClass:[LMRLabel class]]) { 589 | CGRect rect = [subview convertRect:subview.bounds toView:self]; 590 | if (CGRectContainsPoint(rect, point)) { 591 | label = (LMRLabel *)subview; 592 | break; 593 | } 594 | } 595 | } 596 | return label; 597 | } 598 | 599 | - (void)p_handleLongPressGR:(UILongPressGestureRecognizer *)longPressGR { 600 | if (longPressGR.state != UIGestureRecognizerStateBegan) { 601 | return; 602 | } 603 | if (_delegateFlags.didLongPressLabel) { 604 | LMRLabel *label = [self p_touchedLabelWithGesture:longPressGR]; 605 | if (label) { 606 | [_delegate reportView:self didLongPressLabel:[self p_touchedLabelWithGesture:longPressGR]]; 607 | } 608 | } 609 | } 610 | 611 | - (void)p_handleTapGR:(UILongPressGestureRecognizer *)tapGR { 612 | if (_delegateFlags.didTapLabel) { 613 | LMRLabel *label = [self p_touchedLabelWithGesture:tapGR]; 614 | if (label) { 615 | [_delegate reportView:self didTapLabel:label]; 616 | } 617 | } 618 | } 619 | 620 | #pragma mark - 621 | 622 | - (NSInteger)numberOfRows { 623 | return _numberOfRows; 624 | } 625 | 626 | - (NSInteger)numberOfCols { 627 | return _numberOfCols; 628 | } 629 | 630 | - (NSInteger)numberOfHeadRows { 631 | return _numberOfHeadRows; 632 | } 633 | 634 | - (LMRGrid *)gridAtIndexPath:(NSIndexPath *)indexPath { 635 | return [_datasource reportView:self gridAtIndexPath:indexPath]; 636 | } 637 | 638 | - (CGFloat)heightOfRow:(NSInteger)row { 639 | if (_datasourceFlags.heightOfRow) { 640 | return [_datasource reportView:self heightOfRow:row]; 641 | } 642 | else { 643 | return row < _numberOfHeadRows ? _style.heightOfHeaderRow : _style.heightOfRow; 644 | } 645 | } 646 | 647 | - (CGFloat)widthOfCol:(NSInteger)col { 648 | if (_datasourceFlags.widthOfCol) { 649 | return [_datasource reportView:self widthOfCol:col]; 650 | } 651 | else { 652 | return col == 0 ? _style.widthOfFirstCol : _style.widthOfCol; 653 | } 654 | } 655 | 656 | #pragma mark - sort 657 | 658 | - (void)sortByCol:(NSInteger)col order:(LMROrder)order { 659 | if (!_delegateFlags.indexesSorted) { 660 | return; 661 | } 662 | 663 | if (_sortedOrder != LMROrderedNature) { 664 | LMRLabel *label; 665 | if (_sortedCol == 0) { 666 | label = _topLeftView.subviews[0]; 667 | } 668 | else { 669 | label = _topRightView.subviews[_sortedCol - 1]; 670 | } 671 | label.text = [label.text substringToIndex:label.text.length - 1]; 672 | } 673 | _sortedCol = col; 674 | _sortedOrder = order; 675 | LMRLabel *label; 676 | if (col == 0) { 677 | label = _topLeftView.subviews[0]; 678 | } 679 | else { 680 | label = _topRightView.subviews[_sortedCol - 1]; 681 | } 682 | NSOrderedSet *indexes; 683 | switch (order) { 684 | case LMROrderedAscending: 685 | { 686 | label.text = [label.text stringByAppendingString:@"↑"]; 687 | indexes = [self.delegate reportView:self indexesSortedByCol:col order:order]; 688 | break; 689 | } 690 | case LMROrderedNature: 691 | { 692 | NSMutableOrderedSet *tempIndexes = [[NSMutableOrderedSet alloc] initWithCapacity:_numberOfRows - 1]; 693 | NSInteger i = 1; 694 | while (i < _numberOfRows) { 695 | [tempIndexes addObject:@(i)]; 696 | i ++; 697 | } 698 | indexes = [tempIndexes copy]; 699 | break; 700 | } 701 | case LMROrderedDescending: 702 | { 703 | label.text = [label.text stringByAppendingString:@"↓"]; 704 | indexes = [self.delegate reportView:self indexesSortedByCol:col order:order]; 705 | break; 706 | } 707 | } 708 | NSMutableArray *yOrigins = [NSMutableArray arrayWithCapacity:_numberOfRows - 1]; 709 | CGFloat y = 0; 710 | for (NSNumber *index in indexes) { 711 | [yOrigins addObject:@(y)]; 712 | LMRLabel *label = _bottomLeftView.subviews[index.integerValue - 1]; 713 | CGRect rect = label.frame; 714 | rect.origin.y = y; 715 | label.frame = rect; 716 | y += CGRectGetHeight(rect) + _style.spacing; 717 | } 718 | for (LMRLabel *label in _bottomRightView.subviews) { 719 | CGRect rect = label.frame; 720 | __block NSInteger index; 721 | [indexes enumerateObjectsUsingBlock:^(NSNumber *obj, NSUInteger idx, BOOL *stop) { 722 | if ([obj isEqualToNumber:@(label.indexPath.row)]) { 723 | index = idx; 724 | *stop = YES; 725 | } 726 | }]; 727 | rect.origin.y = [yOrigins[index] floatValue]; 728 | label.frame = rect; 729 | } 730 | } 731 | 732 | @end -------------------------------------------------------------------------------- /Demo/LMReport/NSIndexPath+LMReport.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+LMReport.h 3 | // LMReportViewDemo 4 | // 5 | // Created by Chenly on 15/3/17. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSIndexPath (LMReport) 12 | 13 | @property (nonatomic, readonly) NSInteger row; 14 | @property (nonatomic, readonly) NSInteger col; 15 | + (NSIndexPath *)indexPathForCol:(NSInteger)col inRow:(NSInteger)row; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Demo/LMReport/NSIndexPath+LMReport.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexPath+LMR.m 3 | // LMReportViewDemo 4 | // 5 | // Created by Chenly on 15/3/17. 6 | // Copyright (c) 2015年 Chenly. All rights reserved. 7 | // 8 | 9 | #import "NSIndexPath+LMReport.h" 10 | 11 | @implementation NSIndexPath (LMReport) 12 | 13 | + (NSIndexPath *)indexPathForCol:(NSInteger)col inRow:(NSInteger)row { 14 | return [NSIndexPath indexPathForRow:row inSection:col]; 15 | } 16 | 17 | - (NSInteger)col { 18 | return self.section; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Demo/LMReportDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 745A5CF81CC3D6190091B4AD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5CF71CC3D6190091B4AD /* main.m */; }; 11 | 745A5CFB1CC3D6190091B4AD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5CFA1CC3D6190091B4AD /* AppDelegate.m */; }; 12 | 745A5CFE1CC3D6190091B4AD /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5CFD1CC3D6190091B4AD /* ViewController.m */; }; 13 | 745A5D011CC3D6190091B4AD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 745A5CFF1CC3D6190091B4AD /* Main.storyboard */; }; 14 | 745A5D031CC3D6190091B4AD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 745A5D021CC3D6190091B4AD /* Assets.xcassets */; }; 15 | 745A5D061CC3D6190091B4AD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 745A5D041CC3D6190091B4AD /* LaunchScreen.storyboard */; }; 16 | 745A5D191CC3D6D40091B4AD /* LMRGrid.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5D101CC3D6D40091B4AD /* LMRGrid.m */; }; 17 | 745A5D1A1CC3D6D40091B4AD /* LMRLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5D121CC3D6D40091B4AD /* LMRLabel.m */; }; 18 | 745A5D1B1CC3D6D40091B4AD /* LMRStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5D141CC3D6D40091B4AD /* LMRStyle.m */; }; 19 | 745A5D1C1CC3D6D40091B4AD /* LMReportView.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5D161CC3D6D40091B4AD /* LMReportView.m */; }; 20 | 745A5D1D1CC3D6D40091B4AD /* NSIndexPath+LMReport.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5D181CC3D6D40091B4AD /* NSIndexPath+LMReport.m */; }; 21 | 745A5D201CC487930091B4AD /* SimpleViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 745A5D1F1CC487930091B4AD /* SimpleViewController.m */; }; 22 | 747092351CC48E3C0062FA08 /* StylesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 747092341CC48E3C0062FA08 /* StylesViewController.m */; }; 23 | 747092381CC48FAF0062FA08 /* LMRStyle+Demo.m in Sources */ = {isa = PBXBuildFile; fileRef = 747092371CC48FAF0062FA08 /* LMRStyle+Demo.m */; }; 24 | 747092421CC499080062FA08 /* ComplexViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 747092411CC499080062FA08 /* ComplexViewController.m */; }; 25 | 747092471CC4A08F0062FA08 /* DetailingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 747092461CC4A08F0062FA08 /* DetailingViewController.m */; }; 26 | 7470924B1CC4A5C90062FA08 /* HandleEventViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7470924A1CC4A5C90062FA08 /* HandleEventViewController.m */; }; 27 | 747092521CC4B3FD0062FA08 /* FitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 747092511CC4B3FD0062FA08 /* FitViewController.m */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 745A5CF31CC3D6190091B4AD /* LMReportDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LMReportDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 745A5CF71CC3D6190091B4AD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 33 | 745A5CF91CC3D6190091B4AD /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 34 | 745A5CFA1CC3D6190091B4AD /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 35 | 745A5CFC1CC3D6190091B4AD /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 36 | 745A5CFD1CC3D6190091B4AD /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 37 | 745A5D001CC3D6190091B4AD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | 745A5D021CC3D6190091B4AD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | 745A5D051CC3D6190091B4AD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | 745A5D071CC3D6190091B4AD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 745A5D0E1CC3D6D40091B4AD /* LMReport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMReport.h; sourceTree = ""; }; 42 | 745A5D0F1CC3D6D40091B4AD /* LMRGrid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMRGrid.h; sourceTree = ""; }; 43 | 745A5D101CC3D6D40091B4AD /* LMRGrid.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMRGrid.m; sourceTree = ""; }; 44 | 745A5D111CC3D6D40091B4AD /* LMRLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMRLabel.h; sourceTree = ""; }; 45 | 745A5D121CC3D6D40091B4AD /* LMRLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMRLabel.m; sourceTree = ""; }; 46 | 745A5D131CC3D6D40091B4AD /* LMRStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMRStyle.h; sourceTree = ""; }; 47 | 745A5D141CC3D6D40091B4AD /* LMRStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMRStyle.m; sourceTree = ""; }; 48 | 745A5D151CC3D6D40091B4AD /* LMReportView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LMReportView.h; sourceTree = ""; }; 49 | 745A5D161CC3D6D40091B4AD /* LMReportView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LMReportView.m; sourceTree = ""; }; 50 | 745A5D171CC3D6D40091B4AD /* NSIndexPath+LMReport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexPath+LMReport.h"; sourceTree = ""; }; 51 | 745A5D181CC3D6D40091B4AD /* NSIndexPath+LMReport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexPath+LMReport.m"; sourceTree = ""; }; 52 | 745A5D1E1CC487930091B4AD /* SimpleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SimpleViewController.h; sourceTree = ""; }; 53 | 745A5D1F1CC487930091B4AD /* SimpleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SimpleViewController.m; sourceTree = ""; }; 54 | 747092331CC48E3C0062FA08 /* StylesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StylesViewController.h; sourceTree = ""; }; 55 | 747092341CC48E3C0062FA08 /* StylesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StylesViewController.m; sourceTree = ""; }; 56 | 747092361CC48FAF0062FA08 /* LMRStyle+Demo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LMRStyle+Demo.h"; sourceTree = ""; }; 57 | 747092371CC48FAF0062FA08 /* LMRStyle+Demo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "LMRStyle+Demo.m"; sourceTree = ""; }; 58 | 747092401CC499080062FA08 /* ComplexViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComplexViewController.h; sourceTree = ""; }; 59 | 747092411CC499080062FA08 /* ComplexViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ComplexViewController.m; sourceTree = ""; }; 60 | 747092451CC4A08F0062FA08 /* DetailingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailingViewController.h; sourceTree = ""; }; 61 | 747092461CC4A08F0062FA08 /* DetailingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailingViewController.m; sourceTree = ""; }; 62 | 747092491CC4A5C90062FA08 /* HandleEventViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HandleEventViewController.h; sourceTree = ""; }; 63 | 7470924A1CC4A5C90062FA08 /* HandleEventViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HandleEventViewController.m; sourceTree = ""; }; 64 | 747092501CC4B3FD0062FA08 /* FitViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FitViewController.h; sourceTree = ""; }; 65 | 747092511CC4B3FD0062FA08 /* FitViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FitViewController.m; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | 745A5CF01CC3D6190091B4AD /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 745A5CEA1CC3D6190091B4AD = { 80 | isa = PBXGroup; 81 | children = ( 82 | 745A5D0D1CC3D6D40091B4AD /* LMReport */, 83 | 745A5CF51CC3D6190091B4AD /* LMReportDemo */, 84 | 745A5CF41CC3D6190091B4AD /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 745A5CF41CC3D6190091B4AD /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 745A5CF31CC3D6190091B4AD /* LMReportDemo.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 745A5CF51CC3D6190091B4AD /* LMReportDemo */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 745A5CFC1CC3D6190091B4AD /* ViewController.h */, 100 | 745A5CFD1CC3D6190091B4AD /* ViewController.m */, 101 | 7470923A1CC48FE70062FA08 /* Simple */, 102 | 745A5D1E1CC487930091B4AD /* SimpleViewController.h */, 103 | 745A5D1F1CC487930091B4AD /* SimpleViewController.m */, 104 | 7470923B1CC48FF40062FA08 /* Styles */, 105 | 7470923F1CC498D40062FA08 /* Complex */, 106 | 747092481CC4A0940062FA08 /* Detailing */, 107 | 7470924C1CC4A5D20062FA08 /* HandleEvent */, 108 | 7470924F1CC4B3E80062FA08 /* Fit */, 109 | 745A5CF61CC3D6190091B4AD /* Supporting Files */, 110 | ); 111 | path = LMReportDemo; 112 | sourceTree = ""; 113 | }; 114 | 745A5CF61CC3D6190091B4AD /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 745A5CF91CC3D6190091B4AD /* AppDelegate.h */, 118 | 745A5CFA1CC3D6190091B4AD /* AppDelegate.m */, 119 | 745A5CFF1CC3D6190091B4AD /* Main.storyboard */, 120 | 745A5D021CC3D6190091B4AD /* Assets.xcassets */, 121 | 745A5D041CC3D6190091B4AD /* LaunchScreen.storyboard */, 122 | 745A5D071CC3D6190091B4AD /* Info.plist */, 123 | 745A5CF71CC3D6190091B4AD /* main.m */, 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | 745A5D0D1CC3D6D40091B4AD /* LMReport */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 745A5D0E1CC3D6D40091B4AD /* LMReport.h */, 132 | 745A5D0F1CC3D6D40091B4AD /* LMRGrid.h */, 133 | 745A5D101CC3D6D40091B4AD /* LMRGrid.m */, 134 | 745A5D111CC3D6D40091B4AD /* LMRLabel.h */, 135 | 745A5D121CC3D6D40091B4AD /* LMRLabel.m */, 136 | 745A5D131CC3D6D40091B4AD /* LMRStyle.h */, 137 | 745A5D141CC3D6D40091B4AD /* LMRStyle.m */, 138 | 745A5D151CC3D6D40091B4AD /* LMReportView.h */, 139 | 745A5D161CC3D6D40091B4AD /* LMReportView.m */, 140 | 745A5D171CC3D6D40091B4AD /* NSIndexPath+LMReport.h */, 141 | 745A5D181CC3D6D40091B4AD /* NSIndexPath+LMReport.m */, 142 | ); 143 | path = LMReport; 144 | sourceTree = ""; 145 | }; 146 | 7470923A1CC48FE70062FA08 /* Simple */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | ); 150 | name = Simple; 151 | sourceTree = ""; 152 | }; 153 | 7470923B1CC48FF40062FA08 /* Styles */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | 747092361CC48FAF0062FA08 /* LMRStyle+Demo.h */, 157 | 747092371CC48FAF0062FA08 /* LMRStyle+Demo.m */, 158 | 747092331CC48E3C0062FA08 /* StylesViewController.h */, 159 | 747092341CC48E3C0062FA08 /* StylesViewController.m */, 160 | ); 161 | name = Styles; 162 | sourceTree = ""; 163 | }; 164 | 7470923F1CC498D40062FA08 /* Complex */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 747092401CC499080062FA08 /* ComplexViewController.h */, 168 | 747092411CC499080062FA08 /* ComplexViewController.m */, 169 | ); 170 | name = Complex; 171 | sourceTree = ""; 172 | }; 173 | 747092481CC4A0940062FA08 /* Detailing */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 747092451CC4A08F0062FA08 /* DetailingViewController.h */, 177 | 747092461CC4A08F0062FA08 /* DetailingViewController.m */, 178 | ); 179 | name = Detailing; 180 | sourceTree = ""; 181 | }; 182 | 7470924C1CC4A5D20062FA08 /* HandleEvent */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 747092491CC4A5C90062FA08 /* HandleEventViewController.h */, 186 | 7470924A1CC4A5C90062FA08 /* HandleEventViewController.m */, 187 | ); 188 | name = HandleEvent; 189 | sourceTree = ""; 190 | }; 191 | 7470924F1CC4B3E80062FA08 /* Fit */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 747092501CC4B3FD0062FA08 /* FitViewController.h */, 195 | 747092511CC4B3FD0062FA08 /* FitViewController.m */, 196 | ); 197 | name = Fit; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXGroup section */ 201 | 202 | /* Begin PBXNativeTarget section */ 203 | 745A5CF21CC3D6190091B4AD /* LMReportDemo */ = { 204 | isa = PBXNativeTarget; 205 | buildConfigurationList = 745A5D0A1CC3D6190091B4AD /* Build configuration list for PBXNativeTarget "LMReportDemo" */; 206 | buildPhases = ( 207 | 745A5CEF1CC3D6190091B4AD /* Sources */, 208 | 745A5CF01CC3D6190091B4AD /* Frameworks */, 209 | 745A5CF11CC3D6190091B4AD /* Resources */, 210 | ); 211 | buildRules = ( 212 | ); 213 | dependencies = ( 214 | ); 215 | name = LMReportDemo; 216 | productName = LMReportDemo; 217 | productReference = 745A5CF31CC3D6190091B4AD /* LMReportDemo.app */; 218 | productType = "com.apple.product-type.application"; 219 | }; 220 | /* End PBXNativeTarget section */ 221 | 222 | /* Begin PBXProject section */ 223 | 745A5CEB1CC3D6190091B4AD /* Project object */ = { 224 | isa = PBXProject; 225 | attributes = { 226 | LastUpgradeCheck = 0730; 227 | ORGANIZATIONNAME = "Little Meaning"; 228 | TargetAttributes = { 229 | 745A5CF21CC3D6190091B4AD = { 230 | CreatedOnToolsVersion = 7.3; 231 | DevelopmentTeam = 28G5Y8M7P8; 232 | }; 233 | }; 234 | }; 235 | buildConfigurationList = 745A5CEE1CC3D6190091B4AD /* Build configuration list for PBXProject "LMReportDemo" */; 236 | compatibilityVersion = "Xcode 3.2"; 237 | developmentRegion = English; 238 | hasScannedForEncodings = 0; 239 | knownRegions = ( 240 | en, 241 | Base, 242 | ); 243 | mainGroup = 745A5CEA1CC3D6190091B4AD; 244 | productRefGroup = 745A5CF41CC3D6190091B4AD /* Products */; 245 | projectDirPath = ""; 246 | projectRoot = ""; 247 | targets = ( 248 | 745A5CF21CC3D6190091B4AD /* LMReportDemo */, 249 | ); 250 | }; 251 | /* End PBXProject section */ 252 | 253 | /* Begin PBXResourcesBuildPhase section */ 254 | 745A5CF11CC3D6190091B4AD /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | 745A5D061CC3D6190091B4AD /* LaunchScreen.storyboard in Resources */, 259 | 745A5D031CC3D6190091B4AD /* Assets.xcassets in Resources */, 260 | 745A5D011CC3D6190091B4AD /* Main.storyboard in Resources */, 261 | ); 262 | runOnlyForDeploymentPostprocessing = 0; 263 | }; 264 | /* End PBXResourcesBuildPhase section */ 265 | 266 | /* Begin PBXSourcesBuildPhase section */ 267 | 745A5CEF1CC3D6190091B4AD /* Sources */ = { 268 | isa = PBXSourcesBuildPhase; 269 | buildActionMask = 2147483647; 270 | files = ( 271 | 745A5CFE1CC3D6190091B4AD /* ViewController.m in Sources */, 272 | 747092521CC4B3FD0062FA08 /* FitViewController.m in Sources */, 273 | 747092471CC4A08F0062FA08 /* DetailingViewController.m in Sources */, 274 | 747092421CC499080062FA08 /* ComplexViewController.m in Sources */, 275 | 745A5D201CC487930091B4AD /* SimpleViewController.m in Sources */, 276 | 745A5D191CC3D6D40091B4AD /* LMRGrid.m in Sources */, 277 | 745A5CFB1CC3D6190091B4AD /* AppDelegate.m in Sources */, 278 | 745A5D1C1CC3D6D40091B4AD /* LMReportView.m in Sources */, 279 | 745A5D1D1CC3D6D40091B4AD /* NSIndexPath+LMReport.m in Sources */, 280 | 747092381CC48FAF0062FA08 /* LMRStyle+Demo.m in Sources */, 281 | 745A5D1B1CC3D6D40091B4AD /* LMRStyle.m in Sources */, 282 | 745A5D1A1CC3D6D40091B4AD /* LMRLabel.m in Sources */, 283 | 7470924B1CC4A5C90062FA08 /* HandleEventViewController.m in Sources */, 284 | 747092351CC48E3C0062FA08 /* StylesViewController.m in Sources */, 285 | 745A5CF81CC3D6190091B4AD /* main.m in Sources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXSourcesBuildPhase section */ 290 | 291 | /* Begin PBXVariantGroup section */ 292 | 745A5CFF1CC3D6190091B4AD /* Main.storyboard */ = { 293 | isa = PBXVariantGroup; 294 | children = ( 295 | 745A5D001CC3D6190091B4AD /* Base */, 296 | ); 297 | name = Main.storyboard; 298 | sourceTree = ""; 299 | }; 300 | 745A5D041CC3D6190091B4AD /* LaunchScreen.storyboard */ = { 301 | isa = PBXVariantGroup; 302 | children = ( 303 | 745A5D051CC3D6190091B4AD /* Base */, 304 | ); 305 | name = LaunchScreen.storyboard; 306 | sourceTree = ""; 307 | }; 308 | /* End PBXVariantGroup section */ 309 | 310 | /* Begin XCBuildConfiguration section */ 311 | 745A5D081CC3D6190091B4AD /* Debug */ = { 312 | isa = XCBuildConfiguration; 313 | buildSettings = { 314 | ALWAYS_SEARCH_USER_PATHS = NO; 315 | CLANG_ANALYZER_NONNULL = YES; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 317 | CLANG_CXX_LIBRARY = "libc++"; 318 | CLANG_ENABLE_MODULES = YES; 319 | CLANG_ENABLE_OBJC_ARC = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 323 | CLANG_WARN_EMPTY_BODY = YES; 324 | CLANG_WARN_ENUM_CONVERSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 330 | COPY_PHASE_STRIP = NO; 331 | DEBUG_INFORMATION_FORMAT = dwarf; 332 | ENABLE_STRICT_OBJC_MSGSEND = YES; 333 | ENABLE_TESTABILITY = YES; 334 | GCC_C_LANGUAGE_STANDARD = gnu99; 335 | GCC_DYNAMIC_NO_PIC = NO; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_OPTIMIZATION_LEVEL = 0; 338 | GCC_PREPROCESSOR_DEFINITIONS = ( 339 | "DEBUG=1", 340 | "$(inherited)", 341 | ); 342 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 343 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 344 | GCC_WARN_UNDECLARED_SELECTOR = YES; 345 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 346 | GCC_WARN_UNUSED_FUNCTION = YES; 347 | GCC_WARN_UNUSED_VARIABLE = YES; 348 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 349 | MTL_ENABLE_DEBUG_INFO = YES; 350 | ONLY_ACTIVE_ARCH = YES; 351 | SDKROOT = iphoneos; 352 | }; 353 | name = Debug; 354 | }; 355 | 745A5D091CC3D6190091B4AD /* Release */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_ANALYZER_NONNULL = YES; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN_UNREACHABLE_CODE = YES; 372 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 373 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 374 | COPY_PHASE_STRIP = NO; 375 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 376 | ENABLE_NS_ASSERTIONS = NO; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | GCC_C_LANGUAGE_STANDARD = gnu99; 379 | GCC_NO_COMMON_BLOCKS = YES; 380 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 381 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 382 | GCC_WARN_UNDECLARED_SELECTOR = YES; 383 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 384 | GCC_WARN_UNUSED_FUNCTION = YES; 385 | GCC_WARN_UNUSED_VARIABLE = YES; 386 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 387 | MTL_ENABLE_DEBUG_INFO = NO; 388 | SDKROOT = iphoneos; 389 | VALIDATE_PRODUCT = YES; 390 | }; 391 | name = Release; 392 | }; 393 | 745A5D0B1CC3D6190091B4AD /* Debug */ = { 394 | isa = XCBuildConfiguration; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | INFOPLIST_FILE = LMReportDemo/Info.plist; 398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 399 | PRODUCT_BUNDLE_IDENTIFIER = lm.LMReportDemo; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | }; 402 | name = Debug; 403 | }; 404 | 745A5D0C1CC3D6190091B4AD /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 408 | INFOPLIST_FILE = LMReportDemo/Info.plist; 409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 410 | PRODUCT_BUNDLE_IDENTIFIER = lm.LMReportDemo; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | }; 413 | name = Release; 414 | }; 415 | /* End XCBuildConfiguration section */ 416 | 417 | /* Begin XCConfigurationList section */ 418 | 745A5CEE1CC3D6190091B4AD /* Build configuration list for PBXProject "LMReportDemo" */ = { 419 | isa = XCConfigurationList; 420 | buildConfigurations = ( 421 | 745A5D081CC3D6190091B4AD /* Debug */, 422 | 745A5D091CC3D6190091B4AD /* Release */, 423 | ); 424 | defaultConfigurationIsVisible = 0; 425 | defaultConfigurationName = Release; 426 | }; 427 | 745A5D0A1CC3D6190091B4AD /* Build configuration list for PBXNativeTarget "LMReportDemo" */ = { 428 | isa = XCConfigurationList; 429 | buildConfigurations = ( 430 | 745A5D0B1CC3D6190091B4AD /* Debug */, 431 | 745A5D0C1CC3D6190091B4AD /* Release */, 432 | ); 433 | defaultConfigurationIsVisible = 0; 434 | defaultConfigurationName = Release; 435 | }; 436 | /* End XCConfigurationList section */ 437 | }; 438 | rootObject = 745A5CEB1CC3D6190091B4AD /* Project object */; 439 | } 440 | -------------------------------------------------------------------------------- /Demo/LMReportDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/17. 6 | // Copyright © 2016年 Little Meaning. 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 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/17. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/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 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/image-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "image-1.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/image-1.imageset/image-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/Demo/LMReportDemo/Assets.xcassets/image-1.imageset/image-1.jpg -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/image-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "image-2.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/image-2.imageset/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/Demo/LMReportDemo/Assets.xcassets/image-2.imageset/image-2.jpg -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/image-3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "image-0.jpg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Demo/LMReportDemo/Assets.xcassets/image-3.imageset/image-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/Demo/LMReportDemo/Assets.xcassets/image-3.imageset/image-0.jpg -------------------------------------------------------------------------------- /Demo/LMReportDemo/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 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/ComplexViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ComplexViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ComplexViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/ComplexViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ComplexViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "ComplexViewController.h" 10 | #import "LMReport.h" 11 | 12 | @interface ComplexViewController () 13 | 14 | @property (nonatomic, strong) LMReportView *reportView; 15 | @property (nonatomic, strong) NSArray *generalDatas; 16 | 17 | @end 18 | 19 | @implementation ComplexViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | self.title = @"复合表头"; 25 | 26 | self.reportView = [[LMReportView alloc] init]; 27 | self.reportView.datasource = self; 28 | [self.view addSubview:self.reportView]; 29 | } 30 | 31 | - (void)didReceiveMemoryWarning { 32 | [super didReceiveMemoryWarning]; 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | - (void)viewDidLayoutSubviews { 37 | [super viewDidLayoutSubviews]; 38 | 39 | CGRect rect = self.view.bounds; 40 | rect = CGRectInset(rect, 16, 16); 41 | rect.origin.y += 60; 42 | rect.size.height -= 64; 43 | self.reportView.frame = rect; 44 | } 45 | 46 | - (NSArray *)generalDatas { 47 | if (_generalDatas) { 48 | return _generalDatas; 49 | } 50 | 51 | NSMutableArray *rows = [NSMutableArray array]; 52 | for (NSInteger rowIndex = 0; rowIndex < 10; rowIndex++) { 53 | NSMutableArray *grids = [NSMutableArray array]; 54 | NSInteger colIndex = 0; 55 | while (colIndex < 10) { 56 | LMRGrid *grid = [[LMRGrid alloc] init]; 57 | grid.text = [NSString stringWithFormat:@"%ld-%ld", rowIndex, colIndex]; 58 | if (rowIndex == 0 && colIndex == 0) { 59 | grid.rowspan = 2; 60 | } 61 | else if (rowIndex == 1 && colIndex == 0) { 62 | [grids addObject:[NSNull null]]; 63 | colIndex++; 64 | continue; 65 | } 66 | if (rowIndex == 0) { 67 | switch (colIndex) { 68 | case 1: 69 | grid.colspan = 2; 70 | break; 71 | case 3: 72 | grid.colspan = 3; 73 | break; 74 | case 6: 75 | grid.colspan = 4; 76 | break; 77 | default: 78 | break; 79 | } 80 | } 81 | [grids addObject:grid]; 82 | for (NSInteger i = 0; i < grid.colspan - 1; i++) { 83 | [grids addObject:[NSNull null]]; 84 | } 85 | colIndex += grid.colspan; 86 | } 87 | [rows addObject:grids]; 88 | } 89 | _generalDatas = rows; 90 | return _generalDatas; 91 | } 92 | 93 | #pragma mark - 94 | 95 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView { 96 | return self.generalDatas.count; 97 | } 98 | 99 | - (NSInteger)numberOfHeadRowsInReportView:(LMReportView *)reportView { 100 | return 2; 101 | } 102 | 103 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView { 104 | return [self.generalDatas.lastObject count]; 105 | } 106 | 107 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath { 108 | LMRGrid *grid = self.generalDatas[indexPath.row][indexPath.col]; 109 | return grid; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/DetailingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DetailingViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DetailingViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/DetailingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DetailingViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "DetailingViewController.h" 10 | #import "LMReport.h" 11 | 12 | @interface DetailingViewController () 13 | 14 | @property (nonatomic, strong) LMReportView *reportView; 15 | @property (nonatomic, strong) NSArray *generalDatas; 16 | 17 | @end 18 | 19 | @implementation DetailingViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | self.title = @"单元格样式设置"; 25 | 26 | self.reportView = [[LMReportView alloc] init]; 27 | self.reportView.datasource = self; 28 | [self.view addSubview:self.reportView]; 29 | } 30 | 31 | - (void)didReceiveMemoryWarning { 32 | [super didReceiveMemoryWarning]; 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | - (void)viewDidLayoutSubviews { 37 | [super viewDidLayoutSubviews]; 38 | 39 | CGRect rect = self.view.bounds; 40 | rect = CGRectInset(rect, 16, 16); 41 | rect.origin.y += 60; 42 | rect.size.height -= 64; 43 | self.reportView.frame = rect; 44 | } 45 | 46 | - (NSArray *)generalDatas { 47 | if (_generalDatas) { 48 | return _generalDatas; 49 | } 50 | 51 | NSMutableArray *rows = [NSMutableArray array]; 52 | for (NSInteger rowIndex = 0; rowIndex < 10; rowIndex++) { 53 | NSMutableArray *grids = [NSMutableArray array]; 54 | NSInteger colIndex = 0; 55 | while (colIndex < 10) { 56 | LMRGrid *grid = [[LMRGrid alloc] init]; 57 | 58 | if (rowIndex == 0) { 59 | switch (colIndex) { 60 | case 0: 61 | grid.text = @"下划线"; 62 | break; 63 | case 1: 64 | grid.text = @"文字"; 65 | break; 66 | case 2: 67 | grid.text = @"颜色"; 68 | break; 69 | case 3: 70 | grid.text = @"图片"; 71 | break; 72 | default: 73 | break; 74 | } 75 | } 76 | else { 77 | switch (colIndex) { 78 | case 0: 79 | grid.underline = YES; 80 | break; 81 | case 1: 82 | grid.textColor = [UIColor orangeColor]; 83 | grid.font = [UIFont boldSystemFontOfSize:15.f]; 84 | grid.textAlignment = NSTextAlignmentRight; 85 | break; 86 | case 2: 87 | grid.backgroundColor = [UIColor colorWithWhite:0.75 alpha:1]; 88 | break; 89 | case 3: 90 | grid.image = [UIImage imageNamed:[NSString stringWithFormat:@"image-%ld", rowIndex]]; 91 | break; 92 | default: 93 | break; 94 | } 95 | grid.text = [NSString stringWithFormat:@"%ld-%ld", rowIndex, colIndex]; 96 | } 97 | [grids addObject:grid]; 98 | colIndex++; 99 | } 100 | [rows addObject:grids]; 101 | } 102 | _generalDatas = rows; 103 | return _generalDatas; 104 | } 105 | 106 | #pragma mark - 107 | 108 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView { 109 | return 4; 110 | } 111 | 112 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView { 113 | return 4; 114 | } 115 | 116 | - (CGFloat)reportView:(LMReportView *)reportView heightOfRow:(NSInteger)row { 117 | if (row == 0) { 118 | return 30; 119 | } 120 | else { 121 | return 60; 122 | } 123 | } 124 | - (CGFloat)reportView:(LMReportView *)reportView widthOfCol:(NSInteger)col { 125 | if (col == 3) { 126 | return 60; 127 | } 128 | else { 129 | return 80; 130 | } 131 | } 132 | 133 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath { 134 | LMRGrid *grid = self.generalDatas[indexPath.row][indexPath.col]; 135 | return grid; 136 | } 137 | 138 | @end 139 | 140 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/FitViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FitViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FitViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/FitViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FitViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "FitViewController.h" 10 | #import "LMReport.h" 11 | 12 | @interface FitViewController () 13 | 14 | @property (nonatomic, strong) LMReportView *reportView; 15 | 16 | @end 17 | 18 | @implementation FitViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.title = @"行高自适应"; 24 | 25 | self.reportView = [[LMReportView alloc] init]; 26 | self.reportView.datasource = self; 27 | self.reportView.style.autoFitHeight = YES; 28 | [self.view addSubview:self.reportView]; 29 | } 30 | 31 | - (void)didReceiveMemoryWarning { 32 | [super didReceiveMemoryWarning]; 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | - (void)viewDidLayoutSubviews { 37 | [super viewDidLayoutSubviews]; 38 | 39 | CGRect rect = self.view.bounds; 40 | rect = CGRectInset(rect, 16, 16); 41 | rect.origin.y += 60; 42 | rect.size.height -= 64; 43 | self.reportView.frame = rect; 44 | } 45 | 46 | #pragma mark - 47 | 48 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView { 49 | return 10; 50 | } 51 | 52 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView { 53 | return 10; 54 | } 55 | 56 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath { 57 | LMRGrid *grid = [[LMRGrid alloc] init]; 58 | NSString *baseString = [NSString stringWithFormat:@"%ld-%ld", indexPath.row, indexPath.col]; 59 | NSMutableString *text = [NSMutableString stringWithString:baseString]; 60 | for (NSInteger i = 0; i < indexPath.row; i++) { 61 | [text appendString:@"\n"]; 62 | [text appendString:baseString]; 63 | } 64 | grid.text = text; 65 | return grid; 66 | } 67 | 68 | @end 69 | 70 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/HandleEventViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HandleEventViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HandleEventViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/HandleEventViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HandleEventViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "HandleEventViewController.h" 10 | #import "LMReport.h" 11 | 12 | @interface HandleEventViewController () 13 | 14 | @property (nonatomic, strong) LMReportView *reportView; 15 | @property (nonatomic, strong) NSArray *generalDatas; 16 | 17 | @end 18 | 19 | @implementation HandleEventViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | self.title = @"响应事件"; 25 | 26 | self.reportView = [[LMReportView alloc] init]; 27 | self.reportView.datasource = self; 28 | self.reportView.delegate = self; 29 | [self.view addSubview:self.reportView]; 30 | } 31 | 32 | - (void)didReceiveMemoryWarning { 33 | [super didReceiveMemoryWarning]; 34 | // Dispose of any resources that can be recreated. 35 | } 36 | 37 | - (void)viewDidLayoutSubviews { 38 | [super viewDidLayoutSubviews]; 39 | 40 | CGRect rect = self.view.bounds; 41 | rect = CGRectInset(rect, 16, 16); 42 | rect.origin.y += 60; 43 | rect.size.height -= 64; 44 | self.reportView.frame = rect; 45 | } 46 | 47 | - (NSArray *)generalDatas { 48 | if (_generalDatas) { 49 | return _generalDatas; 50 | } 51 | 52 | NSMutableArray *rows = [NSMutableArray array]; 53 | for (NSInteger rowIndex = 0; rowIndex < 10; rowIndex++) { 54 | NSMutableArray *grids = [NSMutableArray array]; 55 | NSInteger colIndex = 0; 56 | while (colIndex < 10) { 57 | LMRGrid *grid = [[LMRGrid alloc] init]; 58 | if (rowIndex == 0) { 59 | grid.text = [NSString stringWithFormat:@"第%ld列", colIndex]; 60 | } 61 | else { 62 | grid.text = @(arc4random() % 1000).stringValue; 63 | } 64 | [grids addObject:grid]; 65 | colIndex++; 66 | } 67 | [rows addObject:grids]; 68 | } 69 | _generalDatas = rows; 70 | return _generalDatas; 71 | } 72 | 73 | - (void)alertMessage:(NSString *)message { 74 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"系统提示" message:message preferredStyle:UIAlertControllerStyleAlert]; 75 | UIAlertAction *action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil]; 76 | [alert addAction:action]; 77 | [self.navigationController presentViewController:alert animated:YES completion:nil]; 78 | } 79 | 80 | - (NSOrderedSet *)ascOrderedSetForSortedCol:(NSInteger)col { 81 | /* 82 | 获取行索引的排序:先获取值的排序,然后一一对应到行索引的排序 83 | */ 84 | NSMutableArray *sourceValues = [self.generalDatas mutableCopy]; 85 | // 去掉第一行表头 86 | [sourceValues removeObjectAtIndex:0]; 87 | //将列值数组从小到大排序 88 | NSArray *sortedValues = [sourceValues sortedArrayUsingComparator:^NSComparisonResult(NSArray *rows1, NSArray *rows2) { 89 | 90 | LMRGrid *grid1 = rows1[col]; 91 | LMRGrid *grid2 = rows2[col]; 92 | 93 | NSString *str1 = grid1.text; 94 | NSString *str2 = grid2.text; 95 | 96 | NSScanner *scanner = [NSScanner scannerWithString:str1]; 97 | float floatValue; 98 | [scanner scanFloat:&floatValue]; 99 | 100 | if ([scanner isAtEnd]) { 101 | //数字 102 | return str2.floatValue > str1.floatValue ? NSOrderedAscending : NSOrderedDescending; 103 | } 104 | else { 105 | //非数字 106 | UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation]; 107 | 108 | NSInteger length = str1.length < str2.length ? str1.length : str2.length; 109 | NSInteger loc = 0; 110 | while (loc < length) { 111 | 112 | NSString *word1 = [str1 substringWithRange:NSMakeRange(loc, 1)]; 113 | NSString *word2 = [str2 substringWithRange:NSMakeRange(loc, 1)]; 114 | 115 | NSInteger section1 = [collation sectionForObject:word1 collationStringSelector:@selector(description)]; 116 | NSInteger section2 = [collation sectionForObject:word2 collationStringSelector:@selector(description)]; 117 | 118 | return section2 > section1 ? NSOrderedDescending : NSOrderedAscending; 119 | } 120 | return NSOrderedAscending; 121 | } 122 | }]; 123 | //获取行索引的排序 124 | NSMutableOrderedSet *sortedIndexes = [NSMutableOrderedSet orderedSet]; 125 | for (id value in sortedValues) { 126 | NSInteger index = [sourceValues indexOfObject:value]; 127 | //相同的字符串指向同一个(@"0" == @"0"),所以要把索引过的移除掉,避免同一个索引被多次加入到索引数组中 128 | sourceValues[index] = [NSNull null]; 129 | [sortedIndexes addObject:[NSNumber numberWithInteger:index + 1]]; 130 | } 131 | return sortedIndexes; 132 | } 133 | 134 | #pragma mark - 135 | 136 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView { 137 | return self.generalDatas.count; 138 | } 139 | 140 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView { 141 | return [self.generalDatas.lastObject count]; 142 | } 143 | 144 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath { 145 | LMRGrid *grid = self.generalDatas[indexPath.row][indexPath.col]; 146 | return grid; 147 | } 148 | 149 | #pragma mark - 150 | 151 | - (void)reportView:(LMReportView *)reportView didTapLabel:(LMRLabel *)label { 152 | if (label.indexPath.row == 0) { 153 | // 点击表头进行排序 154 | LMROrder order; 155 | if (reportView.sortedCol == label.indexPath.col && reportView.sortedOrder == LMROrderedDescending) { 156 | order = LMROrderedAscending; 157 | } 158 | else { 159 | order = LMROrderedDescending; 160 | } 161 | [reportView sortByCol:label.indexPath.col order:order]; 162 | } 163 | else { 164 | [self alertMessage:[NSString stringWithFormat:@"点击 %ld-%ld", label.indexPath.row, label.indexPath.col]]; 165 | } 166 | } 167 | 168 | - (void)reportView:(LMReportView *)reportView didLongPressLabel:(LMRLabel *)label { 169 | [self alertMessage:[NSString stringWithFormat:@"长按 %ld-%ld", label.indexPath.row, label.indexPath.col]]; 170 | } 171 | 172 | - (NSOrderedSet *)reportView:(LMReportView *)reportView indexesSortedByCol:(NSInteger)col order:(LMROrder)order { 173 | if (order == LMROrderedAscending) { 174 | return [self ascOrderedSetForSortedCol:col]; 175 | } 176 | else if (order == LMROrderedDescending) { 177 | return [[self ascOrderedSetForSortedCol:col] reversedOrderedSet]; 178 | } 179 | else { 180 | return nil; 181 | } 182 | } 183 | 184 | @end 185 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/LMRStyle+Demo.h: -------------------------------------------------------------------------------- 1 | // 2 | // LMRStyle+Demo.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "LMRStyle.h" 10 | 11 | typedef NS_ENUM(NSUInteger, LMRStyleType) { 12 | LMRStyleTypeDefault, 13 | LMRStyleTypeBlue, 14 | LMRStyleTypeGreen, 15 | }; 16 | 17 | @interface LMRStyle (Demo) 18 | 19 | + (instancetype)blueStyle; 20 | + (instancetype)greenStyle; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/LMRStyle+Demo.m: -------------------------------------------------------------------------------- 1 | // 2 | // LMRStyle+Demo.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "LMRStyle+Demo.h" 10 | 11 | @implementation LMRStyle (Demo) 12 | 13 | + (NSDictionary *)defaultLayoutSettings { 14 | return @{ 15 | LMRHeightOfHeaderRowSettingName: @36.f, 16 | LMRHeightOfRowSettingName: @36.f, 17 | LMRWidthOfFirstColSettingName: @74.f, 18 | LMRWidthOfColSettingName: @75.f, 19 | LMRFontOfHeaderSettingName: [UIFont systemFontOfSize:13.f], 20 | LMRFontSettingName: [UIFont systemFontOfSize:13.f], 21 | LMRBorderInsetsSettingName: [NSValue valueWithUIEdgeInsets:UIEdgeInsetsMake(1.f, 1.f, 1.f, 1.f)] 22 | }; 23 | } 24 | 25 | + (instancetype)blueStyle { 26 | NSMutableDictionary *settings = [[self defaultLayoutSettings] mutableCopy]; 27 | [settings addEntriesFromDictionary:@{ 28 | LMRBackgroundColorOfHeaderSettingName: [UIColor colorWithRed:57/255.0 green:152/255.0 blue:216/255.0 alpha:1], 29 | LMRBorderColorSettingName: [UIColor colorWithRed:46/255.0 green:128/255.0 blue:182/255.0 alpha:1], 30 | LMRTextColorOfHeaderSettingName: [UIColor whiteColor], 31 | }]; 32 | return [LMRStyle styleWithSettings:settings]; 33 | } 34 | 35 | + (instancetype)greenStyle { 36 | NSMutableDictionary *settings = [[self defaultLayoutSettings] mutableCopy]; 37 | [settings addEntriesFromDictionary:@{ 38 | LMRBackgroundColorOfHeaderSettingName: [UIColor colorWithRed:0.086 green:0.627 blue:0.521 alpha:1], 39 | LMRBorderColorSettingName: [UIColor colorWithRed:0.2 green:0.7 blue:0.6 alpha:1], 40 | LMRTextColorOfHeaderSettingName: [UIColor whiteColor], 41 | LMRTextColorSettingName: [UIColor colorWithWhite:0.25 alpha:1], 42 | LMRStripeTextColorSettingName: [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1], 43 | }]; 44 | return [LMRStyle styleWithSettings:settings]; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/SimpleViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SimpleViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/SimpleViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "SimpleViewController.h" 10 | #import "LMReport.h" 11 | 12 | @interface SimpleViewController () 13 | 14 | @property (nonatomic, strong) LMReportView *reportView; 15 | 16 | @end 17 | 18 | @implementation SimpleViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.title = @"简单使用"; 24 | 25 | self.reportView = [[LMReportView alloc] init]; 26 | self.reportView.datasource = self; 27 | [self.view addSubview:self.reportView]; 28 | } 29 | 30 | - (void)didReceiveMemoryWarning { 31 | [super didReceiveMemoryWarning]; 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | - (void)viewDidLayoutSubviews { 36 | [super viewDidLayoutSubviews]; 37 | 38 | CGRect rect = self.view.bounds; 39 | rect = CGRectInset(rect, 16, 16); 40 | rect.origin.y += 60; 41 | rect.size.height -= 64; 42 | self.reportView.frame = rect; 43 | } 44 | 45 | #pragma mark - 46 | 47 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView { 48 | return 20; 49 | } 50 | 51 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView { 52 | return 10; 53 | } 54 | 55 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath { 56 | LMRGrid *grid = [[LMRGrid alloc] init]; 57 | grid.text = [NSString stringWithFormat:@"%ld-%ld", indexPath.row, indexPath.col]; 58 | return grid; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/StylesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // StylesViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface StylesViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/StylesViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // StylesViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/18. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "StylesViewController.h" 10 | #import "LMReport.h" 11 | #import "LMRStyle+Demo.h" 12 | 13 | @interface StylesViewController () 14 | 15 | @property (nonatomic, strong) LMReportView *reportView; 16 | @property (nonatomic, assign) LMRStyleType currentStyle; 17 | 18 | @end 19 | 20 | @implementation StylesViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | 25 | self.title = @"风格样式"; 26 | 27 | LMRStyle *style = [LMRStyle blueStyle]; 28 | 29 | self.reportView = [[LMReportView alloc] init]; 30 | self.reportView.datasource = self; 31 | self.reportView.style = style; 32 | [self.view addSubview:self.reportView]; 33 | 34 | self.currentStyle = LMRStyleTypeBlue; 35 | } 36 | 37 | - (void)didReceiveMemoryWarning { 38 | [super didReceiveMemoryWarning]; 39 | // Dispose of any resources that can be recreated. 40 | } 41 | 42 | - (void)viewDidLayoutSubviews { 43 | [super viewDidLayoutSubviews]; 44 | 45 | CGRect rect = self.view.bounds; 46 | rect = CGRectInset(rect, 16, 16); 47 | rect.origin.y += 60; 48 | rect.size.height -= 64; 49 | self.reportView.frame = rect; 50 | } 51 | 52 | - (IBAction)changeStyle:(id)sender { 53 | UIAlertAction *actionBlue = [UIAlertAction actionWithTitle:@"蓝色" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 54 | [self setStyle:LMRStyleTypeBlue]; 55 | }]; 56 | UIAlertAction *actionGreen = [UIAlertAction actionWithTitle:@"绿色" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { 57 | [self setStyle:LMRStyleTypeGreen]; 58 | }]; 59 | 60 | UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"切换样式" message:nil preferredStyle:UIAlertControllerStyleActionSheet]; 61 | [alert addAction:actionBlue]; 62 | [alert addAction:actionGreen]; 63 | [self.navigationController presentViewController:alert animated:YES completion:nil]; 64 | } 65 | 66 | - (void)setStyle:(LMRStyleType)style { 67 | if (self.currentStyle == style) { 68 | return; 69 | } 70 | self.reportView.style = style == LMRStyleTypeBlue ? [LMRStyle blueStyle] : [LMRStyle greenStyle]; 71 | self.currentStyle = style; 72 | [UIView animateWithDuration:0.5 animations:^{ 73 | [self.reportView reloadData]; 74 | }]; 75 | } 76 | 77 | #pragma mark - 78 | 79 | - (NSInteger)numberOfRowsInReportView:(LMReportView *)reportView { 80 | return 20; 81 | } 82 | 83 | - (NSInteger)numberOfColsInReportView:(LMReportView *)reportView { 84 | return 10; 85 | } 86 | 87 | - (LMRGrid *)reportView:(LMReportView *)reportView gridAtIndexPath:(NSIndexPath *)indexPath { 88 | LMRGrid *grid = [[LMRGrid alloc] init]; 89 | grid.text = [NSString stringWithFormat:@"%ld-%ld", indexPath.row, indexPath.col]; 90 | return grid; 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/17. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UITableViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/17. 6 | // Copyright © 2016年 Little Meaning. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | } 21 | 22 | - (void)didReceiveMemoryWarning { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Demo/LMReportDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // LMReportDemo 4 | // 5 | // Created by Chenly on 16/4/17. 6 | // Copyright © 2016年 Little Meaning. 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 | -------------------------------------------------------------------------------- /DemoDisplay/complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/complex.png -------------------------------------------------------------------------------- /DemoDisplay/detailing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/detailing.png -------------------------------------------------------------------------------- /DemoDisplay/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/fit.png -------------------------------------------------------------------------------- /DemoDisplay/handleEvent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/handleEvent.png -------------------------------------------------------------------------------- /DemoDisplay/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/simple.png -------------------------------------------------------------------------------- /DemoDisplay/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/sort.png -------------------------------------------------------------------------------- /DemoDisplay/styles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/littleMeaning/LMReport/b470157167b41412a6025752b2e52212469fe21b/DemoDisplay/styles.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 陈庆明 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LMReport.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "LMReport" 3 | s.version = "0.0.1" 4 | s.license = "MIT" 5 | s.summary = "多功能报表控件,支持整体样式和单元格样式的设置,支持复合表头、自适应行高、触摸事件响应已经排序,基本满足常见报表功能。" 6 | s.homepage = "https://github.com/littleMeaning/LMReport" 7 | s.author = { "littleMeaning" => "littleMeaning@yeah.net" } 8 | s.source = { :git => "https://github.com/littleMeaning/LMReport.git", :tag => "v0.0.1" } 9 | s.source_files = 'Classes' 10 | s.requires_arc = true 11 | s.platform = :ios, "7.0" 12 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LMReport 2 | 多功能报表控件,支持整体样式和单元格样式的设置,支持复合表头、自适应行高、触摸事件响应已经排序,基本满足常见报表功能。 3 | 4 | Installation 5 | ============== 6 | 7 | ### CocoaPods 8 | 9 | 1. Add `pod 'LMReport'` to your Podfile. 10 | 2. Run `pod install` or `pod update`. 11 | 3. Import \. 12 | 13 | 14 | Feature 15 | ============== 16 | 17 | 18 | ### 简单使用 19 | 20 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/simple.png) 21 | 22 | ### 风格样式 23 | 24 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/styles.png) 25 | 26 | 27 | ### 复合表头 28 | 29 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/complex.png) 30 | 31 | ### 单元格样式 32 | 33 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/detailing.png) 34 | 35 | ### 响应事件 36 | 37 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/handleEvent.png) 38 | 39 | ### 排序 40 | 41 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/sort.png) 42 | 43 | ### 自适应行高 44 | 45 | ![image](https://github.com/littleMeaning/LMReport/blob/master/DemoDisplay/fit.png) --------------------------------------------------------------------------------