Commit aec37142 authored by Sandy's avatar Sandy

销售录入模块功能完成

parent 448e593b
......@@ -102,6 +102,19 @@
8FEF5EB01F28612B001496E3 /* ZJBaseFileModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FEF5E9A1F28612B001496E3 /* ZJBaseFileModel.m */; };
8FEF5EB21F28612B001496E3 /* ZJBaseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FEF5E9C1F28612B001496E3 /* ZJBaseModel.m */; };
8FF9F6DE1F3450AC006B0EC0 /* SaleInputDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6DD1F3450AC006B0EC0 /* SaleInputDetailViewController.m */; };
8FF9F6E31F347517006B0EC0 /* SaleInputState.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6E21F347517006B0EC0 /* SaleInputState.m */; };
8FF9F6E61F37FF09006B0EC0 /* SaleInputHistoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6E51F37FF09006B0EC0 /* SaleInputHistoryViewController.m */; };
8FF9F6FB1F37FF6C006B0EC0 /* MMGridCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6E91F37FF6C006B0EC0 /* MMGridCell.m */; };
8FF9F6FC1F37FF6C006B0EC0 /* MMLeftColumnCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6EB1F37FF6C006B0EC0 /* MMLeftColumnCell.m */; };
8FF9F6FD1F37FF6C006B0EC0 /* CustomMMGridLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6EE1F37FF6C006B0EC0 /* CustomMMGridLayout.m */; };
8FF9F6FE1F37FF6C006B0EC0 /* CustomMMSpreadsheetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6F01F37FF6C006B0EC0 /* CustomMMSpreadsheetView.m */; };
8FF9F6FF1F37FF6C006B0EC0 /* MMGridLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6F21F37FF6C006B0EC0 /* MMGridLayout.m */; };
8FF9F7001F37FF6C006B0EC0 /* MMSpreadsheetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6F41F37FF6C006B0EC0 /* MMSpreadsheetView.m */; };
8FF9F7011F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6F61F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.m */; };
8FF9F7021F37FF6C006B0EC0 /* MMTopRowCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6F81F37FF6C006B0EC0 /* MMTopRowCell.m */; };
8FF9F7031F37FF6C006B0EC0 /* MMViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F6FA1F37FF6C006B0EC0 /* MMViewController.m */; };
8FF9F7071F37FF95006B0EC0 /* HistoryTopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F7061F37FF95006B0EC0 /* HistoryTopView.m */; };
8FF9F70A1F380286006B0EC0 /* SaleInputHistory.m in Sources */ = {isa = PBXBuildFile; fileRef = 8FF9F7081F380286006B0EC0 /* SaleInputHistory.m */; };
B3A759A50113DA2AD3E85C40 /* libPods-hdmall_base-HDMall_TEST.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3784BD616CA8AF0775D5526F /* libPods-hdmall_base-HDMall_TEST.a */; };
/* End PBXBuildFile section */
......@@ -296,6 +309,32 @@
8FEF5E9C1F28612B001496E3 /* ZJBaseModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZJBaseModel.m; sourceTree = "<group>"; };
8FF9F6DC1F3450AC006B0EC0 /* SaleInputDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaleInputDetailViewController.h; sourceTree = "<group>"; };
8FF9F6DD1F3450AC006B0EC0 /* SaleInputDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaleInputDetailViewController.m; sourceTree = "<group>"; };
8FF9F6E11F347517006B0EC0 /* SaleInputState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaleInputState.h; sourceTree = "<group>"; };
8FF9F6E21F347517006B0EC0 /* SaleInputState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaleInputState.m; sourceTree = "<group>"; };
8FF9F6E41F37FF09006B0EC0 /* SaleInputHistoryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaleInputHistoryViewController.h; sourceTree = "<group>"; };
8FF9F6E51F37FF09006B0EC0 /* SaleInputHistoryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaleInputHistoryViewController.m; sourceTree = "<group>"; };
8FF9F6E81F37FF6C006B0EC0 /* MMGridCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMGridCell.h; sourceTree = "<group>"; };
8FF9F6E91F37FF6C006B0EC0 /* MMGridCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMGridCell.m; sourceTree = "<group>"; };
8FF9F6EA1F37FF6C006B0EC0 /* MMLeftColumnCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMLeftColumnCell.h; sourceTree = "<group>"; };
8FF9F6EB1F37FF6C006B0EC0 /* MMLeftColumnCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMLeftColumnCell.m; sourceTree = "<group>"; };
8FF9F6ED1F37FF6C006B0EC0 /* CustomMMGridLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMMGridLayout.h; sourceTree = "<group>"; };
8FF9F6EE1F37FF6C006B0EC0 /* CustomMMGridLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMMGridLayout.m; sourceTree = "<group>"; };
8FF9F6EF1F37FF6C006B0EC0 /* CustomMMSpreadsheetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMMSpreadsheetView.h; sourceTree = "<group>"; };
8FF9F6F01F37FF6C006B0EC0 /* CustomMMSpreadsheetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMMSpreadsheetView.m; sourceTree = "<group>"; };
8FF9F6F11F37FF6C006B0EC0 /* MMGridLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMGridLayout.h; sourceTree = "<group>"; };
8FF9F6F21F37FF6C006B0EC0 /* MMGridLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMGridLayout.m; sourceTree = "<group>"; };
8FF9F6F31F37FF6C006B0EC0 /* MMSpreadsheetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMSpreadsheetView.h; sourceTree = "<group>"; };
8FF9F6F41F37FF6C006B0EC0 /* MMSpreadsheetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMSpreadsheetView.m; sourceTree = "<group>"; };
8FF9F6F51F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexPath+MMSpreadsheetView.h"; sourceTree = "<group>"; };
8FF9F6F61F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexPath+MMSpreadsheetView.m"; sourceTree = "<group>"; };
8FF9F6F71F37FF6C006B0EC0 /* MMTopRowCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMTopRowCell.h; sourceTree = "<group>"; };
8FF9F6F81F37FF6C006B0EC0 /* MMTopRowCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMTopRowCell.m; sourceTree = "<group>"; };
8FF9F6F91F37FF6C006B0EC0 /* MMViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMViewController.h; sourceTree = "<group>"; };
8FF9F6FA1F37FF6C006B0EC0 /* MMViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMViewController.m; sourceTree = "<group>"; };
8FF9F7051F37FF95006B0EC0 /* HistoryTopView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HistoryTopView.h; sourceTree = "<group>"; };
8FF9F7061F37FF95006B0EC0 /* HistoryTopView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HistoryTopView.m; sourceTree = "<group>"; };
8FF9F7081F380286006B0EC0 /* SaleInputHistory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SaleInputHistory.m; sourceTree = "<group>"; };
8FF9F7091F380286006B0EC0 /* SaleInputHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SaleInputHistory.h; sourceTree = "<group>"; };
B7647FC31ADEF96A4D33BB34 /* Pods-hdmall_base-HDMall.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hdmall_base-HDMall.release.xcconfig"; path = "Pods/Target Support Files/Pods-hdmall_base-HDMall/Pods-hdmall_base-HDMall.release.xcconfig"; sourceTree = "<group>"; };
D3A3D2FD83D7ADD6AC921E3B /* Pods-hdmall_base-HDMall_TEST.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hdmall_base-HDMall_TEST.debug.xcconfig"; path = "Pods/Target Support Files/Pods-hdmall_base-HDMall_TEST/Pods-hdmall_base-HDMall_TEST.debug.xcconfig"; sourceTree = "<group>"; };
DE3C94E5EBEB6D5CCA6E7916 /* Pods-hdmall_base-HDMall.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-hdmall_base-HDMall.debug.xcconfig"; path = "Pods/Target Support Files/Pods-hdmall_base-HDMall/Pods-hdmall_base-HDMall.debug.xcconfig"; sourceTree = "<group>"; };
......@@ -606,6 +645,8 @@
8F4BFCCB1F2EF508003EA16B /* Models */ = {
isa = PBXGroup;
children = (
8FF9F7081F380286006B0EC0 /* SaleInputHistory.m */,
8FF9F7091F380286006B0EC0 /* SaleInputHistory.h */,
8F4BFCD71F2EF6CF003EA16B /* HMSaleInputQuery.h */,
8F4BFCD81F2EF6CF003EA16B /* HMSaleInputQuery.m */,
8F4BFCE91F2F09D0003EA16B /* HMSaleInputDetail.h */,
......@@ -623,6 +664,8 @@
8F4BFCDE1F2F0279003EA16B /* SaleInputAddViewController.m */,
8FF9F6DC1F3450AC006B0EC0 /* SaleInputDetailViewController.h */,
8FF9F6DD1F3450AC006B0EC0 /* SaleInputDetailViewController.m */,
8FF9F6E41F37FF09006B0EC0 /* SaleInputHistoryViewController.h */,
8FF9F6E51F37FF09006B0EC0 /* SaleInputHistoryViewController.m */,
);
path = ViewControllers;
sourceTree = "<group>";
......@@ -632,6 +675,8 @@
children = (
8F4BFCE61F2F07C1003EA16B /* SaleInputViewModel.h */,
8F4BFCE71F2F07C1003EA16B /* SaleInputViewModel.m */,
8FF9F6E11F347517006B0EC0 /* SaleInputState.h */,
8FF9F6E21F347517006B0EC0 /* SaleInputState.m */,
);
path = ViewModels;
sourceTree = "<group>";
......@@ -639,6 +684,8 @@
8F4BFCCE1F2EF508003EA16B /* Views */ = {
isa = PBXGroup;
children = (
8FF9F7051F37FF95006B0EC0 /* HistoryTopView.h */,
8FF9F7061F37FF95006B0EC0 /* HistoryTopView.m */,
8F4BFD0B1F3019AC003EA16B /* SalesInputTotalVIew.h */,
8F4BFD0C1F3019AC003EA16B /* SalesInputTotalVIew.m */,
8F4BFCEC1F2F0F16003EA16B /* SaleInputAmountCollectionViewCell.h */,
......@@ -738,6 +785,7 @@
8F973C041F21FB67003D3ADB /* Tools */ = {
isa = PBXGroup;
children = (
8FF9F6E71F37FF6C006B0EC0 /* CustomMMSpreadSheetView */,
8F4BFCF51F2F3238003EA16B /* PictureTableViewCell */,
8F6C923F1F2AE4AD0060E983 /* LocationHelper */,
8F250D931F29D8420031B4BC /* ZJPermissionManager */,
......@@ -975,6 +1023,39 @@
path = BaseClass;
sourceTree = "<group>";
};
8FF9F6E71F37FF6C006B0EC0 /* CustomMMSpreadSheetView */ = {
isa = PBXGroup;
children = (
8FF9F6E81F37FF6C006B0EC0 /* MMGridCell.h */,
8FF9F6E91F37FF6C006B0EC0 /* MMGridCell.m */,
8FF9F6EA1F37FF6C006B0EC0 /* MMLeftColumnCell.h */,
8FF9F6EB1F37FF6C006B0EC0 /* MMLeftColumnCell.m */,
8FF9F6EC1F37FF6C006B0EC0 /* MMSpreadsheetView */,
8FF9F6F71F37FF6C006B0EC0 /* MMTopRowCell.h */,
8FF9F6F81F37FF6C006B0EC0 /* MMTopRowCell.m */,
8FF9F6F91F37FF6C006B0EC0 /* MMViewController.h */,
8FF9F6FA1F37FF6C006B0EC0 /* MMViewController.m */,
);
path = CustomMMSpreadSheetView;
sourceTree = "<group>";
};
8FF9F6EC1F37FF6C006B0EC0 /* MMSpreadsheetView */ = {
isa = PBXGroup;
children = (
8FF9F6ED1F37FF6C006B0EC0 /* CustomMMGridLayout.h */,
8FF9F6EE1F37FF6C006B0EC0 /* CustomMMGridLayout.m */,
8FF9F6EF1F37FF6C006B0EC0 /* CustomMMSpreadsheetView.h */,
8FF9F6F01F37FF6C006B0EC0 /* CustomMMSpreadsheetView.m */,
8FF9F6F11F37FF6C006B0EC0 /* MMGridLayout.h */,
8FF9F6F21F37FF6C006B0EC0 /* MMGridLayout.m */,
8FF9F6F31F37FF6C006B0EC0 /* MMSpreadsheetView.h */,
8FF9F6F41F37FF6C006B0EC0 /* MMSpreadsheetView.m */,
8FF9F6F51F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.h */,
8FF9F6F61F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.m */,
);
path = MMSpreadsheetView;
sourceTree = "<group>";
};
910BF16968E181D62593128E /* Frameworks */ = {
isa = PBXGroup;
children = (
......@@ -1114,6 +1195,7 @@
buildActionMask = 2147483647;
files = (
8FEF5EB21F28612B001496E3 /* ZJBaseModel.m in Sources */,
8FF9F6E31F347517006B0EC0 /* SaleInputState.m in Sources */,
8F250D211F297DA80031B4BC /* UINavigationController+XWTransition.m in Sources */,
8F250D6A1F2981200031B4BC /* ZJShowLogView.m in Sources */,
8FEF5EA41F28612B001496E3 /* BaseChooseViewController.m in Sources */,
......@@ -1136,7 +1218,9 @@
8F973C321F21FE32003D3ADB /* AppDelegate.m in Sources */,
8F250D231F297DA80031B4BC /* UIView+Snapshot.m in Sources */,
8F973C341F21FE32003D3ADB /* main.m in Sources */,
8FF9F6FE1F37FF6C006B0EC0 /* CustomMMSpreadsheetView.m in Sources */,
8FEF5EA61F28612B001496E3 /* BaseIQManagerView.m in Sources */,
8FF9F7001F37FF6C006B0EC0 /* MMSpreadsheetView.m in Sources */,
8F4BFCEE1F2F0F16003EA16B /* SaleInputAmountCollectionViewCell.m in Sources */,
8F250D871F29C2420031B4BC /* LoginRequest.m in Sources */,
8F6C92421F2AE4AD0060E983 /* LocationHelper.m in Sources */,
......@@ -1152,11 +1236,15 @@
8FEF5E721F285E56001496E3 /* NSString+Additions.m in Sources */,
8F250D371F297DA80031B4BC /* XWCoolAnimator+XWLines.m in Sources */,
8F250D251F297DA80031B4BC /* UIViewController+XWTransition.m in Sources */,
8FF9F7011F37FF6C006B0EC0 /* NSIndexPath+MMSpreadsheetView.m in Sources */,
8FEF5E7C1F285E56001496E3 /* UIView+Category.m in Sources */,
8F250D8F1F29CF190031B4BC /* ZJAppInstance.m in Sources */,
8F4BFD071F2F3314003EA16B /* GalleryViewController.m in Sources */,
8F4BFCE81F2F07C1003EA16B /* SaleInputViewModel.m in Sources */,
8FF9F6FC1F37FF6C006B0EC0 /* MMLeftColumnCell.m in Sources */,
8FEF5EA21F28612B001496E3 /* BaseChooseModel.m in Sources */,
8FF9F7071F37FF95006B0EC0 /* HistoryTopView.m in Sources */,
8FF9F6FF1F37FF6C006B0EC0 /* MMGridLayout.m in Sources */,
8F973C1F1F21FD54003D3ADB /* ViewController.m in Sources */,
8F4BFCC51F2EDCAF003EA16B /* BusinessFunctionModel.m in Sources */,
8F250D451F297DA80031B4BC /* XWFilterAnimator+XWBarSwipe.m in Sources */,
......@@ -1170,18 +1258,23 @@
8F250D3D1F297DA80031B4BC /* XWCoolAnimator+XWPortal.m in Sources */,
8F4BFCEB1F2F09D0003EA16B /* HMSaleInputDetail.m in Sources */,
8FEF5E741F285E56001496E3 /* UIImage+Helper.m in Sources */,
8FF9F70A1F380286006B0EC0 /* SaleInputHistory.m in Sources */,
8FEF5E6A1F285E56001496E3 /* MBProgressHUD+Addtions.m in Sources */,
8F4BFCFB1F2F32D5003EA16B /* RepairAddPicCollectionViewCell.m in Sources */,
8FEF5E701F285E56001496E3 /* NSNumber+Addtions.m in Sources */,
8F4BFCD91F2EF6CF003EA16B /* HMSaleInputQuery.m in Sources */,
8FF9F7021F37FF6C006B0EC0 /* MMTopRowCell.m in Sources */,
8F250D271F297DA80031B4BC /* XWInteractiveTransition.m in Sources */,
8F250D391F297DA80031B4BC /* XWCoolAnimator+XWMiddlePageFlip.m in Sources */,
8FF9F7031F37FF6C006B0EC0 /* MMViewController.m in Sources */,
8F250D531F297DA80031B4BC /* XWFilterAnimator+XWRipple.m in Sources */,
8F4BFCD61F2EF56A003EA16B /* SaleInputListTableViewCell.m in Sources */,
8FEF5E841F285F4F001496E3 /* CalculateHelper.m in Sources */,
8F8ED4B71F29EEE600F48461 /* RootNavigationController.m in Sources */,
8F250D641F2980E50031B4BC /* ZJBaseHttpManager.m in Sources */,
8FF9F6FD1F37FF6C006B0EC0 /* CustomMMGridLayout.m in Sources */,
8F4BFCC41F2EDCAF003EA16B /* BusinessFunction.m in Sources */,
8FF9F6E61F37FF09006B0EC0 /* SaleInputHistoryViewController.m in Sources */,
8FEF5E661F285E56001496E3 /* ChineseLogHelper.m in Sources */,
8F250D591F297DA80031B4BC /* XWFilterTransitionView.m in Sources */,
8F250D571F297DA80031B4BC /* XWFilterAnimator.m in Sources */,
......@@ -1190,6 +1283,7 @@
8FEF5EA81F28612B001496E3 /* BaseListViewController.m in Sources */,
8F250D551F297DA80031B4BC /* XWFilterAnimator+XWSwipe.m in Sources */,
8F250D491F297DA80031B4BC /* XWFilterAnimator+XWCopyMachine.m in Sources */,
8FF9F6FB1F37FF6C006B0EC0 /* MMGridCell.m in Sources */,
8F4BFCF41F2F2C0D003EA16B /* SaleInputAddAmountCell.m in Sources */,
8F4BFCE51F2F04CC003EA16B /* NSDate+Additions.m in Sources */,
8F250D331F297DA80031B4BC /* XWCoolAnimator+XWExplode.m in Sources */,
......
#import "SaleInputState.h"
@interface HMSaleInputDetail_contract : ZJBaseModel
@property (nonatomic, strong) NSString <Optional> * code;
......@@ -79,11 +79,14 @@
@property (nonatomic, strong) NSString <Optional> * receiver;
@property (nonatomic, strong) NSString <Optional> * billNumber;
@property (nonatomic, strong) NSString <Optional> * state;
@property (nonatomic, assign) BOOL balance;
@property (nonatomic, strong) HMSaleInputDetail_tenant <Optional> * tenant;
@property (nonatomic, strong) NSMutableArray <HMSaleInputDetail_payments,Optional> * payments;
@property (nonatomic, strong) SaleInputState <Ignore> *z_state;
@end
......@@ -2,6 +2,14 @@
@implementation HMSaleInputDetail
- (SaleInputState<Ignore> *)z_state {
if (!_z_state) {
_z_state = [SaleInputState new];
_z_state.code = self.state;
}
return _z_state;
}
@end
......
@protocol SaleInputHistory_details
@end
@interface SaleInputHistory_details_payment : ZJBaseModel
@property (nonatomic, strong) NSString <Optional> * code;
@property (nonatomic, strong) NSString <Optional> * name;
@property (nonatomic, strong) NSString <Optional> * uuid;
@end
@interface SaleInputHistory_details : ZJBaseModel
@property (nonatomic, strong) NSNumber <Optional> * total;
@property (nonatomic, strong) SaleInputHistory_details_payment <Optional> * payment;
@end
@interface SaleInputHistory : ZJBaseModel
@property (nonatomic, strong) NSString <Optional> * date;
@property (nonatomic, strong) NSNumber <Optional> * total;
@property (nonatomic, strong) NSMutableArray <SaleInputHistory_details,Optional> * details;
@end
#import "SaleInputHistory.h"
@implementation SaleInputHistory
@end
@implementation SaleInputHistory_details
@end
@implementation SaleInputHistory_details_payment
@end
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -7,7 +7,7 @@
//
#import "SaleInputAddViewController.h"
#import "SaleInputHistoryViewController.h"
@interface SaleInputAddViewController ()<UITableViewDelegate, UITableViewDataSource>
@end
......@@ -16,6 +16,9 @@
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 84, 0);
UIBarButtonItem *rightBtn = [[UIBarButtonItem alloc] initWithTitle:@"历史查询" style:UIBarButtonItemStylePlain target:self action:@selector(actionGoHistory)];
self.navigationItem.rightBarButtonItem = rightBtn;
WS(weakSelf);
[self.viewModel httpAllPayments:^(BOOL isSuccess) {
......@@ -107,5 +110,14 @@
}];
}
/**
* 跳转查看历史界面
*/
- (void)actionGoHistory {
SaleInputHistoryViewController *historyVC = [SaleInputHistoryViewController viewControllerWithStoryBoardType:(STORYBOARD_TYPE_SALEINPUT)];
historyVC.contract = self.topView.contract;
[self.navigationController pushViewController:historyVC animated:YES];
}
ZJLazy(SaleInputViewModel, viewModel);
@end
......@@ -16,12 +16,29 @@
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.viewModel.type = SaleInputTypeReadOnly;
self.viewModel.picVM.type = kPicCellTypeRead;
self.topView.right1.hidden = YES;
self.topView.right2.hidden = YES;
self.topView.userInteractionEnabled = NO;
self.totalView.textFieldBillCount.userInteractionEnabled = NO;
self.btnBottom.hidden = YES;
self.title = @"销售录入详情";
WS(weakSelf);
[self.viewModel httpDetailWithUuid:self.listModel.billNumber callBack:^(BOOL isSuccess) {
[weakSelf configUIWithDetail];
}];
}
- (void)configUIWithDetail {
self.topView.contract = self.viewModel.detail.contract;
self.totalView.textFieldBillCount.text = self.viewModel.detail.saleCount.stringValue;
[self.topView.btnStore setTitle:self.viewModel.detail.contract.name forState:UIControlStateNormal];
self.topView.textFieldDate.text = [self.viewModel.detail.saleDate substringToIndex:11];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
......
//
// SaleInputHistoryViewController.h
// RealEstateManagement
//
// Created by Javen on 2016/10/12.
// Copyright © 2016年 上海勾芒信息科技. All rights reserved.
//
#import "BaseViewController.h"
#import "HMSaleInputDetail.h"
@interface SaleInputHistoryViewController : BaseViewController
@property (strong, nonatomic) HMSaleInputDetail_contract *contract;
@end
//
// SaleInputHistoryViewController.m
// RealEstateManagement
//
// Created by Javen on 2016/10/12.
// Copyright © 2016年 上海勾芒信息科技. All rights reserved.
//
#import "CalculateHelper.h"
#import "HistoryTopView.h"
#import "MMViewController.h"
#import "SaleInputHistoryViewController.h"
#import "SaleInputHistory.h"
@interface SaleInputHistoryViewController ()
@property (weak, nonatomic) IBOutlet HistoryTopView *topView;
@property (nonatomic, strong) MMViewController *sheetVC;
@property (nonatomic, strong) UIImageView *imageNoData;
@property (nonatomic, strong) NSMutableArray *arrData;
@end
@implementation SaleInputHistoryViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"历史查询";
WS(weakSelf);
[self.topView setBlockQuery:^{
[weakSelf httpHistoryData];
}];
[self updateUI];
[self configNoDataPic];
// Do any additional setup after loading the view.
}
- (void)viewDidLayoutSubviews
{
self.imageNoData.frame = CGRectMake(0, self.topView.bottom + 20, 150, 150);
self.imageNoData.centerX = self.view.centerX;
self.imageNoData.centerY = self.sheetVC.view.centerY + self.topView.bottom;
}
- (void)updateUI {
if (self.contract == nil) {
self.topView.labelShopName.text = @"请选择合同";
return;
}
self.topView.labelShopName.text = self.contract.name;
}
- (void)httpHistoryData
{
NSString *startDate = [NSString stringWithFormat:@"%@ 00:00:00", self.topView.startDate.text];
NSString *endDate = [NSString stringWithFormat:@"%@ 00:00:00", self.topView.endDate.text];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:self.contract.uuid, @"contract", [NSDictionary dictionaryWithObjectsAndKeys:startDate, @"beginDate", endDate, @"endDate", nil], @"dateRange", nil];
[MBProgressHUD j_loading];
WS(weakSelf);
[ZJHttpManager POST:@"hdmall/salesinput/getSalesTotal" parameters:params complete:^(id responseObject, NSError *error) {
[MBProgressHUD j_hideLoadingView];
if (kIsResponse) {
[weakSelf configDataArray:responseObject[@"data"]];
} else {
kFalseHttpTips;
}
}];
}
- (void)configNoDataPic
{
if (self.contract == nil) {
self.sheetVC.view.hidden = YES;
self.imageNoData.hidden = NO;
}
if (self.arrData.count > 0) {
self.sheetVC.view.hidden = NO;
self.imageNoData.hidden = YES;
} else {
self.sheetVC.view.hidden = YES;
self.imageNoData.hidden = NO;
}
}
/**
* 跳转选择商铺界面
*/
- (IBAction)actionChooseShop:(UIButton *)sender
{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"选择合同" message:nil preferredStyle:(UIAlertControllerStyleAlert)];
for (UserInfo_contracts *contract in AppGlobal.user.contracts) {
UIAlertAction *action = [UIAlertAction actionWithTitle:contract.name style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
self.contract = [HMSaleInputDetail_contract modelWithDic:contract.toDictionary];
self.topView.labelShopName.text = contract.name;
}];
[alert addAction:action];
}
UIAlertAction *action = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:action];
[self.navigationController presentViewController:alert animated:YES completion:nil];
}
- (void)configDataArray:(NSArray *)arr
{
NSMutableArray *arrData = [NSMutableArray array];
self.arrData = arrData;
for (NSDictionary *dict in arr) {
SaleInputHistory *model = [SaleInputHistory modelWithDic:dict];
[arrData addObject:model];
}
NSMutableArray *totalData = [NSMutableArray array];
//第一行的数据
NSMutableArray *data = [NSMutableArray array];
/**
* 支付方式
*/
NSMutableArray *payments = [NSMutableArray array];
[data addObject:@"日期"];
[data addObject:@"金额(元)"];
for (SaleInputHistory *model in arrData) {
for (NSInteger i = 0; i < model.details.count; i++) {
SaleInputHistory_details *payment = model.details[i];
if (![data containsObject:payment.payment.name]) {
[data addObject:payment.payment.name];
[payments addObject:payment.payment.name];
}
}
}
// [data addObject:@"附件"];
[totalData addObject:data];
//把所有支付金额分类归纳
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (SaleInputHistory *model in arrData) {
for (SaleInputHistory_details *payment in model.details) {
NSMutableArray *array = [dict objectForKey:payment.payment.name];
if (array) {
[array addObject:payment.total];
} else {
NSMutableArray *array = [NSMutableArray array];
[array addObject:payment.total];
[dict setObject:array forKey:payment.payment.name];
}
}
}
//中间的数据
for (NSInteger i = 0; i < arrData.count; i++) {
SaleInputHistory *historyModel = arrData[i];
NSMutableArray *arrCenter = [NSMutableArray array];
[arrCenter addObject:historyModel.date];
[arrCenter addObject:[NSString stringWithFormat:@"%.2f", historyModel.total.doubleValue]];
for (NSInteger j = 0; j < payments.count; j++) {
NSString *paymentName = payments[j];
BOOL isHas = NO;
for (SaleInputHistory_details *payment in historyModel.details) {
if ([paymentName isEqualToString:payment.payment.name]) {
[arrCenter addObject:[CalculateHelper getMoneyStringFrom:payment.total]];
isHas = YES;
}
}
if (!isHas) {
[arrCenter addObject:@"0.00"];
}
}
// [arrCenter addObject:@"查看附件"];
[totalData addObject:arrCenter];
}
//最后一行的数据
NSMutableArray *bottomRow = [NSMutableArray array];
[bottomRow addObject:@"总计"];
NSDecimalNumber *total = [NSDecimalNumber decimalNumberWithString:@"0"];
for (SaleInputHistory *model in arrData) {
total = [CalculateHelper add:total num2:model.total];
}
[bottomRow addObject:[CalculateHelper getMoneyStringFrom:total]];
for (NSInteger i = 0; i < payments.count; i++) {
NSString *paymentName = payments[i];
NSMutableArray *arrPaymentNumber = [dict objectForKey:paymentName];
NSDecimalNumber *number = [NSDecimalNumber decimalNumberWithString:@"0"];
for (NSNumber *obj in arrPaymentNumber) {
number = [CalculateHelper add:number num2:obj];
}
[bottomRow addObject:[CalculateHelper getMoneyStringFrom:number]];
}
// [bottomRow addObject:@"附件"];
NSInteger totalRowCount = totalData.count;
if (totalRowCount < 9) {
for (NSInteger i = totalRowCount; i < 9; i++) {
NSMutableArray *arr = [NSMutableArray array];
for (NSString *str in bottomRow) {
[arr addObject:@""];
}
[totalData addObject:arr];
}
}
[totalData addObject:bottomRow];
self.sheetVC.tableData = totalData;
[self.sheetVC reloadData];
[self configNoDataPic];
}
- (UIImageView *)imageNoData
{
if (!_imageNoData) {
_imageNoData = [[UIImageView alloc] init];
_imageNoData.center = self.view.center;
_imageNoData.contentMode = UIViewContentModeScaleAspectFit;
_imageNoData.image = [UIImage imageNamed:@"list_nodata"];
[self.view addSubview:_imageNoData];
}
return _imageNoData;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
if ([segue.identifier isEqualToString:@"MMViewController"]) {
self.sheetVC = segue.destinationViewController;
}
}
/*
*/
@end
......@@ -24,6 +24,7 @@
// Do any additional setup after loading the view.
self.title = @"销售录入记录";
[self refresh];
self.tableView.rowHeight = 159;
self.btnAdd.hidden = !AppGlobal.permission.saleinput.newField;
}
......
//
// SaleInputState.h
// HDMall
//
// Created by Javen on 2017/8/4.
// Copyright © 2017年 上海勾芒信息科技. All rights reserved.
//
#import <Foundation/Foundation.h>
#define kSaleInputStateTypeEffect @"effect"
#define kSaleInputStateTypeIneffect @"ineffect"
@interface SaleInputState : NSObject
@property (strong, nonatomic) NSString *text;
@property (strong, nonatomic) NSString *code;
@property (strong, nonatomic) NSString *img;
/**
存放图片名状态名等信息
*/
@property (strong, nonatomic) NSDictionary *dicData;
//+ (SaleInputState *)stateWithText:(NSString *)text;
@end
//
// SaleInputState.m
// HDMall
//
// Created by Javen on 2017/8/4.
// Copyright © 2017年 上海勾芒信息科技. All rights reserved.
//
#import "SaleInputState.h"
@implementation SaleInputState
- (instancetype)init {
self = [super init];
if (self) {
self.dicData = @{ kSaleInputStateTypeIneffect : @{@"text" : @"未生效",
@"img" : @"StateIneffect"},
kSaleInputStateTypeEffect : @{@"text" : @"已生效",
@"img" : @"StateEffect"} };
}
return self;
}
- (void)setCode:(NSString *)code {
_code = code;
self.text = [[self.dicData objectForKey:code] objectForKey:@"text"];
self.img = [[self.dicData objectForKey:code] objectForKey:@"img"];
}
@end
......@@ -36,16 +36,10 @@
}];
}
- (void)httpUpload:(void (^)(void))complete {
}
- (void)httpSubmit:(blockCallBack)callBack {
//赋值
if (self.request.contract == nil) {
[MBProgressHUD j_error:@"请选择合同" complete:nil];
return;
}
kCanNotBeNil(self.request.contract, @"请选择合同");
self.request.oper = AppGlobal.user.code;
self.request.store = [HMSaleInputDetail_store modelWithDic:AppGlobal.user.store.toDictionary];
// self.request.tenant = [HMSaleInputDetail_tenant modelWithDic:AppGlobal.user.tenant.toDictionary];
......@@ -93,8 +87,11 @@
[ZJHttpManager GET:url parameters:nil complete:^(id responseObject, NSError *error) {
if (kIsResponse) {
self.detail = [HMSaleInputDetail modelWithDic:responseObject[@"data"]];
self.arrPayments = self.detail.payments;
callBack(true);
}else{
callBack(false);
kFalseHttpTips;
}
}];
......
//
// HistoryTopView.h
// vanke
//
// Created by Z on 16/4/18.
// Copyright © 2016年 gomore. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface HistoryTopView : UIView
@property (weak, nonatomic) IBOutlet UIButton *buttonStartDate;
@property (weak, nonatomic) IBOutlet UIButton *buttonEndDate;
@property (weak, nonatomic) IBOutlet UITextField *startDate;
@property (weak, nonatomic) IBOutlet UITextField *endDate;
@property (weak, nonatomic) IBOutlet UILabel *labelShopName;
@property (weak, nonatomic) IBOutlet UIButton *buttonChooseShop;
@property (weak, nonatomic) IBOutlet UIButton *buttonQuery;
@property (weak, nonatomic) IBOutlet UIImageView *imgRight;
@property (nonatomic, copy) void (^blockQuery)(void);
- (void)startDateBecomeFistRespond;
- (void)endDateBecomeFistRespond;
@end
//
// HistoryTopView.m
// vanke
//
// Created by Z on 16/4/18.
// Copyright © 2016年 gomore. All rights reserved.
//
#import "HistoryTopView.h"
@interface HistoryTopView ()<UITextFieldDelegate>
@property (nonatomic, strong) UIDatePicker *startDatePicker;
@property (nonatomic, strong) UIDatePicker *endDatePicker;
@end
@implementation HistoryTopView
-(void)awakeFromNib {
[super awakeFromNib];
self.startDate.text = [[NSDate date] yearMonthDayString];
self.startDate.delegate = self;
self.startDate.tag = 1;
self.endDate.text = [[NSDate date] yearMonthDayString];
self.endDate.delegate = self;
self.endDate.tag = 2;
self.startDatePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 0, kWidth, 216)];
self.startDatePicker.datePickerMode = UIDatePickerModeDate;
self.startDatePicker.maximumDate = [NSDate date];
self.startDate.inputView = self.startDatePicker;
self.endDatePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 0, kWidth, 216)];
self.endDatePicker.datePickerMode = UIDatePickerModeDate;
self.endDatePicker.maximumDate = [NSDate date];
self.endDate.inputView = self.endDatePicker;
if (!AppGlobal.isMall) {
self.imgRight.hidden = YES;
self.buttonChooseShop.userInteractionEnabled = NO;
}
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField.tag == 1) {
textField.text = [self.startDatePicker.date yearMonthDayString];
}else{
textField.text = [self.endDatePicker.date yearMonthDayString];
}
}
- (IBAction)actionStart:(id)sender {
[self startDateBecomeFistRespond];
}
- (IBAction)actionEnd:(id)sender {
[self endDateBecomeFistRespond];
}
- (void)startDateBecomeFistRespond {
[self.startDate becomeFirstResponder];
}
- (void)endDateBecomeFistRespond {
[self.endDate becomeFirstResponder];
}
- (IBAction)queryAction:(id)sender {
HIDE_KEYBOARD;
NSLog(@"查询");
self.blockQuery();
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
@end
......@@ -20,7 +20,7 @@
self.labelTItle.text = self.model.contract.name;
self.labelBillCount.text = [NSString stringWithFormat:@"%ld",self.model.saleCount.integerValue];
self.labelTime.text = self.model.saleDate;
// self.imgState.image = [UIImage imageNamed:self.model.stateImageName];
self.imgState.image = [UIImage imageNamed:self.model.z_state.img];
self.labelBillNumber.text = self.model.billNumber;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
......
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ReadingStateEffect.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ReadingStateEffect@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ReadingStateEffect@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ReadingStateIneffect.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ReadingStateIneffect@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ReadingStateIneffect@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
......@@ -61,4 +61,6 @@
+ (void)j_hideLoadingView;
+ (void)j_hideOnView:(UIView *)view;
+ (void)j_warningMessage:(NSString *)msg;
@end
......@@ -142,4 +142,51 @@ static CGFloat const delay = 1.2;
}
}
+ (void)j_warningMessage:(NSString *)msg {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:msg preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:nil];
[alert addAction:action];
[[self getCurrentVC].navigationController presentViewController:alert animated:YES completion:nil];
}
//获取当前屏幕显示的viewcontroller
+ (UIViewController *)getCurrentVC
{
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *currentVC = [self getCurrentVCFrom:rootViewController];
return currentVC;
}
+ (UIViewController *)getCurrentVCFrom:(UIViewController *)rootVC
{
UIViewController *currentVC;
if ([rootVC presentedViewController]) {
// 视图是被presented出来的
rootVC = [rootVC presentedViewController];
}
if ([rootVC isKindOfClass:[UITabBarController class]]) {
// 根视图为UITabBarController
currentVC = [self getCurrentVCFrom:[(UITabBarController *)rootVC selectedViewController]];
} else if ([rootVC isKindOfClass:[UINavigationController class]]){
// 根视图为UINavigationController
currentVC = [self getCurrentVCFrom:[(UINavigationController *)rootVC visibleViewController]];
} else {
// 根视图为非导航类
currentVC = rootVC;
}
return currentVC;
}
@end
......@@ -26,33 +26,32 @@ typedef NS_ENUM(NSInteger, STORYBOARD_TYPE_) {
};
/**
* 判断不能为空并提示
* 字符串/数组/
*
* @param obj 对象
* @param message 为空时提示信息
*/
#define kCanNotBeNil(obj, message) \
if (obj == nil) { \
[MBProgressHUD j_error:message complete:nil]; \
[MBProgressHUD j_warningMessage:message];; \
return; \
} \
if (obj == false) { \
[MBProgressHUD j_error:message complete:nil]; \
[MBProgressHUD j_warningMessage:message]; \
return; \
} \
if ([obj isKindOfClass:[NSString class]]) { \
NSString *string = (NSString *) obj; \
NSString *temp = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; \
if ([temp length] == 0) { \
[MBProgressHUD j_error:message \
complete:nil]; \
[MBProgressHUD j_warningMessage:message]; \
return; \
} \
} \
if ([obj isKindOfClass:[NSArray class]]) { \
NSArray *array = (NSArray *) obj; \
if (array.count == 0) { \
[MBProgressHUD j_error:message complete:nil]; \
[MBProgressHUD j_warningMessage:message]; \
return; \
} \
}
......
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
@interface MMGridCell : UICollectionViewCell
@property (nonatomic, strong) UILabel *textLabel;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMGridCell.h"
@implementation MMGridCell
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
CGRect rect = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height);
_textLabel = [[UILabel alloc] initWithFrame:rect];
_textLabel.backgroundColor = [UIColor clearColor];
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_textLabel.font = [UIFont boldSystemFontOfSize:17];
_textLabel.numberOfLines = 0;
[self.contentView addSubview:_textLabel];
}
return self;
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
if (selected) {
[self.contentView setBackgroundColor:[UIColor lightGrayColor]];
} else {
[self.contentView setBackgroundColor:[UIColor clearColor]];
}
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
@interface MMLeftColumnCell : UICollectionViewCell
@property (nonatomic, strong) UILabel *textLabel;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMLeftColumnCell.h"
@implementation MMLeftColumnCell
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
CGRect rect = CGRectMake(10.0f, 0.0f, frame.size.width-20.0f, frame.size.height);
_textLabel = [[UILabel alloc] initWithFrame:rect];
_textLabel.backgroundColor = [UIColor clearColor];
_textLabel.font = [UIFont boldSystemFontOfSize:14.0f];
_textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:_textLabel];
}
return self;
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
/**
`MMGridLayout` provides the grid layout of cells for the spreadsheet.
Code for the layout was inspired by https://github.com/mattlangtree/grid-view-csv
*/
@interface CustomMMGridLayout : UICollectionViewLayout
///---------------------------------------
/// @name Accessing Layout Properties
///---------------------------------------
/**
The size for each cell in the grid.
@discussion This will be set either by `MMSpreadsheetView` or `MMGridLayout` using a delegate call to collectionView:layout:sizeForItemAtIndexPath: via the `setItemSize:` method. The layout is invalidated when the itemSize is changed.
*/
@property (nonatomic, assign) CGSize itemSize;
/**
The space between cells.
@discussion This allows for setting the spacing between cells. Lines are not drawn, but show the background of the collection view. The layout is invalidated when the cellSpacing is changed. Default is 1.
*/
@property (nonatomic, assign) CGFloat cellSpacing;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "CustomMMGridLayout.h"
#import "CustomMMSpreadsheetView.h"
@interface CustomMMGridLayout ()
@property (nonatomic, assign) NSInteger gridRowCount;
@property (nonatomic, assign) NSInteger gridColumnCount;
@property (nonatomic, assign) BOOL isInitialized;
@end
@implementation CustomMMGridLayout
- (id)init {
self = [super init];
if (self) {
_cellSpacing = 1.0f;
_itemSize = CGSizeMake(120.0f, 120.0f);
}
return self;
}
- (void)setItemSize:(CGSize)itemSize {
_itemSize = CGSizeMake(itemSize.width + self.cellSpacing, itemSize.height + self.cellSpacing);
[self invalidateLayout];
}
- (void)setCellSpacing:(CGFloat)cellSpacing {
_cellSpacing = cellSpacing;
[self invalidateLayout];
}
- (void)prepareLayout {
[super prepareLayout];
self.gridRowCount = [self.collectionView numberOfSections];
self.gridColumnCount = [self.collectionView numberOfItemsInSection:0];
if (!self.isInitialized) {
id<UICollectionViewDelegateFlowLayout> delegate = (id)self.collectionView.delegate;
CGSize size = [delegate collectionView:self.collectionView
layout:self
sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
self.itemSize = size;
self.isInitialized = YES;
}
}
- (CGSize)collectionViewContentSize {
if (!self.isInitialized) {
[self prepareLayout];
}
CGSize size = CGSizeMake(self.gridColumnCount * self.itemSize.width, self.gridRowCount * self.itemSize.height);
return size;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *attributes = [NSMutableArray array];
NSUInteger startRow = floorf(rect.origin.y / self.itemSize.height);
NSUInteger startCol = floorf(rect.origin.x / self.itemSize.width);
NSUInteger endRow = MIN(self.gridRowCount - 1, ceilf(CGRectGetMaxY(rect) / self.itemSize.height));
NSUInteger endCol = MIN(self.gridColumnCount - 1, ceilf(CGRectGetMaxX(rect) / self.itemSize.width));
NSParameterAssert(self.gridRowCount > 0);
NSParameterAssert(self.gridColumnCount > 0);
for (NSUInteger row = startRow; row <= endRow; row++) {
for (NSUInteger col = startCol; col <= endCol; col++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:col inSection:row];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[attributes addObject:layoutAttributes];
}
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(indexPath.item * self.itemSize.width, indexPath.section * self.itemSize.height, self.itemSize.width-self.cellSpacing, self.itemSize.height-self.cellSpacing);
return attributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return NO;
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
@class CustomMMSpreadsheetView;
/**
An object that adopts the `MMSpreadsheetViewDataSource` protocol is responsible for providing the data and views required by a spreadsheet view. A data source object represents your app’s data model and vends information to the spreadsheet view as needed. It also handles the creation and configuration of cells used by the spreadsheet view to display your data.
All data source objects must implement the `numberOfRowsInSpreadsheetView:`, `numberOfColumnsInSpreadsheetView:` and `spreadsheetView:cellForItemAtIndexPath:` methods. These methods are responsible for returning the number of items in the spreadsheet view along with the items themselves. In addition, the data source should implement the `spreadsheetView:sizeForItemAtIndexPath:` method for determining cell sizes in the individual collection views.
When configuring the spreadsheet view object, assign your data source to its dataSource property. For more information about how a spreadsheet view works with its data source to present content, see `MMSpreadsheetView` Class Reference.
@warning An NSIndexPath category is provided so that *indexPath.mmSpreadsheetRow* represents a row and *indexPath.mmSpreadsheetColumn* represents a column.
*/
@protocol MMSpreadsheetViewDataSource <NSObject>
@optional
///---------------------------------------
/// @name Spreadsheet Data Source Protocol
///---------------------------------------
/**
The cell size for all cells in a collection view. Header cells can have a different size than non-header cells, but all cells within a single collection view (or spreadsheet view section) will be the same size.
@param spreadsheetView The spreadsheet view object that is requesting the size information.
@param indexPath The index path of the cell.
@return A CGSize containing the height and width of the cell at the given indexPath.
*/
- (CGSize)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
@required
/**
The number of horizontal rows for the entire `MMSpreadsheetView`.
@param spreadsheetView The spreadsheet view object that is requesting the information.
@return An NSInteger containing the number of horizontal rows in the spreadsheet view.
*/
- (NSInteger)numberOfRowsInSpreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView;
/**
The number of vertical columns for the entire `MMSpreadsheetView`.
@param spreadsheetView The spreadsheet view object that is requesting the information.
@return An NSInteger containing the number of vertical columns in the spreadsheet view.
*/
- (NSInteger)numberOfColumnsInSpreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView;
/**
Create and configure a cell for the `MMSpreadsheetView`. The cell that is returned must be retrieved from a call to -`dequeueReusableCellWithReuseIdentifier:forIndexPath:`.
@param spreadsheetView The spreadsheet view object that is requesting the cell.
@param indexPath The index path of the cell.
@return The dequeued `UICollectionViewCell` object.
*/
- (UICollectionViewCell *)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView cellForItemAtIndexPath:(NSIndexPath *)indexPath collectionView:(UICollectionView *)collectionView;
@end
/**
The `MMSpreadsheetViewDelegate` protocol defines methods that allow you to manage the selection and highlighting of items in a spreadsheet view and to perform actions on those items. The methods of this protocol are all optional.
When configuring the spreadsheet view object, assign your delegate object to its delegate property. For more information, see `MMSpreadsheetView` Class Reference.
@warning An NSIndexPath category is provided so that *indexPath.mmSpreadsheetRow* represents a row and *indexPath.mmSpreadsheetColumn* represents a column.
*/
@protocol MMSpreadsheetViewDelegate <NSObject>
@optional
///---------------------------------------
/// @name Managing the Selected Cells
///---------------------------------------
/**
Tells the delegate that the item at the specified index path was selected.
@param spreadsheetView The spreadsheet view object that is notifying you of the selection change.
@param indexPath The index path of the cell that was selected.
*/
- (void)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
///---------------------------------------
/// @name Managing Actions for Cells (Implement All or None)
///---------------------------------------
/**
Asks the delegate if an action menu should be displayed for the specified item.
@param spreadsheetView The spreadsheet view object that is making the request.
@param indexPath The index path of the cell that was selected.
@return YES if the editing menu should be shown positioned near the item and pointing to it, otherwise NO.
@discussion If the user tap-holds a certain item in the spreadsheet view, this method (if implemented) is invoked first. Return YES if you want to permit the editing menu to be displayed. Return NO if the editing menu shouldn’t be shown—for example, you might return NO if the corresponding item contains data that should not be copied or pasted over.
If you do not implement this method, the default return value is NO.
**Note**: This is part of a set of three methods providing support for copy/paste actions on cells. All three should be implemented if any are.
<p><strong>Availability</strong><br /></p><p>Available in iOS 6.0 and later.</p>
@see spreadsheetView:canPerformAction:forItemAtIndexPath:withSender:
spreadsheetView:performAction:forItemAtIndexPath:withSender:
*/
- (BOOL)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
/**
Asks the delegate if it can perform the specified action on an item in the collection view.
@param spreadsheetView The spreadsheet view object that is making the request.
@param action The selector identifying the action to be performed.
@param indexPath The index path of the affected item.
@param sender The object that wants to initiate the action.
@return YES if the command corresponding to action should appear in the editing menu or NO if it should not.
@discussion This method is invoked after the `spreadsheetView:shouldShowMenuForItemAtIndexPath:` method. It gives you the opportunity to exclude commands from the editing menu. For example, the user might have copied some content from one item and wants to paste it into another item that cannot accept the content. In such a case, your method could return NO to prevent the display of the relevant command.
If you do not implement this method, the default return value is NO.
**Note**: This is part of a set of three methods providing support for copy/paste actions on cells. All three should be implemented if any are.
<p><strong>Availability</strong><br /></p><p>Available in iOS 6.0 and later.</p>
@see spreadsheetView:performAction:forItemAtIndexPath:withSender:
spreadsheetView:shouldShowMenuForItemAtIndexPath:
*/
- (BOOL)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
/**
Tells the delegate to perform the specified action on an item in the spreadsheet view.
@param spreadsheetView The spreadsheet view object that is making the request.
@param action The selector representing the action to be performed.
@param indexPath The index path of the affected item.
@param sender The object that initiated the action.
@discussion If the user taps an action in the editing menu, the spreadsheet view calls this method. Your implementation of this method should do whatever is appropriate for the action. For example, for a copy action, it should extract the relevant item content and write it to the general pasteboard or an application (private) pasteboard.
For information about how to perform pasteboard-related operations, see `UIPasteboard` Class Reference.
**Note**: This is part of a set of three methods providing support for copy/paste actions on cells. All three should be implemented if any are.
<p><strong>Availability</strong><br /></p><p>Available in iOS 6.0 and later.</p>
@see spreadsheetView:shouldShowMenuForItemAtIndexPath:
spreadsheetView:canPerformAction:forItemAtIndexPath:withSender:
*/
- (void)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
@end
/**
`MMSpreadsheetView` is a configurable spreadsheet-like view. You choose to configure the view with zero or more header rows and zero or more header columns. Depending on this configuration, up to four collection views will be coordinated to provide the user interface.
When adding a spreadsheet view to your user interface, your app’s main job is to manage the data associated with the view. The spreadsheet view gets its data from the data source object, which is an object that conforms to the `MMSpreadsheetViewDataSource` protocol and is provided by your app. The spreadsheet view presents items onscreen using a cell, which is an instance of the UICollectionViewCell class that your data source configures and provides.
## Creating an MMSpreadsheetView
`MMSpreadsheetView` is similar to a `UICollectionView` or `UITableView`. To create an `MMSpreadsheetView`, you instantiate the view using initWithNumberOfHeaderRows:numberOfHeaderColumns:frame:.
@discussion **Note**: Set the background color of the spreadsheet view to change the separator line color. **However**, you will also see this color when the scroll goes into a bounce.
@warning An NSIndexPath category is provided so that *indexPath.mmSpreadsheetRow* represents a row and *indexPath.mmSpreadsheetColumn* represents a column.
@warning as the number of cells shown increases, scrolling performance declines. A large grid (1000x1000) takes a long time to initialize, but if the cell sizes are large enough (150x150), scrolling performance is not affected. However, a small grid (50x50) of (20x20) cells basically doesn't scroll.
*/
typedef NS_ENUM(NSUInteger, MMSpreadsheetViewCollection) {
MMSpreadsheetViewCollectionUpperLeft = 1,
MMSpreadsheetViewCollectionUpperRight,
MMSpreadsheetViewCollectionLowerLeft,
MMSpreadsheetViewCollectionLowerRight,
MMSpreadsheetViewCollectionBottom,
};
@interface CustomMMSpreadsheetView : UIView
///---------------------------------------
/// @name Spreadsheet View Properties
///---------------------------------------
/**
The object that acts as the delegate of the spreadsheet view.
@discussion The delegate must adopt the `MMSpreadsheetViewDelegate` protocol. The spreadsheet view maintains a weak reference to the delegate object.
The delegate object is responsible for managing selection behavior and interactions with individual items.
*/
@property (nonatomic, weak) id<MMSpreadsheetViewDelegate> delegate;
/**
The object that provides the data for the spreadsheet view.
@discussion The data source must adopt the `MMSpreadsheetViewDataSource` protocol. The spreadsheet view maintains a weak reference to the data source object.
*/
@property (nonatomic, weak) id<MMSpreadsheetViewDataSource> dataSource;
///---------------------------------------
/// @name Initializing & Setup
///---------------------------------------
/**
Initializes and returns a spreadsheet view object given the number of header rows, columns and bounds. This method is the *designated initializer*.
@param headerRowCount The number of header **rows** at the top of the view.
@param headerColumnCount The number of header **columns** on the left side of the view.
@param frame The frame rectangle for the spreadsheet view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
@return An initialized spreadsheet view object or nil if the object could not be created.
*/
- (id)initWithNumberOfHeaderRows:(NSUInteger)headerRowCount numberOfHeaderColumns:(NSUInteger)headerColumnCount frame:(CGRect)frame;
/**
Register a class for use in creating new spreadsheet view cells.
@param cellClass The class of a cell that you want to use in the spreadsheet view.
@param identifier The reuse identifier to associate with the specified class. This parameter must not be nil and must not be an empty string.
@discussion Prior to calling the dequeueReusableCellWithReuseIdentifier:forIndexPath: method of the spreadsheet view, you must use this method to tell the spreadsheet view how to create a new cell of the given type. If a cell of the specified type is not currently in a reuse queue, the spreadsheet view uses the provided information to create a new cell object automatically.
If you previously registered a class with the same reuse identifier, the class you specify in the cellClass parameter replaces the old entry. You may specify nil for cellClass if you want to unregister the class from the specified reuse identifier.
*/
- (void)registerCellClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
///---------------------------------------
/// @name Creating Spreadsheet View Cells
///---------------------------------------
/**
Returns a reusable cell object located by its identifier.
@param identifier The reuse identifier for the specified cell. This parameter must not be nil.
@param indexPath The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the spreadsheet view.
@return A valid `UICollectionViewCell` object.
@discussion Call this method from your data source object when asked to provide a new cell for the spreadsheet view. This method dequeues an existing cell if one is available or creates a new one based on the class you previously registered.
@warning You must register a class using the `registerCellClass:forCellWithReuseIdentifier:` method before calling this method.
@discussion If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its initWithFrame: method. If an existing cell was available for reuse, this method calls the cell’s prepareForReuse method instead.
*/
- (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
///---------------------------------------
/// @name Managing the Selection
///---------------------------------------
/**
Deselects the item at the specified index.
@param indexPath The index path of the item to deselect. Specifying `nil` for this parameter removes the current selection.
@param animated Specify YES to animate the change in the selection or NO to make the change without animation.
@discussion If the allowsSelection property is NO, calling this method has no effect.
This method does not cause any selection-related delegate methods to be called.
*/
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
///---------------------------------------
/// @name Reloading Content
///---------------------------------------
/**
Reloads all of the data for the spreadsheet view.
@discussion Call this method to reload all of the items in the spreadsheet view. This causes the spreadsheet view to discard any currently visible items and redisplay them. For efficiency, the spreadsheet view only displays those cells that are visible. If the spreadsheet data shrinks as a result of the reload, the spreadsheet view adjusts its scrolling offsets accordingly.
*/
- (void)reloadData;
///---------------------------------------
/// @name Managing the Scroll Indicator
///---------------------------------------
/**
The distance the scroll indicators are inset from the edge of the scroll view.
@discussion The default value is UIEdgeInsetsZero.
*/
@property (nonatomic, assign) UIEdgeInsets scrollIndicatorInsets;
/**
A Boolean value that controls whether the horizontal scroll indicator is visible.
@discussion The default value is YES. The indicator is visible while tracking is underway and fades out after tracking.
*/
@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;
/**
A Boolean value that controls whether the vertical scroll indicator is visible.
@discussion The default value is YES. The indicator is visible while tracking is underway and fades out after tracking.
*/
@property (nonatomic, assign) BOOL showsVerticalScrollIndicator;
/**
Displays the scroll indicators momentarily.
@discussion You should call this method whenever you bring the scroll view to front.
*/
- (void)flashScrollIndicators;
///---------------------------------------
/// @name Scroll View Properties
///---------------------------------------
/**
A Boolean value that controls whether the spreadsheet view bounces past the edge of content and back again.
@discussion If the value of this property is YES, the spreadsheet view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is NO, scrolling stops immediately at the content boundary without bouncing. The default value is YES.
*/
@property (nonatomic, assign) BOOL bounces;
@property (nonatomic, strong) NSMutableArray *tableData;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "CustomMMGridLayout.h"
#import "CustomMMSpreadsheetView.h"
#import "NSIndexPath+MMSpreadsheetView.h"
#import <QuartzCore/QuartzCore.h>
typedef NS_ENUM(NSUInteger, MMSpreadsheetHeaderConfiguration) {
MMSpreadsheetHeaderConfigurationNone = 0,
MMSpreadsheetHeaderConfigurationColumnOnly,
MMSpreadsheetHeaderConfigurationRowOnly,
MMSpreadsheetHeaderConfigurationBoth,
};
const static CGFloat MMSpreadsheetViewGridSpace = 1.0f;
const static CGFloat MMSpreadsheetViewScrollIndicatorWidth = 5.0f;
const static CGFloat MMSpreadsheetViewScrollIndicatorSpace = 3.0f;
const static CGFloat MMSpreadsheetViewScrollIndicatorMinimum = 25.0f;
const static CGFloat MMScrollIndicatorDefaultInsetSpace = 2.0f;
const static NSUInteger MMScrollIndicatorTag = 12345;
@interface CustomMMSpreadsheetView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, assign) NSUInteger headerRowCount;
@property (nonatomic, assign) NSUInteger headerColumnCount;
@property (nonatomic, assign) MMSpreadsheetHeaderConfiguration spreadsheetHeaderConfiguration;
@property (nonatomic, strong) UIScrollView *controllingScrollView;
@property (nonatomic, strong) UIView *upperLeftContainerView;
@property (nonatomic, strong) UIView *upperRightContainerView;
@property (nonatomic, strong) UIView *lowerLeftContainerView;
@property (nonatomic, strong) UIView *lowerRightContainerView;
@property (nonatomic, strong) UIView *bottomContainerView;
@property (nonatomic, strong) UICollectionView *upperLeftCollectionView;
@property (nonatomic, strong) UICollectionView *upperRightCollectionView;
@property (nonatomic, strong) UICollectionView *lowerLeftCollectionView;
@property (nonatomic, strong) UICollectionView *lowerRightCollectionView;
@property (nonatomic, strong) UICollectionView *bottomCollectionView;
@property (nonatomic, strong) UILabel *bottomLeftView;
@property (nonatomic, assign, getter=isUpperRightBouncing) BOOL upperRightBouncing;
@property (nonatomic, assign, getter=isLowerLeftBouncing) BOOL lowerLeftBouncing;
@property (nonatomic, assign, getter=isLowerRightBouncing) BOOL lowerRightBouncing;
@property (nonatomic, strong) UIView *verticalScrollIndicator;
@property (nonatomic, strong) UIView *horizontalScrollIndicator;
@property (nonatomic, strong) UICollectionView *selectedItemCollectionView;
@property (nonatomic, strong) NSIndexPath *selectedItemIndexPath;
@end
@implementation CustomMMSpreadsheetView
- (id)init
{
return [self initWithNumberOfHeaderRows:0 numberOfHeaderColumns:0 frame:CGRectZero];
}
- (id)initWithFrame:(CGRect)frame
{
return [self initWithNumberOfHeaderRows:0 numberOfHeaderColumns:0 frame:frame];
}
#pragma mark - MMSpreadsheetView designated initializer
- (id)initWithNumberOfHeaderRows:(NSUInteger)headerRowCount numberOfHeaderColumns:(NSUInteger)headerColumnCount frame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_scrollIndicatorInsets = UIEdgeInsetsZero;
_showsVerticalScrollIndicator = YES;
_showsHorizontalScrollIndicator = YES;
_headerRowCount = headerRowCount;
_headerColumnCount = headerColumnCount;
if (headerColumnCount == 0 && headerRowCount == 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationNone;
} else if (headerColumnCount > 0 && headerRowCount == 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationColumnOnly;
} else if (headerColumnCount == 0 && headerRowCount > 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationRowOnly;
} else if (headerColumnCount > 0 && headerRowCount > 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationBoth;
}
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.backgroundColor = [UIColor grayColor];
[self setupSubviews];
}
return self;
}
#pragma mark - Public Functions
- (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *collectionViewIndexPath = [self collectionViewIndexPathFromDataSourceIndexPath:indexPath];
UICollectionView *collectionView = [self collectionViewForDataSourceIndexPath:indexPath];
NSAssert(collectionView, @"No collectionView Returned!");
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:collectionViewIndexPath];
return cell;
}
- (void)registerCellClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier
{
[self.upperLeftCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.upperRightCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.lowerLeftCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.lowerRightCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.bottomCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
}
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated
{
NSIndexPath *collectionViewIndexPath = [self collectionViewIndexPathFromDataSourceIndexPath:indexPath];
UICollectionView *collectionView = [self collectionViewForDataSourceIndexPath:indexPath];
NSAssert(collectionView, @"No collectionView Returned!");
[collectionView deselectItemAtIndexPath:collectionViewIndexPath animated:animated];
}
- (void)reloadData
{
[self.upperLeftCollectionView reloadData];
[self.upperRightCollectionView reloadData];
[self.lowerLeftCollectionView reloadData];
[self.lowerRightCollectionView reloadData];
[self.bottomCollectionView reloadData];
}
- (void)flashScrollIndicators
{
[self showScrollIndicators];
[self performSelector:@selector(hideScrollIndicators) withObject:nil afterDelay:1];
}
#pragma mark - View Setup functions
- (void)setupSubviews
{
switch (self.spreadsheetHeaderConfiguration) {
case MMSpreadsheetHeaderConfigurationNone: [self setupLowerRightView]; break;
case MMSpreadsheetHeaderConfigurationColumnOnly:
[self setupLowerLeftView];
[self setupLowerRightView];
break;
case MMSpreadsheetHeaderConfigurationRowOnly:
[self setupUpperRightView];
[self setupLowerRightView];
break;
case MMSpreadsheetHeaderConfigurationBoth:
[self setupUpperLeftView];
[self setupUpperRightView];
[self setupLowerLeftView];
[self setupLowerRightView];
[self setupBottomView];
[self setupBottomLeftView];
break;
default: NSAssert(NO, @"What have you done?"); break;
}
self.verticalScrollIndicator = [self setupScrollIndicator];
self.horizontalScrollIndicator = [self setupScrollIndicator];
}
- (void)setupContainerSubview:(UIView *)container collectionView:(UICollectionView *)collectionView tag:(NSInteger)tag
{
container.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:container];
collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
collectionView.backgroundColor = [UIColor clearColor];
collectionView.tag = tag;
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
[container addSubview:collectionView];
}
- (UICollectionView *)setupCollectionViewWithGridLayout
{
CustomMMGridLayout *layout = [[CustomMMGridLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
return collectionView;
}
- (void)setupUpperLeftView
{
self.upperLeftContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.upperLeftCollectionView = [self setupCollectionViewWithGridLayout];
[self setupContainerSubview:self.upperLeftContainerView collectionView:self.upperLeftCollectionView tag:MMSpreadsheetViewCollectionUpperLeft];
self.upperLeftCollectionView.scrollEnabled = NO;
}
- (void)setupUpperRightView
{
self.upperRightContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.upperRightCollectionView = [self setupCollectionViewWithGridLayout];
[self.upperRightCollectionView.panGestureRecognizer addTarget:self action:@selector(handleUpperRightPanGesture:)];
[self setupContainerSubview:self.upperRightContainerView collectionView:self.upperRightCollectionView tag:MMSpreadsheetViewCollectionUpperRight];
}
- (void)setupLowerLeftView
{
self.lowerLeftContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.lowerLeftCollectionView = [self setupCollectionViewWithGridLayout];
[self.lowerLeftCollectionView.panGestureRecognizer addTarget:self action:@selector(handleLowerLeftPanGesture:)];
[self setupContainerSubview:self.lowerLeftContainerView collectionView:self.lowerLeftCollectionView tag:MMSpreadsheetViewCollectionLowerLeft];
}
- (void)setupLowerRightView
{
self.lowerRightContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.lowerRightCollectionView = [self setupCollectionViewWithGridLayout];
[self.lowerRightCollectionView.panGestureRecognizer addTarget:self action:@selector(handleLowerRightPanGesture:)];
[self setupContainerSubview:self.lowerRightContainerView collectionView:self.lowerRightCollectionView tag:MMSpreadsheetViewCollectionLowerRight];
}
- (void)setupBottomView
{
self.bottomContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.bottomCollectionView = [self setupCollectionViewWithGridLayout];
[self.bottomCollectionView.panGestureRecognizer addTarget:self action:@selector(handleUpperRightPanGesture:)];
[self setupContainerSubview:self.bottomContainerView collectionView:self.bottomCollectionView tag:MMSpreadsheetViewCollectionBottom];
}
- (void)setupBottomLeftView
{
self.bottomLeftView = [[UILabel alloc] initWithFrame:CGRectZero];
self.bottomLeftView.backgroundColor = [UIColor colorWithWhite:0.902 alpha:1.000];
self.bottomLeftView.text = @"总计";
self.bottomLeftView.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.bottomLeftView];
}
- (void)layoutSubviews
{
[super layoutSubviews];
NSIndexPath *indexPathZero = [NSIndexPath indexPathForItem:0 inSection:0];
switch (self.spreadsheetHeaderConfiguration) {
case MMSpreadsheetHeaderConfigurationNone: self.lowerRightContainerView.frame = self.bounds; break;
case MMSpreadsheetHeaderConfigurationColumnOnly: {
CGSize size = self.lowerLeftCollectionView.collectionViewLayout.collectionViewContentSize;
CGSize cellSize = [self collectionView:self.lowerRightCollectionView layout:self.lowerRightCollectionView.collectionViewLayout sizeForItemAtIndexPath:indexPathZero];
CGFloat maxLockDistance = self.bounds.size.width - cellSize.width;
if (size.width > maxLockDistance) {
NSAssert(NO, @"Width of header too large! Reduce the number of header columns.");
}
self.lowerLeftContainerView.frame = CGRectMake(0.0f, 0.0f, size.width, self.bounds.size.height);
self.lowerRightContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace, 0.0f, self.bounds.size.width - size.width - MMSpreadsheetViewGridSpace, self.bounds.size.height);
break;
}
case MMSpreadsheetHeaderConfigurationRowOnly: {
CGSize size = self.upperRightCollectionView.collectionViewLayout.collectionViewContentSize;
CGSize cellSize = [self collectionView:self.lowerRightCollectionView layout:self.lowerRightCollectionView.collectionViewLayout sizeForItemAtIndexPath:indexPathZero];
CGFloat maxLockDistance = self.bounds.size.height - cellSize.height;
if (size.height > maxLockDistance) {
NSAssert(NO, @"Height of header too large! Reduce the number of header rows.");
}
self.upperRightContainerView.frame = CGRectMake(0.0f, 0.0f, self.bounds.size.width, size.height);
self.lowerRightContainerView.frame = CGRectMake(0.0f, size.height + MMSpreadsheetViewGridSpace, self.bounds.size.width, self.bounds.size.height - size.height - MMSpreadsheetViewGridSpace);
break;
}
case MMSpreadsheetHeaderConfigurationBoth: {
CGSize size = self.upperLeftCollectionView.collectionViewLayout.collectionViewContentSize;
CGSize cellSize = [self collectionView:self.lowerRightCollectionView layout:self.lowerRightCollectionView.collectionViewLayout sizeForItemAtIndexPath:indexPathZero];
CGFloat maxLockDistance = self.bounds.size.height - cellSize.height;
if (size.height > maxLockDistance) {
NSAssert(NO, @"Height of header too large! Reduce the number of header rows.");
}
maxLockDistance = self.bounds.size.width - cellSize.width;
if (size.width > maxLockDistance) {
NSAssert(NO, @"Width of header too large! Reduce the number of header columns.");
}
self.upperLeftContainerView.frame = CGRectMake(0.0f, 0.0f, size.width, size.height);
self.upperRightContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace, 0.0f, self.bounds.size.width - size.width - MMSpreadsheetViewGridSpace, size.height);
self.lowerLeftContainerView.frame = CGRectMake(0.0f, size.height + MMSpreadsheetViewGridSpace, size.width, self.bounds.size.height - size.height - MMSpreadsheetViewGridSpace - size.height);
self.lowerRightContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace, size.height + MMSpreadsheetViewGridSpace, self.bounds.size.width - size.width - MMSpreadsheetViewGridSpace, self.bounds.size.height - size.height - MMSpreadsheetViewGridSpace - size.height);
self.bottomContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace, self.lowerRightContainerView.frame.origin.y + MMSpreadsheetViewGridSpace * 2 + self.lowerRightContainerView.bounds.size.height, self.lowerRightContainerView.bounds.size.width, size.height);
self.bottomLeftView.frame = CGRectMake(0, self.lowerLeftContainerView.frame.origin.y + MMSpreadsheetViewGridSpace * 2 + self.lowerLeftContainerView.frame.size.height, size.width - MMSpreadsheetViewGridSpace, size.height);
break;
}
default: NSAssert(NO, @"What have you done?"); break;
}
// Resize the indicators.
self.verticalScrollIndicator.frame = CGRectMake(self.frame.size.width - MMSpreadsheetViewScrollIndicatorWidth - self.scrollIndicatorInsets.right - MMScrollIndicatorDefaultInsetSpace, self.scrollIndicatorInsets.top + MMSpreadsheetViewScrollIndicatorSpace, MMSpreadsheetViewScrollIndicatorWidth, self.frame.size.height - 4 * MMSpreadsheetViewScrollIndicatorSpace);
[self updateVerticalScrollIndicator];
self.horizontalScrollIndicator.frame = CGRectMake(self.scrollIndicatorInsets.left + MMSpreadsheetViewScrollIndicatorSpace, self.frame.size.height - MMSpreadsheetViewScrollIndicatorWidth - self.scrollIndicatorInsets.bottom - MMScrollIndicatorDefaultInsetSpace, self.frame.size.width - 4 * MMSpreadsheetViewScrollIndicatorSpace, MMSpreadsheetViewScrollIndicatorWidth);
[self updateHorizontalScrollIndicator];
}
#pragma mark - UIPanGestureRecognizer callbacks
- (void)handleUpperRightPanGesture:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.lowerRightContainerView.userInteractionEnabled = NO;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.isUpperRightBouncing == NO) {
self.lowerLeftContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
}
}
}
- (void)handleLowerLeftPanGesture:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerRightContainerView.userInteractionEnabled = NO;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.isLowerLeftBouncing == NO) {
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
}
}
}
- (void)handleLowerRightPanGesture:(UIPanGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.isLowerRightBouncing == NO) {
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerLeftContainerView.userInteractionEnabled = YES;
}
}
}
#pragma mark - bounces property setter
- (void)setBounces:(BOOL)bounces
{
_bounces = bounces;
self.upperLeftCollectionView.bounces = bounces;
self.upperRightCollectionView.bounces = bounces;
self.lowerLeftCollectionView.bounces = bounces;
self.lowerRightCollectionView.bounces = bounces;
self.bottomCollectionView.bounces = bounces;
}
#pragma mark - DataSource property setter
- (void)setDataSource:(id<MMSpreadsheetViewDataSource>)dataSource
{
_dataSource = dataSource;
if (self.upperLeftCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.upperLeftCollectionView];
}
if (self.upperRightCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.upperRightCollectionView];
}
if (self.lowerLeftCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.lowerLeftCollectionView];
}
if (self.lowerRightCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.lowerRightCollectionView];
}
if (self.bottomCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.bottomCollectionView];
}
// Validate dataSource & header configuration
NSInteger maxRows = [_dataSource numberOfRowsInSpreadsheetView:self];
NSInteger maxCols = [_dataSource numberOfColumnsInSpreadsheetView:self];
NSAssert(self.headerColumnCount < maxCols, @"Invalid configuration: number of header columns must be less than (dataSource) numberOfColumnsInSpreadsheetView");
NSAssert(self.headerRowCount < maxRows, @"Invalid configuration: number of header rows must be less than (dataSource) numberOfRowsInSpreadsheetView");
}
- (void)initializeCollectionViewLayoutItemSize:(UICollectionView *)collectionView
{
NSIndexPath *indexPathZero = [NSIndexPath indexPathForItem:0 inSection:0];
CustomMMGridLayout *layout = (CustomMMGridLayout *)collectionView.collectionViewLayout;
CGSize size = [self collectionView:collectionView layout:layout sizeForItemAtIndexPath:indexPathZero];
layout.itemSize = size;
}
#pragma mark - Scroll Indicator
- (UIView *)setupScrollIndicator
{
UIView *scrollIndicator = [[UIView alloc] initWithFrame:CGRectZero];
scrollIndicator.alpha = 0.0f;
scrollIndicator.layer.cornerRadius = MMSpreadsheetViewScrollIndicatorWidth / 2;
scrollIndicator.clipsToBounds = YES;
[self addSubview:scrollIndicator];
UIView *scrollIndicatorSegment = [[UIView alloc] initWithFrame:CGRectZero];
scrollIndicatorSegment.backgroundColor = [UIColor colorWithWhite:0.44f alpha:1.0f];
scrollIndicatorSegment.layer.cornerRadius = MMSpreadsheetViewScrollIndicatorWidth / 2;
scrollIndicatorSegment.tag = MMScrollIndicatorTag;
[scrollIndicator addSubview:scrollIndicatorSegment];
return scrollIndicator;
}
- (void)showScrollIndicators
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:_cmd object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideScrollIndicators) object:nil];
[UIView animateWithDuration:0.4f
animations:^{
self.verticalScrollIndicator.alpha = self.showsVerticalScrollIndicator ? 1.0f : 0.0f;
self.horizontalScrollIndicator.alpha = self.showsHorizontalScrollIndicator ? 1.0f : 0.0f;
}];
}
- (void)hideScrollIndicators
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:_cmd object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showScrollIndicators) object:nil];
[UIView animateWithDuration:0.5f
animations:^{
self.verticalScrollIndicator.alpha = 0.0f;
self.horizontalScrollIndicator.alpha = 0.0f;
}];
}
- (void)updateVerticalScrollIndicator
{
if (self.showsVerticalScrollIndicator) {
UIView *scrollIndicator = self.verticalScrollIndicator;
UIView *indicatorView = [scrollIndicator viewWithTag:MMScrollIndicatorTag];
UICollectionView *collectionView = self.lowerRightCollectionView;
CGSize contentSize = collectionView.collectionViewLayout.collectionViewContentSize;
CGRect collectionViewFrame = collectionView.frame;
if (collectionViewFrame.size.height > contentSize.height) {
indicatorView.frame = CGRectZero;
} else {
CGFloat indicatorHeight = collectionViewFrame.size.height / contentSize.height * scrollIndicator.frame.size.height;
if (indicatorHeight < MMSpreadsheetViewScrollIndicatorMinimum) {
indicatorHeight = MMSpreadsheetViewScrollIndicatorMinimum;
}
CGFloat indicatorOffsetY = collectionView.contentOffset.y / (contentSize.height - collectionViewFrame.size.height) * (scrollIndicator.frame.size.height - indicatorHeight);
indicatorView.frame = CGRectMake(0.0f, indicatorOffsetY, MMSpreadsheetViewScrollIndicatorWidth, indicatorHeight);
}
}
}
- (void)updateHorizontalScrollIndicator
{
if (self.showsHorizontalScrollIndicator) {
UIView *scrollIndicator = self.horizontalScrollIndicator;
UIView *indicatorView = [scrollIndicator viewWithTag:MMScrollIndicatorTag];
UICollectionView *collectionView = self.lowerRightCollectionView;
CGSize contentSize = collectionView.collectionViewLayout.collectionViewContentSize;
CGRect collectionViewFrame = collectionView.frame;
if (collectionView.frame.size.width > contentSize.width) {
indicatorView.frame = CGRectZero;
} else {
CGFloat indicatorWidth = collectionViewFrame.size.width / contentSize.width * scrollIndicator.frame.size.width;
if (indicatorWidth < MMSpreadsheetViewScrollIndicatorMinimum) {
indicatorWidth = MMSpreadsheetViewScrollIndicatorMinimum;
}
CGFloat indicatorOffsetX = collectionView.contentOffset.x / (contentSize.width - collectionViewFrame.size.width) * (scrollIndicator.frame.size.width - indicatorWidth);
indicatorView.frame = CGRectMake(indicatorOffsetX, 0.0f, indicatorWidth, MMSpreadsheetViewScrollIndicatorWidth);
}
}
}
#pragma mark - Custom functions that don't go anywhere else
//根据indexpath判断是哪个collectionView
- (UICollectionView *)collectionViewForDataSourceIndexPath:(NSIndexPath *)indexPath
{
UICollectionView *collectionView = nil;
switch (self.spreadsheetHeaderConfiguration) {
case MMSpreadsheetHeaderConfigurationNone: collectionView = self.lowerRightCollectionView; break;
case MMSpreadsheetHeaderConfigurationColumnOnly:
if (indexPath.mmSpreadsheetColumn >= self.headerColumnCount) {
collectionView = self.lowerRightCollectionView;
} else {
collectionView = self.lowerLeftCollectionView;
}
break;
case MMSpreadsheetHeaderConfigurationRowOnly:
if (indexPath.mmSpreadsheetRow >= self.headerRowCount) {
collectionView = self.lowerRightCollectionView;
} else {
collectionView = self.upperRightCollectionView;
}
break;
case MMSpreadsheetHeaderConfigurationBoth:
//如果行数大于顶部的悬浮的行数,说明不是顶部悬浮的
if (indexPath.mmSpreadsheetRow >= self.headerRowCount) {
//如果列数大于左侧悬浮的列数,说明不是下方左边悬浮的内容
if (indexPath.mmSpreadsheetColumn >= self.headerColumnCount) {
collectionView = self.lowerRightCollectionView;
} else {
collectionView = self.lowerLeftCollectionView;
}
} else {//顶部悬浮的部分的内容
if (indexPath.mmSpreadsheetColumn >= self.headerColumnCount) {
collectionView = self.upperRightCollectionView;
} else {
collectionView = self.upperLeftCollectionView;
}
}
break;
default: NSAssert(NO, @"What have you done?"); break;
}
return collectionView;
}
- (NSIndexPath *)dataSourceIndexPathFromCollectionView:(UICollectionView *)collectionView indexPath:(NSIndexPath *)indexPath
{
NSInteger mmSpreadsheetRow = indexPath.mmSpreadsheetRow;
NSInteger mmSpreadsheetColumn = indexPath.mmSpreadsheetColumn;
if (collectionView != nil) {
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft: break;
case MMSpreadsheetViewCollectionUpperRight: mmSpreadsheetColumn += self.headerColumnCount; break;
case MMSpreadsheetViewCollectionLowerLeft: mmSpreadsheetRow += self.headerRowCount; break;
case MMSpreadsheetViewCollectionLowerRight:
mmSpreadsheetRow += self.headerRowCount;
mmSpreadsheetColumn += self.headerColumnCount;
break;
case MMSpreadsheetViewCollectionBottom: mmSpreadsheetColumn += self.headerColumnCount; break;
default: NSAssert(NO, @"What have you done?"); break;
}
}
return [NSIndexPath indexPathForItem:mmSpreadsheetColumn inSection:mmSpreadsheetRow];
}
- (NSIndexPath *)collectionViewIndexPathFromDataSourceIndexPath:(NSIndexPath *)indexPath
{
UICollectionView *collectionView = [self collectionViewForDataSourceIndexPath:indexPath];
NSAssert(collectionView, @"No collectionView Returned!");
NSInteger mmSpreadsheetRow = indexPath.mmSpreadsheetRow;
NSInteger mmSpreadsheetColumn = indexPath.mmSpreadsheetColumn;
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft: break;
case MMSpreadsheetViewCollectionUpperRight: mmSpreadsheetColumn -= self.headerColumnCount; break;
case MMSpreadsheetViewCollectionLowerLeft: mmSpreadsheetRow -= self.headerRowCount; break;
case MMSpreadsheetViewCollectionLowerRight:
mmSpreadsheetRow -= self.headerRowCount;
mmSpreadsheetColumn -= self.headerColumnCount;
break;
default: NSAssert(NO, @"What have you done?"); break;
}
return [NSIndexPath indexPathForItem:mmSpreadsheetColumn inSection:mmSpreadsheetRow];
}
- (void)setScrollEnabledValue:(BOOL)scrollEnabled scrollView:(UIScrollView *)scrollView
{
switch (scrollView.tag) {
case MMSpreadsheetViewCollectionUpperLeft:
// Don't think we need to do anything here
break;
case MMSpreadsheetViewCollectionUpperRight:
self.lowerLeftCollectionView.scrollEnabled = scrollEnabled;
self.lowerRightCollectionView.scrollEnabled = scrollEnabled;
break;
case MMSpreadsheetViewCollectionLowerLeft:
self.upperRightCollectionView.scrollEnabled = scrollEnabled;
self.lowerRightCollectionView.scrollEnabled = scrollEnabled;
break;
case MMSpreadsheetViewCollectionLowerRight:
self.upperRightCollectionView.scrollEnabled = scrollEnabled;
self.lowerLeftCollectionView.scrollEnabled = scrollEnabled;
break;
case MMSpreadsheetViewCollectionBottom:
self.lowerLeftCollectionView.scrollEnabled = scrollEnabled;
self.lowerRightCollectionView.scrollEnabled = scrollEnabled;
break;
}
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
CGSize size = [self.dataSource spreadsheetView:self sizeForItemAtIndexPath:dataSourceIndexPath];
return size;
}
#pragma mark - UICollectionViewDataSource pass-through
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
NSInteger rowCount = [self.dataSource numberOfRowsInSpreadsheetView:self];
NSInteger adjustedRows = 1;
/**
* 根据不同的collectionView返回不同的分区数(行数)
*/
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft: adjustedRows = self.headerRowCount; break;
case MMSpreadsheetViewCollectionUpperRight: adjustedRows = self.headerRowCount; break;
case MMSpreadsheetViewCollectionLowerLeft: adjustedRows = rowCount - self.headerRowCount - 1; break;
case MMSpreadsheetViewCollectionLowerRight: adjustedRows = rowCount - self.headerRowCount - 1; break;
case MMSpreadsheetViewCollectionBottom: adjustedRows = self.headerRowCount; break;
default: NSAssert(NO, @"What have you done?"); break;
}
NSLog(@"这里好像是用分区作为一行cell,分区数量:%lu", adjustedRows);
return adjustedRows;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSInteger items = 0;
NSInteger columnCount = [self.dataSource numberOfColumnsInSpreadsheetView:self];
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft: items = self.headerColumnCount; break;
case MMSpreadsheetViewCollectionUpperRight: items = columnCount - self.headerColumnCount; break;
case MMSpreadsheetViewCollectionLowerLeft: items = self.headerColumnCount; break;
case MMSpreadsheetViewCollectionLowerRight: items = columnCount - self.headerColumnCount; break;
case MMSpreadsheetViewCollectionBottom:
items = columnCount - self.headerColumnCount;
;
break;
default: NSAssert(NO, @"What have you done?"); break;
}
NSLog(@"cell数量:%lu", items);
return items;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
UICollectionViewCell *cell = [self.dataSource spreadsheetView:self cellForItemAtIndexPath:dataSourceIndexPath collectionView:collectionView];
return cell;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (self.selectedItemCollectionView != nil) {
if (collectionView == self.selectedItemCollectionView) {
self.selectedItemIndexPath = indexPath;
} else {
[self.selectedItemCollectionView deselectItemAtIndexPath:self.selectedItemIndexPath animated:NO];
self.selectedItemCollectionView = collectionView;
self.selectedItemIndexPath = indexPath;
}
} else {
self.selectedItemCollectionView = collectionView;
self.selectedItemIndexPath = indexPath;
}
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:didSelectItemAtIndexPath:)]) {
[self.delegate spreadsheetView:self didSelectItemAtIndexPath:dataSourceIndexPath];
}
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:shouldShowMenuForItemAtIndexPath:)]) {
return [self.delegate spreadsheetView:self shouldShowMenuForItemAtIndexPath:dataSourceIndexPath];
}
return NO;
}
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:canPerformAction:forItemAtIndexPath:withSender:)]) {
return [self.delegate spreadsheetView:self canPerformAction:action forItemAtIndexPath:dataSourceIndexPath withSender:sender];
}
return NO;
}
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
{
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:performAction:forItemAtIndexPath:withSender:)]) {
return [self.delegate spreadsheetView:self performAction:action forItemAtIndexPath:dataSourceIndexPath withSender:sender];
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.controllingScrollView) {
switch (scrollView.tag) {
case MMSpreadsheetViewCollectionLowerLeft: [self lowerLeftCollectionViewDidScrollForScrollView:scrollView]; break;
case MMSpreadsheetViewCollectionUpperRight: [self upperRightCollectionViewDidScrollForScrollView:scrollView]; break;
case MMSpreadsheetViewCollectionLowerRight: [self lowerRightCollectionViewDidScrollForScrollView:scrollView]; break;
case MMSpreadsheetViewCollectionBottom: [self bottomRightCollectionViewDidScrollForScrollView:scrollView]; break;
}
} else {
[scrollView setContentOffset:scrollView.contentOffset animated:NO];
}
}
- (void)lowerLeftCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView
{
[self.lowerRightCollectionView setContentOffset:CGPointMake(self.lowerRightCollectionView.contentOffset.x, scrollView.contentOffset.y) animated:NO];
[self updateVerticalScrollIndicator];
if (scrollView.contentOffset.y <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0 - scrollView.contentOffset.y;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0 - scrollView.contentOffset.y;
self.upperRightContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0.0f;
self.upperRightContainerView.frame = rect;
}
}
- (void)bottomRightCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView
{
CGPoint point = self.lowerRightCollectionView.contentOffset;
point.x = scrollView.contentOffset.x;
[self.lowerRightCollectionView setContentOffset:point animated:NO];
[self.upperRightCollectionView setContentOffset:CGPointMake(point.x, self.upperRightCollectionView.contentOffset.y) animated:NO];
[self updateHorizontalScrollIndicator];
}
- (void)upperRightCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView
{
[self.lowerRightCollectionView setContentOffset:CGPointMake(scrollView.contentOffset.x, self.lowerRightCollectionView.contentOffset.y) animated:NO];
[self.bottomCollectionView setContentOffset:CGPointMake(scrollView.contentOffset.x, self.bottomCollectionView.contentOffset.y) animated:NO];
[self updateHorizontalScrollIndicator];
if (scrollView.contentOffset.x <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0 - scrollView.contentOffset.x;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0 - scrollView.contentOffset.x;
self.lowerLeftContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0.0f;
self.lowerLeftContainerView.frame = rect;
}
}
- (void)lowerRightCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView
{
[self updateVerticalScrollIndicator];
[self updateHorizontalScrollIndicator];
CGPoint offset = CGPointMake(0.0f, scrollView.contentOffset.y);
[self.lowerLeftCollectionView setContentOffset:offset animated:NO];
offset = CGPointMake(scrollView.contentOffset.x, 0.0f);
[self.upperRightCollectionView setContentOffset:offset animated:NO];
[self.bottomCollectionView setContentOffset:CGPointMake(scrollView.contentOffset.x, 0.0f) animated:NO];
if (scrollView.contentOffset.y <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0 - scrollView.contentOffset.y;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0 - scrollView.contentOffset.y;
self.upperRightContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0.0f;
self.upperRightContainerView.frame = rect;
}
if (scrollView.contentOffset.x <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0 - scrollView.contentOffset.x;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0 - scrollView.contentOffset.x;
self.lowerLeftContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0.0f;
self.lowerLeftContainerView.frame = rect;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self setScrollEnabledValue:NO scrollView:scrollView];
if (self.controllingScrollView != scrollView) {
[self.lowerLeftCollectionView setContentOffset:self.lowerLeftCollectionView.contentOffset animated:NO];
[self.upperRightCollectionView setContentOffset:self.upperRightCollectionView.contentOffset animated:NO];
[self.lowerRightCollectionView setContentOffset:self.lowerRightCollectionView.contentOffset animated:NO];
self.controllingScrollView = scrollView;
}
[self showScrollIndicators];
[self setScrollEnabledValue:YES scrollView:scrollView];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
// Block UI if we're in a bounce.
// Without this, you can lock the scroll views in a scroll which looks weird.
CGPoint toffset = *targetContentOffset;
switch (scrollView.tag) {
case MMSpreadsheetViewCollectionLowerLeft: {
BOOL willBouncePastZeroY = velocity.y < 0.0f && !(toffset.y > 0.0f);
BOOL willBouncePastMaxY = toffset.y > self.lowerLeftCollectionView.contentSize.height - self.lowerLeftCollectionView.frame.size.height - 0.1f && velocity.y > 0.0f;
if (willBouncePastZeroY || willBouncePastMaxY) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerRightContainerView.userInteractionEnabled = NO;
self.lowerLeftBouncing = YES;
}
break;
}
case MMSpreadsheetViewCollectionUpperRight: {
BOOL willBouncePastZeroX = velocity.x < 0.0f && !(toffset.x > 0.0f);
BOOL willBouncePastMaxX = toffset.x > self.upperRightCollectionView.contentSize.width - self.upperRightCollectionView.frame.size.width - 0.1f && velocity.x > 0.0f;
if (willBouncePastZeroX || willBouncePastMaxX) {
self.lowerRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.upperRightBouncing = YES;
}
break;
}
case MMSpreadsheetViewCollectionLowerRight: {
BOOL willBouncePastZeroX = velocity.x < 0.0f && !(toffset.x > 0.0f);
BOOL willBouncePastMaxX = toffset.x > self.upperRightCollectionView.contentSize.width - self.upperRightCollectionView.frame.size.width - 0.1f && velocity.x > 0.0f;
BOOL willBouncePastZeroY = velocity.y < 0.0f && !(toffset.y > 0.0f);
BOOL willBouncePastMaxY = toffset.y > self.lowerLeftCollectionView.contentSize.height - self.lowerLeftCollectionView.frame.size.height - 0.1f && velocity.y > 0.0f;
if (willBouncePastZeroX || willBouncePastMaxX || willBouncePastZeroY || willBouncePastMaxY) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.lowerRightBouncing = YES;
}
break;
}
case MMSpreadsheetViewCollectionBottom: {
BOOL willBouncePastZeroX = velocity.x < 0.0f && !(toffset.x > 0.0f);
BOOL willBouncePastMaxX = toffset.x > self.bottomCollectionView.contentSize.width - self.bottomCollectionView.frame.size.width - 0.1f && velocity.x > 0.0f;
BOOL willBouncePastZeroY = velocity.y < 0.0f && !(toffset.y > 0.0f);
BOOL willBouncePastMaxY = toffset.y > self.lowerLeftCollectionView.contentSize.height - self.lowerLeftCollectionView.frame.size.height - 0.1f && velocity.y > 0.0f;
if (willBouncePastZeroX || willBouncePastMaxX || willBouncePastZeroY || willBouncePastMaxY) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.lowerRightBouncing = YES;
}
break;
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self scrollViewDidStop:scrollView];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
self.lowerLeftContainerView.userInteractionEnabled = YES;
self.bottomContainerView.userInteractionEnabled = YES;
self.upperRightBouncing = NO;
self.lowerLeftBouncing = NO;
self.lowerRightBouncing = NO;
[self setNeedsLayout];
[self hideScrollIndicators];
}
}
- (void)scrollViewDidStop:(UIScrollView *)scrollView
{
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
self.lowerLeftContainerView.userInteractionEnabled = YES;
self.bottomContainerView.userInteractionEnabled = YES;
self.upperRightBouncing = NO;
self.lowerLeftBouncing = NO;
self.lowerRightBouncing = NO;
if (!scrollView.isDecelerating && !scrollView.isDragging && !scrollView.isTracking) {
[self setNeedsLayout];
[self hideScrollIndicators];
}
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
/**
`MMGridLayout` provides the grid layout of cells for the spreadsheet.
Code for the layout was inspired by https://github.com/mattlangtree/grid-view-csv
*/
@interface MMGridLayout : UICollectionViewLayout
///---------------------------------------
/// @name Accessing Layout Properties
///---------------------------------------
/**
The size for each cell in the grid.
@discussion This will be set either by `MMSpreadsheetView` or `MMGridLayout` using a delegate call to collectionView:layout:sizeForItemAtIndexPath: via the `setItemSize:` method. The layout is invalidated when the itemSize is changed.
*/
@property (nonatomic, assign) CGSize itemSize;
/**
The space between cells.
@discussion This allows for setting the spacing between cells. Lines are not drawn, but show the background of the collection view. The layout is invalidated when the cellSpacing is changed. Default is 1.
*/
@property (nonatomic, assign) CGFloat cellSpacing;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMGridLayout.h"
#import "MMSpreadsheetView.h"
@interface MMGridLayout ()
@property (nonatomic, assign) NSInteger gridRowCount;
@property (nonatomic, assign) NSInteger gridColumnCount;
@property (nonatomic, assign) BOOL isInitialized;
@end
@implementation MMGridLayout
- (id)init {
self = [super init];
if (self) {
_cellSpacing = 1.0f;
_itemSize = CGSizeMake(120.0f, 120.0f);
}
return self;
}
- (void)setItemSize:(CGSize)itemSize {
_itemSize = CGSizeMake(itemSize.width + self.cellSpacing, itemSize.height + self.cellSpacing);
[self invalidateLayout];
}
- (void)setCellSpacing:(CGFloat)cellSpacing {
_cellSpacing = cellSpacing;
[self invalidateLayout];
}
- (void)prepareLayout {
[super prepareLayout];
self.gridRowCount = [self.collectionView numberOfSections];
self.gridColumnCount = [self.collectionView numberOfItemsInSection:0];
if (!self.isInitialized) {
id<UICollectionViewDelegateFlowLayout> delegate = (id)self.collectionView.delegate;
CGSize size = [delegate collectionView:self.collectionView
layout:self
sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
self.itemSize = size;
self.isInitialized = YES;
}
}
- (CGSize)collectionViewContentSize {
if (!self.isInitialized) {
[self prepareLayout];
}
CGSize size = CGSizeMake(self.gridColumnCount * self.itemSize.width, self.gridRowCount * self.itemSize.height);
return size;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *attributes = [NSMutableArray array];
NSUInteger startRow = floorf(rect.origin.y / self.itemSize.height);
NSUInteger startCol = floorf(rect.origin.x / self.itemSize.width);
NSUInteger endRow = MIN(self.gridRowCount - 1, ceilf(CGRectGetMaxY(rect) / self.itemSize.height));
NSUInteger endCol = MIN(self.gridColumnCount - 1, ceilf(CGRectGetMaxX(rect) / self.itemSize.width));
NSParameterAssert(self.gridRowCount > 0);
NSParameterAssert(self.gridColumnCount > 0);
for (NSUInteger row = startRow; row <= endRow; row++) {
for (NSUInteger col = startCol; col <= endCol; col++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:col inSection:row];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[attributes addObject:layoutAttributes];
}
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectMake(indexPath.item * self.itemSize.width, indexPath.section * self.itemSize.height, self.itemSize.width-self.cellSpacing, self.itemSize.height-self.cellSpacing);
return attributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return NO;
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
@class MMSpreadsheetView;
/**
An object that adopts the `MMSpreadsheetViewDataSource` protocol is responsible for providing the data and views required by a spreadsheet view. A data source object represents your app’s data model and vends information to the spreadsheet view as needed. It also handles the creation and configuration of cells used by the spreadsheet view to display your data.
All data source objects must implement the `numberOfRowsInSpreadsheetView:`, `numberOfColumnsInSpreadsheetView:` and `spreadsheetView:cellForItemAtIndexPath:` methods. These methods are responsible for returning the number of items in the spreadsheet view along with the items themselves. In addition, the data source should implement the `spreadsheetView:sizeForItemAtIndexPath:` method for determining cell sizes in the individual collection views.
When configuring the spreadsheet view object, assign your data source to its dataSource property. For more information about how a spreadsheet view works with its data source to present content, see `MMSpreadsheetView` Class Reference.
@warning An NSIndexPath category is provided so that *indexPath.mmSpreadsheetRow* represents a row and *indexPath.mmSpreadsheetColumn* represents a column.
*/
@protocol MMSpreadsheetViewDataSource <NSObject>
@optional
///---------------------------------------
/// @name Spreadsheet Data Source Protocol
///---------------------------------------
/**
The cell size for all cells in a collection view. Header cells can have a different size than non-header cells, but all cells within a single collection view (or spreadsheet view section) will be the same size.
@param spreadsheetView The spreadsheet view object that is requesting the size information.
@param indexPath The index path of the cell.
@return A CGSize containing the height and width of the cell at the given indexPath.
*/
- (CGSize)spreadsheetView:(MMSpreadsheetView *)spreadsheetView sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
@required
/**
The number of horizontal rows for the entire `MMSpreadsheetView`.
@param spreadsheetView The spreadsheet view object that is requesting the information.
@return An NSInteger containing the number of horizontal rows in the spreadsheet view.
*/
- (NSInteger)numberOfRowsInSpreadsheetView:(MMSpreadsheetView *)spreadsheetView;
/**
The number of vertical columns for the entire `MMSpreadsheetView`.
@param spreadsheetView The spreadsheet view object that is requesting the information.
@return An NSInteger containing the number of vertical columns in the spreadsheet view.
*/
- (NSInteger)numberOfColumnsInSpreadsheetView:(MMSpreadsheetView *)spreadsheetView;
/**
Create and configure a cell for the `MMSpreadsheetView`. The cell that is returned must be retrieved from a call to -`dequeueReusableCellWithReuseIdentifier:forIndexPath:`.
@param spreadsheetView The spreadsheet view object that is requesting the cell.
@param indexPath The index path of the cell.
@return The dequeued `UICollectionViewCell` object.
*/
- (UICollectionViewCell *)spreadsheetView:(MMSpreadsheetView *)spreadsheetView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
@end
/**
The `MMSpreadsheetViewDelegate` protocol defines methods that allow you to manage the selection and highlighting of items in a spreadsheet view and to perform actions on those items. The methods of this protocol are all optional.
When configuring the spreadsheet view object, assign your delegate object to its delegate property. For more information, see `MMSpreadsheetView` Class Reference.
@warning An NSIndexPath category is provided so that *indexPath.mmSpreadsheetRow* represents a row and *indexPath.mmSpreadsheetColumn* represents a column.
*/
@protocol MMSpreadsheetViewDelegate <NSObject>
@optional
///---------------------------------------
/// @name Managing the Selected Cells
///---------------------------------------
/**
Tells the delegate that the item at the specified index path was selected.
@param spreadsheetView The spreadsheet view object that is notifying you of the selection change.
@param indexPath The index path of the cell that was selected.
*/
- (void)spreadsheetView:(MMSpreadsheetView *)spreadsheetView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
///---------------------------------------
/// @name Managing Actions for Cells (Implement All or None)
///---------------------------------------
/**
Asks the delegate if an action menu should be displayed for the specified item.
@param spreadsheetView The spreadsheet view object that is making the request.
@param indexPath The index path of the cell that was selected.
@return YES if the editing menu should be shown positioned near the item and pointing to it, otherwise NO.
@discussion If the user tap-holds a certain item in the spreadsheet view, this method (if implemented) is invoked first. Return YES if you want to permit the editing menu to be displayed. Return NO if the editing menu shouldn’t be shown—for example, you might return NO if the corresponding item contains data that should not be copied or pasted over.
If you do not implement this method, the default return value is NO.
**Note**: This is part of a set of three methods providing support for copy/paste actions on cells. All three should be implemented if any are.
<p><strong>Availability</strong><br /></p><p>Available in iOS 6.0 and later.</p>
@see spreadsheetView:canPerformAction:forItemAtIndexPath:withSender:
spreadsheetView:performAction:forItemAtIndexPath:withSender:
*/
- (BOOL)spreadsheetView:(MMSpreadsheetView *)spreadsheetView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath;
/**
Asks the delegate if it can perform the specified action on an item in the collection view.
@param spreadsheetView The spreadsheet view object that is making the request.
@param action The selector identifying the action to be performed.
@param indexPath The index path of the affected item.
@param sender The object that wants to initiate the action.
@return YES if the command corresponding to action should appear in the editing menu or NO if it should not.
@discussion This method is invoked after the `spreadsheetView:shouldShowMenuForItemAtIndexPath:` method. It gives you the opportunity to exclude commands from the editing menu. For example, the user might have copied some content from one item and wants to paste it into another item that cannot accept the content. In such a case, your method could return NO to prevent the display of the relevant command.
If you do not implement this method, the default return value is NO.
**Note**: This is part of a set of three methods providing support for copy/paste actions on cells. All three should be implemented if any are.
<p><strong>Availability</strong><br /></p><p>Available in iOS 6.0 and later.</p>
@see spreadsheetView:performAction:forItemAtIndexPath:withSender:
spreadsheetView:shouldShowMenuForItemAtIndexPath:
*/
- (BOOL)spreadsheetView:(MMSpreadsheetView *)spreadsheetView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
/**
Tells the delegate to perform the specified action on an item in the spreadsheet view.
@param spreadsheetView The spreadsheet view object that is making the request.
@param action The selector representing the action to be performed.
@param indexPath The index path of the affected item.
@param sender The object that initiated the action.
@discussion If the user taps an action in the editing menu, the spreadsheet view calls this method. Your implementation of this method should do whatever is appropriate for the action. For example, for a copy action, it should extract the relevant item content and write it to the general pasteboard or an application (private) pasteboard.
For information about how to perform pasteboard-related operations, see `UIPasteboard` Class Reference.
**Note**: This is part of a set of three methods providing support for copy/paste actions on cells. All three should be implemented if any are.
<p><strong>Availability</strong><br /></p><p>Available in iOS 6.0 and later.</p>
@see spreadsheetView:shouldShowMenuForItemAtIndexPath:
spreadsheetView:canPerformAction:forItemAtIndexPath:withSender:
*/
- (void)spreadsheetView:(MMSpreadsheetView *)spreadsheetView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
@end
/**
`MMSpreadsheetView` is a configurable spreadsheet-like view. You choose to configure the view with zero or more header rows and zero or more header columns. Depending on this configuration, up to four collection views will be coordinated to provide the user interface.
When adding a spreadsheet view to your user interface, your app’s main job is to manage the data associated with the view. The spreadsheet view gets its data from the data source object, which is an object that conforms to the `MMSpreadsheetViewDataSource` protocol and is provided by your app. The spreadsheet view presents items onscreen using a cell, which is an instance of the UICollectionViewCell class that your data source configures and provides.
## Creating an MMSpreadsheetView
`MMSpreadsheetView` is similar to a `UICollectionView` or `UITableView`. To create an `MMSpreadsheetView`, you instantiate the view using initWithNumberOfHeaderRows:numberOfHeaderColumns:frame:.
@discussion **Note**: Set the background color of the spreadsheet view to change the separator line color. **However**, you will also see this color when the scroll goes into a bounce.
@warning An NSIndexPath category is provided so that *indexPath.mmSpreadsheetRow* represents a row and *indexPath.mmSpreadsheetColumn* represents a column.
@warning as the number of cells shown increases, scrolling performance declines. A large grid (1000x1000) takes a long time to initialize, but if the cell sizes are large enough (150x150), scrolling performance is not affected. However, a small grid (50x50) of (20x20) cells basically doesn't scroll.
*/
@interface MMSpreadsheetView : UIView
///---------------------------------------
/// @name Spreadsheet View Properties
///---------------------------------------
/**
The object that acts as the delegate of the spreadsheet view.
@discussion The delegate must adopt the `MMSpreadsheetViewDelegate` protocol. The spreadsheet view maintains a weak reference to the delegate object.
The delegate object is responsible for managing selection behavior and interactions with individual items.
*/
@property (nonatomic, weak) id<MMSpreadsheetViewDelegate> delegate;
/**
The object that provides the data for the spreadsheet view.
@discussion The data source must adopt the `MMSpreadsheetViewDataSource` protocol. The spreadsheet view maintains a weak reference to the data source object.
*/
@property (nonatomic, weak) id<MMSpreadsheetViewDataSource> dataSource;
///---------------------------------------
/// @name Initializing & Setup
///---------------------------------------
/**
Initializes and returns a spreadsheet view object given the number of header rows, columns and bounds. This method is the *designated initializer*.
@param headerRowCount The number of header **rows** at the top of the view.
@param headerColumnCount The number of header **columns** on the left side of the view.
@param frame The frame rectangle for the spreadsheet view, measured in points. The origin of the frame is relative to the superview in which you plan to add it. This frame is passed to the superclass during initialization.
@return An initialized spreadsheet view object or nil if the object could not be created.
*/
- (id)initWithNumberOfHeaderRows:(NSUInteger)headerRowCount numberOfHeaderColumns:(NSUInteger)headerColumnCount frame:(CGRect)frame;
/**
Register a class for use in creating new spreadsheet view cells.
@param cellClass The class of a cell that you want to use in the spreadsheet view.
@param identifier The reuse identifier to associate with the specified class. This parameter must not be nil and must not be an empty string.
@discussion Prior to calling the dequeueReusableCellWithReuseIdentifier:forIndexPath: method of the spreadsheet view, you must use this method to tell the spreadsheet view how to create a new cell of the given type. If a cell of the specified type is not currently in a reuse queue, the spreadsheet view uses the provided information to create a new cell object automatically.
If you previously registered a class with the same reuse identifier, the class you specify in the cellClass parameter replaces the old entry. You may specify nil for cellClass if you want to unregister the class from the specified reuse identifier.
*/
- (void)registerCellClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
///---------------------------------------
/// @name Creating Spreadsheet View Cells
///---------------------------------------
/**
Returns a reusable cell object located by its identifier.
@param identifier The reuse identifier for the specified cell. This parameter must not be nil.
@param indexPath The index path specifying the location of the cell. The data source receives this information when it is asked for the cell and should just pass it along. This method uses the index path to perform additional configuration based on the cell’s position in the spreadsheet view.
@return A valid `UICollectionViewCell` object.
@discussion Call this method from your data source object when asked to provide a new cell for the spreadsheet view. This method dequeues an existing cell if one is available or creates a new one based on the class you previously registered.
@warning You must register a class using the `registerCellClass:forCellWithReuseIdentifier:` method before calling this method.
@discussion If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its initWithFrame: method. If an existing cell was available for reuse, this method calls the cell’s prepareForReuse method instead.
*/
- (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;
///---------------------------------------
/// @name Managing the Selection
///---------------------------------------
/**
Deselects the item at the specified index.
@param indexPath The index path of the item to deselect. Specifying `nil` for this parameter removes the current selection.
@param animated Specify YES to animate the change in the selection or NO to make the change without animation.
@discussion If the allowsSelection property is NO, calling this method has no effect.
This method does not cause any selection-related delegate methods to be called.
*/
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated;
///---------------------------------------
/// @name Reloading Content
///---------------------------------------
/**
Reloads all of the data for the spreadsheet view.
@discussion Call this method to reload all of the items in the spreadsheet view. This causes the spreadsheet view to discard any currently visible items and redisplay them. For efficiency, the spreadsheet view only displays those cells that are visible. If the spreadsheet data shrinks as a result of the reload, the spreadsheet view adjusts its scrolling offsets accordingly.
*/
- (void)reloadData;
///---------------------------------------
/// @name Managing the Scroll Indicator
///---------------------------------------
/**
The distance the scroll indicators are inset from the edge of the scroll view.
@discussion The default value is UIEdgeInsetsZero.
*/
@property (nonatomic, assign) UIEdgeInsets scrollIndicatorInsets;
/**
A Boolean value that controls whether the horizontal scroll indicator is visible.
@discussion The default value is YES. The indicator is visible while tracking is underway and fades out after tracking.
*/
@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;
/**
A Boolean value that controls whether the vertical scroll indicator is visible.
@discussion The default value is YES. The indicator is visible while tracking is underway and fades out after tracking.
*/
@property (nonatomic, assign) BOOL showsVerticalScrollIndicator;
/**
Displays the scroll indicators momentarily.
@discussion You should call this method whenever you bring the scroll view to front.
*/
- (void)flashScrollIndicators;
///---------------------------------------
/// @name Scroll View Properties
///---------------------------------------
/**
A Boolean value that controls whether the spreadsheet view bounces past the edge of content and back again.
@discussion If the value of this property is YES, the spreadsheet view bounces when it encounters a boundary of the content. Bouncing visually indicates that scrolling has reached an edge of the content. If the value is NO, scrolling stops immediately at the content boundary without bouncing. The default value is YES.
*/
@property (nonatomic, assign) BOOL bounces;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMSpreadsheetView.h"
#import <QuartzCore/QuartzCore.h>
#import "MMGridLayout.h"
#import "NSIndexPath+MMSpreadsheetView.h"
typedef NS_ENUM(NSUInteger, MMSpreadsheetViewCollection) {
MMSpreadsheetViewCollectionUpperLeft = 1,
MMSpreadsheetViewCollectionUpperRight,
MMSpreadsheetViewCollectionLowerLeft,
MMSpreadsheetViewCollectionLowerRight,
};
typedef NS_ENUM(NSUInteger, MMSpreadsheetHeaderConfiguration) {
MMSpreadsheetHeaderConfigurationNone = 0,
MMSpreadsheetHeaderConfigurationColumnOnly,
MMSpreadsheetHeaderConfigurationRowOnly,
MMSpreadsheetHeaderConfigurationBoth,
};
const static CGFloat MMSpreadsheetViewGridSpace = 1.0f;
const static CGFloat MMSpreadsheetViewScrollIndicatorWidth = 5.0f;
const static CGFloat MMSpreadsheetViewScrollIndicatorSpace = 3.0f;
const static CGFloat MMSpreadsheetViewScrollIndicatorMinimum = 25.0f;
const static CGFloat MMScrollIndicatorDefaultInsetSpace = 2.0f;
const static NSUInteger MMScrollIndicatorTag = 12345;
@interface MMSpreadsheetView () <UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
@property (nonatomic, assign) NSUInteger headerRowCount;
@property (nonatomic, assign) NSUInteger headerColumnCount;
@property (nonatomic, assign) MMSpreadsheetHeaderConfiguration spreadsheetHeaderConfiguration;
@property (nonatomic, strong) UIScrollView *controllingScrollView;
@property (nonatomic, strong) UIView *upperLeftContainerView;
@property (nonatomic, strong) UIView *upperRightContainerView;
@property (nonatomic, strong) UIView *lowerLeftContainerView;
@property (nonatomic, strong) UIView *lowerRightContainerView;
@property (nonatomic, strong) UICollectionView *upperLeftCollectionView;
@property (nonatomic, strong) UICollectionView *upperRightCollectionView;
@property (nonatomic, strong) UICollectionView *lowerLeftCollectionView;
@property (nonatomic, strong) UICollectionView *lowerRightCollectionView;
@property (nonatomic, assign, getter = isUpperRightBouncing) BOOL upperRightBouncing;
@property (nonatomic, assign, getter = isLowerLeftBouncing) BOOL lowerLeftBouncing;
@property (nonatomic, assign, getter = isLowerRightBouncing) BOOL lowerRightBouncing;
@property (nonatomic, strong) UIView *verticalScrollIndicator;
@property (nonatomic, strong) UIView *horizontalScrollIndicator;
@property (nonatomic, strong) UICollectionView *selectedItemCollectionView;
@property (nonatomic, strong) NSIndexPath *selectedItemIndexPath;
@end
@implementation MMSpreadsheetView
- (id)init {
return [self initWithNumberOfHeaderRows:0 numberOfHeaderColumns:0 frame:CGRectZero];
}
- (id)initWithFrame:(CGRect)frame {
return [self initWithNumberOfHeaderRows:0 numberOfHeaderColumns:0 frame:frame];
}
#pragma mark - MMSpreadsheetView designated initializer
- (id)initWithNumberOfHeaderRows:(NSUInteger)headerRowCount numberOfHeaderColumns:(NSUInteger)headerColumnCount frame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_scrollIndicatorInsets = UIEdgeInsetsZero;
_showsVerticalScrollIndicator = YES;
_showsHorizontalScrollIndicator = YES;
_headerRowCount = headerRowCount;
_headerColumnCount = headerColumnCount;
if (headerColumnCount == 0 && headerRowCount == 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationNone;
}
else if (headerColumnCount > 0 && headerRowCount == 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationColumnOnly;
}
else if (headerColumnCount == 0 && headerRowCount > 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationRowOnly;
}
else if (headerColumnCount > 0 && headerRowCount > 0) {
_spreadsheetHeaderConfiguration = MMSpreadsheetHeaderConfigurationBoth;
}
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.backgroundColor = [UIColor grayColor];
[self setupSubviews];
}
return self;
}
#pragma mark - Public Functions
- (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *collectionViewIndexPath = [self collectionViewIndexPathFromDataSourceIndexPath:indexPath];
UICollectionView *collectionView = [self collectionViewForDataSourceIndexPath:indexPath];
NSAssert(collectionView, @"No collectionView Returned!");
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:collectionViewIndexPath];
return cell;
}
- (void)registerCellClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier {
[self.upperLeftCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.upperRightCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.lowerLeftCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
[self.lowerRightCollectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
}
- (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
NSIndexPath *collectionViewIndexPath = [self collectionViewIndexPathFromDataSourceIndexPath:indexPath];
UICollectionView *collectionView = [self collectionViewForDataSourceIndexPath:indexPath];
NSAssert(collectionView, @"No collectionView Returned!");
[collectionView deselectItemAtIndexPath:collectionViewIndexPath animated:animated];
}
- (void)reloadData {
[self.upperLeftCollectionView reloadData];
[self.upperRightCollectionView reloadData];
[self.lowerLeftCollectionView reloadData];
[self.lowerRightCollectionView reloadData];
}
- (void)flashScrollIndicators {
[self showScrollIndicators];
[self performSelector:@selector(hideScrollIndicators) withObject:nil afterDelay:1];
}
#pragma mark - View Setup functions
- (void)setupSubviews {
switch (self.spreadsheetHeaderConfiguration) {
case MMSpreadsheetHeaderConfigurationNone:
[self setupLowerRightView];
break;
case MMSpreadsheetHeaderConfigurationColumnOnly:
[self setupLowerLeftView];
[self setupLowerRightView];
break;
case MMSpreadsheetHeaderConfigurationRowOnly:
[self setupUpperRightView];
[self setupLowerRightView];
break;
case MMSpreadsheetHeaderConfigurationBoth:
[self setupUpperLeftView];
[self setupUpperRightView];
[self setupLowerLeftView];
[self setupLowerRightView];
break;
default:
NSAssert(NO, @"What have you done?");
break;
}
self.verticalScrollIndicator = [self setupScrollIndicator];
self.horizontalScrollIndicator = [self setupScrollIndicator];
}
- (void)setupContainerSubview:(UIView *)container collectionView:(UICollectionView *)collectionView tag:(NSInteger)tag {
container.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:container];
collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
collectionView.backgroundColor = [UIColor clearColor];
collectionView.tag = tag;
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
[container addSubview:collectionView];
}
- (UICollectionView *)setupCollectionViewWithGridLayout {
MMGridLayout *layout = [[MMGridLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
return collectionView;
}
- (void)setupUpperLeftView {
self.upperLeftContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.upperLeftCollectionView = [self setupCollectionViewWithGridLayout];
[self setupContainerSubview:self.upperLeftContainerView
collectionView:self.upperLeftCollectionView
tag:MMSpreadsheetViewCollectionUpperLeft];
self.upperLeftCollectionView.scrollEnabled = NO;
}
- (void)setupUpperRightView {
self.upperRightContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.upperRightCollectionView = [self setupCollectionViewWithGridLayout];
[self.upperRightCollectionView.panGestureRecognizer addTarget:self
action:@selector(handleUpperRightPanGesture:)];
[self setupContainerSubview:self.upperRightContainerView
collectionView:self.upperRightCollectionView
tag:MMSpreadsheetViewCollectionUpperRight];
}
- (void)setupLowerLeftView {
self.lowerLeftContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.lowerLeftCollectionView = [self setupCollectionViewWithGridLayout];
[self.lowerLeftCollectionView.panGestureRecognizer addTarget:self
action:@selector(handleLowerLeftPanGesture:)];
[self setupContainerSubview:self.lowerLeftContainerView
collectionView:self.lowerLeftCollectionView
tag:MMSpreadsheetViewCollectionLowerLeft];
}
- (void)setupLowerRightView {
self.lowerRightContainerView = [[UIView alloc] initWithFrame:CGRectZero];
self.lowerRightCollectionView = [self setupCollectionViewWithGridLayout];
[self.lowerRightCollectionView.panGestureRecognizer addTarget:self
action:@selector(handleLowerRightPanGesture:)];
[self setupContainerSubview:self.lowerRightContainerView
collectionView:self.lowerRightCollectionView
tag:MMSpreadsheetViewCollectionLowerRight];
}
- (void)layoutSubviews {
[super layoutSubviews];
NSIndexPath *indexPathZero = [NSIndexPath indexPathForItem:0 inSection:0];
switch (self.spreadsheetHeaderConfiguration) {
case MMSpreadsheetHeaderConfigurationNone:
self.lowerRightContainerView.frame = self.bounds;
break;
case MMSpreadsheetHeaderConfigurationColumnOnly: {
CGSize size = self.lowerLeftCollectionView.collectionViewLayout.collectionViewContentSize;
CGSize cellSize = [self collectionView:self.lowerRightCollectionView
layout:self.lowerRightCollectionView.collectionViewLayout
sizeForItemAtIndexPath:indexPathZero];
CGFloat maxLockDistance = self.bounds.size.width - cellSize.width;
if (size.width > maxLockDistance) {
NSAssert(NO, @"Width of header too large! Reduce the number of header columns.");
}
self.lowerLeftContainerView.frame = CGRectMake(0.0f,
0.0f,
size.width,
self.bounds.size.height);
self.lowerRightContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace,
0.0f,
self.bounds.size.width - size.width - MMSpreadsheetViewGridSpace,
self.bounds.size.height);
break;
}
case MMSpreadsheetHeaderConfigurationRowOnly: {
CGSize size = self.upperRightCollectionView.collectionViewLayout.collectionViewContentSize;
CGSize cellSize = [self collectionView:self.lowerRightCollectionView
layout:self.lowerRightCollectionView.collectionViewLayout
sizeForItemAtIndexPath:indexPathZero];
CGFloat maxLockDistance = self.bounds.size.height - cellSize.height;
if (size.height > maxLockDistance) {
NSAssert(NO, @"Height of header too large! Reduce the number of header rows.");
}
self.upperRightContainerView.frame = CGRectMake(0.0f,
0.0f,
self.bounds.size.width,
size.height);
self.lowerRightContainerView.frame = CGRectMake(0.0f,
size.height + MMSpreadsheetViewGridSpace,
self.bounds.size.width,
self.bounds.size.height - size.height - MMSpreadsheetViewGridSpace);
break;
}
case MMSpreadsheetHeaderConfigurationBoth: {
CGSize size = self.upperLeftCollectionView.collectionViewLayout.collectionViewContentSize;
CGSize cellSize = [self collectionView:self.lowerRightCollectionView
layout:self.lowerRightCollectionView.collectionViewLayout
sizeForItemAtIndexPath:indexPathZero];
CGFloat maxLockDistance = self.bounds.size.height - cellSize.height;
if (size.height > maxLockDistance) {
NSAssert(NO, @"Height of header too large! Reduce the number of header rows.");
}
maxLockDistance = self.bounds.size.width - cellSize.width;
if (size.width > maxLockDistance) {
NSAssert(NO, @"Width of header too large! Reduce the number of header columns.");
}
self.upperLeftContainerView.frame = CGRectMake(0.0f,
0.0f,
size.width,
size.height);
self.upperRightContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace,
0.0f,
self.bounds.size.width - size.width - MMSpreadsheetViewGridSpace,
size.height);
self.lowerLeftContainerView.frame = CGRectMake(0.0f,
size.height + MMSpreadsheetViewGridSpace,
size.width,
self.bounds.size.height - size.height - MMSpreadsheetViewGridSpace);
self.lowerRightContainerView.frame = CGRectMake(size.width + MMSpreadsheetViewGridSpace,
size.height + MMSpreadsheetViewGridSpace,
self.bounds.size.width - size.width - MMSpreadsheetViewGridSpace,
self.bounds.size.height - size.height - MMSpreadsheetViewGridSpace);
break;
}
default:
NSAssert(NO, @"What have you done?");
break;
}
// Resize the indicators.
self.verticalScrollIndicator.frame = CGRectMake(self.frame.size.width - MMSpreadsheetViewScrollIndicatorWidth - self.scrollIndicatorInsets.right - MMScrollIndicatorDefaultInsetSpace,
self.scrollIndicatorInsets.top + MMSpreadsheetViewScrollIndicatorSpace,
MMSpreadsheetViewScrollIndicatorWidth,
self.frame.size.height - 4*MMSpreadsheetViewScrollIndicatorSpace);
[self updateVerticalScrollIndicator];
self.horizontalScrollIndicator.frame = CGRectMake(self.scrollIndicatorInsets.left + MMSpreadsheetViewScrollIndicatorSpace,
self.frame.size.height - MMSpreadsheetViewScrollIndicatorWidth - self.scrollIndicatorInsets.bottom - MMScrollIndicatorDefaultInsetSpace,
self.frame.size.width - 4*MMSpreadsheetViewScrollIndicatorSpace,
MMSpreadsheetViewScrollIndicatorWidth);
[self updateHorizontalScrollIndicator];
}
#pragma mark - UIPanGestureRecognizer callbacks
- (void)handleUpperRightPanGesture:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.lowerRightContainerView.userInteractionEnabled = NO;
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.isUpperRightBouncing == NO) {
self.lowerLeftContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
}
}
}
- (void)handleLowerLeftPanGesture:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerRightContainerView.userInteractionEnabled = NO;
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.isLowerLeftBouncing == NO) {
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
}
}
}
- (void)handleLowerRightPanGesture:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (self.isLowerRightBouncing == NO) {
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerLeftContainerView.userInteractionEnabled = YES;
}
}
}
#pragma mark - bounces property setter
- (void)setBounces:(BOOL)bounces {
_bounces = bounces;
self.upperLeftCollectionView.bounces = bounces;
self.upperRightCollectionView.bounces = bounces;
self.lowerLeftCollectionView.bounces = bounces;
self.lowerRightCollectionView.bounces = bounces;
}
#pragma mark - DataSource property setter
- (void)setDataSource:(id<MMSpreadsheetViewDataSource>)dataSource {
_dataSource = dataSource;
if (self.upperLeftCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.upperLeftCollectionView];
}
if (self.upperRightCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.upperRightCollectionView];
}
if (self.lowerLeftCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.lowerLeftCollectionView];
}
if (self.lowerRightCollectionView) {
[self initializeCollectionViewLayoutItemSize:self.lowerRightCollectionView];
}
// Validate dataSource & header configuration
NSInteger maxRows = [_dataSource numberOfRowsInSpreadsheetView:self];
NSInteger maxCols = [_dataSource numberOfColumnsInSpreadsheetView:self];
NSAssert(self.headerColumnCount < maxCols, @"Invalid configuration: number of header columns must be less than (dataSource) numberOfColumnsInSpreadsheetView");
NSAssert(self.headerRowCount < maxRows, @"Invalid configuration: number of header rows must be less than (dataSource) numberOfRowsInSpreadsheetView");
}
- (void)initializeCollectionViewLayoutItemSize:(UICollectionView *)collectionView {
NSIndexPath *indexPathZero = [NSIndexPath indexPathForItem:0 inSection:0];
MMGridLayout *layout = (MMGridLayout *)collectionView.collectionViewLayout;
CGSize size = [self collectionView:collectionView
layout:layout
sizeForItemAtIndexPath:indexPathZero];
layout.itemSize = size;
}
#pragma mark - Scroll Indicator
- (UIView *)setupScrollIndicator {
UIView *scrollIndicator = [[UIView alloc] initWithFrame:CGRectZero];
scrollIndicator.alpha = 0.0f;
scrollIndicator.layer.cornerRadius = MMSpreadsheetViewScrollIndicatorWidth/2;
scrollIndicator.clipsToBounds = YES;
[self addSubview:scrollIndicator];
UIView *scrollIndicatorSegment = [[UIView alloc] initWithFrame:CGRectZero];
scrollIndicatorSegment.backgroundColor = [UIColor colorWithWhite:0.44f alpha:1.0f];
scrollIndicatorSegment.layer.cornerRadius = MMSpreadsheetViewScrollIndicatorWidth/2;
scrollIndicatorSegment.tag = MMScrollIndicatorTag;
[scrollIndicator addSubview:scrollIndicatorSegment];
return scrollIndicator;
}
- (void)showScrollIndicators {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:_cmd object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideScrollIndicators) object:nil];
[UIView animateWithDuration:0.4f animations:^{
self.verticalScrollIndicator.alpha = self.showsVerticalScrollIndicator ? 1.0f : 0.0f;
self.horizontalScrollIndicator.alpha = self.showsHorizontalScrollIndicator ? 1.0f : 0.0f;
}];
}
- (void)hideScrollIndicators {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:_cmd object:nil];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(showScrollIndicators) object:nil];
[UIView animateWithDuration:0.5f animations:^{
self.verticalScrollIndicator.alpha = 0.0f;
self.horizontalScrollIndicator.alpha = 0.0f;
}];
}
- (void)updateVerticalScrollIndicator {
if (self.showsVerticalScrollIndicator) {
UIView *scrollIndicator = self.verticalScrollIndicator;
UIView *indicatorView = [scrollIndicator viewWithTag:MMScrollIndicatorTag];
UICollectionView *collectionView = self.lowerRightCollectionView;
CGSize contentSize = collectionView.collectionViewLayout.collectionViewContentSize;
CGRect collectionViewFrame = collectionView.frame;
if (collectionViewFrame.size.height > contentSize.height) {
indicatorView.frame = CGRectZero;
} else {
CGFloat indicatorHeight = collectionViewFrame.size.height / contentSize.height * scrollIndicator.frame.size.height;
if (indicatorHeight < MMSpreadsheetViewScrollIndicatorMinimum) {
indicatorHeight = MMSpreadsheetViewScrollIndicatorMinimum;
}
CGFloat indicatorOffsetY = collectionView.contentOffset.y / (contentSize.height - collectionViewFrame.size.height) * (scrollIndicator.frame.size.height - indicatorHeight);
indicatorView.frame = CGRectMake(0.0f,
indicatorOffsetY,
MMSpreadsheetViewScrollIndicatorWidth,
indicatorHeight);
}
}
}
- (void)updateHorizontalScrollIndicator {
if (self.showsHorizontalScrollIndicator) {
UIView *scrollIndicator = self.horizontalScrollIndicator;
UIView *indicatorView = [scrollIndicator viewWithTag:MMScrollIndicatorTag];
UICollectionView *collectionView = self.lowerRightCollectionView;
CGSize contentSize = collectionView.collectionViewLayout.collectionViewContentSize;
CGRect collectionViewFrame = collectionView.frame;
if (collectionView.frame.size.width > contentSize.width) {
indicatorView.frame = CGRectZero;
} else {
CGFloat indicatorWidth = collectionViewFrame.size.width/contentSize.width * scrollIndicator.frame.size.width;
if (indicatorWidth < MMSpreadsheetViewScrollIndicatorMinimum) {
indicatorWidth = MMSpreadsheetViewScrollIndicatorMinimum;
}
CGFloat indicatorOffsetX = collectionView.contentOffset.x / (contentSize.width - collectionViewFrame.size.width) * (scrollIndicator.frame.size.width-indicatorWidth);
indicatorView.frame = CGRectMake(indicatorOffsetX,
0.0f,
indicatorWidth,
MMSpreadsheetViewScrollIndicatorWidth);
}
}
}
#pragma mark - Custom functions that don't go anywhere else
- (UICollectionView *)collectionViewForDataSourceIndexPath:(NSIndexPath *)indexPath {
UICollectionView *collectionView = nil;
switch (self.spreadsheetHeaderConfiguration) {
case MMSpreadsheetHeaderConfigurationNone:
collectionView = self.lowerRightCollectionView;
break;
case MMSpreadsheetHeaderConfigurationColumnOnly:
if (indexPath.mmSpreadsheetColumn >= self.headerColumnCount) {
collectionView = self.lowerRightCollectionView;
} else {
collectionView = self.lowerLeftCollectionView;
}
break;
case MMSpreadsheetHeaderConfigurationRowOnly:
if (indexPath.mmSpreadsheetRow >= self.headerRowCount) {
collectionView = self.lowerRightCollectionView;
}
else {
collectionView = self.upperRightCollectionView;
}
break;
case MMSpreadsheetHeaderConfigurationBoth:
if (indexPath.mmSpreadsheetRow >= self.headerRowCount) {
if (indexPath.mmSpreadsheetColumn >= self.headerColumnCount) {
collectionView = self.lowerRightCollectionView;
} else {
collectionView = self.lowerLeftCollectionView;
}
}
else {
if (indexPath.mmSpreadsheetColumn >= self.headerColumnCount) {
collectionView = self.upperRightCollectionView;
} else {
collectionView = self.upperLeftCollectionView;
}
}
break;
default:
NSAssert(NO, @"What have you done?");
break;
}
return collectionView;
}
- (NSIndexPath *)dataSourceIndexPathFromCollectionView:(UICollectionView *)collectionView indexPath:(NSIndexPath *)indexPath {
NSInteger mmSpreadsheetRow = indexPath.mmSpreadsheetRow;
NSInteger mmSpreadsheetColumn = indexPath.mmSpreadsheetColumn;
if (collectionView != nil) {
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft:
break;
case MMSpreadsheetViewCollectionUpperRight:
mmSpreadsheetColumn += self.headerColumnCount;
break;
case MMSpreadsheetViewCollectionLowerLeft:
mmSpreadsheetRow += self.headerRowCount;
break;
case MMSpreadsheetViewCollectionLowerRight:
mmSpreadsheetRow += self.headerRowCount;
mmSpreadsheetColumn += self.headerColumnCount;
break;
default:
NSAssert(NO, @"What have you done?");
break;
}
}
return [NSIndexPath indexPathForItem:mmSpreadsheetColumn inSection:mmSpreadsheetRow];
}
- (NSIndexPath *)collectionViewIndexPathFromDataSourceIndexPath:(NSIndexPath *)indexPath {
UICollectionView *collectionView = [self collectionViewForDataSourceIndexPath:indexPath];
NSAssert(collectionView, @"No collectionView Returned!");
NSInteger mmSpreadsheetRow = indexPath.mmSpreadsheetRow;
NSInteger mmSpreadsheetColumn = indexPath.mmSpreadsheetColumn;
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft:
break;
case MMSpreadsheetViewCollectionUpperRight:
mmSpreadsheetColumn -= self.headerColumnCount;
break;
case MMSpreadsheetViewCollectionLowerLeft:
mmSpreadsheetRow -= self.headerRowCount;
break;
case MMSpreadsheetViewCollectionLowerRight:
mmSpreadsheetRow -= self.headerRowCount;
mmSpreadsheetColumn -= self.headerColumnCount;
break;
default:
NSAssert(NO, @"What have you done?");
break;
}
return [NSIndexPath indexPathForItem:mmSpreadsheetColumn inSection:mmSpreadsheetRow];
}
- (void)setScrollEnabledValue:(BOOL)scrollEnabled scrollView:(UIScrollView *)scrollView {
switch (scrollView.tag) {
case MMSpreadsheetViewCollectionUpperLeft:
// Don't think we need to do anything here
break;
case MMSpreadsheetViewCollectionUpperRight:
self.lowerLeftCollectionView.scrollEnabled = scrollEnabled;
self.lowerRightCollectionView.scrollEnabled = scrollEnabled;
break;
case MMSpreadsheetViewCollectionLowerLeft:
self.upperRightCollectionView.scrollEnabled = scrollEnabled;
self.lowerRightCollectionView.scrollEnabled = scrollEnabled;
break;
case MMSpreadsheetViewCollectionLowerRight:
self.upperRightCollectionView.scrollEnabled = scrollEnabled;
self.lowerLeftCollectionView.scrollEnabled = scrollEnabled;
break;
}
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
CGSize size = [self.dataSource spreadsheetView:self sizeForItemAtIndexPath:dataSourceIndexPath];
return size;
}
#pragma mark - UICollectionViewDataSource pass-through
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
NSInteger rowCount = [self.dataSource numberOfRowsInSpreadsheetView:self];
NSInteger adjustedRows = 1;
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft:
adjustedRows = self.headerRowCount;
break;
case MMSpreadsheetViewCollectionUpperRight:
adjustedRows = self.headerRowCount;
break;
case MMSpreadsheetViewCollectionLowerLeft:
adjustedRows = rowCount - self.headerRowCount;
break;
case MMSpreadsheetViewCollectionLowerRight:
adjustedRows = rowCount - self.headerRowCount;
break;
default:
NSAssert(NO, @"What have you done?");
break;
}
return adjustedRows;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
NSInteger items = 0;
NSInteger columnCount = [self.dataSource numberOfColumnsInSpreadsheetView:self];
switch (collectionView.tag) {
case MMSpreadsheetViewCollectionUpperLeft:
items = self.headerColumnCount;
break;
case MMSpreadsheetViewCollectionUpperRight:
items = columnCount - self.headerColumnCount;
break;
case MMSpreadsheetViewCollectionLowerLeft:
items = self.headerColumnCount;
break;
case MMSpreadsheetViewCollectionLowerRight:
items = columnCount - self.headerColumnCount;
break;
default:
NSAssert(NO, @"What have you done?");
break;
}
return items;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
UICollectionViewCell *cell = [self.dataSource spreadsheetView:self cellForItemAtIndexPath:dataSourceIndexPath];
return cell;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.selectedItemCollectionView != nil) {
if (collectionView == self.selectedItemCollectionView) {
self.selectedItemIndexPath = indexPath;
} else {
[self.selectedItemCollectionView deselectItemAtIndexPath:self.selectedItemIndexPath animated:NO];
self.selectedItemCollectionView = collectionView;
self.selectedItemIndexPath = indexPath;
}
} else {
self.selectedItemCollectionView = collectionView;
self.selectedItemIndexPath = indexPath;
}
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:didSelectItemAtIndexPath:)]) {
[self.delegate spreadsheetView:self didSelectItemAtIndexPath:dataSourceIndexPath];
}
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:shouldShowMenuForItemAtIndexPath:)]) {
return [self.delegate spreadsheetView:self shouldShowMenuForItemAtIndexPath:dataSourceIndexPath];
}
return NO;
}
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:canPerformAction:forItemAtIndexPath:withSender:)]) {
return [self.delegate spreadsheetView:self canPerformAction:action forItemAtIndexPath:dataSourceIndexPath withSender:sender];
}
return NO;
}
- (void)collectionView:(UICollectionView *)collectionView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
NSIndexPath *dataSourceIndexPath = [self dataSourceIndexPathFromCollectionView:collectionView indexPath:indexPath];
if ([self.delegate respondsToSelector:@selector(spreadsheetView:performAction:forItemAtIndexPath:withSender:)]) {
return [self.delegate spreadsheetView:self performAction:action forItemAtIndexPath:dataSourceIndexPath withSender:sender];
}
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.controllingScrollView) {
switch (scrollView.tag) {
case MMSpreadsheetViewCollectionLowerLeft:
[self lowerLeftCollectionViewDidScrollForScrollView:scrollView];
break;
case MMSpreadsheetViewCollectionUpperRight:
[self upperRightCollectionViewDidScrollForScrollView:scrollView];
break;
case MMSpreadsheetViewCollectionLowerRight:
[self lowerRightCollectionViewDidScrollForScrollView:scrollView];
break;
}
} else {
[scrollView setContentOffset:scrollView.contentOffset animated:NO];
}
}
- (void)lowerLeftCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView {
[self.lowerRightCollectionView setContentOffset:CGPointMake(self.lowerRightCollectionView.contentOffset.x, scrollView.contentOffset.y) animated:NO];
[self updateVerticalScrollIndicator];
if (scrollView.contentOffset.y <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0-scrollView.contentOffset.y;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0-scrollView.contentOffset.y;
self.upperRightContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0.0f;
self.upperRightContainerView.frame = rect;
}
}
- (void)upperRightCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView {
[self.lowerRightCollectionView setContentOffset:CGPointMake(scrollView.contentOffset.x, self.lowerRightCollectionView.contentOffset.y) animated:NO];
[self updateHorizontalScrollIndicator];
if (scrollView.contentOffset.x <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0-scrollView.contentOffset.x;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0-scrollView.contentOffset.x;
self.lowerLeftContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0.0f;
self.lowerLeftContainerView.frame = rect;
}
}
- (void)lowerRightCollectionViewDidScrollForScrollView:(UIScrollView *)scrollView {
[self updateVerticalScrollIndicator];
[self updateHorizontalScrollIndicator];
CGPoint offset = CGPointMake(0.0f, scrollView.contentOffset.y);
[self.lowerLeftCollectionView setContentOffset:offset animated:NO];
offset = CGPointMake(scrollView.contentOffset.x, 0.0f);
[self.upperRightCollectionView setContentOffset:offset animated:NO];
if (scrollView.contentOffset.y <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0-scrollView.contentOffset.y;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0-scrollView.contentOffset.y;
self.upperRightContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.y = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.upperRightContainerView.frame;
rect.origin.y = 0.0f;
self.upperRightContainerView.frame = rect;
}
if (scrollView.contentOffset.x <= 0.0f) {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0-scrollView.contentOffset.x;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0-scrollView.contentOffset.x;
self.lowerLeftContainerView.frame = rect;
} else {
CGRect rect = self.upperLeftContainerView.frame;
rect.origin.x = 0.0f;
self.upperLeftContainerView.frame = rect;
rect = self.lowerLeftContainerView.frame;
rect.origin.x = 0.0f;
self.lowerLeftContainerView.frame = rect;
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[self setScrollEnabledValue:NO scrollView:scrollView];
if (self.controllingScrollView != scrollView) {
[self.lowerLeftCollectionView setContentOffset:self.lowerLeftCollectionView.contentOffset animated:NO];
[self.upperRightCollectionView setContentOffset:self.upperRightCollectionView.contentOffset animated:NO];
[self.lowerRightCollectionView setContentOffset:self.lowerRightCollectionView.contentOffset animated:NO];
self.controllingScrollView = scrollView;
}
[self showScrollIndicators];
[self setScrollEnabledValue:YES scrollView:scrollView];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
// Block UI if we're in a bounce.
// Without this, you can lock the scroll views in a scroll which looks weird.
CGPoint toffset = *targetContentOffset;
switch (scrollView.tag) {
case MMSpreadsheetViewCollectionLowerLeft: {
BOOL willBouncePastZeroY = velocity.y < 0.0f && !(toffset.y > 0.0f);
BOOL willBouncePastMaxY = toffset.y > self.lowerLeftCollectionView.contentSize.height - self.lowerLeftCollectionView.frame.size.height - 0.1f && velocity.y > 0.0f;
if (willBouncePastZeroY || willBouncePastMaxY) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerRightContainerView.userInteractionEnabled = NO;
self.lowerLeftBouncing = YES;
}
break;
}
case MMSpreadsheetViewCollectionUpperRight: {
BOOL willBouncePastZeroX = velocity.x < 0.0f && !(toffset.x > 0.0f);
BOOL willBouncePastMaxX = toffset.x > self.upperRightCollectionView.contentSize.width - self.upperRightCollectionView.frame.size.width - 0.1f && velocity.x > 0.0f;
if (willBouncePastZeroX || willBouncePastMaxX) {
self.lowerRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.upperRightBouncing = YES;
}
break;
}
case MMSpreadsheetViewCollectionLowerRight: {
BOOL willBouncePastZeroX = velocity.x < 0.0f && !(toffset.x > 0.0f);
BOOL willBouncePastMaxX = toffset.x > self.upperRightCollectionView.contentSize.width - self.upperRightCollectionView.frame.size.width - 0.1f && velocity.x > 0.0f;
BOOL willBouncePastZeroY = velocity.y < 0.0f && !(toffset.y > 0.0f);
BOOL willBouncePastMaxY = toffset.y > self.lowerLeftCollectionView.contentSize.height - self.lowerLeftCollectionView.frame.size.height - 0.1f && velocity.y > 0.0f;
if (willBouncePastZeroX || willBouncePastMaxX ||
willBouncePastZeroY || willBouncePastMaxY) {
self.upperRightContainerView.userInteractionEnabled = NO;
self.lowerLeftContainerView.userInteractionEnabled = NO;
self.lowerRightBouncing = YES;
}
break;
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[self scrollViewDidStop:scrollView];
}
- (void)scrollViewDidStop:(UIScrollView *)scrollView {
self.upperRightContainerView.userInteractionEnabled = YES;
self.lowerRightContainerView.userInteractionEnabled = YES;
self.lowerLeftContainerView.userInteractionEnabled = YES;
self.upperRightBouncing = NO;
self.lowerLeftBouncing = NO;
self.lowerRightBouncing = NO;
if (!scrollView.isDecelerating && !scrollView.isDragging && !scrollView.isTracking) {
[self setNeedsLayout];
[self hideScrollIndicators];
}
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
/**
This category renames section and row values from NSIndexPath to reduce confusion when referring to a particular cell in a `MMSpreadsheetView`.
*/
@interface NSIndexPath (MMSpreadsheetView)
/**
The column value of an `NSIndexPath`.
*/
- (NSInteger)mmSpreadsheetColumn;
/**
The row value of an `NSIndexPath`.
*/
- (NSInteger)mmSpreadsheetRow;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "NSIndexPath+MMSpreadsheetView.h"
@implementation NSIndexPath (MMSpreadsheetView)
- (NSInteger)mmSpreadsheetRow {
return self.section;
}
- (NSInteger)mmSpreadsheetColumn {
return self.row;
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
@interface MMTopRowCell : UICollectionViewCell
@property (nonatomic, strong) UILabel *textLabel;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMTopRowCell.h"
@implementation MMTopRowCell
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
CGRect rect = CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height);
_textLabel = [[UILabel alloc] initWithFrame:rect];
_textLabel.backgroundColor = [UIColor clearColor];
_textLabel.textAlignment = NSTextAlignmentCenter;
_textLabel.numberOfLines = 2;
_textLabel.font = [UIFont boldSystemFontOfSize:16.0f];
_textLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:_textLabel];
}
return self;
}
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <UIKit/UIKit.h>
@interface MMViewController : UIViewController
@property (nonatomic, strong) NSMutableArray *tableData;
- (void)reloadData;
@end
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMViewController.h"
#import "CustomMMSpreadsheetView.h"
#import "MMGridCell.h"
#import "MMTopRowCell.h"
#import "MMLeftColumnCell.h"
#import "NSIndexPath+MMSpreadsheetView.h"
@interface MMViewController () <MMSpreadsheetViewDataSource, MMSpreadsheetViewDelegate>
@property (nonatomic, strong) NSMutableSet *selectedGridCells;
@property (nonatomic, strong) NSString *cellDataBuffer;
@property (nonatomic, strong) CustomMMSpreadsheetView *spreadSheetView;
@end
@implementation MMViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSUInteger rows = 4;
NSUInteger cols = 4;
// Create some fake grid data for the demo.
self.tableData = [NSMutableArray array];
for (NSUInteger rowNumber = 0; rowNumber < rows; rowNumber++) {
NSMutableArray *row = [NSMutableArray array];
for (NSUInteger columnNumber = 0; columnNumber < cols; columnNumber++) {
[row addObject:@""];
}
[self.tableData addObject:row];
}
self.selectedGridCells = [NSMutableSet set];
// Create the spreadsheet in code.
CustomMMSpreadsheetView *spreadSheetView = [[CustomMMSpreadsheetView alloc] initWithNumberOfHeaderRows:1 numberOfHeaderColumns:1 frame:self.view.bounds];
self.spreadSheetView = spreadSheetView;
spreadSheetView.tableData = self.tableData;
spreadSheetView.bounces = NO;
spreadSheetView.backgroundColor = [UIColor whiteColor];
// Register your cell classes.
[spreadSheetView registerCellClass:[MMGridCell class] forCellWithReuseIdentifier:@"GridCell"];
[spreadSheetView registerCellClass:[MMTopRowCell class] forCellWithReuseIdentifier:@"TopRowCell"];
[spreadSheetView registerCellClass:[MMTopRowCell class] forCellWithReuseIdentifier:@"BottomRowCell"];
[spreadSheetView registerCellClass:[MMLeftColumnCell class] forCellWithReuseIdentifier:@"LeftColumnCell"];
// Set the delegate & datasource for the spreadsheet view.
spreadSheetView.delegate = self;
spreadSheetView.dataSource = self;
// Add the spreadsheet view as a subview.
[self.view addSubview:spreadSheetView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - MMSpreadsheetViewDataSource
- (CGSize)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGFloat leftColumnWidth = 120.0f;
CGFloat topRowHeight = 60.0f;
CGFloat gridCellWidth = 100;
CGFloat gridCellHeight = 50.0f;
// Upper left.
if (indexPath.mmSpreadsheetRow == 0 && indexPath.mmSpreadsheetColumn == 0) {
return CGSizeMake(leftColumnWidth, topRowHeight);
}
// Upper right.
if (indexPath.mmSpreadsheetRow == 0 && indexPath.mmSpreadsheetColumn > 0) {
return CGSizeMake(gridCellWidth, topRowHeight);
}
// Lower left.
if (indexPath.mmSpreadsheetRow > 0 && indexPath.mmSpreadsheetColumn == 0) {
return CGSizeMake(leftColumnWidth, gridCellHeight);
}
return CGSizeMake(gridCellWidth, gridCellHeight);
}
- (NSInteger)numberOfRowsInSpreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView {
NSInteger rows = [self.tableData count];
return rows;
}
- (NSInteger)numberOfColumnsInSpreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView {
NSArray *rowData = [self.tableData lastObject];
NSInteger cols = [rowData count];
return cols;
}
- (UICollectionViewCell *)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView cellForItemAtIndexPath:(NSIndexPath *)indexPath collectionView:(UICollectionView *)collectionView{
UICollectionViewCell *cell = nil;
if (indexPath.mmSpreadsheetRow == 0 && indexPath.mmSpreadsheetColumn == 0) {
// Upper left.
cell = [spreadsheetView dequeueReusableCellWithReuseIdentifier:@"GridCell" forIndexPath:indexPath];
// NSLog(@"顶部左边 %p",cell);
// NSLog(@"clumn %lu row %lu", indexPath.mmSpreadsheetColumn, indexPath.mmSpreadsheetRow);
MMGridCell *gc = (MMGridCell *)cell;
NSArray *colData = [self.tableData objectAtIndex:indexPath.mmSpreadsheetRow];
NSString *rowData = [colData objectAtIndex:indexPath.mmSpreadsheetColumn];
gc.textLabel.text = rowData;
gc.textLabel.numberOfLines = 0;
cell.backgroundColor = [UIColor colorWithRed:0.996 green:0.976 blue:0.953 alpha:1.000];
}
else if (indexPath.mmSpreadsheetRow == 0 && indexPath.mmSpreadsheetColumn > 0 && collectionView.tag != MMSpreadsheetViewCollectionBottom) {
// Upper right. 顶部那一行
cell = [spreadsheetView dequeueReusableCellWithReuseIdentifier:@"TopRowCell" forIndexPath:indexPath];
// NSLog(@"顶部右边 %p",cell);
// NSLog(@"clumn %lu row %lu", indexPath.mmSpreadsheetColumn, indexPath.mmSpreadsheetRow);
MMTopRowCell *tr = (MMTopRowCell *)cell;
NSArray *colData = [self.tableData objectAtIndex:indexPath.mmSpreadsheetRow];
NSString *rowData = [colData objectAtIndex:indexPath.mmSpreadsheetColumn];
tr.textLabel.text = rowData;
if (indexPath.mmSpreadsheetColumn == 1) {
tr.textLabel.textColor = [UIColor redColor];
}else{
tr.textLabel.textColor = [UIColor blackColor];
}
tr.textLabel.numberOfLines = 2;
cell.backgroundColor = [UIColor colorWithRed:0.996 green:0.976 blue:0.953 alpha:1.000];
}
else if (indexPath.mmSpreadsheetRow > 0 && indexPath.mmSpreadsheetColumn == 0) {
// Lower left.
cell = [spreadsheetView dequeueReusableCellWithReuseIdentifier:@"LeftColumnCell" forIndexPath:indexPath];
NSLog(@"左栏 %p", cell);
NSLog(@"clumn %lu row %lu", indexPath.mmSpreadsheetColumn, indexPath.mmSpreadsheetRow);
MMLeftColumnCell *lc = (MMLeftColumnCell *)cell;
NSArray *colData = [self.tableData objectAtIndex:indexPath.mmSpreadsheetRow];
NSString *rowData = [colData objectAtIndex:indexPath.mmSpreadsheetColumn];
lc.textLabel.text = rowData;
BOOL isDarker = indexPath.mmSpreadsheetRow % 2 == 0;
if (isDarker) {
cell.backgroundColor = [UIColor colorWithRed:222.0f / 255.0f green:243.0f / 255.0f blue:250.0f / 255.0f alpha:1.0f];
} else {
cell.backgroundColor = [UIColor colorWithRed:233.0f / 255.0f green:247.0f / 255.0f blue:252.0f / 255.0f alpha:1.0f];
}
}
else if (indexPath.mmSpreadsheetRow > 0 && indexPath.mmSpreadsheetColumn > 0 && indexPath.mmSpreadsheetRow < self.tableData.count - 1 ) {
// Lower right.
cell = [spreadsheetView dequeueReusableCellWithReuseIdentifier:@"GridCell" forIndexPath:indexPath];
NSLog(@"LR %p",cell);
NSLog(@"clumn %lu row %lu", indexPath.mmSpreadsheetColumn, indexPath.mmSpreadsheetRow);
MMGridCell *gc = (MMGridCell *)cell;
NSArray *colData = [self.tableData objectAtIndex:indexPath.mmSpreadsheetRow];
NSString *rowData = [colData objectAtIndex:indexPath.mmSpreadsheetColumn];
gc.textLabel.text = rowData;
if (indexPath.mmSpreadsheetColumn == 1) {
gc.textLabel.textColor = [UIColor redColor];
}else{
gc.textLabel.textColor = [UIColor blackColor];
}
BOOL isDarker = indexPath.mmSpreadsheetRow % 2 == 0;
if (isDarker) {
cell.backgroundColor = [UIColor colorWithRed:242.0f / 255.0f green:242.0f / 255.0f blue:242.0f / 255.0f alpha:1.0f];
} else {
cell.backgroundColor = [UIColor colorWithRed:250.0f / 255.0f green:250.0f / 255.0f blue:250.0f / 255.0f alpha:1.0f];
}
}else{
cell = [spreadsheetView dequeueReusableCellWithReuseIdentifier:@"BottomRowCell" forIndexPath:indexPath];
NSLog(@"BR %p",cell);
NSLog(@"clumn %lu row %lu", indexPath.mmSpreadsheetColumn, indexPath.mmSpreadsheetRow);
MMTopRowCell *tr = (MMTopRowCell *)cell;
NSArray *colData = [self.tableData lastObject];
NSString *rowData = [colData objectAtIndex:indexPath.mmSpreadsheetColumn];
tr.textLabel.text = rowData;
if (indexPath.mmSpreadsheetColumn == 1) {
tr.textLabel.textColor = [UIColor redColor];
}else{
tr.textLabel.textColor = [UIColor blackColor];
}
cell.backgroundColor = [UIColor whiteColor];
}
return cell;
}
#pragma mark - MMSpreadsheetViewDelegate
- (void)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if ([self.selectedGridCells containsObject:indexPath]) {
[self.selectedGridCells removeObject:indexPath];
[spreadsheetView deselectItemAtIndexPath:indexPath animated:YES];
} else {
[self.selectedGridCells removeAllObjects];
[self.selectedGridCells addObject:indexPath];
}
}
- (BOOL)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (BOOL)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView canPerformAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
/*
These are the selectors the sender (a UIMenuController) sends by default.
_insertImage:
cut:
copy:
select:
selectAll:
paste:
delete:
_promptForReplace:
_showTextStyleOptions:
_define:
_addShortcut:
_accessibilitySpeak:
_accessibilitySpeakLanguageSelection:
_accessibilityPauseSpeaking:
makeTextWritingDirectionRightToLeft:
makeTextWritingDirectionLeftToRight:
We're only interested in 3 of them at this point
*/
if (action == @selector(cut:) ||
action == @selector(copy:) ||
action == @selector(paste:)) {
return YES;
}
return NO;
}
- (void)spreadsheetView:(CustomMMSpreadsheetView *)spreadsheetView performAction:(SEL)action forItemAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
NSMutableArray *rowData = [self.tableData objectAtIndex:indexPath.mmSpreadsheetRow];
if (action == @selector(cut:)) {
self.cellDataBuffer = [rowData objectAtIndex:indexPath.row];
[rowData replaceObjectAtIndex:indexPath.row withObject:@""];
[spreadsheetView reloadData];
} else if (action == @selector(copy:)) {
self.cellDataBuffer = [rowData objectAtIndex:indexPath.row];
} else if (action == @selector(paste:)) {
if (self.cellDataBuffer) {
[rowData replaceObjectAtIndex:indexPath.row withObject:self.cellDataBuffer];
[spreadsheetView reloadData];
}
}
}
- (void)reloadData {
[self.spreadSheetView reloadData];
}
@end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment