Commit a4fd38ad authored by 刘俊宏's avatar 刘俊宏

完善日历选择器

parent 9d9d2a91
......@@ -127,6 +127,23 @@
F950FDAD245163E500C40530 /* GoodsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F950FDAC245163E500C40530 /* GoodsListModel.swift */; };
F950FDB12451811A00C40530 /* TitleAndSelCalendarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F950FDB02451811A00C40530 /* TitleAndSelCalendarView.xib */; };
F950FDB32451820D00C40530 /* TitleAndSelCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F950FDB22451820D00C40530 /* TitleAndSelCalendarView.swift */; };
F950FDD724518B9500C40530 /* FSCalendarCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDB724518B9500C40530 /* FSCalendarCollectionView.m */; };
F950FDD824518B9500C40530 /* FSCalendarDelegationProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDBA24518B9500C40530 /* FSCalendarDelegationProxy.m */; };
F950FDD924518B9500C40530 /* FSCalendarDelegationFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDBC24518B9500C40530 /* FSCalendarDelegationFactory.m */; };
F950FDDA24518B9500C40530 /* FSCalendarCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDC324518B9500C40530 /* FSCalendarCell.m */; };
F950FDDB24518B9500C40530 /* FSCalendarStickyHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDC424518B9500C40530 /* FSCalendarStickyHeader.m */; };
F950FDDC24518B9500C40530 /* FSCalendar.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDC524518B9500C40530 /* FSCalendar.m */; };
F950FDDD24518B9500C40530 /* FSCalendarCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDC624518B9500C40530 /* FSCalendarCollectionViewLayout.m */; };
F950FDDE24518B9500C40530 /* FSCalendarWeekdayView.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDC724518B9500C40530 /* FSCalendarWeekdayView.m */; };
F950FDDF24518B9500C40530 /* FSCalendarSeparatorDecorationView.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDCA24518B9500C40530 /* FSCalendarSeparatorDecorationView.m */; };
F950FDE024518B9500C40530 /* FSCalendarExtensions.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDCC24518B9500C40530 /* FSCalendarExtensions.m */; };
F950FDE124518B9500C40530 /* FSCalendarConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDCE24518B9500C40530 /* FSCalendarConstants.m */; };
F950FDE224518B9500C40530 /* FSCalendarCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDCF24518B9500C40530 /* FSCalendarCalculator.m */; };
F950FDE324518B9500C40530 /* FSCalendarHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDD024518B9500C40530 /* FSCalendarHeaderView.m */; };
F950FDE424518B9500C40530 /* FSCalendarTransitionCoordinator.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDD424518B9500C40530 /* FSCalendarTransitionCoordinator.m */; };
F950FDE624518B9500C40530 /* FSCalendarAppearance.m in Sources */ = {isa = PBXBuildFile; fileRef = F950FDD624518B9500C40530 /* FSCalendarAppearance.m */; };
F950FDE824518CD800C40530 /* TimeSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F950FDE724518CD800C40530 /* TimeSelectView.swift */; };
F950FDEA2451920200C40530 /* DIYCalendarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F950FDE92451920200C40530 /* DIYCalendarCell.swift */; };
F95D9B5024484FAE0080D6E3 /* GeliAlertView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F95D9B4F24484FAE0080D6E3 /* GeliAlertView.xib */; };
F95D9B522448530E0080D6E3 /* GeliAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95D9B512448530E0080D6E3 /* GeliAlertView.swift */; };
F95D9CA424493E840080D6E3 /* ShopInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F95D9CA224493E840080D6E3 /* ShopInfoViewController.swift */; };
......@@ -300,6 +317,39 @@
F950FDAC245163E500C40530 /* GoodsListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoodsListModel.swift; sourceTree = "<group>"; };
F950FDB02451811A00C40530 /* TitleAndSelCalendarView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TitleAndSelCalendarView.xib; sourceTree = "<group>"; };
F950FDB22451820D00C40530 /* TitleAndSelCalendarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleAndSelCalendarView.swift; sourceTree = "<group>"; };
F950FDB724518B9500C40530 /* FSCalendarCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarCollectionView.m; sourceTree = "<group>"; };
F950FDB824518B9500C40530 /* FSCalendarWeekdayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarWeekdayView.h; sourceTree = "<group>"; };
F950FDB924518B9500C40530 /* FSCalendarCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarCollectionViewLayout.h; sourceTree = "<group>"; };
F950FDBA24518B9500C40530 /* FSCalendarDelegationProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarDelegationProxy.m; sourceTree = "<group>"; };
F950FDBB24518B9500C40530 /* FSCalendarExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarExtensions.h; sourceTree = "<group>"; };
F950FDBC24518B9500C40530 /* FSCalendarDelegationFactory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarDelegationFactory.m; sourceTree = "<group>"; };
F950FDBD24518B9500C40530 /* FSCalendarSeparatorDecorationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarSeparatorDecorationView.h; sourceTree = "<group>"; };
F950FDBE24518B9500C40530 /* FSCalendarCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarCalculator.h; sourceTree = "<group>"; };
F950FDBF24518B9500C40530 /* FSCalendarHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarHeaderView.h; sourceTree = "<group>"; };
F950FDC024518B9500C40530 /* FSCalendarConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarConstants.h; sourceTree = "<group>"; };
F950FDC124518B9500C40530 /* FSCalendarAppearance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarAppearance.h; sourceTree = "<group>"; };
F950FDC224518B9500C40530 /* FSCalendarTransitionCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarTransitionCoordinator.h; sourceTree = "<group>"; };
F950FDC324518B9500C40530 /* FSCalendarCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarCell.m; sourceTree = "<group>"; };
F950FDC424518B9500C40530 /* FSCalendarStickyHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarStickyHeader.m; sourceTree = "<group>"; };
F950FDC524518B9500C40530 /* FSCalendar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendar.m; sourceTree = "<group>"; };
F950FDC624518B9500C40530 /* FSCalendarCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarCollectionViewLayout.m; sourceTree = "<group>"; };
F950FDC724518B9500C40530 /* FSCalendarWeekdayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarWeekdayView.m; sourceTree = "<group>"; };
F950FDC824518B9500C40530 /* FSCalendarCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarCollectionView.h; sourceTree = "<group>"; };
F950FDC924518B9500C40530 /* FSCalendarDynamicHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarDynamicHeader.h; sourceTree = "<group>"; };
F950FDCA24518B9500C40530 /* FSCalendarSeparatorDecorationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarSeparatorDecorationView.m; sourceTree = "<group>"; };
F950FDCB24518B9500C40530 /* FSCalendarDelegationFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarDelegationFactory.h; sourceTree = "<group>"; };
F950FDCC24518B9500C40530 /* FSCalendarExtensions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarExtensions.m; sourceTree = "<group>"; };
F950FDCD24518B9500C40530 /* FSCalendarDelegationProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarDelegationProxy.h; sourceTree = "<group>"; };
F950FDCE24518B9500C40530 /* FSCalendarConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarConstants.m; sourceTree = "<group>"; };
F950FDCF24518B9500C40530 /* FSCalendarCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarCalculator.m; sourceTree = "<group>"; };
F950FDD024518B9500C40530 /* FSCalendarHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarHeaderView.m; sourceTree = "<group>"; };
F950FDD124518B9500C40530 /* FSCalendarStickyHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarStickyHeader.h; sourceTree = "<group>"; };
F950FDD224518B9500C40530 /* FSCalendar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendar.h; sourceTree = "<group>"; };
F950FDD324518B9500C40530 /* FSCalendarCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarCell.h; sourceTree = "<group>"; };
F950FDD424518B9500C40530 /* FSCalendarTransitionCoordinator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarTransitionCoordinator.m; sourceTree = "<group>"; };
F950FDD624518B9500C40530 /* FSCalendarAppearance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarAppearance.m; sourceTree = "<group>"; };
F950FDE724518CD800C40530 /* TimeSelectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeSelectView.swift; sourceTree = "<group>"; };
F950FDE92451920200C40530 /* DIYCalendarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DIYCalendarCell.swift; sourceTree = "<group>"; };
F95D9B4F24484FAE0080D6E3 /* GeliAlertView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GeliAlertView.xib; sourceTree = "<group>"; };
F95D9B512448530E0080D6E3 /* GeliAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeliAlertView.swift; sourceTree = "<group>"; };
F95D9CA224493E840080D6E3 /* ShopInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopInfoViewController.swift; sourceTree = "<group>"; };
......@@ -515,6 +565,7 @@
E088CC882446FFA1000DAA8A /* Other */ = {
isa = PBXGroup;
children = (
F950FDB524518B3300C40530 /* 日历 */,
E0D197A72448430E002B080C /* 蓝牙 */,
E088CC8B2446FFB5000DAA8A /* 验证码 */,
);
......@@ -810,10 +861,58 @@
F950FDAF245171E000C40530 /* TimeSelectView */ = {
isa = PBXGroup;
children = (
F950FDE724518CD800C40530 /* TimeSelectView.swift */,
F950FDE92451920200C40530 /* DIYCalendarCell.swift */,
);
path = TimeSelectView;
sourceTree = "<group>";
};
F950FDB524518B3300C40530 /* 日历 */ = {
isa = PBXGroup;
children = (
F950FDB624518B9500C40530 /* FSCalendar */,
);
path = "日历";
sourceTree = "<group>";
};
F950FDB624518B9500C40530 /* FSCalendar */ = {
isa = PBXGroup;
children = (
F950FDB724518B9500C40530 /* FSCalendarCollectionView.m */,
F950FDB824518B9500C40530 /* FSCalendarWeekdayView.h */,
F950FDB924518B9500C40530 /* FSCalendarCollectionViewLayout.h */,
F950FDBA24518B9500C40530 /* FSCalendarDelegationProxy.m */,
F950FDBB24518B9500C40530 /* FSCalendarExtensions.h */,
F950FDBC24518B9500C40530 /* FSCalendarDelegationFactory.m */,
F950FDBD24518B9500C40530 /* FSCalendarSeparatorDecorationView.h */,
F950FDBE24518B9500C40530 /* FSCalendarCalculator.h */,
F950FDBF24518B9500C40530 /* FSCalendarHeaderView.h */,
F950FDC024518B9500C40530 /* FSCalendarConstants.h */,
F950FDC124518B9500C40530 /* FSCalendarAppearance.h */,
F950FDC224518B9500C40530 /* FSCalendarTransitionCoordinator.h */,
F950FDC324518B9500C40530 /* FSCalendarCell.m */,
F950FDC424518B9500C40530 /* FSCalendarStickyHeader.m */,
F950FDC524518B9500C40530 /* FSCalendar.m */,
F950FDC624518B9500C40530 /* FSCalendarCollectionViewLayout.m */,
F950FDC724518B9500C40530 /* FSCalendarWeekdayView.m */,
F950FDC824518B9500C40530 /* FSCalendarCollectionView.h */,
F950FDC924518B9500C40530 /* FSCalendarDynamicHeader.h */,
F950FDCA24518B9500C40530 /* FSCalendarSeparatorDecorationView.m */,
F950FDCB24518B9500C40530 /* FSCalendarDelegationFactory.h */,
F950FDCC24518B9500C40530 /* FSCalendarExtensions.m */,
F950FDCD24518B9500C40530 /* FSCalendarDelegationProxy.h */,
F950FDCE24518B9500C40530 /* FSCalendarConstants.m */,
F950FDCF24518B9500C40530 /* FSCalendarCalculator.m */,
F950FDD024518B9500C40530 /* FSCalendarHeaderView.m */,
F950FDD124518B9500C40530 /* FSCalendarStickyHeader.h */,
F950FDD224518B9500C40530 /* FSCalendar.h */,
F950FDD324518B9500C40530 /* FSCalendarCell.h */,
F950FDD424518B9500C40530 /* FSCalendarTransitionCoordinator.m */,
F950FDD624518B9500C40530 /* FSCalendarAppearance.m */,
);
path = FSCalendar;
sourceTree = "<group>";
};
F95D9B4E24484F140080D6E3 /* AlertView */ = {
isa = PBXGroup;
children = (
......@@ -1115,25 +1214,34 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F950FDE624518B9500C40530 /* FSCalendarAppearance.m in Sources */,
F9023F6D2449A9B100DD5A63 /* TitleAndTFCell.swift in Sources */,
E0B6036E244D376900A56B38 /* KuWeiCell.swift in Sources */,
E0D197BA2448431C002B080C /* BabyDefine.m in Sources */,
F961D264244FD744007FBE70 /* SearchBarView.swift in Sources */,
F950FDE124518B9500C40530 /* FSCalendarConstants.m in Sources */,
F9023F712449ACA800DD5A63 /* TitleAndSwitchCell.swift in Sources */,
E0C4F4BD244EF53D00CC7E2F /* BindStepTwoViewController.swift in Sources */,
E0F97B1724517C87001BD600 /* AutoTextField.swift in Sources */,
E0DDCB13244ADF2C00DF54C7 /* ChangePriceNormalCell.swift in Sources */,
E09B03B62445B85A00211A51 /* LoginViewController.swift in Sources */,
E0C4F4B9244EEA7C00CC7E2F /* JiaTiJiaCell.swift in Sources */,
F950FDDE24518B9500C40530 /* FSCalendarWeekdayView.m in Sources */,
E02DDD90244FE54C00ABB707 /* NewCreateHeader.swift in Sources */,
F950FDA92451613500C40530 /* GetMobileModel.swift in Sources */,
E0336DD5244EC40000380BE9 /* CreatNewSpecsViewController.swift in Sources */,
E0DDCB18244AE2AC00DF54C7 /* XiuGaiYuShouJiaViewController.swift in Sources */,
F950FDEA2451920200C40530 /* DIYCalendarCell.swift in Sources */,
F950FDE224518B9500C40530 /* FSCalendarCalculator.m in Sources */,
E0D197C12448431C002B080C /* BabyToy.m in Sources */,
E0F97B0C24516B1F001BD600 /* AddImgCell.swift in Sources */,
E09B03C32445BD9500211A51 /* NavViewController.swift in Sources */,
E0F97B102451728D001BD600 /* AddGuiGeCell.swift in Sources */,
E0F97B1A2451951D001BD600 /* ShangPinGguiGeCell.swift in Sources */,
F950FDDB24518B9500C40530 /* FSCalendarStickyHeader.m in Sources */,
E09B03C32445BD9500211A51 /* NavViewController.swift in Sources */,
F950FDD924518B9500C40530 /* FSCalendarDelegationFactory.m in Sources */,
F950FDE824518CD800C40530 /* TimeSelectView.swift in Sources */,
E02DDD85244FDC6A00ABB707 /* TitleAndSwitchHeaderView.swift in Sources */,
E0D197C02448431C002B080C /* BabyCentralManager.m in Sources */,
F95D9CA424493E840080D6E3 /* ShopInfoViewController.swift in Sources */,
......@@ -1146,20 +1254,25 @@
E0516F7F244806C500E373CE /* MsgCenterViewController.swift in Sources */,
F950FDA52451453A00C40530 /* ShopInfoModel.swift in Sources */,
E0516F822448086700E373CE /* MsgCenterCell.swift in Sources */,
F950FDE324518B9500C40530 /* FSCalendarHeaderView.m in Sources */,
F950FDA32451438A00C40530 /* AdminInfoModel.swift in Sources */,
E0D197D5244860B1002B080C /* BlueToothCell.swift in Sources */,
F961D27724503F51007FBE70 /* OrderSelectMoreView.swift in Sources */,
F95D9B522448530E0080D6E3 /* GeliAlertView.swift in Sources */,
E0D197BB2448431C002B080C /* BabyCallback.m in Sources */,
F950FDDD24518B9500C40530 /* FSCalendarCollectionViewLayout.m in Sources */,
F961D26B24500FF6007FBE70 /* HeaderSelectView.swift in Sources */,
E0D4166424483058008DCAB4 /* ChangePassViewController.swift in Sources */,
E0C4F4A7244EC82A00CC7E2F /* TitleAndBtnCell.swift in Sources */,
F950FDD724518B9500C40530 /* FSCalendarCollectionView.m in Sources */,
E09B03C62445BF6700211A51 /* NavBarView.swift in Sources */,
E0C9EF12244E922200277435 /* OrderDViewController.swift in Sources */,
F950FDE424518B9500C40530 /* FSCalendarTransitionCoordinator.m in Sources */,
F9171F0224513C5B00359C2B /* UpDataModel.swift in Sources */,
F950FDAD245163E500C40530 /* GoodsListModel.swift in Sources */,
E0D197BD2448431C002B080C /* BabySpeaker.m in Sources */,
E00D85E1244561FD00F5F816 /* SceneDelegate.swift in Sources */,
F950FDDA24518B9500C40530 /* FSCalendarCell.m in Sources */,
F969BF1E244EE67900C79953 /* GoodsManageListVC.swift in Sources */,
E09B03A12445AF7100211A51 /* Define.swift in Sources */,
F961D271245016B7007FBE70 /* SelectContentView.swift in Sources */,
......@@ -1176,6 +1289,9 @@
E09B03AB2445B0A300211A51 /* LoginModel.swift in Sources */,
E0D197BE2448431C002B080C /* BabyOptions.m in Sources */,
E0D197C22448431C002B080C /* BabyRhythm.m in Sources */,
F950FDDC24518B9500C40530 /* FSCalendar.m in Sources */,
F950FDD824518B9500C40530 /* FSCalendarDelegationProxy.m in Sources */,
F950FDDF24518B9500C40530 /* FSCalendarSeparatorDecorationView.m in Sources */,
F9023F692449A50900DD5A63 /* TitleContentCell.swift in Sources */,
E0DDCB1D244AE81800DF54C7 /* GuanLianKeHuCell.swift in Sources */,
E0D197BF2448431C002B080C /* BabyBluetooth.m in Sources */,
......@@ -1190,6 +1306,7 @@
F9023F62244999F500DD5A63 /* WarehoseMangementCell.swift in Sources */,
E0516F772448003D00E373CE /* PayViewController.swift in Sources */,
E02DDD952450106C00ABB707 /* PuTongJieTiCell.swift in Sources */,
F950FDE024518B9500C40530 /* FSCalendarExtensions.m in Sources */,
E09B03A32445AF9500211A51 /* NetworkRequest.swift in Sources */,
E0D46C5624503EC50087B564 /* TitleAndSelectCell.swift in Sources */,
E0F97B022451228A001BD600 /* GLAlertSelectView.swift in Sources */,
......
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "wangnian.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "wangnian@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wangnian@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "wangyue.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "wangyue@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "wangyue@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "xiageyue.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "xiageyue@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "xiageyue@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "xiayinian.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "xiayinian@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "xiayinian@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -11,5 +11,6 @@
#import "MJRefresh.h"
#import "WYHead.h"
#import "FSCalendar.h"
#endif /* Bridge_Header_h */
//
// FSCalendar.h
// FSCalendar
//
// Created by Wenchao Ding on 29/1/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// https://github.com/WenchaoD
//
// FSCalendar is a superior awesome calendar control with high performance, high customizablility and very simple usage.
//
// @see FSCalendarDataSource
// @see FSCalendarDelegate
// @see FSCalendarDelegateAppearance
// @see FSCalendarAppearance
//
#import <UIKit/UIKit.h>
#import "FSCalendarAppearance.h"
#import "FSCalendarConstants.h"
#import "FSCalendarCell.h"
#import "FSCalendarWeekdayView.h"
#import "FSCalendarHeaderView.h"
//! Project version number for FSCalendar.
FOUNDATION_EXPORT double FSCalendarVersionNumber;
//! Project version string for FSCalendar.
FOUNDATION_EXPORT const unsigned char FSCalendarVersionString[];
typedef NS_ENUM(NSUInteger, FSCalendarScope) {
FSCalendarScopeMonth,
FSCalendarScopeWeek
};
typedef NS_ENUM(NSUInteger, FSCalendarScrollDirection) {
FSCalendarScrollDirectionVertical,
FSCalendarScrollDirectionHorizontal
};
typedef NS_ENUM(NSUInteger, FSCalendarPlaceholderType) {
FSCalendarPlaceholderTypeNone = 0,
FSCalendarPlaceholderTypeFillHeadTail = 1,
FSCalendarPlaceholderTypeFillSixRows = 2
};
typedef NS_ENUM(NSUInteger, FSCalendarMonthPosition) {
FSCalendarMonthPositionPrevious,
FSCalendarMonthPositionCurrent,
FSCalendarMonthPositionNext,
FSCalendarMonthPositionNotFound = NSNotFound
};
NS_ASSUME_NONNULL_BEGIN
@class FSCalendar;
/**
* FSCalendarDataSource is a source set of FSCalendar. The basic role is to provide event、subtitle and min/max day to display, or customized day cell for the calendar.
*/
@protocol FSCalendarDataSource <NSObject>
@optional
/**
* Asks the dataSource for a title for the specific date as a replacement of the day text
*/
- (nullable NSString *)calendar:(FSCalendar *)calendar titleForDate:(NSDate *)date;
/**
* Asks the dataSource for a subtitle for the specific date under the day text.
*/
- (nullable NSString *)calendar:(FSCalendar *)calendar subtitleForDate:(NSDate *)date;
/**
* Asks the dataSource for an image for the specific date.
*/
- (nullable UIImage *)calendar:(FSCalendar *)calendar imageForDate:(NSDate *)date;
/**
* Asks the dataSource the minimum date to display.
*/
- (NSDate *)minimumDateForCalendar:(FSCalendar *)calendar;
/**
* Asks the dataSource the maximum date to display.
*/
- (NSDate *)maximumDateForCalendar:(FSCalendar *)calendar;
/**
* Asks the data source for a cell to insert in a particular data of the calendar.
*/
- (__kindof FSCalendarCell *)calendar:(FSCalendar *)calendar cellForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position;
/**
* Asks the dataSource the number of event dots for a specific date.
*
* @see
* - (UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventColorForDate:(NSDate *)date;
* - (NSArray *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventColorsForDate:(NSDate *)date;
*/
- (NSInteger)calendar:(FSCalendar *)calendar numberOfEventsForDate:(NSDate *)date;
@end
/**
* The delegate of a FSCalendar object must adopt the FSCalendarDelegate protocol. The optional methods of FSCalendarDelegate manage selections、 user events and help to manager the frame of the calendar.
*/
@protocol FSCalendarDelegate <NSObject>
@optional
/**
Asks the delegate whether the specific date is allowed to be selected by tapping.
*/
- (BOOL)calendar:(FSCalendar *)calendar shouldSelectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
Tells the delegate a date in the calendar is selected by tapping.
*/
- (void)calendar:(FSCalendar *)calendar didSelectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
Asks the delegate whether the specific date is allowed to be deselected by tapping.
*/
- (BOOL)calendar:(FSCalendar *)calendar shouldDeselectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
Tells the delegate a date in the calendar is deselected by tapping.
*/
- (void)calendar:(FSCalendar *)calendar didDeselectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
Tells the delegate the calendar is about to change the bounding rect.
*/
- (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated;
/**
Tells the delegate that the specified cell is about to be displayed in the calendar.
*/
- (void)calendar:(FSCalendar *)calendar willDisplayCell:(FSCalendarCell *)cell forDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
Tells the delegate the calendar is about to change the current page.
*/
- (void)calendarCurrentPageDidChange:(FSCalendar *)calendar;
@end
/**
* FSCalendarDelegateAppearance determines the fonts and colors of components in the calendar, but more specificly. Basically, if you need to make a global customization of appearance of the calendar, use FSCalendarAppearance. But if you need different appearance for different days, use FSCalendarDelegateAppearance.
*
* @see FSCalendarAppearance
*/
@protocol FSCalendarDelegateAppearance <FSCalendarDelegate>
@optional
/**
* Asks the delegate for a fill color in unselected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance fillDefaultColorForDate:(NSDate *)date;
/**
* Asks the delegate for a fill color in selected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance fillSelectionColorForDate:(NSDate *)date;
/**
* Asks the delegate for day text color in unselected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance titleDefaultColorForDate:(NSDate *)date;
/**
* Asks the delegate for day text color in selected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance titleSelectionColorForDate:(NSDate *)date;
/**
* Asks the delegate for subtitle text color in unselected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance subtitleDefaultColorForDate:(NSDate *)date;
/**
* Asks the delegate for subtitle text color in selected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance subtitleSelectionColorForDate:(NSDate *)date;
/**
* Asks the delegate for event colors for the specific date.
*/
- (nullable NSArray<UIColor *> *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventDefaultColorsForDate:(NSDate *)date;
/**
* Asks the delegate for multiple event colors in selected state for the specific date.
*/
- (nullable NSArray<UIColor *> *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventSelectionColorsForDate:(NSDate *)date;
/**
* Asks the delegate for a border color in unselected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance borderDefaultColorForDate:(NSDate *)date;
/**
* Asks the delegate for a border color in selected state for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance borderSelectionColorForDate:(NSDate *)date;
/**
* Asks the delegate for an offset for day text for the specific date.
*/
- (CGPoint)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance titleOffsetForDate:(NSDate *)date;
/**
* Asks the delegate for an offset for subtitle for the specific date.
*/
- (CGPoint)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance subtitleOffsetForDate:(NSDate *)date;
/**
* Asks the delegate for an offset for image for the specific date.
*/
- (CGPoint)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance imageOffsetForDate:(NSDate *)date;
/**
* Asks the delegate for an offset for event dots for the specific date.
*/
- (CGPoint)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventOffsetForDate:(NSDate *)date;
/**
* Asks the delegate for a border radius for the specific date.
*/
- (CGFloat)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance borderRadiusForDate:(NSDate *)date;
@end
#pragma mark - Primary
IB_DESIGNABLE
@interface FSCalendar : UIView
/**
* The object that acts as the delegate of the calendar.
*/
@property (weak, nonatomic) IBOutlet id<FSCalendarDelegate> delegate;
/**
* The object that acts as the data source of the calendar.
*/
@property (weak, nonatomic) IBOutlet id<FSCalendarDataSource> dataSource;
/**
* A special mark will be put on 'today' of the calendar.
*/
@property (nullable, strong, nonatomic) NSDate *today;
/**
* The current page of calendar
*
* @desc In week mode, current page represents the current visible week; In month mode, it means current visible month.
*/
@property (strong, nonatomic) NSDate *currentPage;
/**
* The locale of month and weekday symbols. Change it to display them in your own language.
*
* e.g. To display them in Chinese:
*
* calendar.locale = [NSLocale localeWithLocaleIdentifier:@"zh-CN"];
*/
@property (copy, nonatomic) NSLocale *locale;
/**
* The scroll direction of FSCalendar.
*
* e.g. To make the calendar scroll vertically
*
* calendar.scrollDirection = FSCalendarScrollDirectionVertical;
*/
@property (assign, nonatomic) FSCalendarScrollDirection scrollDirection;
/**
* The scope of calendar, change scope will trigger an inner frame change, make sure the frame has been correctly adjusted in
*
* - (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated;
*/
@property (assign, nonatomic) FSCalendarScope scope;
/**
A UIPanGestureRecognizer instance which enables the control of scope on the whole day-area. Not available if the scrollDirection is vertical.
@deprecated Use -handleScopeGesture: instead
e.g.
UIPanGestureRecognizer *scopeGesture = [[UIPanGestureRecognizer alloc] initWithTarget:calendar action:@selector(handleScopeGesture:)];
[calendar addGestureRecognizer:scopeGesture];
@see DIYExample
@see FSCalendarScopeExample
*/
@property (readonly, nonatomic) UIPanGestureRecognizer *scopeGesture FSCalendarDeprecated(handleScopeGesture:);
/**
* A UILongPressGestureRecognizer instance which enables the swipe-to-choose feature of the calendar.
*
* e.g.
*
* calendar.swipeToChooseGesture.enabled = YES;
*/
@property (readonly, nonatomic) UILongPressGestureRecognizer *swipeToChooseGesture;
/**
* The placeholder type of FSCalendar. Default is FSCalendarPlaceholderTypeFillSixRows.
*
* e.g. To hide all placeholder of the calendar
*
* calendar.placeholderType = FSCalendarPlaceholderTypeNone;
*/
@property (assign, nonatomic) FSCalendarPlaceholderType placeholderType;
/**
The index of the first weekday of the calendar. Give a '2' to make Monday in the first column.
*/
@property (assign, nonatomic) IBInspectable NSUInteger firstWeekday;
/**
The height of month header of the calendar. Give a '0' to remove the header.
*/
@property (assign, nonatomic) IBInspectable CGFloat headerHeight;
/**
The height of weekday header of the calendar.
*/
@property (assign, nonatomic) IBInspectable CGFloat weekdayHeight;
/**
The weekday view of the calendar
*/
@property (strong, nonatomic) FSCalendarWeekdayView *calendarWeekdayView;
/**
The header view of the calendar
*/
@property (strong, nonatomic) FSCalendarHeaderView *calendarHeaderView;
/**
A Boolean value that determines whether users can select a date.
*/
@property (assign, nonatomic) IBInspectable BOOL allowsSelection;
/**
A Boolean value that determines whether users can select more than one date.
*/
@property (assign, nonatomic) IBInspectable BOOL allowsMultipleSelection;
/**
A Boolean value that determines whether the bounding rect changes when the displayed month of the calendar is changed.
*/
@property (assign, nonatomic) IBInspectable BOOL adjustsBoundingRectWhenChangingMonths;
/**
A Boolean value that determines whether paging is enabled for the calendar.
*/
@property (assign, nonatomic) IBInspectable BOOL pagingEnabled;
/**
A Boolean value that determines whether scrolling is enabled for the calendar.
*/
@property (assign, nonatomic) IBInspectable BOOL scrollEnabled;
/**
The row height of the calendar if paging enabled is NO.;
*/
@property (assign, nonatomic) IBInspectable CGFloat rowHeight;
/**
The calendar appearance used to control the global fonts、colors .etc
*/
@property (readonly, nonatomic) FSCalendarAppearance *appearance;
/**
A date object representing the minimum day enable、visible and selectable. (read-only)
*/
@property (readonly, nonatomic) NSDate *minimumDate;
/**
A date object representing the maximum day enable、visible and selectable. (read-only)
*/
@property (readonly, nonatomic) NSDate *maximumDate;
/**
A date object identifying the section of the selected date. (read-only)
*/
@property (nullable, readonly, nonatomic) NSDate *selectedDate;
/**
The dates representing the selected dates. (read-only)
*/
@property (readonly, nonatomic) NSArray<NSDate *> *selectedDates;
/**
Reload the dates and appearance of the calendar.
*/
- (void)reloadData;
/**
Change the scope of the calendar. Make sure `-calendar:boundingRectWillChange:animated` is correctly adopted.
@param scope The target scope to change.
@param animated YES if you want to animate the scoping; NO if the change should be immediate.
*/
- (void)setScope:(FSCalendarScope)scope animated:(BOOL)animated;
/**
Selects a given date in the calendar.
@param date A date in the calendar.
*/
- (void)selectDate:(nullable NSDate *)date;
/**
Selects a given date in the calendar, optionally scrolling the date to visible area.
@param date A date in the calendar.
@param scrollToDate A Boolean value that determines whether the calendar should scroll to the selected date to visible area.
*/
- (void)selectDate:(nullable NSDate *)date scrollToDate:(BOOL)scrollToDate;
/**
Deselects a given date of the calendar.
@param date A date in the calendar.
*/
- (void)deselectDate:(NSDate *)date;
/**
Changes the current page of the calendar.
@param currentPage Representing weekOfYear in week mode, or month in month mode.
@param animated YES if you want to animate the change in position; NO if it should be immediate.
*/
- (void)setCurrentPage:(NSDate *)currentPage animated:(BOOL)animated;
/**
Register a class for use in creating new calendar cells.
@param cellClass The class of a cell that you want to use in the calendar.
@param identifier The reuse identifier to associate with the specified class. This parameter must not be nil and must not be an empty string.
*/
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier;
/**
Returns a reusable calendar cell object located by its identifier.
@param identifier The reuse identifier for the specified cell. This parameter must not be nil.
@param date The specific date of the cell.
@return A valid FSCalendarCell object.
*/
- (__kindof FSCalendarCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position;
/**
Returns the calendar cell for the specified date.
@param date The date of the cell
@param position The month position for the cell
@return An object representing a cell of the calendar, or nil if the cell is not visible or date is out of range.
*/
- (nullable FSCalendarCell *)cellForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position;
/**
Returns the date of the specified cell.
@param cell The cell object whose date you want.
@return The date of the cell or nil if the specified cell is not in the calendar.
*/
- (nullable NSDate *)dateForCell:(FSCalendarCell *)cell;
/**
Returns the month position of the specified cell.
@param cell The cell object whose month position you want.
@return The month position of the cell or FSCalendarMonthPositionNotFound if the specified cell is not in the calendar.
*/
- (FSCalendarMonthPosition)monthPositionForCell:(FSCalendarCell *)cell;
/**
Returns an array of visible cells currently displayed by the calendar.
@return An array of FSCalendarCell objects. If no cells are visible, this method returns an empty array.
*/
- (NSArray<__kindof FSCalendarCell *> *)visibleCells;
/**
Returns the frame for a non-placeholder cell relative to the super view of the calendar.
@param date A date is the calendar.
*/
- (CGRect)frameForDate:(NSDate *)date;
/**
An action selector for UIPanGestureRecognizer instance to control the scope transition
@param sender A UIPanGestureRecognizer instance which controls the scope of the calendar
*/
- (void)handleScopeGesture:(UIPanGestureRecognizer *)sender;
@end
IB_DESIGNABLE
@interface FSCalendar (IBExtension)
#if TARGET_INTERFACE_BUILDER
@property (assign, nonatomic) IBInspectable CGFloat titleTextSize;
@property (assign, nonatomic) IBInspectable CGFloat subtitleTextSize;
@property (assign, nonatomic) IBInspectable CGFloat weekdayTextSize;
@property (assign, nonatomic) IBInspectable CGFloat headerTitleTextSize;
@property (strong, nonatomic) IBInspectable UIColor *eventDefaultColor;
@property (strong, nonatomic) IBInspectable UIColor *eventSelectionColor;
@property (strong, nonatomic) IBInspectable UIColor *weekdayTextColor;
@property (strong, nonatomic) IBInspectable UIColor *headerTitleColor;
@property (strong, nonatomic) IBInspectable NSString *headerDateFormat;
@property (assign, nonatomic) IBInspectable CGFloat headerMinimumDissolvedAlpha;
@property (strong, nonatomic) IBInspectable UIColor *titleDefaultColor;
@property (strong, nonatomic) IBInspectable UIColor *titleSelectionColor;
@property (strong, nonatomic) IBInspectable UIColor *titleTodayColor;
@property (strong, nonatomic) IBInspectable UIColor *titlePlaceholderColor;
@property (strong, nonatomic) IBInspectable UIColor *titleWeekendColor;
@property (strong, nonatomic) IBInspectable UIColor *subtitleDefaultColor;
@property (strong, nonatomic) IBInspectable UIColor *subtitleSelectionColor;
@property (strong, nonatomic) IBInspectable UIColor *subtitleTodayColor;
@property (strong, nonatomic) IBInspectable UIColor *subtitlePlaceholderColor;
@property (strong, nonatomic) IBInspectable UIColor *subtitleWeekendColor;
@property (strong, nonatomic) IBInspectable UIColor *selectionColor;
@property (strong, nonatomic) IBInspectable UIColor *todayColor;
@property (strong, nonatomic) IBInspectable UIColor *todaySelectionColor;
@property (strong, nonatomic) IBInspectable UIColor *borderDefaultColor;
@property (strong, nonatomic) IBInspectable UIColor *borderSelectionColor;
@property (assign, nonatomic) IBInspectable CGFloat borderRadius;
@property (assign, nonatomic) IBInspectable BOOL useVeryShortWeekdaySymbols;
@property (assign, nonatomic) IBInspectable BOOL fakeSubtitles;
@property (assign, nonatomic) IBInspectable BOOL fakeEventDots;
@property (assign, nonatomic) IBInspectable NSInteger fakedSelectedDay;
#endif
@end
NS_ASSUME_NONNULL_END
//
// FSCalendar.m
// FSCalendar
//
// Created by Wenchao Ding on 29/1/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendar.h"
#import "FSCalendarHeaderView.h"
#import "FSCalendarWeekdayView.h"
#import "FSCalendarStickyHeader.h"
#import "FSCalendarCollectionViewLayout.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarTransitionCoordinator.h"
#import "FSCalendarCalculator.h"
#import "FSCalendarDelegationFactory.h"
NS_ASSUME_NONNULL_BEGIN
static inline void FSCalendarAssertDateInBounds(NSDate *date, NSCalendar *calendar, NSDate *minimumDate, NSDate *maximumDate) {
BOOL valid = YES;
NSInteger minOffset = [calendar components:NSCalendarUnitDay fromDate:minimumDate toDate:date options:0].day;
valid &= minOffset >= 0;
if (valid) {
NSInteger maxOffset = [calendar components:NSCalendarUnitDay fromDate:maximumDate toDate:date options:0].day;
valid &= maxOffset <= 0;
}
if (!valid) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy/MM/dd";
[NSException raise:@"FSCalendar date out of bounds exception" format:@"Target date %@ beyond bounds [%@ - %@]", [formatter stringFromDate:date], [formatter stringFromDate:minimumDate], [formatter stringFromDate:maximumDate]];
}
}
NS_ASSUME_NONNULL_END
typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
FSCalendarOrientationLandscape,
FSCalendarOrientationPortrait
};
@interface FSCalendar ()<UICollectionViewDataSource,UICollectionViewDelegate,FSCalendarCollectionViewInternalDelegate,UIGestureRecognizerDelegate>
{
NSMutableArray *_selectedDates;
}
@property (strong, nonatomic) NSCalendar *gregorian;
@property (strong, nonatomic) NSDateFormatter *formatter;
@property (strong, nonatomic) NSTimeZone *timeZone;
@property (weak , nonatomic) UIView *contentView;
@property (weak , nonatomic) UIView *daysContainer;
@property (weak , nonatomic) FSCalendarCollectionView *collectionView;
@property (weak , nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout;
@property (strong, nonatomic) FSCalendarTransitionCoordinator *transitionCoordinator;
@property (strong, nonatomic) FSCalendarCalculator *calculator;
@property (weak , nonatomic) FSCalendarHeaderTouchDeliver *deliver;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
@property (assign, nonatomic) BOOL needsRequestingBoundingDates;
@property (assign, nonatomic) CGFloat preferredHeaderHeight;
@property (assign, nonatomic) CGFloat preferredWeekdayHeight;
@property (assign, nonatomic) CGFloat preferredRowHeight;
@property (assign, nonatomic) FSCalendarOrientation orientation;
@property (strong, nonatomic) NSMutableArray<NSOperation *> *didLayoutOperations;
@property (readonly, nonatomic) BOOL floatingMode;
@property (readonly, nonatomic) BOOL hasValidateVisibleLayout;
@property (readonly, nonatomic) NSArray *visibleStickyHeaders;
@property (readonly, nonatomic) FSCalendarOrientation currentCalendarOrientation;
@property (strong, nonatomic) FSCalendarDelegationProxy *dataSourceProxy;
@property (strong, nonatomic) FSCalendarDelegationProxy *delegateProxy;
@property (strong, nonatomic) NSIndexPath *lastPressedIndexPath;
@property (strong, nonatomic) NSMapTable *visibleSectionHeaders;
- (void)orientationDidChange:(NSNotification *)notification;
- (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope;
- (void)scrollToDate:(NSDate *)date;
- (void)scrollToDate:(NSDate *)date animated:(BOOL)animated;
- (void)scrollToPageForDate:(NSDate *)date animated:(BOOL)animated;
- (BOOL)isPageInRange:(NSDate *)page;
- (BOOL)isDateInRange:(NSDate *)date;
- (BOOL)isDateSelected:(NSDate *)date;
- (BOOL)isDateInDifferentPage:(NSDate *)date;
- (void)selectDate:(NSDate *)date scrollToDate:(BOOL)scrollToDate atMonthPosition:(FSCalendarMonthPosition)monthPosition;
- (void)enqueueSelectedDate:(NSDate *)date;
- (void)invalidateDateTools;
- (void)invalidateLayout;
- (void)invalidateHeaders;
- (void)invalidateAppearanceForCell:(FSCalendarCell *)cell forDate:(NSDate *)date;
- (void)invalidateViewFrames;
- (void)handleSwipeToChoose:(UILongPressGestureRecognizer *)pressGesture;
- (void)selectCounterpartDate:(NSDate *)date;
- (void)deselectCounterpartDate:(NSDate *)date;
- (void)reloadDataForCell:(FSCalendarCell *)cell atIndexPath:(NSIndexPath *)indexPath;
- (void)adjustMonthPosition;
- (BOOL)requestBoundingDatesIfNecessary;
- (void)executePendingOperationsIfNeeded;
- (void)configureAppearance;
@end
@implementation FSCalendar
@dynamic selectedDate;
@synthesize scopeGesture = _scopeGesture, swipeToChooseGesture = _swipeToChooseGesture;
#pragma mark - Life Cycle && Initialize
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initialize];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initialize];
}
return self;
}
- (void)initialize
{
_appearance = [[FSCalendarAppearance alloc] init];
_appearance.calendar = self;
_gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
_formatter = [[NSDateFormatter alloc] init];
_formatter.dateFormat = @"yyyy-MM-dd";
_locale = [NSLocale currentLocale];
_timeZone = [NSTimeZone defaultTimeZone];
_firstWeekday = 1;
[self invalidateDateTools];
_today = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:[NSDate date] options:0];
_currentPage = [self.gregorian fs_firstDayOfMonth:_today];
_minimumDate = [self.formatter dateFromString:@"1970-01-01"];
_maximumDate = [self.formatter dateFromString:@"2099-12-31"];
_headerHeight = FSCalendarAutomaticDimension;
_weekdayHeight = FSCalendarAutomaticDimension;
_rowHeight = FSCalendarStandardRowHeight*MAX(1, FSCalendarDeviceIsIPad*1.5);
_preferredHeaderHeight = FSCalendarAutomaticDimension;
_preferredWeekdayHeight = FSCalendarAutomaticDimension;
_preferredRowHeight = FSCalendarAutomaticDimension;
_scrollDirection = FSCalendarScrollDirectionHorizontal;
_scope = FSCalendarScopeMonth;
_selectedDates = [NSMutableArray arrayWithCapacity:1];
_visibleSectionHeaders = [NSMapTable weakToWeakObjectsMapTable];
_pagingEnabled = YES;
_scrollEnabled = YES;
_needsAdjustingViewFrame = YES;
_needsRequestingBoundingDates = YES;
_orientation = self.currentCalendarOrientation;
_placeholderType = FSCalendarPlaceholderTypeFillSixRows;
_dataSourceProxy = [FSCalendarDelegationFactory dataSourceProxy];
_delegateProxy = [FSCalendarDelegationFactory delegateProxy];
self.didLayoutOperations = NSMutableArray.array;
UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero];
contentView.backgroundColor = [UIColor clearColor];
contentView.clipsToBounds = YES;
[self addSubview:contentView];
self.contentView = contentView;
UIView *daysContainer = [[UIView alloc] initWithFrame:CGRectZero];
daysContainer.backgroundColor = [UIColor clearColor];
daysContainer.clipsToBounds = YES;
[contentView addSubview:daysContainer];
self.daysContainer = daysContainer;
FSCalendarCollectionViewLayout *collectionViewLayout = [[FSCalendarCollectionViewLayout alloc] init];
collectionViewLayout.calendar = self;
FSCalendarCollectionView *collectionView = [[FSCalendarCollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:collectionViewLayout];
collectionView.dataSource = self;
collectionView.delegate = self;
collectionView.internalDelegate = self;
collectionView.backgroundColor = [UIColor clearColor];
collectionView.pagingEnabled = YES;
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
collectionView.allowsMultipleSelection = NO;
collectionView.clipsToBounds = YES;
[collectionView registerClass:[FSCalendarCell class] forCellWithReuseIdentifier:FSCalendarDefaultCellReuseIdentifier];
[collectionView registerClass:[FSCalendarBlankCell class] forCellWithReuseIdentifier:FSCalendarBlankCellReuseIdentifier];
[collectionView registerClass:[FSCalendarStickyHeader class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
[collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"placeholderHeader"];
[daysContainer addSubview:collectionView];
self.collectionView = collectionView;
self.collectionViewLayout = collectionViewLayout;
[self invalidateLayout];
// Assistants
self.transitionCoordinator = [[FSCalendarTransitionCoordinator alloc] initWithCalendar:self];
self.calculator = [[FSCalendarCalculator alloc] initWithCalendar:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)dealloc
{
self.collectionView.delegate = nil;
self.collectionView.dataSource = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}
#pragma mark - Overriden methods
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
if (!CGRectIsEmpty(bounds) && self.transitionCoordinator.state == FSCalendarTransitionStateIdle) {
[self invalidateViewFrames];
}
}
- (void)setFrame:(CGRect)frame
{
[super setFrame:frame];
if (!CGRectIsEmpty(frame) && self.transitionCoordinator.state == FSCalendarTransitionStateIdle) {
[self invalidateViewFrames];
}
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
#if !TARGET_INTERFACE_BUILDER
if ([key hasPrefix:@"fake"]) {
return;
}
#endif
if (key.length) {
NSString *setter = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
SEL selector = NSSelectorFromString(setter);
if ([self.appearance respondsToSelector:selector]) {
return [self.appearance setValue:value forKey:key];
} else if ([self.collectionViewLayout respondsToSelector:selector]) {
return [self.collectionViewLayout setValue:value forKey:key];
}
}
return [super setValue:value forUndefinedKey:key];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (_needsAdjustingViewFrame) {
_needsAdjustingViewFrame = NO;
if (CGSizeEqualToSize(_transitionCoordinator.cachedMonthSize, CGSizeZero)) {
_transitionCoordinator.cachedMonthSize = self.frame.size;
}
_contentView.frame = self.bounds;
CGFloat headerHeight = self.preferredHeaderHeight;
CGFloat weekdayHeight = self.preferredWeekdayHeight;
CGFloat rowHeight = self.preferredRowHeight;
CGFloat padding = 5;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
rowHeight = FSCalendarFloor(rowHeight*2)*0.5; // Round to nearest multiple of 0.5. e.g. (16.8->16.5),(16.2->16.0)
}
self.calendarHeaderView.frame = CGRectMake(0, 0, self.fs_width, headerHeight);
self.calendarWeekdayView.frame = CGRectMake(0, self.calendarHeaderView.fs_bottom, self.contentView.fs_width, weekdayHeight);
_deliver.frame = CGRectMake(self.calendarHeaderView.fs_left, self.calendarHeaderView.fs_top, self.calendarHeaderView.fs_width, headerHeight+weekdayHeight);
_deliver.hidden = self.calendarHeaderView.hidden;
if (!self.floatingMode) {
switch (self.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
CGFloat contentHeight = rowHeight*6 + padding*2;
_daysContainer.frame = CGRectMake(0, headerHeight+weekdayHeight, self.fs_width, contentHeight);
_collectionView.frame = CGRectMake(0, 0, _daysContainer.fs_width, contentHeight);
break;
}
case FSCalendarScopeWeek: {
CGFloat contentHeight = rowHeight + padding*2;
_daysContainer.frame = CGRectMake(0, headerHeight+weekdayHeight, self.fs_width, contentHeight);
_collectionView.frame = CGRectMake(0, 0, _daysContainer.fs_width, contentHeight);
break;
}
}
} else {
CGFloat contentHeight = _contentView.fs_height;
_daysContainer.frame = CGRectMake(0, 0, self.fs_width, contentHeight);
_collectionView.frame = _daysContainer.bounds;
}
_collectionView.fs_height = FSCalendarHalfFloor(_collectionView.fs_height);
}
}
#if TARGET_INTERFACE_BUILDER
- (void)prepareForInterfaceBuilder
{
NSDate *date = [NSDate date];
NSDateComponents *components = [self.gregorian components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:date];
components.day = _appearance.fakedSelectedDay?:1;
[_selectedDates addObject:[self.gregorian dateFromComponents:components]];
[self.collectionView reloadData];
}
#endif
- (CGSize)sizeThatFits:(CGSize)size
{
return [self sizeThatFits:size scope:self.transitionCoordinator.representingScope];
}
- (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope
{
CGFloat headerHeight = self.preferredHeaderHeight;
CGFloat weekdayHeight = self.preferredWeekdayHeight;
CGFloat rowHeight = self.preferredRowHeight;
CGFloat paddings = self.collectionViewLayout.sectionInsets.top + self.collectionViewLayout.sectionInsets.bottom;
if (!self.floatingMode) {
switch (scope) {
case FSCalendarScopeMonth: {
CGFloat height = weekdayHeight + headerHeight + [self.calculator numberOfRowsInMonth:_currentPage]*rowHeight + paddings;
return CGSizeMake(size.width, height);
}
case FSCalendarScopeWeek: {
CGFloat height = weekdayHeight + headerHeight + rowHeight + paddings;
return CGSizeMake(size.width, height);
}
}
} else {
return CGSizeMake(size.width, self.fs_height);
}
return size;
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
[self requestBoundingDatesIfNecessary];
return self.calculator.numberOfSections;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (self.floatingMode) {
NSInteger numberOfRows = [self.calculator numberOfRowsInSection:section];
return numberOfRows * 7;
}
switch (self.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
return 42;
}
case FSCalendarScopeWeek: {
return 7;
}
}
return 7;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
switch (self.placeholderType) {
case FSCalendarPlaceholderTypeNone: {
if (self.transitionCoordinator.representingScope == FSCalendarScopeMonth && monthPosition != FSCalendarMonthPositionCurrent) {
return [collectionView dequeueReusableCellWithReuseIdentifier:FSCalendarBlankCellReuseIdentifier forIndexPath:indexPath];
}
break;
}
case FSCalendarPlaceholderTypeFillHeadTail: {
if (self.transitionCoordinator.representingScope == FSCalendarScopeMonth) {
if (indexPath.item >= 7 * [self.calculator numberOfRowsInSection:indexPath.section]) {
return [collectionView dequeueReusableCellWithReuseIdentifier:FSCalendarBlankCellReuseIdentifier forIndexPath:indexPath];
}
}
break;
}
case FSCalendarPlaceholderTypeFillSixRows: {
break;
}
}
NSDate *date = [self.calculator dateForIndexPath:indexPath];
FSCalendarCell *cell = [self.dataSourceProxy calendar:self cellForDate:date atMonthPosition:monthPosition];
if (!cell) {
cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:FSCalendarDefaultCellReuseIdentifier forIndexPath:indexPath];
}
[self reloadDataForCell:cell atIndexPath:indexPath];
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
if (self.floatingMode) {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
FSCalendarStickyHeader *stickyHeader = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header" forIndexPath:indexPath];
stickyHeader.calendar = self;
stickyHeader.month = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:indexPath.section toDate:[self.gregorian fs_firstDayOfMonth:_minimumDate] options:0];
self.visibleSectionHeaders[indexPath] = stickyHeader;
[stickyHeader setNeedsLayout];
return stickyHeader;
}
}
return [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"placeholderHeader" forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if (self.floatingMode) {
if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) {
self.visibleSectionHeaders[indexPath] = nil;
}
}
}
#pragma mark - <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
if (self.placeholderType == FSCalendarPlaceholderTypeNone && monthPosition != FSCalendarMonthPositionCurrent) {
return NO;
}
NSDate *date = [self.calculator dateForIndexPath:indexPath];
return [self isDateInRange:date] && (![self.delegateProxy respondsToSelector:@selector(calendar:shouldSelectDate:atMonthPosition:)] || [self.delegateProxy calendar:self shouldSelectDate:date atMonthPosition:monthPosition]);
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSDate *selectedDate = [self.calculator dateForIndexPath:indexPath];
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
FSCalendarCell *cell;
if (monthPosition == FSCalendarMonthPositionCurrent) {
cell = (FSCalendarCell *)[collectionView cellForItemAtIndexPath:indexPath];
} else {
cell = [self cellForDate:selectedDate atMonthPosition:FSCalendarMonthPositionCurrent];
NSIndexPath *indexPath = [collectionView indexPathForCell:cell];
if (indexPath) {
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
}
if (![_selectedDates containsObject:selectedDate]) {
cell.selected = YES;
[cell performSelecting];
}
[self enqueueSelectedDate:selectedDate];
[self.delegateProxy calendar:self didSelectDate:selectedDate atMonthPosition:monthPosition];
[self selectCounterpartDate:selectedDate];
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
if (self.placeholderType == FSCalendarPlaceholderTypeNone && monthPosition != FSCalendarMonthPositionCurrent) {
return NO;
}
NSDate *date = [self.calculator dateForIndexPath:indexPath];
return [self isDateInRange:date] && (![self.delegateProxy respondsToSelector:@selector(calendar:shouldDeselectDate:atMonthPosition:)]||[self.delegateProxy calendar:self shouldDeselectDate:date atMonthPosition:monthPosition]);
}
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSDate *selectedDate = [self.calculator dateForIndexPath:indexPath];
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
FSCalendarCell *cell;
if (monthPosition == FSCalendarMonthPositionCurrent) {
cell = (FSCalendarCell *)[collectionView cellForItemAtIndexPath:indexPath];
} else {
cell = [self cellForDate:selectedDate atMonthPosition:FSCalendarMonthPositionCurrent];
NSIndexPath *indexPath = [collectionView indexPathForCell:cell];
if (indexPath) {
[collectionView deselectItemAtIndexPath:indexPath animated:NO];
}
}
cell.selected = NO;
[cell configureAppearance];
[_selectedDates removeObject:selectedDate];
[self.delegateProxy calendar:self didDeselectDate:selectedDate atMonthPosition:monthPosition];
[self deselectCounterpartDate:selectedDate];
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
if (![cell isKindOfClass:[FSCalendarCell class]]) {
return;
}
NSDate *date = [self.calculator dateForIndexPath:indexPath];
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
[self.delegateProxy calendar:self willDisplayCell:(FSCalendarCell *)cell forDate:date atMonthPosition:monthPosition];
}
- (void)collectionViewDidFinishLayoutSubviews:(FSCalendarCollectionView *)collectionView
{
[self executePendingOperationsIfNeeded];
}
#pragma mark - <UIScrollViewDelegate>
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (!self.window) return;
if (self.floatingMode && _collectionView.indexPathsForVisibleItems.count) {
// Do nothing on bouncing
if (_collectionView.contentOffset.y < 0 || _collectionView.contentOffset.y > _collectionView.contentSize.height-_collectionView.fs_height) {
return;
}
NSDate *currentPage = _currentPage;
CGPoint significantPoint = CGPointMake(_collectionView.fs_width*0.5,MIN(self.collectionViewLayout.estimatedItemSize.height*2.75, _collectionView.fs_height*0.5)+_collectionView.contentOffset.y);
NSIndexPath *significantIndexPath = [_collectionView indexPathForItemAtPoint:significantPoint];
if (significantIndexPath) {
currentPage = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:significantIndexPath.section toDate:[self.gregorian fs_firstDayOfMonth:_minimumDate] options:0];
} else {
FSCalendarStickyHeader *significantHeader = [self.visibleStickyHeaders filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FSCalendarStickyHeader * _Nonnull evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return CGRectContainsPoint(evaluatedObject.frame, significantPoint);
}]].firstObject;
if (significantHeader) {
currentPage = significantHeader.month;
}
}
if (![self.gregorian isDate:currentPage equalToDate:_currentPage toUnitGranularity:NSCalendarUnitMonth]) {
[self willChangeValueForKey:@"currentPage"];
_currentPage = currentPage;
[self.delegateProxy calendarCurrentPageDidChange:self];
[self didChangeValueForKey:@"currentPage"];
}
} else if (self.hasValidateVisibleLayout) {
CGFloat scrollOffset = 0;
switch (_collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
scrollOffset = scrollView.contentOffset.x/scrollView.fs_width;
break;
}
case UICollectionViewScrollDirectionVertical: {
scrollOffset = scrollView.contentOffset.y/scrollView.fs_height;
break;
}
}
_calendarHeaderView.scrollOffset = scrollOffset;
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
if (!_pagingEnabled || !_scrollEnabled) {
return;
}
CGFloat targetOffset = 0, contentSize = 0;
switch (_collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
targetOffset = targetContentOffset->x;
contentSize = scrollView.fs_width;
break;
}
case UICollectionViewScrollDirectionVertical: {
targetOffset = targetContentOffset->y;
contentSize = scrollView.fs_height;
break;
}
}
NSInteger sections = lrint(targetOffset/contentSize);
NSDate *targetPage = nil;
switch (_scope) {
case FSCalendarScopeMonth: {
NSDate *minimumPage = [self.gregorian fs_firstDayOfMonth:_minimumDate];
targetPage = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:sections toDate:minimumPage options:0];
break;
}
case FSCalendarScopeWeek: {
NSDate *minimumPage = [self.gregorian fs_firstDayOfWeek:_minimumDate];
targetPage = [self.gregorian dateByAddingUnit:NSCalendarUnitWeekOfYear value:sections toDate:minimumPage options:0];
break;
}
}
BOOL shouldTriggerPageChange = [self isDateInDifferentPage:targetPage];
if (shouldTriggerPageChange) {
NSDate *lastPage = _currentPage;
[self willChangeValueForKey:@"currentPage"];
_currentPage = targetPage;
[self.delegateProxy calendarCurrentPageDidChange:self];
if (_placeholderType != FSCalendarPlaceholderTypeFillSixRows) {
[self.transitionCoordinator performBoundingRectTransitionFromMonth:lastPage toMonth:_currentPage duration:0.25];
}
[self didChangeValueForKey:@"currentPage"];
}
}
#pragma mark - <UIGestureRecognizerDelegate>
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
#pragma mark - Notification
- (void)orientationDidChange:(NSNotification *)notification
{
self.orientation = self.currentCalendarOrientation;
}
#pragma mark - Properties
- (void)setScrollDirection:(FSCalendarScrollDirection)scrollDirection
{
if (_scrollDirection != scrollDirection) {
_scrollDirection = scrollDirection;
if (self.floatingMode) return;
switch (_scope) {
case FSCalendarScopeMonth: {
_collectionViewLayout.scrollDirection = (UICollectionViewScrollDirection)scrollDirection;
_calendarHeaderView.scrollDirection = _collectionViewLayout.scrollDirection;
if (self.hasValidateVisibleLayout) {
[_collectionView reloadData];
[_calendarHeaderView reloadData];
}
_needsAdjustingViewFrame = YES;
[self setNeedsLayout];
break;
}
case FSCalendarScopeWeek: {
break;
}
}
}
}
+ (BOOL)automaticallyNotifiesObserversOfScope
{
return NO;
}
- (void)setScope:(FSCalendarScope)scope
{
[self setScope:scope animated:NO];
}
- (void)setFirstWeekday:(NSUInteger)firstWeekday
{
if (_firstWeekday != firstWeekday) {
_firstWeekday = firstWeekday;
_needsRequestingBoundingDates = YES;
[self invalidateDateTools];
[self invalidateHeaders];
[self.collectionView reloadData];
[self configureAppearance];
}
}
- (void)setToday:(NSDate *)today
{
if (!today) {
_today = nil;
} else {
FSCalendarAssertDateInBounds(today,self.gregorian,self.minimumDate,self.maximumDate);
_today = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:today options:0];
}
if (self.hasValidateVisibleLayout) {
[self.visibleCells makeObjectsPerformSelector:@selector(setDateIsToday:) withObject:nil];
if (today) [[_collectionView cellForItemAtIndexPath:[self.calculator indexPathForDate:today]] setValue:@YES forKey:@"dateIsToday"];
[self.visibleCells makeObjectsPerformSelector:@selector(configureAppearance)];
}
}
- (void)setCurrentPage:(NSDate *)currentPage
{
[self setCurrentPage:currentPage animated:NO];
}
- (void)setCurrentPage:(NSDate *)currentPage animated:(BOOL)animated
{
[self requestBoundingDatesIfNecessary];
if (self.floatingMode || [self isDateInDifferentPage:currentPage]) {
currentPage = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:currentPage options:0];
if ([self isPageInRange:currentPage]) {
[self scrollToPageForDate:currentPage animated:animated];
}
}
}
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier
{
if (!identifier.length) {
[NSException raise:FSCalendarInvalidArgumentsExceptionName format:@"This identifier must not be nil and must not be an empty string."];
}
if (![cellClass isSubclassOfClass:[FSCalendarCell class]]) {
[NSException raise:@"The cell class must be a subclass of FSCalendarCell." format:@""];
}
if ([identifier isEqualToString:FSCalendarBlankCellReuseIdentifier]) {
[NSException raise:FSCalendarInvalidArgumentsExceptionName format:@"Do not use %@ as the cell reuse identifier.", identifier];
}
[self.collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier];
}
- (FSCalendarCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position;
{
if (!identifier.length) {
[NSException raise:FSCalendarInvalidArgumentsExceptionName format:@"This identifier must not be nil and must not be an empty string."];
}
NSIndexPath *indexPath = [self.calculator indexPathForDate:date atMonthPosition:position];
if (!indexPath) {
[NSException raise:FSCalendarInvalidArgumentsExceptionName format:@"Attempting to dequeue a cell with invalid date."];
}
FSCalendarCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
return cell;
}
- (nullable FSCalendarCell *)cellForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position
{
NSIndexPath *indexPath = [self.calculator indexPathForDate:date atMonthPosition:position];
return (FSCalendarCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
}
- (NSDate *)dateForCell:(FSCalendarCell *)cell
{
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
return [self.calculator dateForIndexPath:indexPath];
}
- (FSCalendarMonthPosition)monthPositionForCell:(FSCalendarCell *)cell
{
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
return [self.calculator monthPositionForIndexPath:indexPath];
}
- (NSArray<FSCalendarCell *> *)visibleCells
{
return [self.collectionView.visibleCells filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return [evaluatedObject isKindOfClass:[FSCalendarCell class]];
}]];
}
- (CGRect)frameForDate:(NSDate *)date
{
if (!self.superview) {
return CGRectZero;
}
CGRect frame = [_collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calculator indexPathForDate:date]].frame;
frame = [self.superview convertRect:frame fromView:_collectionView];
return frame;
}
- (void)setHeaderHeight:(CGFloat)headerHeight
{
if (_headerHeight != headerHeight) {
_headerHeight = headerHeight;
_needsAdjustingViewFrame = YES;
[self setNeedsLayout];
}
}
- (void)setWeekdayHeight:(CGFloat)weekdayHeight
{
if (_weekdayHeight != weekdayHeight) {
_weekdayHeight = weekdayHeight;
_needsAdjustingViewFrame = YES;
[self setNeedsLayout];
}
}
- (void)setLocale:(NSLocale *)locale
{
if (![_locale isEqual:locale]) {
_locale = locale.copy;
[self invalidateDateTools];
[self configureAppearance];
if (self.hasValidateVisibleLayout) {
[self invalidateHeaders];
}
}
}
- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection
{
_collectionView.allowsMultipleSelection = allowsMultipleSelection;
}
- (BOOL)allowsMultipleSelection
{
return _collectionView.allowsMultipleSelection;
}
- (void)setAllowsSelection:(BOOL)allowsSelection
{
_collectionView.allowsSelection = allowsSelection;
}
- (BOOL)allowsSelection
{
return _collectionView.allowsSelection;
}
- (void)setPagingEnabled:(BOOL)pagingEnabled
{
if (_pagingEnabled != pagingEnabled) {
_pagingEnabled = pagingEnabled;
[self invalidateLayout];
}
}
- (void)setScrollEnabled:(BOOL)scrollEnabled
{
if (_scrollEnabled != scrollEnabled) {
_scrollEnabled = scrollEnabled;
_collectionView.scrollEnabled = scrollEnabled;
_calendarHeaderView.scrollEnabled = scrollEnabled;
[self invalidateLayout];
}
}
- (void)setOrientation:(FSCalendarOrientation)orientation
{
if (_orientation != orientation) {
_orientation = orientation;
_needsAdjustingViewFrame = YES;
_preferredWeekdayHeight = FSCalendarAutomaticDimension;
_preferredRowHeight = FSCalendarAutomaticDimension;
_preferredHeaderHeight = FSCalendarAutomaticDimension;
[self setNeedsLayout];
}
}
- (NSDate *)selectedDate
{
return _selectedDates.lastObject;
}
- (NSArray *)selectedDates
{
return [NSArray arrayWithArray:_selectedDates];
}
- (CGFloat)preferredHeaderHeight
{
if (_headerHeight == FSCalendarAutomaticDimension) {
if (_preferredWeekdayHeight == FSCalendarAutomaticDimension) {
if (!self.floatingMode) {
CGFloat DIYider = FSCalendarStandardMonthlyPageHeight;
CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height;
_preferredHeaderHeight = (FSCalendarStandardHeaderHeight/DIYider)*contentHeight;
_preferredHeaderHeight -= (_preferredHeaderHeight-FSCalendarStandardHeaderHeight)*0.5;
} else {
_preferredHeaderHeight = FSCalendarStandardHeaderHeight*MAX(1, FSCalendarDeviceIsIPad*1.5);
}
}
return _preferredHeaderHeight;
}
return _headerHeight;
}
- (CGFloat)preferredWeekdayHeight
{
if (_weekdayHeight == FSCalendarAutomaticDimension) {
if (_preferredWeekdayHeight == FSCalendarAutomaticDimension) {
if (!self.floatingMode) {
CGFloat DIYider = FSCalendarStandardMonthlyPageHeight;
CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height;
_preferredWeekdayHeight = (FSCalendarStandardWeekdayHeight/DIYider)*contentHeight;
} else {
_preferredWeekdayHeight = FSCalendarStandardWeekdayHeight*MAX(1, FSCalendarDeviceIsIPad*1.5);
}
}
return _preferredWeekdayHeight;
}
return _weekdayHeight;
}
- (CGFloat)preferredRowHeight
{
if (_preferredRowHeight == FSCalendarAutomaticDimension) {
CGFloat headerHeight = self.preferredHeaderHeight;
CGFloat weekdayHeight = self.preferredWeekdayHeight;
CGFloat contentHeight = self.transitionCoordinator.cachedMonthSize.height-headerHeight-weekdayHeight;
CGFloat padding = 5;
if (!self.floatingMode) {
_preferredRowHeight = (contentHeight-padding*2)/6.0;
} else {
_preferredRowHeight = _rowHeight;
}
}
return _preferredRowHeight;
}
- (BOOL)floatingMode
{
return _scope == FSCalendarScopeMonth && _scrollEnabled && !_pagingEnabled;
}
- (UIPanGestureRecognizer *)scopeGesture
{
if (!_scopeGesture) {
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self.transitionCoordinator action:@selector(handleScopeGesture:)];
panGesture.delegate = self.transitionCoordinator;
panGesture.minimumNumberOfTouches = 1;
panGesture.maximumNumberOfTouches = 2;
panGesture.enabled = NO;
[self.daysContainer addGestureRecognizer:panGesture];
_scopeGesture = panGesture;
}
return _scopeGesture;
}
- (UILongPressGestureRecognizer *)swipeToChooseGesture
{
if (!_swipeToChooseGesture) {
UILongPressGestureRecognizer *pressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeToChoose:)];
pressGesture.enabled = NO;
pressGesture.numberOfTapsRequired = 0;
pressGesture.numberOfTouchesRequired = 1;
pressGesture.minimumPressDuration = 0.7;
[self.daysContainer addGestureRecognizer:pressGesture];
[self.collectionView.panGestureRecognizer requireGestureRecognizerToFail:pressGesture];
_swipeToChooseGesture = pressGesture;
}
return _swipeToChooseGesture;
}
- (void)setDataSource:(id<FSCalendarDataSource>)dataSource
{
self.dataSourceProxy.delegation = dataSource;
}
- (id<FSCalendarDataSource>)dataSource
{
return self.dataSourceProxy.delegation;
}
- (void)setDelegate:(id<FSCalendarDelegate>)delegate
{
self.delegateProxy.delegation = delegate;
}
- (id<FSCalendarDelegate>)delegate
{
return self.delegateProxy.delegation;
}
#pragma mark - Public methods
- (void)reloadData
{
_needsRequestingBoundingDates = YES;
if ([self requestBoundingDatesIfNecessary] || !self.collectionView.indexPathsForVisibleItems.count) {
[self invalidateHeaders];
}
[self.collectionView reloadData];
}
- (void)setScope:(FSCalendarScope)scope animated:(BOOL)animated
{
if (self.floatingMode) return;
if (self.transitionCoordinator.state != FSCalendarTransitionStateIdle) return;
[self performEnsuringValidLayout:^{
[self.transitionCoordinator performScopeTransitionFromScope:self.scope toScope:scope animated:animated];
}];
}
- (void)setPlaceholderType:(FSCalendarPlaceholderType)placeholderType
{
_placeholderType = placeholderType;
if (self.hasValidateVisibleLayout) {
_preferredRowHeight = FSCalendarAutomaticDimension;
[_collectionView reloadData];
}
[self adjustBoundingRectIfNecessary];
}
- (void)setAdjustsBoundingRectWhenChangingMonths:(BOOL)adjustsBoundingRectWhenChangingMonths
{
_adjustsBoundingRectWhenChangingMonths = adjustsBoundingRectWhenChangingMonths;
[self adjustBoundingRectIfNecessary];
}
- (void)selectDate:(NSDate *)date
{
[self selectDate:date scrollToDate:YES];
}
- (void)selectDate:(NSDate *)date scrollToDate:(BOOL)scrollToDate
{
[self selectDate:date scrollToDate:scrollToDate atMonthPosition:FSCalendarMonthPositionCurrent];
}
- (void)deselectDate:(NSDate *)date
{
date = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:date options:0];
if (![_selectedDates containsObject:date]) {
return;
}
[_selectedDates removeObject:date];
[self deselectCounterpartDate:date];
NSIndexPath *indexPath = [self.calculator indexPathForDate:date];
if ([_collectionView.indexPathsForSelectedItems containsObject:indexPath]) {
[_collectionView deselectItemAtIndexPath:indexPath animated:YES];
FSCalendarCell *cell = (FSCalendarCell *)[_collectionView cellForItemAtIndexPath:indexPath];
cell.selected = NO;
[cell configureAppearance];
}
}
- (void)selectDate:(NSDate *)date scrollToDate:(BOOL)scrollToDate atMonthPosition:(FSCalendarMonthPosition)monthPosition
{
if (!self.allowsSelection || !date) return;
[self requestBoundingDatesIfNecessary];
FSCalendarAssertDateInBounds(date,self.gregorian,self.minimumDate,self.maximumDate);
NSDate *targetDate = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:date options:0];
NSIndexPath *targetIndexPath = [self.calculator indexPathForDate:targetDate];
BOOL shouldSelect = YES;
// 跨月份点击
if (monthPosition==FSCalendarMonthPositionPrevious||monthPosition==FSCalendarMonthPositionNext) {
if (self.allowsMultipleSelection) {
if ([self isDateSelected:targetDate]) {
BOOL shouldDeselect = ![self.delegateProxy respondsToSelector:@selector(calendar:shouldDeselectDate:atMonthPosition:)] || [self.delegateProxy calendar:self shouldDeselectDate:targetDate atMonthPosition:monthPosition];
if (!shouldDeselect) {
return;
}
} else {
shouldSelect &= (![self.delegateProxy respondsToSelector:@selector(calendar:shouldSelectDate:atMonthPosition:)] || [self.delegateProxy calendar:self shouldSelectDate:targetDate atMonthPosition:monthPosition]);
if (!shouldSelect) {
return;
}
[_collectionView selectItemAtIndexPath:targetIndexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
[self collectionView:_collectionView didSelectItemAtIndexPath:targetIndexPath];
}
} else {
shouldSelect &= (![self.delegateProxy respondsToSelector:@selector(calendar:shouldSelectDate:atMonthPosition:)] || [self.delegateProxy calendar:self shouldSelectDate:targetDate atMonthPosition:monthPosition]);
if (shouldSelect) {
if ([self isDateSelected:targetDate]) {
[self.delegateProxy calendar:self didSelectDate:targetDate atMonthPosition:monthPosition];
} else {
NSDate *selectedDate = self.selectedDate;
if (selectedDate) {
[self deselectDate:selectedDate];
}
[_collectionView selectItemAtIndexPath:targetIndexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
[self collectionView:_collectionView didSelectItemAtIndexPath:targetIndexPath];
}
} else {
return;
}
}
} else if (![self isDateSelected:targetDate]){
if (self.selectedDate && !self.allowsMultipleSelection) {
[self deselectDate:self.selectedDate];
}
[_collectionView selectItemAtIndexPath:targetIndexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
FSCalendarCell *cell = (FSCalendarCell *)[_collectionView cellForItemAtIndexPath:targetIndexPath];
[cell performSelecting];
[self enqueueSelectedDate:targetDate];
[self selectCounterpartDate:targetDate];
} else if (![_collectionView.indexPathsForSelectedItems containsObject:targetIndexPath]) {
[_collectionView selectItemAtIndexPath:targetIndexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
if (scrollToDate) {
if (!shouldSelect) {
return;
}
[self scrollToPageForDate:targetDate animated:YES];
}
}
- (void)handleScopeGesture:(UIPanGestureRecognizer *)sender
{
if (self.floatingMode) return;
[self.transitionCoordinator handleScopeGesture:sender];
}
#pragma mark - Private methods
- (void)scrollToDate:(NSDate *)date
{
[self scrollToDate:date animated:NO];
}
- (void)scrollToDate:(NSDate *)date animated:(BOOL)animated
{
if (!_minimumDate || !_maximumDate) {
return;
}
animated &= _scrollEnabled; // No animation if _scrollEnabled == NO;
date = [self.calculator safeDateForDate:date];
NSInteger scrollOffset = [self.calculator indexPathForDate:date atMonthPosition:FSCalendarMonthPositionCurrent].section;
if (!self.floatingMode) {
switch (_collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionVertical: {
[_collectionView setContentOffset:CGPointMake(0, scrollOffset * _collectionView.fs_height) animated:animated];
break;
}
case UICollectionViewScrollDirectionHorizontal: {
[_collectionView setContentOffset:CGPointMake(scrollOffset * _collectionView.fs_width, 0) animated:animated];
break;
}
}
} else if (self.hasValidateVisibleLayout) {
[_collectionViewLayout layoutAttributesForElementsInRect:_collectionView.bounds];
CGRect headerFrame = [_collectionViewLayout layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:scrollOffset]].frame;
CGPoint targetOffset = CGPointMake(0, MIN(headerFrame.origin.y,MAX(0,_collectionViewLayout.collectionViewContentSize.height-_collectionView.fs_bottom)));
[_collectionView setContentOffset:targetOffset animated:animated];
}
if (!animated) {
self.calendarHeaderView.scrollOffset = scrollOffset;
}
}
- (void)scrollToPageForDate:(NSDate *)date animated:(BOOL)animated
{
if (!date) return;
if (![self isDateInRange:date]) {
date = [self.calculator safeDateForDate:date];
if (!date) return;
}
if (!self.floatingMode) {
if ([self isDateInDifferentPage:date]) {
[self willChangeValueForKey:@"currentPage"];
NSDate *lastPage = _currentPage;
switch (self.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
_currentPage = [self.gregorian fs_firstDayOfMonth:date];
break;
}
case FSCalendarScopeWeek: {
_currentPage = [self.gregorian fs_firstDayOfWeek:date];
break;
}
}
if (self.hasValidateVisibleLayout) {
[self.delegateProxy calendarCurrentPageDidChange:self];
if (_placeholderType != FSCalendarPlaceholderTypeFillSixRows && self.transitionCoordinator.state == FSCalendarTransitionStateIdle) {
[self.transitionCoordinator performBoundingRectTransitionFromMonth:lastPage toMonth:_currentPage duration:0.33];
}
}
[self didChangeValueForKey:@"currentPage"];
}
[self scrollToDate:_currentPage animated:animated];
} else {
[self scrollToDate:[self.gregorian fs_firstDayOfMonth:date] animated:animated];
}
}
- (BOOL)isDateInRange:(NSDate *)date
{
BOOL flag = YES;
flag &= [self.gregorian components:NSCalendarUnitDay fromDate:date toDate:self.minimumDate options:0].day <= 0;
flag &= [self.gregorian components:NSCalendarUnitDay fromDate:date toDate:self.maximumDate options:0].day >= 0;;
return flag;
}
- (BOOL)isPageInRange:(NSDate *)page
{
BOOL flag = YES;
switch (self.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
NSDateComponents *c1 = [self.gregorian components:NSCalendarUnitDay fromDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] toDate:page options:0];
flag &= (c1.day>=0);
if (!flag) break;
NSDateComponents *c2 = [self.gregorian components:NSCalendarUnitDay fromDate:page toDate:[self.gregorian fs_lastDayOfMonth:self.maximumDate] options:0];
flag &= (c2.day>=0);
break;
}
case FSCalendarScopeWeek: {
NSDateComponents *c1 = [self.gregorian components:NSCalendarUnitDay fromDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] toDate:page options:0];
flag &= (c1.day>=0);
if (!flag) break;
NSDateComponents *c2 = [self.gregorian components:NSCalendarUnitDay fromDate:page toDate:[self.gregorian fs_lastDayOfWeek:self.maximumDate] options:0];
flag &= (c2.day>=0);
break;
}
default:
break;
}
return flag;
}
- (BOOL)isDateSelected:(NSDate *)date
{
return [_selectedDates containsObject:date] || [_collectionView.indexPathsForSelectedItems containsObject:[self.calculator indexPathForDate:date]];
}
- (BOOL)isDateInDifferentPage:(NSDate *)date
{
if (self.floatingMode) {
return ![self.gregorian isDate:date equalToDate:_currentPage toUnitGranularity:NSCalendarUnitMonth];
}
switch (_scope) {
case FSCalendarScopeMonth:
return ![self.gregorian isDate:date equalToDate:_currentPage toUnitGranularity:NSCalendarUnitMonth];
case FSCalendarScopeWeek:
return ![self.gregorian isDate:date equalToDate:_currentPage toUnitGranularity:NSCalendarUnitWeekOfYear];
}
}
- (BOOL)hasValidateVisibleLayout
{
#if TARGET_INTERFACE_BUILDER
return YES;
#else
return self.superview && !CGRectIsEmpty(_collectionView.frame) && !CGSizeEqualToSize(_collectionViewLayout.collectionViewContentSize, CGSizeZero);
#endif
}
- (void)invalidateDateTools
{
_gregorian.locale = _locale;
_gregorian.timeZone = _timeZone;
_gregorian.firstWeekday = _firstWeekday;
_formatter.calendar = _gregorian;
_formatter.timeZone = _timeZone;
_formatter.locale = _locale;
}
- (void)invalidateLayout
{
if (!self.floatingMode) {
if (!_calendarHeaderView) {
FSCalendarHeaderView *headerView = [[FSCalendarHeaderView alloc] initWithFrame:CGRectZero];
headerView.calendar = self;
headerView.scrollEnabled = _scrollEnabled;
[_contentView addSubview:headerView];
self.calendarHeaderView = headerView;
}
if (!_calendarWeekdayView) {
FSCalendarWeekdayView *calendarWeekdayView = [[FSCalendarWeekdayView alloc] initWithFrame:CGRectZero];
calendarWeekdayView.calendar = self;
[_contentView addSubview:calendarWeekdayView];
_calendarWeekdayView = calendarWeekdayView;
}
if (_scrollEnabled) {
if (!_deliver) {
FSCalendarHeaderTouchDeliver *deliver = [[FSCalendarHeaderTouchDeliver alloc] initWithFrame:CGRectZero];
deliver.header = _calendarHeaderView;
deliver.calendar = self;
[_contentView addSubview:deliver];
self.deliver = deliver;
}
} else if (_deliver) {
[_deliver removeFromSuperview];
}
_collectionView.pagingEnabled = YES;
_collectionViewLayout.scrollDirection = (UICollectionViewScrollDirection)self.scrollDirection;
} else {
[self.calendarHeaderView removeFromSuperview];
[self.deliver removeFromSuperview];
[self.calendarWeekdayView removeFromSuperview];
_collectionView.pagingEnabled = NO;
_collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
}
_preferredHeaderHeight = FSCalendarAutomaticDimension;
_preferredWeekdayHeight = FSCalendarAutomaticDimension;
_preferredRowHeight = FSCalendarAutomaticDimension;
_needsAdjustingViewFrame = YES;
[self setNeedsLayout];
}
- (void)invalidateHeaders
{
[self.calendarHeaderView.collectionView reloadData];
[self.visibleStickyHeaders makeObjectsPerformSelector:@selector(configureAppearance)];
}
- (void)invalidateAppearanceForCell:(FSCalendarCell *)cell forDate:(NSDate *)date
{
#define FSCalendarInvalidateCellAppearance(SEL1,SEL2) \
cell.SEL1 = [self.delegateProxy calendar:self appearance:self.appearance SEL2:date];
#define FSCalendarInvalidateCellAppearanceWithDefault(SEL1,SEL2,DEFAULT) \
if ([self.delegateProxy respondsToSelector:@selector(calendar:appearance:SEL2:)]) { \
cell.SEL1 = [self.delegateProxy calendar:self appearance:self.appearance SEL2:date]; \
} else { \
cell.SEL1 = DEFAULT; \
}
FSCalendarInvalidateCellAppearance(preferredFillDefaultColor,fillDefaultColorForDate);
FSCalendarInvalidateCellAppearance(preferredFillSelectionColor,fillSelectionColorForDate);
FSCalendarInvalidateCellAppearance(preferredTitleDefaultColor,titleDefaultColorForDate);
FSCalendarInvalidateCellAppearance(preferredTitleSelectionColor,titleSelectionColorForDate);
FSCalendarInvalidateCellAppearanceWithDefault(preferredTitleOffset,titleOffsetForDate,CGPointInfinity);
if (cell.subtitle) {
FSCalendarInvalidateCellAppearance(preferredSubtitleDefaultColor,subtitleDefaultColorForDate);
FSCalendarInvalidateCellAppearance(preferredSubtitleSelectionColor,subtitleSelectionColorForDate);
FSCalendarInvalidateCellAppearanceWithDefault(preferredSubtitleOffset,subtitleOffsetForDate,CGPointInfinity);
}
if (cell.numberOfEvents) {
FSCalendarInvalidateCellAppearance(preferredEventDefaultColors,eventDefaultColorsForDate);
FSCalendarInvalidateCellAppearance(preferredEventSelectionColors,eventSelectionColorsForDate);
FSCalendarInvalidateCellAppearanceWithDefault(preferredEventOffset,eventOffsetForDate,CGPointInfinity);
}
FSCalendarInvalidateCellAppearance(preferredBorderDefaultColor,borderDefaultColorForDate);
FSCalendarInvalidateCellAppearance(preferredBorderSelectionColor,borderSelectionColorForDate);
FSCalendarInvalidateCellAppearanceWithDefault(preferredBorderRadius,borderRadiusForDate,-1);
if (cell.image) {
FSCalendarInvalidateCellAppearanceWithDefault(preferredImageOffset,imageOffsetForDate,CGPointInfinity);
}
#undef FSCalendarInvalidateCellAppearance
#undef FSCalendarInvalidateCellAppearanceWithDefault
}
- (void)reloadDataForCell:(FSCalendarCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.calendar = self;
NSDate *date = [self.calculator dateForIndexPath:indexPath];
cell.image = [self.dataSourceProxy calendar:self imageForDate:date];
cell.numberOfEvents = [self.dataSourceProxy calendar:self numberOfEventsForDate:date];
cell.titleLabel.text = [self.dataSourceProxy calendar:self titleForDate:date] ?: @([self.gregorian component:NSCalendarUnitDay fromDate:date]).stringValue;
cell.subtitle = [self.dataSourceProxy calendar:self subtitleForDate:date];
cell.selected = [_selectedDates containsObject:date];
cell.dateIsToday = self.today?[self.gregorian isDate:date inSameDayAsDate:self.today]:NO;
cell.weekend = [self.gregorian isDateInWeekend:date];
cell.monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
switch (self.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
cell.placeholder = (cell.monthPosition == FSCalendarMonthPositionPrevious || cell.monthPosition == FSCalendarMonthPositionNext) || ![self isDateInRange:date];
if (cell.placeholder) {
cell.selected &= _pagingEnabled;
cell.dateIsToday &= _pagingEnabled;
}
break;
}
case FSCalendarScopeWeek: {
cell.placeholder = ![self isDateInRange:date];
break;
}
}
// Synchronize selecion state to the collection view, otherwise delegate methods would not be triggered.
if (cell.selected) {
[self.collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];
} else {
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
}
[self invalidateAppearanceForCell:cell forDate:date];
[cell configureAppearance];
}
- (void)handleSwipeToChoose:(UILongPressGestureRecognizer *)pressGesture
{
switch (pressGesture.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged: {
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[pressGesture locationInView:self.collectionView]];
if (indexPath && ![indexPath isEqual:self.lastPressedIndexPath]) {
NSDate *date = [self.calculator dateForIndexPath:indexPath];
FSCalendarMonthPosition monthPosition = [self.calculator monthPositionForIndexPath:indexPath];
if (![self.selectedDates containsObject:date] && [self collectionView:self.collectionView shouldSelectItemAtIndexPath:indexPath]) {
[self selectDate:date scrollToDate:NO atMonthPosition:monthPosition];
[self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
} else if (self.collectionView.allowsMultipleSelection && [self collectionView:self.collectionView shouldDeselectItemAtIndexPath:indexPath]) {
[self deselectDate:date];
[self collectionView:self.collectionView didDeselectItemAtIndexPath:indexPath];
}
}
self.lastPressedIndexPath = indexPath;
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
self.lastPressedIndexPath = nil;
break;
}
default:
break;
}
}
- (void)selectCounterpartDate:(NSDate *)date
{
if (_placeholderType == FSCalendarPlaceholderTypeNone) return;
if (self.scope == FSCalendarScopeWeek) return;
NSInteger numberOfDays = [self.gregorian fs_numberOfDaysInMonth:date];
NSInteger day = [self.gregorian component:NSCalendarUnitDay fromDate:date];
FSCalendarCell *cell;
if (day < numberOfDays/2+1) {
cell = [self cellForDate:date atMonthPosition:FSCalendarMonthPositionNext];
} else {
cell = [self cellForDate:date atMonthPosition:FSCalendarMonthPositionPrevious];
}
if (cell) {
cell.selected = YES;
if (self.collectionView.allowsMultipleSelection) {
[self.collectionView selectItemAtIndexPath:[self.collectionView indexPathForCell:cell] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
}
[cell configureAppearance];
}
- (void)deselectCounterpartDate:(NSDate *)date
{
if (_placeholderType == FSCalendarPlaceholderTypeNone) return;
if (self.scope == FSCalendarScopeWeek) return;
NSInteger numberOfDays = [self.gregorian fs_numberOfDaysInMonth:date];
NSInteger day = [self.gregorian component:NSCalendarUnitDay fromDate:date];
FSCalendarCell *cell;
if (day < numberOfDays/2+1) {
cell = [self cellForDate:date atMonthPosition:FSCalendarMonthPositionNext];
} else {
cell = [self cellForDate:date atMonthPosition:FSCalendarMonthPositionPrevious];
}
if (cell) {
cell.selected = NO;
[self.collectionView deselectItemAtIndexPath:[self.collectionView indexPathForCell:cell] animated:NO];
}
[cell configureAppearance];
}
- (void)enqueueSelectedDate:(NSDate *)date
{
if (!self.allowsMultipleSelection) {
[_selectedDates removeAllObjects];
}
if (![_selectedDates containsObject:date]) {
[_selectedDates addObject:date];
}
}
- (NSArray *)visibleStickyHeaders
{
return [self.visibleSectionHeaders.dictionaryRepresentation allValues];
}
- (void)invalidateViewFrames
{
_needsAdjustingViewFrame = YES;
_preferredHeaderHeight = FSCalendarAutomaticDimension;
_preferredWeekdayHeight = FSCalendarAutomaticDimension;
_preferredRowHeight = FSCalendarAutomaticDimension;
[self setNeedsLayout];
}
// The best way to detect orientation
// http://stackoverflow.com/questions/25830448/what-is-the-best-way-to-detect-orientation-in-an-app-extension/26023538#26023538
- (FSCalendarOrientation)currentCalendarOrientation
{
CGFloat scale = [UIScreen mainScreen].scale;
CGSize nativeSize = [UIScreen mainScreen].currentMode.size;
CGSize sizeInPoints = [UIScreen mainScreen].bounds.size;
FSCalendarOrientation orientation = scale * sizeInPoints.width == nativeSize.width ? FSCalendarOrientationPortrait : FSCalendarOrientationLandscape;
return orientation;
}
- (void)adjustMonthPosition
{
[self requestBoundingDatesIfNecessary];
NSDate *targetPage = self.pagingEnabled?self.currentPage:(self.currentPage?:self.selectedDate);
[self scrollToPageForDate:targetPage animated:NO];
}
- (BOOL)requestBoundingDatesIfNecessary
{
if (_needsRequestingBoundingDates) {
_needsRequestingBoundingDates = NO;
self.formatter.dateFormat = @"yyyy-MM-dd";
NSDate *newMin = [self.dataSourceProxy minimumDateForCalendar:self]?:[self.formatter dateFromString:@"1970-01-01"];
newMin = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:newMin options:0];
NSDate *newMax = [self.dataSourceProxy maximumDateForCalendar:self]?:[self.formatter dateFromString:@"2099-12-31"];
newMax = [self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:newMax options:0];
NSAssert([self.gregorian compareDate:newMin toDate:newMax toUnitGranularity:NSCalendarUnitDay] != NSOrderedDescending, @"The minimum date of calendar should be earlier than the maximum.");
BOOL res = ![self.gregorian isDate:newMin inSameDayAsDate:_minimumDate] || ![self.gregorian isDate:newMax inSameDayAsDate:_maximumDate];
_minimumDate = newMin;
_maximumDate = newMax;
[self.calculator reloadSections];
return res;
}
return NO;
}
- (void)configureAppearance
{
[self.visibleCells makeObjectsPerformSelector:@selector(configureAppearance)];
[self.visibleStickyHeaders makeObjectsPerformSelector:@selector(configureAppearance)];
[self.calendarHeaderView configureAppearance];
[self.calendarWeekdayView configureAppearance];
}
- (void)adjustBoundingRectIfNecessary
{
if (self.placeholderType == FSCalendarPlaceholderTypeFillSixRows) {
return;
}
if (!self.adjustsBoundingRectWhenChangingMonths) {
return;
}
[self performEnsuringValidLayout:^{
[self.transitionCoordinator performBoundingRectTransitionFromMonth:nil toMonth:self.currentPage duration:0];
}];
}
- (void)performEnsuringValidLayout:(void (^)(void))block
{
if (self.collectionView.visibleCells.count) {
block();
} else {
[self setNeedsLayout];
[self.didLayoutOperations addObject:[NSBlockOperation blockOperationWithBlock:block]];
}
}
- (void)executePendingOperationsIfNeeded
{
NSArray<NSOperation *> *operations = nil;
if (self.didLayoutOperations.count) {
operations = self.didLayoutOperations.copy;
[self.didLayoutOperations removeAllObjects];
}
[operations makeObjectsPerformSelector:@selector(start)];
}
@end
//
// FSCalendarAppearance.h
// Pods
//
// Created by DingWenchao on 6/29/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// https://github.com/WenchaoD
//
#import "FSCalendarConstants.h"
@class FSCalendar;
typedef NS_ENUM(NSInteger, FSCalendarCellState) {
FSCalendarCellStateNormal = 0,
FSCalendarCellStateSelected = 1,
FSCalendarCellStatePlaceholder = 1 << 1,
FSCalendarCellStateDisabled = 1 << 2,
FSCalendarCellStateToday = 1 << 3,
FSCalendarCellStateWeekend = 1 << 4,
FSCalendarCellStateTodaySelected = FSCalendarCellStateToday|FSCalendarCellStateSelected
};
typedef NS_ENUM(NSUInteger, FSCalendarSeparators) {
FSCalendarSeparatorNone = 0,
FSCalendarSeparatorInterRows = 1
};
typedef NS_OPTIONS(NSUInteger, FSCalendarCaseOptions) {
FSCalendarCaseOptionsHeaderUsesDefaultCase = 0,
FSCalendarCaseOptionsHeaderUsesUpperCase = 1,
FSCalendarCaseOptionsWeekdayUsesDefaultCase = 0 << 4,
FSCalendarCaseOptionsWeekdayUsesUpperCase = 1 << 4,
FSCalendarCaseOptionsWeekdayUsesSingleUpperCase = 2 << 4,
};
/**
* FSCalendarAppearance determines the fonts and colors of components in the calendar.
*
* @see FSCalendarDelegateAppearance
*/
@interface FSCalendarAppearance : NSObject
/**
* The font of the day text.
*/
@property (strong, nonatomic) UIFont *titleFont;
/**
* The font of the subtitle text.
*/
@property (strong, nonatomic) UIFont *subtitleFont;
/**
* The font of the weekday text.
*/
@property (strong, nonatomic) UIFont *weekdayFont;
/**
* The font of the month text.
*/
@property (strong, nonatomic) UIFont *headerTitleFont;
/**
* The offset of the day text from default position.
*/
@property (assign, nonatomic) CGPoint titleOffset;
/**
* The offset of the day text from default position.
*/
@property (assign, nonatomic) CGPoint subtitleOffset;
/**
* The offset of the event dots from default position.
*/
@property (assign, nonatomic) CGPoint eventOffset;
/**
* The offset of the image from default position.
*/
@property (assign, nonatomic) CGPoint imageOffset;
/**
* The color of event dots.
*/
@property (strong, nonatomic) UIColor *eventDefaultColor;
/**
* The color of event dots.
*/
@property (strong, nonatomic) UIColor *eventSelectionColor;
/**
* The color of weekday text.
*/
@property (strong, nonatomic) UIColor *weekdayTextColor;
/**
* The color of month header text.
*/
@property (strong, nonatomic) UIColor *headerTitleColor;
/**
* The date format of the month header.
*/
@property (strong, nonatomic) NSString *headerDateFormat;
/**
* The alpha value of month label staying on the fringes.
*/
@property (assign, nonatomic) CGFloat headerMinimumDissolvedAlpha;
/**
* The day text color for unselected state.
*/
@property (strong, nonatomic) UIColor *titleDefaultColor;
/**
* The day text color for selected state.
*/
@property (strong, nonatomic) UIColor *titleSelectionColor;
/**
* The day text color for today in the calendar.
*/
@property (strong, nonatomic) UIColor *titleTodayColor;
/**
* The day text color for days out of current month.
*/
@property (strong, nonatomic) UIColor *titlePlaceholderColor;
/**
* The day text color for weekend.
*/
@property (strong, nonatomic) UIColor *titleWeekendColor;
/**
* The subtitle text color for unselected state.
*/
@property (strong, nonatomic) UIColor *subtitleDefaultColor;
/**
* The subtitle text color for selected state.
*/
@property (strong, nonatomic) UIColor *subtitleSelectionColor;
/**
* The subtitle text color for today in the calendar.
*/
@property (strong, nonatomic) UIColor *subtitleTodayColor;
/**
* The subtitle text color for days out of current month.
*/
@property (strong, nonatomic) UIColor *subtitlePlaceholderColor;
/**
* The subtitle text color for weekend.
*/
@property (strong, nonatomic) UIColor *subtitleWeekendColor;
/**
* The fill color of the shape for selected state.
*/
@property (strong, nonatomic) UIColor *selectionColor;
/**
* The fill color of the shape for today.
*/
@property (strong, nonatomic) UIColor *todayColor;
/**
* The fill color of the shape for today and selected state.
*/
@property (strong, nonatomic) UIColor *todaySelectionColor;
/**
* The border color of the shape for unselected state.
*/
@property (strong, nonatomic) UIColor *borderDefaultColor;
/**
* The border color of the shape for selected state.
*/
@property (strong, nonatomic) UIColor *borderSelectionColor;
/**
* The border radius, while 1 means a circle, 0 means a rectangle, and the middle value will give it a corner radius.
*/
@property (assign, nonatomic) CGFloat borderRadius;
/**
* The case options manage the case of month label and weekday symbols.
*
* @see FSCalendarCaseOptions
*/
@property (assign, nonatomic) FSCalendarCaseOptions caseOptions;
/**
* The line integrations for calendar.
*
*/
@property (assign, nonatomic) FSCalendarSeparators separators;
@property (assign, nonatomic) BOOL useVeryShortWeekdaySymbols;
#if TARGET_INTERFACE_BUILDER
// For preview only
@property (assign, nonatomic) BOOL fakeSubtitles;
@property (assign, nonatomic) BOOL fakeEventDots;
@property (assign, nonatomic) NSInteger fakedSelectedDay;
#endif
@end
//
// FSCalendarAppearance.m
// Pods
//
// Created by DingWenchao on 6/29/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// https://github.com/WenchaoD
//
#import "FSCalendarAppearance.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarAppearance ()
@property (weak , nonatomic) FSCalendar *calendar;
@property (strong, nonatomic) NSMutableDictionary *backgroundColors;
@property (strong, nonatomic) NSMutableDictionary *titleColors;
@property (strong, nonatomic) NSMutableDictionary *subtitleColors;
@property (strong, nonatomic) NSMutableDictionary *borderColors;
@end
@implementation FSCalendarAppearance
- (instancetype)init
{
self = [super init];
if (self) {
_titleFont = [UIFont systemFontOfSize:FSCalendarStandardTitleTextSize];
_subtitleFont = [UIFont systemFontOfSize:FSCalendarStandardSubtitleTextSize];
_weekdayFont = [UIFont systemFontOfSize:FSCalendarStandardWeekdayTextSize];
_headerTitleFont = [UIFont systemFontOfSize:FSCalendarStandardHeaderTextSize];
_headerTitleColor = FSCalendarStandardTitleTextColor;
_headerDateFormat = @"MMMM yyyy";
_headerMinimumDissolvedAlpha = 0.2;
_weekdayTextColor = FSCalendarStandardTitleTextColor;
_caseOptions = FSCalendarCaseOptionsHeaderUsesDefaultCase|FSCalendarCaseOptionsWeekdayUsesDefaultCase;
_backgroundColors = [NSMutableDictionary dictionaryWithCapacity:5];
_backgroundColors[@(FSCalendarCellStateNormal)] = [UIColor clearColor];
_backgroundColors[@(FSCalendarCellStateSelected)] = FSCalendarStandardSelectionColor;
_backgroundColors[@(FSCalendarCellStateDisabled)] = [UIColor clearColor];
_backgroundColors[@(FSCalendarCellStatePlaceholder)] = [UIColor clearColor];
_backgroundColors[@(FSCalendarCellStateToday)] = FSCalendarStandardTodayColor;
_titleColors = [NSMutableDictionary dictionaryWithCapacity:5];
_titleColors[@(FSCalendarCellStateNormal)] = [UIColor blackColor];
_titleColors[@(FSCalendarCellStateSelected)] = [UIColor whiteColor];
_titleColors[@(FSCalendarCellStateDisabled)] = [UIColor grayColor];
_titleColors[@(FSCalendarCellStatePlaceholder)] = [UIColor lightGrayColor];
_titleColors[@(FSCalendarCellStateToday)] = [UIColor whiteColor];
_subtitleColors = [NSMutableDictionary dictionaryWithCapacity:5];
_subtitleColors[@(FSCalendarCellStateNormal)] = [UIColor darkGrayColor];
_subtitleColors[@(FSCalendarCellStateSelected)] = [UIColor whiteColor];
_subtitleColors[@(FSCalendarCellStateDisabled)] = [UIColor lightGrayColor];
_subtitleColors[@(FSCalendarCellStatePlaceholder)] = [UIColor lightGrayColor];
_subtitleColors[@(FSCalendarCellStateToday)] = [UIColor whiteColor];
_borderColors[@(FSCalendarCellStateSelected)] = [UIColor clearColor];
_borderColors[@(FSCalendarCellStateNormal)] = [UIColor clearColor];
_borderRadius = 1.0;
_eventDefaultColor = FSCalendarStandardEventDotColor;
_eventSelectionColor = FSCalendarStandardEventDotColor;
_borderColors = [NSMutableDictionary dictionaryWithCapacity:2];
#if TARGET_INTERFACE_BUILDER
_fakeEventDots = YES;
#endif
}
return self;
}
- (void)setTitleFont:(UIFont *)titleFont
{
if (![_titleFont isEqual:titleFont]) {
_titleFont = titleFont;
[self.calendar configureAppearance];
}
}
- (void)setSubtitleFont:(UIFont *)subtitleFont
{
if (![_subtitleFont isEqual:subtitleFont]) {
_subtitleFont = subtitleFont;
[self.calendar configureAppearance];
}
}
- (void)setWeekdayFont:(UIFont *)weekdayFont
{
if (![_weekdayFont isEqual:weekdayFont]) {
_weekdayFont = weekdayFont;
[self.calendar configureAppearance];
}
}
- (void)setHeaderTitleFont:(UIFont *)headerTitleFont
{
if (![_headerTitleFont isEqual:headerTitleFont]) {
_headerTitleFont = headerTitleFont;
[self.calendar configureAppearance];
}
}
- (void)setTitleOffset:(CGPoint)titleOffset
{
if (!CGPointEqualToPoint(_titleOffset, titleOffset)) {
_titleOffset = titleOffset;
[_calendar.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
- (void)setSubtitleOffset:(CGPoint)subtitleOffset
{
if (!CGPointEqualToPoint(_subtitleOffset, subtitleOffset)) {
_subtitleOffset = subtitleOffset;
[_calendar.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
- (void)setImageOffset:(CGPoint)imageOffset
{
if (!CGPointEqualToPoint(_imageOffset, imageOffset)) {
_imageOffset = imageOffset;
[_calendar.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
- (void)setEventOffset:(CGPoint)eventOffset
{
if (!CGPointEqualToPoint(_eventOffset, eventOffset)) {
_eventOffset = eventOffset;
[_calendar.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
- (void)setTitleDefaultColor:(UIColor *)color
{
if (color) {
_titleColors[@(FSCalendarCellStateNormal)] = color;
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateNormal)];
}
[self.calendar configureAppearance];
}
- (UIColor *)titleDefaultColor
{
return _titleColors[@(FSCalendarCellStateNormal)];
}
- (void)setTitleSelectionColor:(UIColor *)color
{
if (color) {
_titleColors[@(FSCalendarCellStateSelected)] = color;
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self.calendar configureAppearance];
}
- (UIColor *)titleSelectionColor
{
return _titleColors[@(FSCalendarCellStateSelected)];
}
- (void)setTitleTodayColor:(UIColor *)color
{
if (color) {
_titleColors[@(FSCalendarCellStateToday)] = color;
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateToday)];
}
[self.calendar configureAppearance];
}
- (UIColor *)titleTodayColor
{
return _titleColors[@(FSCalendarCellStateToday)];
}
- (void)setTitlePlaceholderColor:(UIColor *)color
{
if (color) {
_titleColors[@(FSCalendarCellStatePlaceholder)] = color;
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStatePlaceholder)];
}
[self.calendar configureAppearance];
}
- (UIColor *)titlePlaceholderColor
{
return _titleColors[@(FSCalendarCellStatePlaceholder)];
}
- (void)setTitleWeekendColor:(UIColor *)color
{
if (color) {
_titleColors[@(FSCalendarCellStateWeekend)] = color;
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateWeekend)];
}
[self.calendar configureAppearance];
}
- (UIColor *)titleWeekendColor
{
return _titleColors[@(FSCalendarCellStateWeekend)];
}
- (void)setSubtitleDefaultColor:(UIColor *)color
{
if (color) {
_subtitleColors[@(FSCalendarCellStateNormal)] = color;
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateNormal)];
}
[self.calendar configureAppearance];
}
-(UIColor *)subtitleDefaultColor
{
return _subtitleColors[@(FSCalendarCellStateNormal)];
}
- (void)setSubtitleSelectionColor:(UIColor *)color
{
if (color) {
_subtitleColors[@(FSCalendarCellStateSelected)] = color;
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self.calendar configureAppearance];
}
- (UIColor *)subtitleSelectionColor
{
return _subtitleColors[@(FSCalendarCellStateSelected)];
}
- (void)setSubtitleTodayColor:(UIColor *)color
{
if (color) {
_subtitleColors[@(FSCalendarCellStateToday)] = color;
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateToday)];
}
[self.calendar configureAppearance];
}
- (UIColor *)subtitleTodayColor
{
return _subtitleColors[@(FSCalendarCellStateToday)];
}
- (void)setSubtitlePlaceholderColor:(UIColor *)color
{
if (color) {
_subtitleColors[@(FSCalendarCellStatePlaceholder)] = color;
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStatePlaceholder)];
}
[self.calendar configureAppearance];
}
- (UIColor *)subtitlePlaceholderColor
{
return _subtitleColors[@(FSCalendarCellStatePlaceholder)];
}
- (void)setSubtitleWeekendColor:(UIColor *)color
{
if (color) {
_subtitleColors[@(FSCalendarCellStateWeekend)] = color;
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateWeekend)];
}
[self.calendar configureAppearance];
}
- (UIColor *)subtitleWeekendColor
{
return _subtitleColors[@(FSCalendarCellStateWeekend)];
}
- (void)setSelectionColor:(UIColor *)color
{
if (color) {
_backgroundColors[@(FSCalendarCellStateSelected)] = color;
} else {
[_backgroundColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self.calendar configureAppearance];
}
- (UIColor *)selectionColor
{
return _backgroundColors[@(FSCalendarCellStateSelected)];
}
- (void)setTodayColor:(UIColor *)todayColor
{
if (todayColor) {
_backgroundColors[@(FSCalendarCellStateToday)] = todayColor;
} else {
[_backgroundColors removeObjectForKey:@(FSCalendarCellStateToday)];
}
[self.calendar configureAppearance];
}
- (UIColor *)todayColor
{
return _backgroundColors[@(FSCalendarCellStateToday)];
}
- (void)setTodaySelectionColor:(UIColor *)todaySelectionColor
{
if (todaySelectionColor) {
_backgroundColors[@(FSCalendarCellStateToday|FSCalendarCellStateSelected)] = todaySelectionColor;
} else {
[_backgroundColors removeObjectForKey:@(FSCalendarCellStateToday|FSCalendarCellStateSelected)];
}
[self.calendar configureAppearance];
}
- (UIColor *)todaySelectionColor
{
return _backgroundColors[@(FSCalendarCellStateToday|FSCalendarCellStateSelected)];
}
- (void)setEventDefaultColor:(UIColor *)eventDefaultColor
{
if (![_eventDefaultColor isEqual:eventDefaultColor]) {
_eventDefaultColor = eventDefaultColor;
[self.calendar configureAppearance];
}
}
- (void)setBorderDefaultColor:(UIColor *)color
{
if (color) {
_borderColors[@(FSCalendarCellStateNormal)] = color;
} else {
[_borderColors removeObjectForKey:@(FSCalendarCellStateNormal)];
}
[self.calendar configureAppearance];
}
- (UIColor *)borderDefaultColor
{
return _borderColors[@(FSCalendarCellStateNormal)];
}
- (void)setBorderSelectionColor:(UIColor *)color
{
if (color) {
_borderColors[@(FSCalendarCellStateSelected)] = color;
} else {
[_borderColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self.calendar configureAppearance];
}
- (UIColor *)borderSelectionColor
{
return _borderColors[@(FSCalendarCellStateSelected)];
}
- (void)setBorderRadius:(CGFloat)borderRadius
{
borderRadius = MAX(0.0, borderRadius);
borderRadius = MIN(1.0, borderRadius);
if (_borderRadius != borderRadius) {
_borderRadius = borderRadius;
[self.calendar configureAppearance];
}
}
- (void)setWeekdayTextColor:(UIColor *)weekdayTextColor
{
if (![_weekdayTextColor isEqual:weekdayTextColor]) {
_weekdayTextColor = weekdayTextColor;
[self.calendar configureAppearance];
}
}
- (void)setHeaderTitleColor:(UIColor *)color
{
if (![_headerTitleColor isEqual:color]) {
_headerTitleColor = color;
[self.calendar configureAppearance];
}
}
- (void)setHeaderMinimumDissolvedAlpha:(CGFloat)headerMinimumDissolvedAlpha
{
if (_headerMinimumDissolvedAlpha != headerMinimumDissolvedAlpha) {
_headerMinimumDissolvedAlpha = headerMinimumDissolvedAlpha;
[self.calendar configureAppearance];
}
}
- (void)setHeaderDateFormat:(NSString *)headerDateFormat
{
if (![_headerDateFormat isEqual:headerDateFormat]) {
_headerDateFormat = headerDateFormat;
[self.calendar configureAppearance];
}
}
- (void)setCaseOptions:(FSCalendarCaseOptions)caseOptions
{
if (_caseOptions != caseOptions) {
_caseOptions = caseOptions;
[self.calendar configureAppearance];
}
}
- (void)setSeparators:(FSCalendarSeparators)separators
{
if (_separators != separators) {
_separators = separators;
[_calendar.collectionView.collectionViewLayout invalidateLayout];
}
}
@end
@implementation FSCalendarAppearance (Deprecated)
- (void)setUseVeryShortWeekdaySymbols:(BOOL)useVeryShortWeekdaySymbols
{
_caseOptions &= 15;
self.caseOptions |= (useVeryShortWeekdaySymbols*FSCalendarCaseOptionsWeekdayUsesSingleUpperCase);
}
- (BOOL)useVeryShortWeekdaySymbols
{
return (_caseOptions & (15<<4) ) == FSCalendarCaseOptionsWeekdayUsesSingleUpperCase;
}
- (void)setTitleVerticalOffset:(CGFloat)titleVerticalOffset
{
self.titleOffset = CGPointMake(0, titleVerticalOffset);
}
- (CGFloat)titleVerticalOffset
{
return self.titleOffset.y;
}
- (void)setSubtitleVerticalOffset:(CGFloat)subtitleVerticalOffset
{
self.subtitleOffset = CGPointMake(0, subtitleVerticalOffset);
}
- (CGFloat)subtitleVerticalOffset
{
return self.subtitleOffset.y;
}
- (void)setEventColor:(UIColor *)eventColor
{
self.eventDefaultColor = eventColor;
}
- (UIColor *)eventColor
{
return self.eventDefaultColor;
}
- (void)setTitleTextSize:(CGFloat)titleTextSize
{
self.titleFont = [self.titleFont fontWithSize:titleTextSize];
}
- (void)setSubtitleTextSize:(CGFloat)subtitleTextSize
{
self.subtitleFont = [self.subtitleFont fontWithSize:subtitleTextSize];
}
- (void)setWeekdayTextSize:(CGFloat)weekdayTextSize
{
self.weekdayFont = [self.weekdayFont fontWithSize:weekdayTextSize];
}
- (void)setHeaderTitleTextSize:(CGFloat)headerTitleTextSize
{
self.headerTitleFont = [self.headerTitleFont fontWithSize:headerTitleTextSize];
}
- (void)invalidateAppearance
{
[self.calendar configureAppearance];
}
@end
//
// FSCalendarCalculator.h
// FSCalendar
//
// Created by dingwenchao on 30/10/2016.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
struct FSCalendarCoordinate {
NSInteger row;
NSInteger column;
};
typedef struct FSCalendarCoordinate FSCalendarCoordinate;
@interface FSCalendarCalculator : NSObject
@property (weak , nonatomic) FSCalendar *calendar;
@property (readonly, nonatomic) NSInteger numberOfSections;
- (instancetype)initWithCalendar:(FSCalendar *)calendar;
- (NSDate *)safeDateForDate:(NSDate *)date;
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath;
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath scope:(FSCalendarScope)scope;
- (NSIndexPath *)indexPathForDate:(NSDate *)date;
- (NSIndexPath *)indexPathForDate:(NSDate *)date scope:(FSCalendarScope)scope;
- (NSIndexPath *)indexPathForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position;
- (NSIndexPath *)indexPathForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position scope:(FSCalendarScope)scope;
- (NSDate *)pageForSection:(NSInteger)section;
- (NSDate *)weekForSection:(NSInteger)section;
- (NSDate *)monthForSection:(NSInteger)section;
- (NSDate *)monthHeadForSection:(NSInteger)section;
- (NSInteger)numberOfHeadPlaceholdersForMonth:(NSDate *)month;
- (NSInteger)numberOfRowsInMonth:(NSDate *)month;
- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (FSCalendarMonthPosition)monthPositionForIndexPath:(NSIndexPath *)indexPath;
- (FSCalendarCoordinate)coordinateForIndexPath:(NSIndexPath *)indexPath;
- (void)reloadSections;
@end
//
// FSCalendarCalculator.m
// FSCalendar
//
// Created by dingwenchao on 30/10/2016.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendar.h"
#import "FSCalendarCalculator.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarCalculator ()
@property (assign, nonatomic) NSInteger numberOfMonths;
@property (strong, nonatomic) NSMutableDictionary<NSNumber *, NSDate *> *months;
@property (strong, nonatomic) NSMutableDictionary<NSNumber *, NSDate *> *monthHeads;
@property (assign, nonatomic) NSInteger numberOfWeeks;
@property (strong, nonatomic) NSMutableDictionary<NSNumber *, NSDate *> *weeks;
@property (strong, nonatomic) NSMutableDictionary<NSDate *, NSNumber *> *rowCounts;
@property (readonly, nonatomic) NSCalendar *gregorian;
@property (readonly, nonatomic) NSDate *minimumDate;
@property (readonly, nonatomic) NSDate *maximumDate;
- (void)didReceiveNotifications:(NSNotification *)notification;
@end
@implementation FSCalendarCalculator
@dynamic gregorian,minimumDate,maximumDate;
- (instancetype)initWithCalendar:(FSCalendar *)calendar
{
self = [super init];
if (self) {
self.calendar = calendar;
self.months = [NSMutableDictionary dictionary];
self.monthHeads = [NSMutableDictionary dictionary];
self.weeks = [NSMutableDictionary dictionary];
self.rowCounts = [NSMutableDictionary dictionary];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotifications:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}
- (id)forwardingTargetForSelector:(SEL)selector
{
if ([self.calendar respondsToSelector:selector]) {
return self.calendar;
}
return [super forwardingTargetForSelector:selector];
}
#pragma mark - Public functions
- (NSDate *)safeDateForDate:(NSDate *)date
{
if ([self.gregorian compareDate:date toDate:self.minimumDate toUnitGranularity:NSCalendarUnitDay] == NSOrderedAscending) {
date = self.minimumDate;
} else if ([self.gregorian compareDate:date toDate:self.maximumDate toUnitGranularity:NSCalendarUnitDay] == NSOrderedDescending) {
date = self.maximumDate;
}
return date;
}
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath scope:(FSCalendarScope)scope
{
if (!indexPath) return nil;
switch (scope) {
case FSCalendarScopeMonth: {
NSDate *head = [self monthHeadForSection:indexPath.section];
NSUInteger daysOffset = indexPath.item;
NSDate *date = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:daysOffset toDate:head options:0];
return date;
break;
}
case FSCalendarScopeWeek: {
NSDate *currentPage = [self weekForSection:indexPath.section];
NSDate *date = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:indexPath.item toDate:currentPage options:0];
return date;
}
}
return nil;
}
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath
{
if (!indexPath) return nil;
return [self dateForIndexPath:indexPath scope:self.calendar.transitionCoordinator.representingScope];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date
{
return [self indexPathForDate:date atMonthPosition:FSCalendarMonthPositionCurrent scope:self.calendar.transitionCoordinator.representingScope];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date scope:(FSCalendarScope)scope
{
return [self indexPathForDate:date atMonthPosition:FSCalendarMonthPositionCurrent scope:scope];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position scope:(FSCalendarScope)scope
{
if (!date) return nil;
NSInteger item = 0;
NSInteger section = 0;
switch (scope) {
case FSCalendarScopeMonth: {
section = [self.gregorian components:NSCalendarUnitMonth fromDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] toDate:[self.gregorian fs_firstDayOfMonth:date] options:0].month;
if (position == FSCalendarMonthPositionPrevious) {
section++;
} else if (position == FSCalendarMonthPositionNext) {
section--;
}
NSDate *head = [self monthHeadForSection:section];
item = [self.gregorian components:NSCalendarUnitDay fromDate:head toDate:date options:0].day;
break;
}
case FSCalendarScopeWeek: {
section = [self.gregorian components:NSCalendarUnitWeekOfYear fromDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] toDate:[self.gregorian fs_firstDayOfWeek:date] options:0].weekOfYear;
item = (([self.gregorian component:NSCalendarUnitWeekday fromDate:date] - self.gregorian.firstWeekday) + 7) % 7;
break;
}
}
if (item < 0 || section < 0) {
return nil;
}
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
return indexPath;
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)position
{
return [self indexPathForDate:date atMonthPosition:position scope:self.calendar.transitionCoordinator.representingScope];
}
- (NSDate *)pageForSection:(NSInteger)section
{
switch (self.calendar.transitionCoordinator.representingScope) {
case FSCalendarScopeWeek:
return [self.gregorian fs_middleDayOfWeek:[self weekForSection:section]];
case FSCalendarScopeMonth:
return [self monthForSection:section];
default:
break;
}
}
- (NSDate *)monthForSection:(NSInteger)section
{
NSNumber *key = @(section);
NSDate *month = self.months[key];
if (!month) {
month = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:section toDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] options:0];
NSInteger numberOfHeadPlaceholders = [self numberOfHeadPlaceholdersForMonth:month];
NSDate *monthHead = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:-numberOfHeadPlaceholders toDate:month options:0];
self.months[key] = month;
self.monthHeads[key] = monthHead;
}
return month;
}
- (NSDate *)monthHeadForSection:(NSInteger)section
{
NSNumber *key = @(section);
NSDate *monthHead = self.monthHeads[key];
if (!monthHead) {
NSDate *month = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:section toDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] options:0];
NSInteger numberOfHeadPlaceholders = [self numberOfHeadPlaceholdersForMonth:month];
monthHead = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:-numberOfHeadPlaceholders toDate:month options:0];
self.months[key] = month;
self.monthHeads[key] = monthHead;
}
return monthHead;
}
- (NSDate *)weekForSection:(NSInteger)section
{
NSNumber *key = @(section);
NSDate *week = self.weeks[key];
if (!week) {
week = [self.gregorian dateByAddingUnit:NSCalendarUnitWeekOfYear value:section toDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] options:0];
self.weeks[key] = week;
}
return week;
}
- (NSInteger)numberOfSections
{
switch (self.calendar.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
return self.numberOfMonths;
}
case FSCalendarScopeWeek: {
return self.numberOfWeeks;
}
}
}
- (NSInteger)numberOfHeadPlaceholdersForMonth:(NSDate *)month
{
NSInteger currentWeekday = [self.gregorian component:NSCalendarUnitWeekday fromDate:month];
NSInteger number = ((currentWeekday- self.gregorian.firstWeekday) + 7) % 7 ?: (7 * (!self.calendar.floatingMode&&(self.calendar.placeholderType == FSCalendarPlaceholderTypeFillSixRows)));
return number;
}
- (NSInteger)numberOfRowsInMonth:(NSDate *)month
{
if (!month) return 0;
if (self.calendar.placeholderType == FSCalendarPlaceholderTypeFillSixRows) return 6;
NSNumber *rowCount = self.rowCounts[month];
if (rowCount == nil) {
NSDate *firstDayOfMonth = [self.gregorian fs_firstDayOfMonth:month];
NSInteger weekdayOfFirstDay = [self.gregorian component:NSCalendarUnitWeekday fromDate:firstDayOfMonth];
NSInteger numberOfDaysInMonth = [self.gregorian fs_numberOfDaysInMonth:month];
NSInteger numberOfPlaceholdersForPrev = ((weekdayOfFirstDay - self.gregorian.firstWeekday) + 7) % 7;
NSInteger headDayCount = numberOfDaysInMonth + numberOfPlaceholdersForPrev;
NSInteger numberOfRows = (headDayCount/7) + (headDayCount%7>0);
rowCount = @(numberOfRows);
self.rowCounts[month] = rowCount;
}
return rowCount.integerValue;
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
if (self.calendar.transitionCoordinator.representingScope == FSCalendarScopeWeek) return 1;
NSDate *month = [self monthForSection:section];
return [self numberOfRowsInMonth:month];
}
- (FSCalendarMonthPosition)monthPositionForIndexPath:(NSIndexPath *)indexPath
{
if (!indexPath) return FSCalendarMonthPositionNotFound;
if (self.calendar.transitionCoordinator.representingScope == FSCalendarScopeWeek) {
return FSCalendarMonthPositionCurrent;
}
NSDate *date = [self dateForIndexPath:indexPath];
NSDate *page = [self pageForSection:indexPath.section];
NSComparisonResult comparison = [self.gregorian compareDate:date toDate:page toUnitGranularity:NSCalendarUnitMonth];
switch (comparison) {
case NSOrderedAscending:
return FSCalendarMonthPositionPrevious;
case NSOrderedSame:
return FSCalendarMonthPositionCurrent;
case NSOrderedDescending:
return FSCalendarMonthPositionNext;
}
}
- (FSCalendarCoordinate)coordinateForIndexPath:(NSIndexPath *)indexPath
{
FSCalendarCoordinate coordinate;
coordinate.row = indexPath.item / 7;
coordinate.column = indexPath.item % 7;
return coordinate;
}
- (void)reloadSections
{
self.numberOfMonths = [self.gregorian components:NSCalendarUnitMonth fromDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] toDate:self.maximumDate options:0].month+1;
self.numberOfWeeks = [self.gregorian components:NSCalendarUnitWeekOfYear fromDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] toDate:self.maximumDate options:0].weekOfYear+1;
[self clearCaches];
}
- (void)clearCaches
{
[self.months removeAllObjects];
[self.monthHeads removeAllObjects];
[self.weeks removeAllObjects];
[self.rowCounts removeAllObjects];
}
#pragma mark - Private functinos
- (void)didReceiveNotifications:(NSNotification *)notification
{
if ([notification.name isEqualToString:UIApplicationDidReceiveMemoryWarningNotification]) {
[self clearCaches];
}
}
@end
//
// FSCalendarCell.h
// Pods
//
// Created by Wenchao Ding on 12/3/15.
//
//
#import <UIKit/UIKit.h>
@class FSCalendar, FSCalendarAppearance, FSCalendarEventIndicator;
typedef NS_ENUM(NSUInteger, FSCalendarMonthPosition);
@interface FSCalendarCell : UICollectionViewCell
#pragma mark - Public properties
/**
The day text label of the cell
*/
@property (weak, nonatomic) UILabel *titleLabel;
/**
The subtitle label of the cell
*/
@property (weak, nonatomic) UILabel *subtitleLabel;
/**
The shape layer of the cell
*/
@property (weak, nonatomic) CAShapeLayer *shapeLayer;
/**
The imageView below shape layer of the cell
*/
@property (weak, nonatomic) UIImageView *imageView;
/**
The collection of event dots of the cell
*/
@property (weak, nonatomic) FSCalendarEventIndicator *eventIndicator;
/**
A boolean value indicates that whether the cell is "placeholder". Default is NO.
*/
@property (assign, nonatomic, getter=isPlaceholder) BOOL placeholder;
#pragma mark - Private properties
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarAppearance *appearance;
@property (strong, nonatomic) NSString *subtitle;
@property (strong, nonatomic) UIImage *image;
@property (assign, nonatomic) FSCalendarMonthPosition monthPosition;
@property (assign, nonatomic) NSInteger numberOfEvents;
@property (assign, nonatomic) BOOL dateIsToday;
@property (assign, nonatomic) BOOL weekend;
@property (strong, nonatomic) UIColor *preferredFillDefaultColor;
@property (strong, nonatomic) UIColor *preferredFillSelectionColor;
@property (strong, nonatomic) UIColor *preferredTitleDefaultColor;
@property (strong, nonatomic) UIColor *preferredTitleSelectionColor;
@property (strong, nonatomic) UIColor *preferredSubtitleDefaultColor;
@property (strong, nonatomic) UIColor *preferredSubtitleSelectionColor;
@property (strong, nonatomic) UIColor *preferredBorderDefaultColor;
@property (strong, nonatomic) UIColor *preferredBorderSelectionColor;
@property (assign, nonatomic) CGPoint preferredTitleOffset;
@property (assign, nonatomic) CGPoint preferredSubtitleOffset;
@property (assign, nonatomic) CGPoint preferredImageOffset;
@property (assign, nonatomic) CGPoint preferredEventOffset;
@property (strong, nonatomic) NSArray<UIColor *> *preferredEventDefaultColors;
@property (strong, nonatomic) NSArray<UIColor *> *preferredEventSelectionColors;
@property (assign, nonatomic) CGFloat preferredBorderRadius;
// Add subviews to self.contentView and set up constraints
- (instancetype)initWithFrame:(CGRect)frame NS_REQUIRES_SUPER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_REQUIRES_SUPER;
// For DIY overridden
- (void)layoutSubviews NS_REQUIRES_SUPER; // Configure frames of subviews
- (void)configureAppearance NS_REQUIRES_SUPER; // Configure appearance for cell
- (UIColor *)colorForCurrentStateInDictionary:(NSDictionary *)dictionary;
- (void)performSelecting;
@end
@interface FSCalendarEventIndicator : UIView
@property (assign, nonatomic) NSInteger numberOfEvents;
@property (strong, nonatomic) id color;
@end
@interface FSCalendarBlankCell : UICollectionViewCell
- (void)configureAppearance;
@end
//
// FSCalendarCell.m
// Pods
//
// Created by Wenchao Ding on 12/3/15.
//
//
#import "FSCalendarCell.h"
#import "FSCalendar.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarConstants.h"
@interface FSCalendarCell ()
@property (readonly, nonatomic) UIColor *colorForCellFill;
@property (readonly, nonatomic) UIColor *colorForTitleLabel;
@property (readonly, nonatomic) UIColor *colorForSubtitleLabel;
@property (readonly, nonatomic) UIColor *colorForCellBorder;
@property (readonly, nonatomic) NSArray<UIColor *> *colorsForEvents;
@property (readonly, nonatomic) CGFloat borderRadius;
@end
@implementation FSCalendarCell
#pragma mark - Life cycle
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
UILabel *label;
CAShapeLayer *shapeLayer;
UIImageView *imageView;
FSCalendarEventIndicator *eventIndicator;
label = [[UILabel alloc] initWithFrame:CGRectZero];
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor blackColor];
[self.contentView addSubview:label];
self.titleLabel = label;
label = [[UILabel alloc] initWithFrame:CGRectZero];
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor lightGrayColor];
[self.contentView addSubview:label];
self.subtitleLabel = label;
shapeLayer = [CAShapeLayer layer];
shapeLayer.backgroundColor = [UIColor clearColor].CGColor;
shapeLayer.borderWidth = 1.0;
shapeLayer.borderColor = [UIColor clearColor].CGColor;
shapeLayer.opacity = 0;
[self.contentView.layer insertSublayer:shapeLayer below:_titleLabel.layer];
self.shapeLayer = shapeLayer;
eventIndicator = [[FSCalendarEventIndicator alloc] initWithFrame:CGRectZero];
eventIndicator.backgroundColor = [UIColor clearColor];
eventIndicator.hidden = YES;
[self.contentView addSubview:eventIndicator];
self.eventIndicator = eventIndicator;
imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
imageView.contentMode = UIViewContentModeBottom|UIViewContentModeCenter;
[self.contentView addSubview:imageView];
self.imageView = imageView;
self.clipsToBounds = NO;
self.contentView.clipsToBounds = NO;
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (_subtitle) {
_subtitleLabel.text = _subtitle;
if (_subtitleLabel.hidden) {
_subtitleLabel.hidden = NO;
}
} else {
if (!_subtitleLabel.hidden) {
_subtitleLabel.hidden = YES;
}
}
if (_subtitle) {
CGFloat titleHeight = self.titleLabel.font.lineHeight;
CGFloat subtitleHeight = self.subtitleLabel.font.lineHeight;
CGFloat height = titleHeight + subtitleHeight;
_titleLabel.frame = CGRectMake(
self.preferredTitleOffset.x,
(self.contentView.fs_height*5.0/6.0-height)*0.5+self.preferredTitleOffset.y,
self.contentView.fs_width,
titleHeight
);
_subtitleLabel.frame = CGRectMake(
self.preferredSubtitleOffset.x,
(_titleLabel.fs_bottom-self.preferredTitleOffset.y) - (_titleLabel.fs_height-_titleLabel.font.pointSize)+self.preferredSubtitleOffset.y,
self.contentView.fs_width,
subtitleHeight
);
} else {
_titleLabel.frame = CGRectMake(
self.preferredTitleOffset.x,
self.preferredTitleOffset.y,
self.contentView.fs_width,
floor(self.contentView.fs_height*5.0/6.0)
);
}
_imageView.frame = CGRectMake(self.preferredImageOffset.x, self.preferredImageOffset.y, self.contentView.fs_width, self.contentView.fs_height);
CGFloat titleHeight = self.bounds.size.height*5.0/6.0;
CGFloat diameter = MIN(self.bounds.size.height*5.0/6.0,self.bounds.size.width);
diameter = diameter > FSCalendarStandardCellDiameter ? (diameter - (diameter-FSCalendarStandardCellDiameter)*0.5) : diameter;
_shapeLayer.frame = CGRectMake((self.bounds.size.width-diameter)/2,
(titleHeight-diameter)/2,
diameter,
diameter);
CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:_shapeLayer.bounds
cornerRadius:CGRectGetWidth(_shapeLayer.bounds)*0.5*self.borderRadius].CGPath;
if (!CGPathEqualToPath(_shapeLayer.path,path)) {
_shapeLayer.path = path;
}
CGFloat eventSize = _shapeLayer.frame.size.height/6.0;
_eventIndicator.frame = CGRectMake(
self.preferredEventOffset.x,
CGRectGetMaxY(_shapeLayer.frame)+eventSize*0.17+self.preferredEventOffset.y,
self.fs_width,
eventSize*0.83
);
}
- (void)prepareForReuse
{
[super prepareForReuse];
if (self.window) { // Avoid interrupt of navigation transition somehow
[CATransaction setDisableActions:YES]; // Avoid blink of shape layer.
}
self.shapeLayer.opacity = 0;
[self.contentView.layer removeAnimationForKey:@"opacity"];
}
#pragma mark - Public
- (void)performSelecting
{
_shapeLayer.opacity = 1;
CAAnimationGroup *group = [CAAnimationGroup animation];
CABasicAnimation *zoomOut = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
zoomOut.fromValue = @0.3;
zoomOut.toValue = @1.2;
zoomOut.duration = FSCalendarDefaultBounceAnimationDuration/4*3;
CABasicAnimation *zoomIn = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
zoomIn.fromValue = @1.2;
zoomIn.toValue = @1.0;
zoomIn.beginTime = FSCalendarDefaultBounceAnimationDuration/4*3;
zoomIn.duration = FSCalendarDefaultBounceAnimationDuration/4;
group.duration = FSCalendarDefaultBounceAnimationDuration;
group.animations = @[zoomOut, zoomIn];
[_shapeLayer addAnimation:group forKey:@"bounce"];
[self configureAppearance];
}
#pragma mark - Private
- (void)configureAppearance
{
UIColor *textColor = self.colorForTitleLabel;
if (![textColor isEqual:_titleLabel.textColor]) {
_titleLabel.textColor = textColor;
}
UIFont *titleFont = self.calendar.appearance.titleFont;
if (![titleFont isEqual:_titleLabel.font]) {
_titleLabel.font = titleFont;
}
if (_subtitle) {
textColor = self.colorForSubtitleLabel;
if (![textColor isEqual:_subtitleLabel.textColor]) {
_subtitleLabel.textColor = textColor;
}
titleFont = self.calendar.appearance.subtitleFont;
if (![titleFont isEqual:_subtitleLabel.font]) {
_subtitleLabel.font = titleFont;
}
}
UIColor *borderColor = self.colorForCellBorder;
UIColor *fillColor = self.colorForCellFill;
BOOL shouldHideShapeLayer = !self.selected && !self.dateIsToday && !borderColor && !fillColor;
if (_shapeLayer.opacity == shouldHideShapeLayer) {
_shapeLayer.opacity = !shouldHideShapeLayer;
}
if (!shouldHideShapeLayer) {
CGColorRef cellFillColor = self.colorForCellFill.CGColor;
if (!CGColorEqualToColor(_shapeLayer.fillColor, cellFillColor)) {
_shapeLayer.fillColor = cellFillColor;
}
CGColorRef cellBorderColor = self.colorForCellBorder.CGColor;
if (!CGColorEqualToColor(_shapeLayer.strokeColor, cellBorderColor)) {
_shapeLayer.strokeColor = cellBorderColor;
}
CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:_shapeLayer.bounds
cornerRadius:CGRectGetWidth(_shapeLayer.bounds)*0.5*self.borderRadius].CGPath;
if (!CGPathEqualToPath(_shapeLayer.path, path)) {
_shapeLayer.path = path;
}
}
if (![_image isEqual:_imageView.image]) {
_imageView.image = _image;
_imageView.hidden = !_image;
}
if (_eventIndicator.hidden == (_numberOfEvents > 0)) {
_eventIndicator.hidden = !_numberOfEvents;
}
_eventIndicator.numberOfEvents = self.numberOfEvents;
_eventIndicator.color = self.colorsForEvents;
}
- (UIColor *)colorForCurrentStateInDictionary:(NSDictionary *)dictionary
{
if (self.isSelected) {
if (self.dateIsToday) {
return dictionary[@(FSCalendarCellStateSelected|FSCalendarCellStateToday)] ?: dictionary[@(FSCalendarCellStateSelected)];
}
return dictionary[@(FSCalendarCellStateSelected)];
}
if (self.dateIsToday && [[dictionary allKeys] containsObject:@(FSCalendarCellStateToday)]) {
return dictionary[@(FSCalendarCellStateToday)];
}
if (self.placeholder && [[dictionary allKeys] containsObject:@(FSCalendarCellStatePlaceholder)]) {
return dictionary[@(FSCalendarCellStatePlaceholder)];
}
if (self.weekend && [[dictionary allKeys] containsObject:@(FSCalendarCellStateWeekend)]) {
return dictionary[@(FSCalendarCellStateWeekend)];
}
return dictionary[@(FSCalendarCellStateNormal)];
}
#pragma mark - Properties
- (UIColor *)colorForCellFill
{
if (self.selected) {
return self.preferredFillSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.backgroundColors];
}
return self.preferredFillDefaultColor ?: [self colorForCurrentStateInDictionary:_appearance.backgroundColors];
}
- (UIColor *)colorForTitleLabel
{
if (self.selected) {
return self.preferredTitleSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.titleColors];
}
return self.preferredTitleDefaultColor ?: [self colorForCurrentStateInDictionary:_appearance.titleColors];
}
- (UIColor *)colorForSubtitleLabel
{
if (self.selected) {
return self.preferredSubtitleSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.subtitleColors];
}
return self.preferredSubtitleDefaultColor ?: [self colorForCurrentStateInDictionary:_appearance.subtitleColors];
}
- (UIColor *)colorForCellBorder
{
if (self.selected) {
return _preferredBorderSelectionColor ?: _appearance.borderSelectionColor;
}
return _preferredBorderDefaultColor ?: _appearance.borderDefaultColor;
}
- (NSArray<UIColor *> *)colorsForEvents
{
if (self.selected) {
return _preferredEventSelectionColors ?: @[_appearance.eventSelectionColor];
}
return _preferredEventDefaultColors ?: @[_appearance.eventDefaultColor];
}
- (CGFloat)borderRadius
{
return _preferredBorderRadius >= 0 ? _preferredBorderRadius : _appearance.borderRadius;
}
#define OFFSET_PROPERTY(NAME,CAPITAL,ALTERNATIVE) \
\
@synthesize NAME = _##NAME; \
\
- (void)set##CAPITAL:(CGPoint)NAME \
{ \
BOOL diff = !CGPointEqualToPoint(NAME, self.NAME); \
_##NAME = NAME; \
if (diff) { \
[self setNeedsLayout]; \
} \
} \
\
- (CGPoint)NAME \
{ \
return CGPointEqualToPoint(_##NAME, CGPointInfinity) ? ALTERNATIVE : _##NAME; \
}
OFFSET_PROPERTY(preferredTitleOffset, PreferredTitleOffset, _appearance.titleOffset);
OFFSET_PROPERTY(preferredSubtitleOffset, PreferredSubtitleOffset, _appearance.subtitleOffset);
OFFSET_PROPERTY(preferredImageOffset, PreferredImageOffset, _appearance.imageOffset);
OFFSET_PROPERTY(preferredEventOffset, PreferredEventOffset, _appearance.eventOffset);
#undef OFFSET_PROPERTY
- (void)setCalendar:(FSCalendar *)calendar
{
if (![_calendar isEqual:calendar]) {
_calendar = calendar;
_appearance = calendar.appearance;
[self configureAppearance];
}
}
- (void)setSubtitle:(NSString *)subtitle
{
if (![_subtitle isEqualToString:subtitle]) {
BOOL diff = (subtitle.length && !_subtitle.length) || (_subtitle.length && !subtitle.length);
_subtitle = subtitle;
if (diff) {
[self setNeedsLayout];
}
}
}
@end
@interface FSCalendarEventIndicator ()
@property (weak, nonatomic) UIView *contentView;
@property (strong, nonatomic) NSPointerArray *eventLayers;
@end
@implementation FSCalendarEventIndicator
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:view];
self.contentView = view;
self.eventLayers = [NSPointerArray weakObjectsPointerArray];
for (int i = 0; i < FSCalendarMaximumNumberOfEvents; i++) {
CALayer *layer = [CALayer layer];
layer.backgroundColor = [UIColor clearColor].CGColor;
[self.contentView.layer addSublayer:layer];
[self.eventLayers addPointer:(__bridge void * _Nullable)(layer)];
}
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat diameter = MIN(MIN(self.fs_width, self.fs_height),FSCalendarMaximumEventDotDiameter);
self.contentView.fs_height = self.fs_height;
self.contentView.fs_width = (self.numberOfEvents*2-1)*diameter;
self.contentView.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
}
- (void)layoutSublayersOfLayer:(CALayer *)layer
{
[super layoutSublayersOfLayer:layer];
if (layer == self.layer) {
CGFloat diameter = MIN(MIN(self.fs_width, self.fs_height),FSCalendarMaximumEventDotDiameter);
for (int i = 0; i < self.eventLayers.count; i++) {
CALayer *eventLayer = [self.eventLayers pointerAtIndex:i];
eventLayer.hidden = i >= self.numberOfEvents;
if (!eventLayer.hidden) {
eventLayer.frame = CGRectMake(2*i*diameter, (self.fs_height-diameter)*0.5, diameter, diameter);
if (eventLayer.cornerRadius != diameter/2) {
eventLayer.cornerRadius = diameter/2;
}
}
}
}
}
- (void)setColor:(id)color
{
if (![_color isEqual:color]) {
_color = color;
if ([_color isKindOfClass:[UIColor class]]) {
for (NSInteger i = 0; i < self.eventLayers.count; i++) {
CALayer *layer = [self.eventLayers pointerAtIndex:i];
layer.backgroundColor = [_color CGColor];
}
} else if ([_color isKindOfClass:[NSArray class]]) {
NSArray<UIColor *> *colors = (NSArray *)_color;
for (int i = 0; i < self.eventLayers.count; i++) {
CALayer *eventLayer = [self.eventLayers pointerAtIndex:i];
eventLayer.backgroundColor = colors[MIN(i,colors.count-1)].CGColor;
}
}
}
}
- (void)setNumberOfEvents:(NSInteger)numberOfEvents
{
if (_numberOfEvents != numberOfEvents) {
_numberOfEvents = MIN(MAX(numberOfEvents,0),FSCalendarMaximumNumberOfEvents);
[self setNeedsLayout];
}
}
@end
@implementation FSCalendarBlankCell
- (void)configureAppearance {}
@end
//
// FSCalendarCollectionView.h
// FSCalendar
//
// Created by Wenchao Ding on 10/25/15.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FSCalendarCollectionView;
@protocol FSCalendarCollectionViewInternalDelegate <UICollectionViewDelegate>
@optional
- (void)collectionViewDidFinishLayoutSubviews:(FSCalendarCollectionView *)collectionView;
@end
@interface FSCalendarCollectionView : UICollectionView
@property (weak, nonatomic) id<FSCalendarCollectionViewInternalDelegate> internalDelegate;
@end
//
// FSCalendarCollectionView.m
// FSCalendar
//
// Created by Wenchao Ding on 10/25/15.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
// Reject -[UIScrollView(UIScrollViewInternal) _adjustContentOffsetIfNecessary]
#import "FSCalendarCollectionView.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarConstants.h"
@interface FSCalendarCollectionView ()
- (void)commonInit;
@end
@implementation FSCalendarCollectionView
@synthesize scrollsToTop = _scrollsToTop, contentInset = _contentInset;
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout
{
self = [super initWithFrame:frame collectionViewLayout:layout];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
self.scrollsToTop = NO;
self.contentInset = UIEdgeInsetsZero;
if (@available(iOS 10.0, *)) self.prefetchingEnabled = NO;
if (@available(iOS 11.0, *)) self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.internalDelegate && [self.internalDelegate respondsToSelector:@selector(collectionViewDidFinishLayoutSubviews:)]) {
[self.internalDelegate collectionViewDidFinishLayoutSubviews:self];
}
}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
[super setContentInset:UIEdgeInsetsZero];
if (contentInset.top) {
self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y+contentInset.top);
}
}
- (void)setScrollsToTop:(BOOL)scrollsToTop
{
[super setScrollsToTop:NO];
}
@end
//
// FSCalendarAnimationLayout.h
// FSCalendar
//
// Created by dingwenchao on 1/3/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FSCalendar;
@interface FSCalendarCollectionViewLayout : UICollectionViewLayout
@property (weak, nonatomic) FSCalendar *calendar;
@property (assign, nonatomic) UIEdgeInsets sectionInsets;
@property (assign, nonatomic) UICollectionViewScrollDirection scrollDirection;
@end
//
// FSCalendarAnimationLayout.m
// FSCalendar
//
// Created by dingwenchao on 1/3/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarCollectionViewLayout.h"
#import "FSCalendar.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarConstants.h"
#import "FSCalendarSeparatorDecorationView.h"
#define kFSCalendarSeparatorInterRows @"FSCalendarSeparatorInterRows"
#define kFSCalendarSeparatorInterColumns @"FSCalendarSeparatorInterColumns"
@interface FSCalendarCollectionViewLayout ()
@property (assign, nonatomic) CGFloat *widths;
@property (assign, nonatomic) CGFloat *heights;
@property (assign, nonatomic) CGFloat *lefts;
@property (assign, nonatomic) CGFloat *tops;
@property (assign, nonatomic) CGFloat *sectionHeights;
@property (assign, nonatomic) CGFloat *sectionTops;
@property (assign, nonatomic) CGFloat *sectionBottoms;
@property (assign, nonatomic) CGFloat *sectionRowCounts;
@property (assign, nonatomic) CGSize estimatedItemSize;
@property (assign, nonatomic) CGSize contentSize;
@property (assign, nonatomic) CGSize collectionViewSize;
@property (assign, nonatomic) CGSize headerReferenceSize;
@property (assign, nonatomic) NSInteger numberOfSections;
@property (assign, nonatomic) FSCalendarSeparators separators;
@property (strong, nonatomic) NSMutableDictionary<NSIndexPath *, UICollectionViewLayoutAttributes *> *itemAttributes;
@property (strong, nonatomic) NSMutableDictionary<NSIndexPath *, UICollectionViewLayoutAttributes *> *headerAttributes;
@property (strong, nonatomic) NSMutableDictionary<NSIndexPath *, UICollectionViewLayoutAttributes *> *rowSeparatorAttributes;
- (void)didReceiveNotifications:(NSNotification *)notification;
- (CGFloat)calculateRowOffset:(NSInteger)row totalRows:(NSInteger)totalRows;
@end
@implementation FSCalendarCollectionViewLayout
- (instancetype)init
{
self = [super init];
if (self) {
self.estimatedItemSize = CGSizeZero;
self.widths = NULL;
self.heights = NULL;
self.tops = NULL;
self.lefts = NULL;
self.sectionHeights = NULL;
self.sectionTops = NULL;
self.sectionBottoms = NULL;
self.sectionRowCounts = NULL;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInsets = UIEdgeInsetsMake(5, 0, 5, 0);
self.itemAttributes = NSMutableDictionary.dictionary;
self.headerAttributes = NSMutableDictionary.dictionary;
self.rowSeparatorAttributes = NSMutableDictionary.dictionary;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotifications:) name:UIDeviceOrientationDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotifications:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[self registerClass:FSCalendarSeparatorDecorationView.class forDecorationViewOfKind:kFSCalendarSeparatorInterRows];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
free(self.widths);
free(self.heights);
free(self.tops);
free(self.lefts);
free(self.sectionHeights);
free(self.sectionTops);
free(self.sectionRowCounts);
free(self.sectionBottoms);
}
- (void)prepareLayout
{
if (CGSizeEqualToSize(self.collectionViewSize, self.collectionView.frame.size) && self.numberOfSections == self.collectionView.numberOfSections && self.separators == self.calendar.appearance.separators) {
return;
}
self.collectionViewSize = self.collectionView.frame.size;
self.separators = self.calendar.appearance.separators;
[self.itemAttributes removeAllObjects];
[self.headerAttributes removeAllObjects];
[self.rowSeparatorAttributes removeAllObjects];
self.headerReferenceSize = ({
CGSize headerSize = CGSizeZero;
if (self.calendar.floatingMode) {
CGFloat headerHeight = self.calendar.preferredWeekdayHeight*1.5+self.calendar.preferredHeaderHeight;
headerSize = CGSizeMake(self.collectionView.fs_width, headerHeight);
}
headerSize;
});
self.estimatedItemSize = ({
CGFloat width = (self.collectionView.fs_width-self.sectionInsets.left-self.sectionInsets.right)/7.0;
CGFloat height = ({
CGFloat height = FSCalendarStandardRowHeight;
if (!self.calendar.floatingMode) {
switch (self.calendar.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
height = (self.collectionView.fs_height-self.sectionInsets.top-self.sectionInsets.bottom)/6.0;
break;
}
case FSCalendarScopeWeek: {
height = (self.collectionView.fs_height-self.sectionInsets.top-self.sectionInsets.bottom);
break;
}
default:
break;
}
} else {
height = self.calendar.rowHeight;
}
height;
});
CGSize size = CGSizeMake(width, height);
size;
});
// Calculate item widths and lefts
free(self.widths);
self.widths = ({
NSInteger columnCount = 7;
size_t columnSize = sizeof(CGFloat)*columnCount;
CGFloat *widths = malloc(columnSize);
CGFloat contentWidth = self.collectionView.fs_width - self.sectionInsets.left - self.sectionInsets.right;
FSCalendarSliceCake(contentWidth, columnCount, widths);
widths;
});
free(self.lefts);
self.lefts = ({
NSInteger columnCount = 7;
size_t columnSize = sizeof(CGFloat)*columnCount;
CGFloat *lefts = malloc(columnSize);
lefts[0] = self.sectionInsets.left;
for (int i = 1; i < columnCount; i++) {
lefts[i] = lefts[i-1] + self.widths[i-1];
}
lefts;
});
// Calculate item heights and tops
free(self.heights);
self.heights = ({
NSInteger rowCount = self.calendar.transitionCoordinator.representingScope == FSCalendarScopeWeek ? 1 : 6;
size_t rowSize = sizeof(CGFloat)*rowCount;
CGFloat *heights = malloc(rowSize);
if (!self.calendar.floatingMode) {
CGFloat contentHeight = self.collectionView.fs_height - self.sectionInsets.top - self.sectionInsets.bottom;
FSCalendarSliceCake(contentHeight, rowCount, heights);
} else {
for (int i = 0; i < rowCount; i++) {
heights[i] = self.estimatedItemSize.height;
}
}
heights;
});
free(self.tops);
self.tops = ({
NSInteger rowCount = self.calendar.transitionCoordinator.representingScope == FSCalendarScopeWeek ? 1 : 6;
size_t rowSize = sizeof(CGFloat)*rowCount;
CGFloat *tops = malloc(rowSize);
tops[0] = self.sectionInsets.top;
for (int i = 1; i < rowCount; i++) {
tops[i] = tops[i-1] + self.heights[i-1];
}
tops;
});
// Calculate content size
self.numberOfSections = self.collectionView.numberOfSections;
self.contentSize = ({
CGSize contentSize = CGSizeZero;
if (!self.calendar.floatingMode) {
CGFloat width = self.collectionView.fs_width;
CGFloat height = self.collectionView.fs_height;
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
width *= self.numberOfSections;
break;
}
case UICollectionViewScrollDirectionVertical: {
height *= self.numberOfSections;
break;
}
default:
break;
}
contentSize = CGSizeMake(width, height);
} else {
free(self.sectionHeights);
self.sectionHeights = malloc(sizeof(CGFloat)*self.numberOfSections);
free(self.sectionRowCounts);
self.sectionRowCounts = malloc(sizeof(NSInteger)*self.numberOfSections);
CGFloat width = self.collectionView.fs_width;
CGFloat height = 0;
for (int i = 0; i < self.numberOfSections; i++) {
NSInteger rowCount = [self.calendar.calculator numberOfRowsInSection:i];
self.sectionRowCounts[i] = rowCount;
CGFloat sectionHeight = self.headerReferenceSize.height;
for (int j = 0; j < rowCount; j++) {
sectionHeight += self.heights[j];
}
self.sectionHeights[i] = sectionHeight;
height += sectionHeight;
}
free(self.sectionTops);
self.sectionTops = malloc(sizeof(CGFloat)*self.numberOfSections);
free(self.sectionBottoms);
self.sectionBottoms = malloc(sizeof(CGFloat)*self.numberOfSections);
self.sectionTops[0] = 0;
self.sectionBottoms[0] = self.sectionHeights[0];
for (int i = 1; i < self.numberOfSections; i++) {
self.sectionTops[i] = self.sectionTops[i-1] + self.sectionHeights[i-1];
self.sectionBottoms[i] = self.sectionTops[i] + self.sectionHeights[i];
}
contentSize = CGSizeMake(width, height);
}
contentSize;
});
[self.calendar adjustMonthPosition];
}
- (CGSize)collectionViewContentSize
{
return self.contentSize;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// Clipping
rect = CGRectIntersection(rect, CGRectMake(0, 0, self.contentSize.width, self.contentSize.height));
if (CGRectIsEmpty(rect)) return nil;
// Calculating attributes
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributes = [NSMutableArray array];
if (!self.calendar.floatingMode) {
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
NSInteger startColumn = ({
NSInteger startSection = rect.origin.x/self.collectionView.fs_width;
CGFloat widthDelta = FSCalendarMod(CGRectGetMinX(rect), self.collectionView.fs_width)-self.sectionInsets.left;
widthDelta = MIN(MAX(0, widthDelta), self.collectionView.fs_width-self.sectionInsets.left);
NSInteger countDelta = FSCalendarFloor(widthDelta/self.estimatedItemSize.width);
NSInteger startColumn = startSection*7 + countDelta;
startColumn;
});
NSInteger endColumn = ({
NSInteger endColumn;
CGFloat section = CGRectGetMaxX(rect)/self.collectionView.fs_width;
CGFloat remainder = FSCalendarMod(section, 1);
// https://stackoverflow.com/a/10335601/2398107
if (remainder <= MAX(100*FLT_EPSILON*ABS(remainder), FLT_MIN)) {
endColumn = FSCalendarFloor(section)*7 - 1;
} else {
CGFloat widthDelta = FSCalendarMod(CGRectGetMaxX(rect), self.collectionView.fs_width)-self.sectionInsets.left;
widthDelta = MIN(MAX(0, widthDelta), self.collectionView.fs_width - self.sectionInsets.left);
NSInteger countDelta = FSCalendarCeil(widthDelta/self.estimatedItemSize.width);
endColumn = FSCalendarFloor(section)*7 + countDelta - 1;
}
endColumn;
});
NSInteger numberOfRows = self.calendar.transitionCoordinator.representingScope == FSCalendarScopeMonth ? 6 : 1;
for (NSInteger column = startColumn; column <= endColumn; column++) {
for (NSInteger row = 0; row < numberOfRows; row++) {
NSInteger section = column / 7;
NSInteger item = column % 7 + row * 7;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[layoutAttributes addObject:itemAttributes];
UICollectionViewLayoutAttributes *rowSeparatorAttributes = [self layoutAttributesForDecorationViewOfKind:kFSCalendarSeparatorInterRows atIndexPath:indexPath];
if (rowSeparatorAttributes) {
[layoutAttributes addObject:rowSeparatorAttributes];
}
}
}
break;
}
case UICollectionViewScrollDirectionVertical: {
NSInteger startRow = ({
NSInteger startSection = rect.origin.y/self.collectionView.fs_height;
CGFloat heightDelta = FSCalendarMod(CGRectGetMinY(rect), self.collectionView.fs_height)-self.sectionInsets.top;
heightDelta = MIN(MAX(0, heightDelta), self.collectionView.fs_height-self.sectionInsets.top);
NSInteger countDelta = FSCalendarFloor(heightDelta/self.estimatedItemSize.height);
NSInteger startRow = startSection*6 + countDelta;
startRow;
});
NSInteger endRow = ({
NSInteger endRow;
CGFloat section = CGRectGetMaxY(rect)/self.collectionView.fs_height;
CGFloat remainder = FSCalendarMod(section, 1);
// https://stackoverflow.com/a/10335601/2398107
if (remainder <= MAX(100*FLT_EPSILON*ABS(remainder), FLT_MIN)) {
endRow = FSCalendarFloor(section)*6 - 1;
} else {
CGFloat heightDelta = FSCalendarMod(CGRectGetMaxY(rect), self.collectionView.fs_height)-self.sectionInsets.top;
heightDelta = MIN(MAX(0, heightDelta), self.collectionView.fs_height-self.sectionInsets.top);
NSInteger countDelta = FSCalendarCeil(heightDelta/self.estimatedItemSize.height);
endRow = FSCalendarFloor(section)*6 + countDelta-1;
}
endRow;
});
for (NSInteger row = startRow; row <= endRow; row++) {
for (NSInteger column = 0; column < 7; column++) {
NSInteger section = row / 6;
NSInteger item = column + (row % 6) * 7;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[layoutAttributes addObject:itemAttributes];
UICollectionViewLayoutAttributes *rowSeparatorAttributes = [self layoutAttributesForDecorationViewOfKind:kFSCalendarSeparatorInterRows atIndexPath:indexPath];
if (rowSeparatorAttributes) {
[layoutAttributes addObject:rowSeparatorAttributes];
}
}
}
break;
}
default:
break;
}
} else {
NSInteger startSection = [self searchStartSection:rect :0 :self.numberOfSections-1];
NSInteger startRowIndex = ({
CGFloat heightDelta1 = MIN(self.sectionBottoms[startSection]-CGRectGetMinY(rect)-self.sectionInsets.bottom, self.sectionRowCounts[startSection]*self.estimatedItemSize.height);
NSInteger startRowCount = FSCalendarCeil(heightDelta1/self.estimatedItemSize.height);
NSInteger startRowIndex = self.sectionRowCounts[startSection]-startRowCount;
startRowIndex;
});
NSInteger endSection = [self searchEndSection:rect :startSection :self.numberOfSections-1];
NSInteger endRowIndex = ({
CGFloat heightDelta2 = MAX(CGRectGetMaxY(rect) - self.sectionTops[endSection]- self.headerReferenceSize.height - self.sectionInsets.top, 0);
NSInteger endRowCount = FSCalendarCeil(heightDelta2/self.estimatedItemSize.height);
NSInteger endRowIndex = endRowCount - 1;
endRowIndex;
});
for (NSInteger section = startSection; section <= endSection; section++) {
NSInteger startRow = (section == startSection) ? startRowIndex : 0;
NSInteger endRow = (section == endSection) ? endRowIndex : self.sectionRowCounts[section]-1;
UICollectionViewLayoutAttributes *headerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
[layoutAttributes addObject:headerAttributes];
for (NSInteger row = startRow; row <= endRow; row++) {
for (NSInteger column = 0; column < 7; column++) {
NSInteger item = row * 7 + column;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[layoutAttributes addObject:itemAttributes];
UICollectionViewLayoutAttributes *rowSeparatorAttributes = [self layoutAttributesForDecorationViewOfKind:kFSCalendarSeparatorInterRows atIndexPath:indexPath];
if (rowSeparatorAttributes) {
[layoutAttributes addObject:rowSeparatorAttributes];
}
}
}
}
}
return [NSArray arrayWithArray:layoutAttributes];
}
// Items
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
FSCalendarCoordinate coordinate = [self.calendar.calculator coordinateForIndexPath:indexPath];
NSInteger column = coordinate.column;
NSInteger row = coordinate.row;
NSInteger numberOfRows = [self.calendar.calculator numberOfRowsInSection:indexPath.section];
UICollectionViewLayoutAttributes *attributes = self.itemAttributes[indexPath];
if (!attributes) {
attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect frame = ({
CGFloat width = self.widths[column];
CGFloat height = self.heights[row];
CGFloat x, y;
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
x = self.lefts[column] + indexPath.section * self.collectionView.fs_width;
y = [self calculateRowOffset:row totalRows:numberOfRows];
break;
}
case UICollectionViewScrollDirectionVertical: {
x = self.lefts[column];
if (!self.calendar.floatingMode) {
CGFloat sectionTop = indexPath.section * self.collectionView.fs_height;
CGFloat rowOffset = [self calculateRowOffset:row totalRows:numberOfRows];
y = sectionTop + rowOffset;
} else {
y = self.sectionTops[indexPath.section] + self.headerReferenceSize.height + self.tops[row];
}
break;
}
default:
break;
}
CGRect frame = CGRectMake(x, y, width, height);
frame;
});
attributes.frame = frame;
self.itemAttributes[indexPath] = attributes;
}
return attributes;
}
// Section headers
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) {
UICollectionViewLayoutAttributes *attributes = self.headerAttributes[indexPath];
if (!attributes) {
attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:indexPath];
attributes.frame = CGRectMake(0, self.sectionTops[indexPath.section], self.collectionView.fs_width, self.headerReferenceSize.height);
self.headerAttributes[indexPath] = attributes;
}
return attributes;
}
return nil;
}
// Separators
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if ([elementKind isEqualToString:kFSCalendarSeparatorInterRows] && (self.separators & FSCalendarSeparatorInterRows)) {
UICollectionViewLayoutAttributes *attributes = self.rowSeparatorAttributes[indexPath];
if (!attributes) {
FSCalendarCoordinate coordinate = [self.calendar.calculator coordinateForIndexPath:indexPath];
if (coordinate.row >= [self.calendar.calculator numberOfRowsInSection:indexPath.section]-1) {
return nil;
}
attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:kFSCalendarSeparatorInterRows withIndexPath:indexPath];
CGFloat x, y;
if (!self.calendar.floatingMode) {
CGFloat rowOffset = [self calculateRowOffset:coordinate.row totalRows:[self.calendar.calculator numberOfRowsInSection:indexPath.section]] + self.heights[coordinate.row];
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
x = self.lefts[coordinate.column] + indexPath.section * self.collectionView.fs_width;
y = rowOffset;
break;
}
case UICollectionViewScrollDirectionVertical: {
x = 0;
y = indexPath.section * self.collectionView.fs_height + rowOffset;
break;
}
default:
break;
}
} else {
x = 0;
y = self.sectionTops[indexPath.section] + self.headerReferenceSize.height + self.tops[coordinate.row] + self.heights[coordinate.row];
}
CGFloat width = self.collectionView.fs_width;
CGFloat height = FSCalendarStandardSeparatorThickness;
attributes.frame = CGRectMake(x, y, width, height);
attributes.zIndex = NSIntegerMax;
self.rowSeparatorAttributes[indexPath] = attributes;
}
return attributes;
}
return nil;
}
- (BOOL)flipsHorizontallyInOppositeLayoutDirection
{
return YES;
}
#pragma mark - Notifications
- (void)didReceiveNotifications:(NSNotification *)notification
{
if ([notification.name isEqualToString:UIDeviceOrientationDidChangeNotification]) {
[self invalidateLayout];
}
if ([notification.name isEqualToString:UIApplicationDidReceiveMemoryWarningNotification]) {
[self.itemAttributes removeAllObjects];
[self.headerAttributes removeAllObjects];
[self.rowSeparatorAttributes removeAllObjects];
}
}
#pragma mark - Private properties
- (void)setScrollDirection:(UICollectionViewScrollDirection)scrollDirection
{
if (_scrollDirection != scrollDirection) {
_scrollDirection = scrollDirection;
self.collectionViewSize = CGSizeAutomatic;
}
}
#pragma mark - Private functions
- (CGFloat)calculateRowOffset:(NSInteger)row totalRows:(NSInteger)totalRows
{
if (self.calendar.adjustsBoundingRectWhenChangingMonths) {
return self.tops[row];
}
CGFloat height = self.heights[row];
switch (totalRows) {
case 4:
case 5: {
CGFloat contentHeight = self.collectionView.fs_height - self.sectionInsets.top - self.sectionInsets.bottom;
CGFloat rowSpan = contentHeight/totalRows;
return (row + 0.5) * rowSpan - height * 0.5 + self.sectionInsets.top;
}
case 6:
default:
return self.tops[row];
}
}
- (NSInteger)searchStartSection:(CGRect)rect :(NSInteger)left :(NSInteger)right
{
NSInteger mid = left + (right-left)/2;
CGFloat y = rect.origin.y;
CGFloat minY = self.sectionTops[mid];
CGFloat maxY = self.sectionBottoms[mid];
if (y >= minY && y < maxY) {
return mid;
} else if (y < minY) {
return [self searchStartSection:rect :left :mid];
} else {
return [self searchStartSection:rect :mid+1 :right];
}
}
- (NSInteger)searchEndSection:(CGRect)rect :(NSInteger)left :(NSInteger)right
{
NSInteger mid = left + (right-left)/2;
CGFloat y = CGRectGetMaxY(rect);
CGFloat minY = self.sectionTops[mid];
CGFloat maxY = self.sectionBottoms[mid];
if (y > minY && y <= maxY) {
return mid;
} else if (y <= minY) {
return [self searchEndSection:rect :left :mid];
} else {
return [self searchEndSection:rect :mid+1 :right];
}
}
@end
#undef kFSCalendarSeparatorInterColumns
#undef kFSCalendarSeparatorInterRows
//
// FSCalendarConstane.h
// FSCalendar
//
// Created by dingwenchao on 8/28/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// https://github.com/WenchaoD
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#pragma mark - Constants
CG_EXTERN CGFloat const FSCalendarStandardHeaderHeight;
CG_EXTERN CGFloat const FSCalendarStandardWeekdayHeight;
CG_EXTERN CGFloat const FSCalendarStandardMonthlyPageHeight;
CG_EXTERN CGFloat const FSCalendarStandardWeeklyPageHeight;
CG_EXTERN CGFloat const FSCalendarStandardCellDiameter;
CG_EXTERN CGFloat const FSCalendarStandardSeparatorThickness;
CG_EXTERN CGFloat const FSCalendarAutomaticDimension;
CG_EXTERN CGFloat const FSCalendarDefaultBounceAnimationDuration;
CG_EXTERN CGFloat const FSCalendarStandardRowHeight;
CG_EXTERN CGFloat const FSCalendarStandardTitleTextSize;
CG_EXTERN CGFloat const FSCalendarStandardSubtitleTextSize;
CG_EXTERN CGFloat const FSCalendarStandardWeekdayTextSize;
CG_EXTERN CGFloat const FSCalendarStandardHeaderTextSize;
CG_EXTERN CGFloat const FSCalendarMaximumEventDotDiameter;
UIKIT_EXTERN NSInteger const FSCalendarDefaultHourComponent;
UIKIT_EXTERN NSInteger const FSCalendarMaximumNumberOfEvents;
UIKIT_EXTERN NSString * const FSCalendarDefaultCellReuseIdentifier;
UIKIT_EXTERN NSString * const FSCalendarBlankCellReuseIdentifier;
UIKIT_EXTERN NSString * const FSCalendarInvalidArgumentsExceptionName;
CG_EXTERN CGPoint const CGPointInfinity;
CG_EXTERN CGSize const CGSizeAutomatic;
#if TARGET_INTERFACE_BUILDER
#define FSCalendarDeviceIsIPad NO
#else
#define FSCalendarDeviceIsIPad [[UIDevice currentDevice].model hasPrefix:@"iPad"]
#endif
#define FSCalendarStandardSelectionColor FSColorRGBA(31,119,219,1.0)
#define FSCalendarStandardTodayColor FSColorRGBA(198,51,42 ,1.0)
#define FSCalendarStandardTitleTextColor FSColorRGBA(14,69,221 ,1.0)
#define FSCalendarStandardEventDotColor FSColorRGBA(31,119,219,0.75)
#define FSCalendarStandardLineColor [[UIColor lightGrayColor] colorWithAlphaComponent:0.30]
#define FSCalendarStandardSeparatorColor [[UIColor lightGrayColor] colorWithAlphaComponent:0.60]
#define FSColorRGBA(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]
#define FSCalendarInAppExtension [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]
#define FSCalendarFloor(c) floorf(c)
#define FSCalendarRound(c) roundf(c)
#define FSCalendarCeil(c) ceilf(c)
#define FSCalendarMod(c1,c2) fmodf(c1,c2)
#define FSCalendarHalfRound(c) (FSCalendarRound(c*2)*0.5)
#define FSCalendarHalfFloor(c) (FSCalendarFloor(c*2)*0.5)
#define FSCalendarHalfCeil(c) (FSCalendarCeil(c*2)*0.5)
#define FSCalendarUseWeakSelf __weak __typeof__(self) FSCalendarWeakSelf = self;
#define FSCalendarUseStrongSelf __strong __typeof__(self) self = FSCalendarWeakSelf;
#pragma mark - Deprecated
#define FSCalendarDeprecated(instead) DEPRECATED_MSG_ATTRIBUTE(" Use " # instead " instead")
static inline void FSCalendarSliceCake(CGFloat cake, NSInteger count, CGFloat *pieces) {
CGFloat total = cake;
for (int i = 0; i < count; i++) {
NSInteger remains = count - i;
CGFloat piece = FSCalendarRound(total/remains*2)*0.5;
total -= piece;
pieces[i] = piece;
}
}
//
// FSCalendarConstane.m
// FSCalendar
//
// Created by dingwenchao on 8/28/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// https://github.com/WenchaoD
//
#import "FSCalendarConstants.h"
CGFloat const FSCalendarStandardHeaderHeight = 40;
CGFloat const FSCalendarStandardWeekdayHeight = 25;
CGFloat const FSCalendarStandardMonthlyPageHeight = 300.0;
CGFloat const FSCalendarStandardWeeklyPageHeight = 108+1/3.0;
CGFloat const FSCalendarStandardCellDiameter = 100/3.0;
CGFloat const FSCalendarStandardSeparatorThickness = 0.5;
CGFloat const FSCalendarAutomaticDimension = -1;
CGFloat const FSCalendarDefaultBounceAnimationDuration = 0.15;
CGFloat const FSCalendarStandardRowHeight = 38;
CGFloat const FSCalendarStandardTitleTextSize = 13.5;
CGFloat const FSCalendarStandardSubtitleTextSize = 10;
CGFloat const FSCalendarStandardWeekdayTextSize = 14;
CGFloat const FSCalendarStandardHeaderTextSize = 16.5;
CGFloat const FSCalendarMaximumEventDotDiameter = 4.8;
NSInteger const FSCalendarDefaultHourComponent = 0;
NSInteger const FSCalendarMaximumNumberOfEvents = 3;
NSString * const FSCalendarDefaultCellReuseIdentifier = @"_FSCalendarDefaultCellReuseIdentifier";
NSString * const FSCalendarBlankCellReuseIdentifier = @"_FSCalendarBlankCellReuseIdentifier";
NSString * const FSCalendarInvalidArgumentsExceptionName = @"Invalid argument exception";
CGPoint const CGPointInfinity = {
.x = CGFLOAT_MAX,
.y = CGFLOAT_MAX
};
CGSize const CGSizeAutomatic = {
.width = FSCalendarAutomaticDimension,
.height = FSCalendarAutomaticDimension
};
//
// FSCalendarDelegationFactory.h
// FSCalendar
//
// Created by dingwenchao on 19/12/2016.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FSCalendarDelegationProxy.h"
@interface FSCalendarDelegationFactory : NSObject
+ (FSCalendarDelegationProxy *)dataSourceProxy;
+ (FSCalendarDelegationProxy *)delegateProxy;
@end
//
// FSCalendarDelegationFactory.m
// FSCalendar
//
// Created by dingwenchao on 19/12/2016.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import "FSCalendarDelegationFactory.h"
@implementation FSCalendarDelegationFactory
+ (FSCalendarDelegationProxy *)dataSourceProxy
{
FSCalendarDelegationProxy *delegation = [[FSCalendarDelegationProxy alloc] init];
delegation.protocol = @protocol(FSCalendarDataSource);
return delegation;
}
+ (FSCalendarDelegationProxy *)delegateProxy
{
FSCalendarDelegationProxy *delegation = [[FSCalendarDelegationProxy alloc] init];
delegation.protocol = @protocol(FSCalendarDelegateAppearance);
return delegation;
}
@end
#undef FSCalendarSelectorEntry
//
// FSCalendarDelegationProxy.h
// FSCalendar
//
// Created by dingwenchao on 11/12/2016.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// https://github.com/WenchaoD
//
// 1. Smart proxy delegation http://petersteinberger.com/blog/2013/smart-proxy-delegation/
// 2. Manage deprecated delegation functions
//
#import <Foundation/Foundation.h>
#import "FSCalendar.h"
NS_ASSUME_NONNULL_BEGIN
@interface FSCalendarDelegationProxy : NSProxy
@property (weak , nonatomic) id delegation;
@property (strong, nonatomic) Protocol *protocol;
@property (strong, nonatomic) NSDictionary<NSString *,NSString *> *deprecations;
- (instancetype)init;
- (SEL)deprecatedSelectorOfSelector:(SEL)selector;
@end
NS_ASSUME_NONNULL_END
//
// FSCalendarDelegationProxy.m
// FSCalendar
//
// Created by dingwenchao on 11/12/2016.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarDelegationProxy.h"
#import <objc/runtime.h>
@implementation FSCalendarDelegationProxy
- (instancetype)init
{
return self;
}
- (BOOL)respondsToSelector:(SEL)selector
{
BOOL responds = [self.delegation respondsToSelector:selector];
if (!responds) responds = [self.delegation respondsToSelector:[self deprecatedSelectorOfSelector:selector]];
if (!responds) responds = [super respondsToSelector:selector];
return responds;
}
- (BOOL)conformsToProtocol:(Protocol *)protocol
{
return [self.delegation conformsToProtocol:protocol];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL selector = invocation.selector;
if (![self.delegation respondsToSelector:selector]) {
selector = [self deprecatedSelectorOfSelector:selector];
invocation.selector = selector;
}
if ([self.delegation respondsToSelector:selector]) {
[invocation invokeWithTarget:self.delegation];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
if ([self.delegation respondsToSelector:sel]) {
return [(NSObject *)self.delegation methodSignatureForSelector:sel];
}
SEL selector = [self deprecatedSelectorOfSelector:sel];
if ([self.delegation respondsToSelector:selector]) {
return [(NSObject *)self.delegation methodSignatureForSelector:selector];
}
#if TARGET_INTERFACE_BUILDER
return [NSObject methodSignatureForSelector:@selector(init)];
#else
struct objc_method_description desc = protocol_getMethodDescription(self.protocol, sel, NO, YES);
const char *types = desc.types;
return types?[NSMethodSignature signatureWithObjCTypes:types]:[NSObject methodSignatureForSelector:@selector(init)];
#endif
}
- (SEL)deprecatedSelectorOfSelector:(SEL)selector
{
NSString *selectorString = NSStringFromSelector(selector);
selectorString = self.deprecations[selectorString];
return NSSelectorFromString(selectorString);
}
@end
//
// FSCalendarDynamicHeader.h
// Pods
//
// Created by DingWenchao on 6/29/15.
//
// 动感头文件,仅供框架内部使用。
// Private header, don't use it.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "FSCalendar.h"
#import "FSCalendarCell.h"
#import "FSCalendarHeaderView.h"
#import "FSCalendarStickyHeader.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarCollectionViewLayout.h"
#import "FSCalendarCalculator.h"
#import "FSCalendarTransitionCoordinator.h"
#import "FSCalendarDelegationProxy.h"
@interface FSCalendar (Dynamic)
@property (readonly, nonatomic) FSCalendarCollectionView *collectionView;
@property (readonly, nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout;
@property (readonly, nonatomic) FSCalendarTransitionCoordinator *transitionCoordinator;
@property (readonly, nonatomic) FSCalendarCalculator *calculator;
@property (readonly, nonatomic) BOOL floatingMode;
@property (readonly, nonatomic) NSArray *visibleStickyHeaders;
@property (readonly, nonatomic) CGFloat preferredHeaderHeight;
@property (readonly, nonatomic) CGFloat preferredWeekdayHeight;
@property (readonly, nonatomic) NSCalendar *gregorian;
@property (readonly, nonatomic) NSDateFormatter *formatter;
@property (readonly, nonatomic) UIView *contentView;
@property (readonly, nonatomic) UIView *daysContainer;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
- (void)adjustMonthPosition;
- (void)configureAppearance;
- (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope;
@end
@interface FSCalendarAppearance (Dynamic)
@property (readwrite, nonatomic) FSCalendar *calendar;
@property (readonly, nonatomic) NSDictionary *backgroundColors;
@property (readonly, nonatomic) NSDictionary *titleColors;
@property (readonly, nonatomic) NSDictionary *subtitleColors;
@property (readonly, nonatomic) NSDictionary *borderColors;
@end
@interface FSCalendarWeekdayView (Dynamic)
@property (readwrite, nonatomic) FSCalendar *calendar;
@end
@interface FSCalendarCollectionViewLayout (Dynamic)
@property (readonly, nonatomic) CGSize estimatedItemSize;
@end
@interface FSCalendarDelegationProxy()<FSCalendarDataSource,FSCalendarDelegate,FSCalendarDelegateAppearance>
@end
//
// FSCalendarExtensions.h
// FSCalendar
//
// Created by dingwenchao on 10/8/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIView (FSCalendarExtensions)
@property (nonatomic) CGFloat fs_width;
@property (nonatomic) CGFloat fs_height;
@property (nonatomic) CGFloat fs_top;
@property (nonatomic) CGFloat fs_left;
@property (nonatomic) CGFloat fs_bottom;
@property (nonatomic) CGFloat fs_right;
@end
@interface CALayer (FSCalendarExtensions)
@property (nonatomic) CGFloat fs_width;
@property (nonatomic) CGFloat fs_height;
@property (nonatomic) CGFloat fs_top;
@property (nonatomic) CGFloat fs_left;
@property (nonatomic) CGFloat fs_bottom;
@property (nonatomic) CGFloat fs_right;
@end
@interface NSCalendar (FSCalendarExtensions)
- (nullable NSDate *)fs_firstDayOfMonth:(NSDate *)month;
- (nullable NSDate *)fs_lastDayOfMonth:(NSDate *)month;
- (nullable NSDate *)fs_firstDayOfWeek:(NSDate *)week;
- (nullable NSDate *)fs_lastDayOfWeek:(NSDate *)week;
- (nullable NSDate *)fs_middleDayOfWeek:(NSDate *)week;
- (NSInteger)fs_numberOfDaysInMonth:(NSDate *)month;
@end
@interface NSMapTable (FSCalendarExtensions)
- (void)setObject:(nullable id)obj forKeyedSubscript:(id<NSCopying>)key;
- (id)objectForKeyedSubscript:(id<NSCopying>)key;
@end
@interface NSCache (FSCalendarExtensions)
- (void)setObject:(nullable id)obj forKeyedSubscript:(id<NSCopying>)key;
- (id)objectForKeyedSubscript:(id<NSCopying>)key;
@end
@interface NSObject (FSCalendarExtensions)
#define IVAR_DEF(SET,GET,TYPE) \
- (void)fs_set##SET##Variable:(TYPE)value forKey:(NSString *)key; \
- (TYPE)fs_##GET##VariableForKey:(NSString *)key;
IVAR_DEF(Bool, bool, BOOL)
IVAR_DEF(Float, float, CGFloat)
IVAR_DEF(Integer, integer, NSInteger)
IVAR_DEF(UnsignedInteger, unsignedInteger, NSUInteger)
#undef IVAR_DEF
- (void)fs_setVariable:(id)variable forKey:(NSString *)key;
- (id)fs_variableForKey:(NSString *)key;
- (nullable id)fs_performSelector:(SEL)selector withObjects:(nullable id)firstObject, ... NS_REQUIRES_NIL_TERMINATION;
@end
NS_ASSUME_NONNULL_END
//
// FSCalendarExtensions.m
// FSCalendar
//
// Created by dingwenchao on 10/8/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarExtensions.h"
#import <objc/runtime.h>
@implementation UIView (FSCalendarExtensions)
- (CGFloat)fs_width
{
return CGRectGetWidth(self.frame);
}
- (void)setFs_width:(CGFloat)fs_width
{
self.frame = CGRectMake(self.fs_left, self.fs_top, fs_width, self.fs_height);
}
- (CGFloat)fs_height
{
return CGRectGetHeight(self.frame);
}
- (void)setFs_height:(CGFloat)fs_height
{
self.frame = CGRectMake(self.fs_left, self.fs_top, self.fs_width, fs_height);
}
- (CGFloat)fs_top
{
return CGRectGetMinY(self.frame);
}
- (void)setFs_top:(CGFloat)fs_top
{
self.frame = CGRectMake(self.fs_left, fs_top, self.fs_width, self.fs_height);
}
- (CGFloat)fs_bottom
{
return CGRectGetMaxY(self.frame);
}
- (void)setFs_bottom:(CGFloat)fs_bottom
{
self.fs_top = fs_bottom - self.fs_height;
}
- (CGFloat)fs_left
{
return CGRectGetMinX(self.frame);
}
- (void)setFs_left:(CGFloat)fs_left
{
self.frame = CGRectMake(fs_left, self.fs_top, self.fs_width, self.fs_height);
}
- (CGFloat)fs_right
{
return CGRectGetMaxX(self.frame);
}
- (void)setFs_right:(CGFloat)fs_right
{
self.fs_left = self.fs_right - self.fs_width;
}
@end
@implementation CALayer (FSCalendarExtensions)
- (CGFloat)fs_width
{
return CGRectGetWidth(self.frame);
}
- (void)setFs_width:(CGFloat)fs_width
{
self.frame = CGRectMake(self.fs_left, self.fs_top, fs_width, self.fs_height);
}
- (CGFloat)fs_height
{
return CGRectGetHeight(self.frame);
}
- (void)setFs_height:(CGFloat)fs_height
{
self.frame = CGRectMake(self.fs_left, self.fs_top, self.fs_width, fs_height);
}
- (CGFloat)fs_top
{
return CGRectGetMinY(self.frame);
}
- (void)setFs_top:(CGFloat)fs_top
{
self.frame = CGRectMake(self.fs_left, fs_top, self.fs_width, self.fs_height);
}
- (CGFloat)fs_bottom
{
return CGRectGetMaxY(self.frame);
}
- (void)setFs_bottom:(CGFloat)fs_bottom
{
self.fs_top = fs_bottom - self.fs_height;
}
- (CGFloat)fs_left
{
return CGRectGetMinX(self.frame);
}
- (void)setFs_left:(CGFloat)fs_left
{
self.frame = CGRectMake(fs_left, self.fs_top, self.fs_width, self.fs_height);
}
- (CGFloat)fs_right
{
return CGRectGetMaxX(self.frame);
}
- (void)setFs_right:(CGFloat)fs_right
{
self.fs_left = self.fs_right - self.fs_width;
}
@end
@interface NSCalendar (FSCalendarExtensionsPrivate)
@property (readonly, nonatomic) NSDateComponents *fs_privateComponents;
@end
@implementation NSCalendar (FSCalendarExtensions)
- (nullable NSDate *)fs_firstDayOfMonth:(NSDate *)month
{
if (!month) return nil;
NSDateComponents *components = [self components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour fromDate:month];
components.day = 1;
return [self dateFromComponents:components];
}
- (nullable NSDate *)fs_lastDayOfMonth:(NSDate *)month
{
if (!month) return nil;
NSDateComponents *components = [self components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour fromDate:month];
components.month++;
components.day = 0;
return [self dateFromComponents:components];
}
- (nullable NSDate *)fs_firstDayOfWeek:(NSDate *)week
{
if (!week) return nil;
NSDateComponents *weekdayComponents = [self components:NSCalendarUnitWeekday fromDate:week];
NSDateComponents *components = self.fs_privateComponents;
components.day = - (weekdayComponents.weekday - self.firstWeekday);
components.day = (components.day-7) % 7;
NSDate *firstDayOfWeek = [self dateByAddingComponents:components toDate:week options:0];
firstDayOfWeek = [self dateBySettingHour:0 minute:0 second:0 ofDate:firstDayOfWeek options:0];
components.day = NSIntegerMax;
return firstDayOfWeek;
}
- (nullable NSDate *)fs_lastDayOfWeek:(NSDate *)week
{
if (!week) return nil;
NSDateComponents *weekdayComponents = [self components:NSCalendarUnitWeekday fromDate:week];
NSDateComponents *components = self.fs_privateComponents;
components.day = - (weekdayComponents.weekday - self.firstWeekday);
components.day = (components.day-7) % 7 + 6;
NSDate *lastDayOfWeek = [self dateByAddingComponents:components toDate:week options:0];
lastDayOfWeek = [self dateBySettingHour:0 minute:0 second:0 ofDate:lastDayOfWeek options:0];
components.day = NSIntegerMax;
return lastDayOfWeek;
}
- (nullable NSDate *)fs_middleDayOfWeek:(NSDate *)week
{
if (!week) return nil;
NSDateComponents *weekdayComponents = [self components:NSCalendarUnitWeekday fromDate:week];
NSDateComponents *componentsToSubtract = self.fs_privateComponents;
componentsToSubtract.day = - (weekdayComponents.weekday - self.firstWeekday) + 3;
// Fix https://github.com/WenchaoD/FSCalendar/issues/1100 and https://github.com/WenchaoD/FSCalendar/issues/1102
// If firstWeekday is not 1, and weekday is less than firstWeekday, the middleDayOfWeek will be the middle day of next week
if (weekdayComponents.weekday < self.firstWeekday) {
componentsToSubtract.day = componentsToSubtract.day - 7;
}
NSDate *middleDayOfWeek = [self dateByAddingComponents:componentsToSubtract toDate:week options:0];
NSDateComponents *components = [self components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour fromDate:middleDayOfWeek];
middleDayOfWeek = [self dateFromComponents:components];
componentsToSubtract.day = NSIntegerMax;
return middleDayOfWeek;
}
- (NSInteger)fs_numberOfDaysInMonth:(NSDate *)month
{
if (!month) return 0;
NSRange days = [self rangeOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitMonth
forDate:month];
return days.length;
}
- (NSDateComponents *)fs_privateComponents
{
NSDateComponents *components = objc_getAssociatedObject(self, _cmd);
if (!components) {
components = [[NSDateComponents alloc] init];
objc_setAssociatedObject(self, _cmd, components, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return components;
}
@end
@implementation NSMapTable (FSCalendarExtensions)
- (void)setObject:(nullable id)obj forKeyedSubscript:(id<NSCopying>)key
{
if (!key) return;
if (obj) {
[self setObject:obj forKey:key];
} else {
[self removeObjectForKey:key];
}
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key
{
return [self objectForKey:key];
}
@end
@implementation NSCache (FSCalendarExtensions)
- (void)setObject:(nullable id)obj forKeyedSubscript:(id<NSCopying>)key
{
if (!key) return;
if (obj) {
[self setObject:obj forKey:key];
} else {
[self removeObjectForKey:key];
}
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key
{
return [self objectForKey:key];
}
@end
@implementation NSObject (FSCalendarExtensions)
#define IVAR_IMP(SET,GET,TYPE) \
- (void)fs_set##SET##Variable:(TYPE)value forKey:(NSString *)key \
{ \
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); \
((void (*)(id, Ivar, TYPE))object_setIvar)(self, ivar, value); \
} \
- (TYPE)fs_##GET##VariableForKey:(NSString *)key \
{ \
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String); \
ptrdiff_t offset = ivar_getOffset(ivar); \
unsigned char *bytes = (unsigned char *)(__bridge void *)self; \
TYPE value = *((TYPE *)(bytes+offset)); \
return value; \
}
IVAR_IMP(Bool,bool,BOOL)
IVAR_IMP(Float,float,CGFloat)
IVAR_IMP(Integer,integer,NSInteger)
IVAR_IMP(UnsignedInteger,unsignedInteger,NSUInteger)
#undef IVAR_IMP
- (void)fs_setVariable:(id)variable forKey:(NSString *)key
{
Ivar ivar = class_getInstanceVariable(self.class, key.UTF8String);
object_setIvar(self, ivar, variable);
}
- (id)fs_variableForKey:(NSString *)key
{
Ivar ivar = class_getInstanceVariable(self.class, key.UTF8String);
id variable = object_getIvar(self, ivar);
return variable;
}
- (id)fs_performSelector:(SEL)selector withObjects:(nullable id)firstObject, ...
{
if (!selector) return nil;
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
if (!signature) return nil;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
if (!invocation) return nil;
invocation.target = self;
invocation.selector = selector;
// Parameters
if (firstObject) {
int index = 2;
va_list args;
va_start(args, firstObject);
if (firstObject) {
id obj = firstObject;
do {
const char *argType = [signature getArgumentTypeAtIndex:index];
if(!strcmp(argType, @encode(id))){
// object
[invocation setArgument:&obj atIndex:index++];
} else {
NSString *argTypeString = [NSString stringWithUTF8String:argType];
if ([argTypeString hasPrefix:@"{"] && [argTypeString hasSuffix:@"}"]) {
// struct
#define PARAM_STRUCT_TYPES(_type,_getter,_default) \
if (!strcmp(argType, @encode(_type))) { \
_type value = [obj respondsToSelector:@selector(_getter)]?[obj _getter]:_default; \
[invocation setArgument:&value atIndex:index]; \
}
PARAM_STRUCT_TYPES(CGPoint, CGPointValue, CGPointZero)
PARAM_STRUCT_TYPES(CGSize, CGSizeValue, CGSizeZero)
PARAM_STRUCT_TYPES(CGRect, CGRectValue, CGRectZero)
PARAM_STRUCT_TYPES(CGAffineTransform, CGAffineTransformValue, CGAffineTransformIdentity)
PARAM_STRUCT_TYPES(CATransform3D, CATransform3DValue, CATransform3DIdentity)
PARAM_STRUCT_TYPES(CGVector, CGVectorValue, CGVectorMake(0, 0))
PARAM_STRUCT_TYPES(UIEdgeInsets, UIEdgeInsetsValue, UIEdgeInsetsZero)
PARAM_STRUCT_TYPES(UIOffset, UIOffsetValue, UIOffsetZero)
PARAM_STRUCT_TYPES(NSRange, rangeValue, NSMakeRange(NSNotFound, 0))
#undef PARAM_STRUCT_TYPES
index++;
} else {
// basic type
#define PARAM_BASIC_TYPES(_type,_getter) \
if (!strcmp(argType, @encode(_type))) { \
_type value = [obj respondsToSelector:@selector(_getter)]?[obj _getter]:0; \
[invocation setArgument:&value atIndex:index]; \
}
PARAM_BASIC_TYPES(BOOL, boolValue)
PARAM_BASIC_TYPES(int, intValue)
PARAM_BASIC_TYPES(unsigned int, unsignedIntValue)
PARAM_BASIC_TYPES(char, charValue)
PARAM_BASIC_TYPES(unsigned char, unsignedCharValue)
PARAM_BASIC_TYPES(long, longValue)
PARAM_BASIC_TYPES(unsigned long, unsignedLongValue)
PARAM_BASIC_TYPES(long long, longLongValue)
PARAM_BASIC_TYPES(unsigned long long, unsignedLongLongValue)
PARAM_BASIC_TYPES(float, floatValue)
PARAM_BASIC_TYPES(double, doubleValue)
#undef PARAM_BASIC_TYPES
index++;
}
}
} while((obj = va_arg(args, id)));
}
va_end(args);
[invocation retainArguments];
}
// Execute
[invocation invoke];
// Return
const char *returnType = signature.methodReturnType;
NSUInteger length = [signature methodReturnLength];
id returnValue;
if (!strcmp(returnType, @encode(void))){
// void
returnValue = nil;
} else if(!strcmp(returnType, @encode(id))){
// id
void *value;
[invocation getReturnValue:&value];
returnValue = (__bridge id)(value);
return returnValue;
} else {
NSString *returnTypeString = [NSString stringWithUTF8String:returnType];
if ([returnTypeString hasPrefix:@"{"] && [returnTypeString hasSuffix:@"}"]) {
// struct
#define RETURN_STRUCT_TYPES(_type) \
if (!strcmp(returnType, @encode(_type))) { \
_type value; \
[invocation getReturnValue:&value]; \
returnValue = [NSValue value:&value withObjCType:@encode(_type)]; \
}
RETURN_STRUCT_TYPES(CGPoint)
RETURN_STRUCT_TYPES(CGSize)
RETURN_STRUCT_TYPES(CGRect)
RETURN_STRUCT_TYPES(CGAffineTransform)
RETURN_STRUCT_TYPES(CATransform3D)
RETURN_STRUCT_TYPES(CGVector)
RETURN_STRUCT_TYPES(UIEdgeInsets)
RETURN_STRUCT_TYPES(UIOffset)
RETURN_STRUCT_TYPES(NSRange)
#undef RETURN_STRUCT_TYPES
} else {
// basic
void *buffer = (void *)malloc(length);
[invocation getReturnValue:buffer];
#define RETURN_BASIC_TYPES(_type) \
if (!strcmp(returnType, @encode(_type))) { \
returnValue = @(*((_type*)buffer)); \
}
RETURN_BASIC_TYPES(BOOL)
RETURN_BASIC_TYPES(int)
RETURN_BASIC_TYPES(unsigned int)
RETURN_BASIC_TYPES(char)
RETURN_BASIC_TYPES(unsigned char)
RETURN_BASIC_TYPES(long)
RETURN_BASIC_TYPES(unsigned long)
RETURN_BASIC_TYPES(long long)
RETURN_BASIC_TYPES(unsigned long long)
RETURN_BASIC_TYPES(float)
RETURN_BASIC_TYPES(double)
#undef RETURN_BASIC_TYPES
free(buffer);
}
}
return returnValue;
}
@end
//
// FSCalendarHeader.h
// Pods
//
// Created by Wenchao Ding on 29/1/15.
//
//
#import <UIKit/UIKit.h>
@class FSCalendar, FSCalendarAppearance, FSCalendarHeaderLayout, FSCalendarCollectionView;
@interface FSCalendarHeaderView : UIView
@property (weak, nonatomic) FSCalendarCollectionView *collectionView;
@property (weak, nonatomic) FSCalendarHeaderLayout *collectionViewLayout;
@property (weak, nonatomic) FSCalendar *calendar;
@property (assign, nonatomic) UICollectionViewScrollDirection scrollDirection;
@property (assign, nonatomic) BOOL scrollEnabled;
- (void)setScrollOffset:(CGFloat)scrollOffset;
- (void)setScrollOffset:(CGFloat)scrollOffset animated:(BOOL)animated;
- (void)reloadData;
- (void)configureAppearance;
@end
@interface FSCalendarHeaderCell : UICollectionViewCell
@property (weak, nonatomic) UILabel *titleLabel;
@property (weak, nonatomic) FSCalendarHeaderView *header;
@end
@interface FSCalendarHeaderLayout : UICollectionViewFlowLayout
@end
@interface FSCalendarHeaderTouchDeliver : UIView
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarHeaderView *header;
@end
//
// FSCalendarHeader.m
// Pods
//
// Created by Wenchao Ding on 29/1/15.
//
//
#import "FSCalendar.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarHeaderView.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarDynamicHeader.h"
@interface FSCalendarHeaderView ()<UICollectionViewDataSource,UICollectionViewDelegate,FSCalendarCollectionViewInternalDelegate>
- (void)scrollToOffset:(CGFloat)scrollOffset animated:(BOOL)animated;
- (void)configureCell:(FSCalendarHeaderCell *)cell atIndexPath:(NSIndexPath *)indexPath;
@end
@implementation FSCalendarHeaderView
#pragma mark - Life cycle
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initialize];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initialize];
}
return self;
}
- (void)initialize
{
_scrollDirection = UICollectionViewScrollDirectionHorizontal;
_scrollEnabled = YES;
FSCalendarHeaderLayout *collectionViewLayout = [[FSCalendarHeaderLayout alloc] init];
self.collectionViewLayout = collectionViewLayout;
FSCalendarCollectionView *collectionView = [[FSCalendarCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:collectionViewLayout];
collectionView.scrollEnabled = NO;
collectionView.userInteractionEnabled = NO;
collectionView.backgroundColor = [UIColor clearColor];
collectionView.dataSource = self;
collectionView.delegate = self;
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
[self addSubview:collectionView];
[collectionView registerClass:[FSCalendarHeaderCell class] forCellWithReuseIdentifier:@"cell"];
self.collectionView = collectionView;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.collectionView.frame = CGRectMake(0, self.fs_height*0.1, self.fs_width, self.fs_height*0.9);
}
- (void)dealloc
{
self.collectionView.dataSource = nil;
self.collectionView.delegate = nil;
}
#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSInteger numberOfSections = self.calendar.collectionView.numberOfSections;
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
return numberOfSections;
}
return numberOfSections + 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
FSCalendarHeaderCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.header = self;
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[_collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
#pragma mark - Properties
- (void)setCalendar:(FSCalendar *)calendar
{
_calendar = calendar;
[self configureAppearance];
}
- (void)setScrollOffset:(CGFloat)scrollOffset
{
[self setScrollOffset:scrollOffset animated:NO];
}
- (void)setScrollOffset:(CGFloat)scrollOffset animated:(BOOL)animated
{
[self scrollToOffset:scrollOffset animated:NO];
}
- (void)scrollToOffset:(CGFloat)scrollOffset animated:(BOOL)animated
{
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
CGFloat step = self.collectionView.fs_width*((self.scrollDirection==UICollectionViewScrollDirectionHorizontal)?0.5:1);
[_collectionView setContentOffset:CGPointMake((scrollOffset+0.5)*step, 0) animated:animated];
} else {
CGFloat step = self.collectionView.fs_height;
[_collectionView setContentOffset:CGPointMake(0, scrollOffset*step) animated:animated];
}
}
- (void)setScrollDirection:(UICollectionViewScrollDirection)scrollDirection
{
if (_scrollDirection != scrollDirection) {
_scrollDirection = scrollDirection;
_collectionViewLayout.scrollDirection = scrollDirection;
[self setNeedsLayout];
}
}
- (void)setScrollEnabled:(BOOL)scrollEnabled
{
if (_scrollEnabled != scrollEnabled) {
_scrollEnabled = scrollEnabled;
[_collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
#pragma mark - Public
- (void)reloadData
{
[_collectionView reloadData];
}
- (void)configureCell:(FSCalendarHeaderCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
FSCalendarAppearance *appearance = self.calendar.appearance;
cell.titleLabel.font = appearance.headerTitleFont;
cell.titleLabel.textColor = appearance.headerTitleColor;
_calendar.formatter.dateFormat = appearance.headerDateFormat;
BOOL usesUpperCase = (appearance.caseOptions & 15) == FSCalendarCaseOptionsHeaderUsesUpperCase;
NSString *text = nil;
switch (self.calendar.transitionCoordinator.representingScope) {
case FSCalendarScopeMonth: {
if (_scrollDirection == UICollectionViewScrollDirectionHorizontal) {
// 多出的两项需要制空
if ((indexPath.item == 0 || indexPath.item == [self.collectionView numberOfItemsInSection:0] - 1)) {
text = nil;
} else {
NSDate *date = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitMonth value:indexPath.item-1 toDate:self.calendar.minimumDate options:0];
text = [_calendar.formatter stringFromDate:date];
}
} else {
NSDate *date = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitMonth value:indexPath.item toDate:self.calendar.minimumDate options:0];
text = [_calendar.formatter stringFromDate:date];
}
break;
}
case FSCalendarScopeWeek: {
if ((indexPath.item == 0 || indexPath.item == [self.collectionView numberOfItemsInSection:0] - 1)) {
text = nil;
} else {
NSDate *firstPage = [self.calendar.gregorian fs_middleDayOfWeek:self.calendar.minimumDate];
NSDate *date = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitWeekOfYear value:indexPath.item-1 toDate:firstPage options:0];
text = [_calendar.formatter stringFromDate:date];
}
break;
}
default: {
break;
}
}
text = usesUpperCase ? text.uppercaseString : text;
cell.titleLabel.text = text;
[cell setNeedsLayout];
}
- (void)configureAppearance
{
[self.collectionView.visibleCells enumerateObjectsUsingBlock:^(__kindof FSCalendarHeaderCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) {
[self configureCell:cell atIndexPath:[self.collectionView indexPathForCell:cell]];
}];
}
@end
@implementation FSCalendarHeaderCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
titleLabel.numberOfLines = 0;
[self.contentView addSubview:titleLabel];
self.titleLabel = titleLabel;
}
return self;
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
self.titleLabel.frame = bounds;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.titleLabel.frame = self.contentView.bounds;
if (self.header.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
CGFloat position = [self.contentView convertPoint:CGPointMake(CGRectGetMidX(self.contentView.bounds), CGRectGetMidY(self.contentView.bounds)) toView:self.header].x;
CGFloat center = CGRectGetMidX(self.header.bounds);
if (self.header.scrollEnabled) {
self.contentView.alpha = 1.0 - (1.0-self.header.calendar.appearance.headerMinimumDissolvedAlpha)*ABS(center-position)/self.fs_width;
} else {
self.contentView.alpha = (position > self.header.fs_width*0.25 && position < self.header.fs_width*0.75);
}
} else if (self.header.scrollDirection == UICollectionViewScrollDirectionVertical) {
CGFloat position = [self.contentView convertPoint:CGPointMake(CGRectGetMidX(self.contentView.bounds), CGRectGetMidY(self.contentView.bounds)) toView:self.header].y;
CGFloat center = CGRectGetMidY(self.header.bounds);
self.contentView.alpha = 1.0 - (1.0-self.header.calendar.appearance.headerMinimumDissolvedAlpha)*ABS(center-position)/self.fs_height;
}
}
@end
@implementation FSCalendarHeaderLayout
- (instancetype)init
{
self = [super init];
if (self) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumInteritemSpacing = 0;
self.minimumLineSpacing = 0;
self.sectionInset = UIEdgeInsetsZero;
self.itemSize = CGSizeMake(1, 1);
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(didReceiveOrientationChangeNotification:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
return self;
}
- (void)dealloc
{
[NSNotificationCenter.defaultCenter removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)prepareLayout
{
[super prepareLayout];
self.itemSize = CGSizeMake(
self.collectionView.fs_width*((self.scrollDirection==UICollectionViewScrollDirectionHorizontal)?0.5:1),
self.collectionView.fs_height
);
}
- (void)didReceiveOrientationChangeNotification:(NSNotification *)notificatino
{
[self invalidateLayout];
}
- (BOOL)flipsHorizontallyInOppositeLayoutDirection
{
return YES;
}
@end
@implementation FSCalendarHeaderTouchDeliver
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self) {
return _calendar.collectionView ?: hitView;
}
return hitView;
}
@end
//
// FSCalendarSeparatorDecorationView.h
// FSCalendar
//
// Created by 丁文超 on 2018/10/10.
// Copyright © 2018 wenchaoios. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FSCalendarSeparatorDecorationView : UICollectionReusableView
@end
NS_ASSUME_NONNULL_END
//
// FSCalendarSeparatorDecorationView.m
// FSCalendar
//
// Created by 丁文超 on 2018/10/10.
// Copyright © 2018 wenchaoios. All rights reserved.
//
#import "FSCalendarSeparatorDecorationView.h"
#import "FSCalendarConstants.h"
@implementation FSCalendarSeparatorDecorationView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = FSCalendarStandardSeparatorColor;
}
return self;
}
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
self.frame = layoutAttributes.frame;
}
@end
//
// FSCalendarStaticHeader.h
// FSCalendar
//
// Created by dingwenchao on 9/17/15.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FSCalendar,FSCalendarAppearance;
@interface FSCalendarStickyHeader : UICollectionReusableView
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) NSDate *month;
- (void)configureAppearance;
@end
//
// FSCalendarStaticHeader.m
// FSCalendar
//
// Created by dingwenchao on 9/17/15.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
#import "FSCalendarStickyHeader.h"
#import "FSCalendar.h"
#import "FSCalendarWeekdayView.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarConstants.h"
#import "FSCalendarDynamicHeader.h"
@interface FSCalendarStickyHeader ()
@property (weak , nonatomic) UIView *contentView;
@property (weak , nonatomic) UIView *bottomBorder;
@property (weak , nonatomic) FSCalendarWeekdayView *weekdayView;
@end
@implementation FSCalendarStickyHeader
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIView *view;
UILabel *label;
view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = [UIColor clearColor];
[self addSubview:view];
self.contentView = view;
label = [[UILabel alloc] initWithFrame:CGRectZero];
label.textAlignment = NSTextAlignmentCenter;
label.numberOfLines = 0;
[_contentView addSubview:label];
self.titleLabel = label;
view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = FSCalendarStandardLineColor;
[_contentView addSubview:view];
self.bottomBorder = view;
FSCalendarWeekdayView *weekdayView = [[FSCalendarWeekdayView alloc] init];
[self.contentView addSubview:weekdayView];
self.weekdayView = weekdayView;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
_contentView.frame = self.bounds;
CGFloat weekdayHeight = _calendar.preferredWeekdayHeight;
CGFloat weekdayMargin = weekdayHeight * 0.1;
CGFloat titleWidth = _contentView.fs_width;
self.weekdayView.frame = CGRectMake(0, _contentView.fs_height-weekdayHeight-weekdayMargin, self.contentView.fs_width, weekdayHeight);
CGFloat titleHeight = [@"1" sizeWithAttributes:@{NSFontAttributeName:self.calendar.appearance.headerTitleFont}].height*1.5 + weekdayMargin*3;
_bottomBorder.frame = CGRectMake(0, _contentView.fs_height-weekdayHeight-weekdayMargin*2, _contentView.fs_width, 1.0);
_titleLabel.frame = CGRectMake(0, _bottomBorder.fs_bottom-titleHeight-weekdayMargin, titleWidth,titleHeight);
}
#pragma mark - Properties
- (void)setCalendar:(FSCalendar *)calendar
{
if (![_calendar isEqual:calendar]) {
_calendar = calendar;
_weekdayView.calendar = calendar;
[self configureAppearance];
}
}
#pragma mark - Private methods
- (void)configureAppearance
{
_titleLabel.font = self.calendar.appearance.headerTitleFont;
_titleLabel.textColor = self.calendar.appearance.headerTitleColor;
[self.weekdayView configureAppearance];
}
- (void)setMonth:(NSDate *)month
{
_month = month;
_calendar.formatter.dateFormat = self.calendar.appearance.headerDateFormat;
BOOL usesUpperCase = (self.calendar.appearance.caseOptions & 15) == FSCalendarCaseOptionsHeaderUsesUpperCase;
NSString *text = [_calendar.formatter stringFromDate:_month];
text = usesUpperCase ? text.uppercaseString : text;
self.titleLabel.text = text;
}
@end
//
// FSCalendarTransitionCoordinator.h
// FSCalendar
//
// Created by dingwenchao on 3/13/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendar.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarCollectionViewLayout.h"
typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) {
FSCalendarTransitionStateIdle,
FSCalendarTransitionStateChanging,
FSCalendarTransitionStateFinishing,
};
@interface FSCalendarTransitionCoordinator : NSObject <UIGestureRecognizerDelegate>
@property (assign, nonatomic) FSCalendarTransitionState state;
@property (assign, nonatomic) CGSize cachedMonthSize;
@property (readonly, nonatomic) FSCalendarScope representingScope;
- (instancetype)initWithCalendar:(FSCalendar *)calendar;
- (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated;
- (void)performBoundingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration;
- (CGRect)boundingRectForScope:(FSCalendarScope)scope page:(NSDate *)page;
- (void)handleScopeGesture:(id)sender;
@end
@interface FSCalendarTransitionAttributes : NSObject
@property (assign, nonatomic) CGRect sourceBounds;
@property (assign, nonatomic) CGRect targetBounds;
@property (strong, nonatomic) NSDate *sourcePage;
@property (strong, nonatomic) NSDate *targetPage;
@property (assign, nonatomic) NSInteger focusedRow;
@property (strong, nonatomic) NSDate *focusedDate;
@property (assign, nonatomic) FSCalendarScope targetScope;
- (void)revert;
@end
//
// FSCalendarTransitionCoordinator.m
// FSCalendar
//
// Created by Wenchao Ding on 3/13/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarTransitionCoordinator.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarDynamicHeader.h"
@interface FSCalendarTransitionCoordinator ()
@property (weak, nonatomic) FSCalendarCollectionView *collectionView;
@property (weak, nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout;
@property (weak, nonatomic) FSCalendar *calendar;
@property (strong, nonatomic) FSCalendarTransitionAttributes *transitionAttributes;
- (FSCalendarTransitionAttributes *)createTransitionAttributesTargetingScope:(FSCalendarScope)targetScope;
- (void)performTransitionCompletionAnimated:(BOOL)animated;
- (void)performAlphaAnimationWithProgress:(CGFloat)progress;
- (void)performPathAnimationWithProgress:(CGFloat)progress;
- (void)scopeTransitionDidBegin:(UIPanGestureRecognizer *)panGesture;
- (void)scopeTransitionDidUpdate:(UIPanGestureRecognizer *)panGesture;
- (void)scopeTransitionDidEnd:(UIPanGestureRecognizer *)panGesture;
- (void)boundingRectWillChange:(CGRect)targetBounds animated:(BOOL)animated;
@end
@implementation FSCalendarTransitionCoordinator
- (instancetype)initWithCalendar:(FSCalendar *)calendar
{
self = [super init];
if (self) {
self.calendar = calendar;
self.collectionView = self.calendar.collectionView;
self.collectionViewLayout = self.calendar.collectionViewLayout;
}
return self;
}
#pragma mark - Target actions
- (void)handleScopeGesture:(UIPanGestureRecognizer *)sender
{
switch (sender.state) {
case UIGestureRecognizerStateBegan: {
[self scopeTransitionDidBegin:sender];
break;
}
case UIGestureRecognizerStateChanged: {
[self scopeTransitionDidUpdate:sender];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:{
[self scopeTransitionDidEnd:sender];
break;
}
default: {
break;
}
}
}
#pragma mark - <UIGestureRecognizerDelegate>
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (self.state != FSCalendarTransitionStateIdle) {
return NO;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
if (gestureRecognizer == self.calendar.scopeGesture && self.calendar.collectionViewLayout.scrollDirection == UICollectionViewScrollDirectionVertical) {
return NO;
}
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [[gestureRecognizer valueForKey:@"_targets"] containsObject:self.calendar]) {
CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:gestureRecognizer.view];
BOOL shouldStart = self.calendar.scope == FSCalendarScopeWeek ? velocity.y >= 0 : velocity.y <= 0;
if (!shouldStart) return NO;
shouldStart = (ABS(velocity.x)<=ABS(velocity.y));
if (shouldStart) {
self.calendar.collectionView.panGestureRecognizer.enabled = NO;
self.calendar.collectionView.panGestureRecognizer.enabled = YES;
}
return shouldStart;
}
return YES;
#pragma GCC diagnostic pop
return NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return otherGestureRecognizer == self.collectionView.panGestureRecognizer && self.collectionView.decelerating;
}
- (void)scopeTransitionDidBegin:(UIPanGestureRecognizer *)panGesture
{
if (self.state != FSCalendarTransitionStateIdle) return;
CGPoint velocity = [panGesture velocityInView:panGesture.view];
if (self.calendar.scope == FSCalendarScopeMonth && velocity.y >= 0) {
return;
}
if (self.calendar.scope == FSCalendarScopeWeek && velocity.y <= 0) {
return;
}
self.state = FSCalendarTransitionStateChanging;
self.transitionAttributes = [self createTransitionAttributesTargetingScope:1-self.calendar.scope];
if (self.transitionAttributes.targetScope == FSCalendarScopeMonth) {
[self prepareWeekToMonthTransition];
}
}
- (void)scopeTransitionDidUpdate:(UIPanGestureRecognizer *)panGesture
{
if (self.state != FSCalendarTransitionStateChanging) return;
CGFloat translation = ABS([panGesture translationInView:panGesture.view].y);
CGFloat progress = ({
CGFloat maxTranslation = ABS(CGRectGetHeight(self.transitionAttributes.targetBounds) - CGRectGetHeight(self.transitionAttributes.sourceBounds));
translation = MIN(maxTranslation, translation);
translation = MAX(0, translation);
CGFloat progress = translation/maxTranslation;
progress;
});
[self performAlphaAnimationWithProgress:progress];
[self performPathAnimationWithProgress:progress];
}
- (void)scopeTransitionDidEnd:(UIPanGestureRecognizer *)panGesture
{
if (self.state != FSCalendarTransitionStateChanging) return;
self.state = FSCalendarTransitionStateFinishing;
CGFloat translation = [panGesture translationInView:panGesture.view].y;
CGFloat velocity = [panGesture velocityInView:panGesture.view].y;
CGFloat progress = ({
CGFloat maxTranslation = CGRectGetHeight(self.transitionAttributes.targetBounds) - CGRectGetHeight(self.transitionAttributes.sourceBounds);
translation = MAX(0, translation);
translation = MIN(maxTranslation, translation);
CGFloat progress = translation/maxTranslation;
progress;
});
if (velocity * translation < 0) {
[self.transitionAttributes revert];
}
[self performTransition:self.transitionAttributes.targetScope fromProgress:progress toProgress:1.0 animated:YES];
}
#pragma mark - Public methods
- (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated
{
if (fromScope == toScope) {
[self.calendar willChangeValueForKey:@"scope"];
[self.calendar fs_setUnsignedIntegerVariable:toScope forKey:@"_scope"];
[self.calendar didChangeValueForKey:@"scope"];
return;
}
// Start transition
self.state = FSCalendarTransitionStateFinishing;
FSCalendarTransitionAttributes *attr = [self createTransitionAttributesTargetingScope:toScope];
self.transitionAttributes = attr;
if (toScope == FSCalendarScopeMonth) {
[self prepareWeekToMonthTransition];
}
[self performTransition:self.transitionAttributes.targetScope fromProgress:0 toProgress:1 animated:animated];
}
- (void)performBoundingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration
{
if (!self.calendar.adjustsBoundingRectWhenChangingMonths) return;
if (self.calendar.scope != FSCalendarScopeMonth) return;
NSInteger lastRowCount = [self.calendar.calculator numberOfRowsInMonth:fromMonth];
NSInteger currentRowCount = [self.calendar.calculator numberOfRowsInMonth:toMonth];
if (lastRowCount != currentRowCount) {
CGFloat animationDuration = duration;
CGRect bounds = [self boundingRectForScope:FSCalendarScopeMonth page:toMonth];
self.state = FSCalendarTransitionStateChanging;
void (^completion)(BOOL) = ^(BOOL finished) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(MAX(0, duration-animationDuration) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar setNeedsLayout];
self.state = FSCalendarTransitionStateIdle;
});
};
if (FSCalendarInAppExtension) {
// Detect today extension: http://stackoverflow.com/questions/25048026/ios-8-extension-how-to-detect-running
[self boundingRectWillChange:bounds animated:YES];
completion(YES);
} else {
[UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
[self boundingRectWillChange:bounds animated:YES];
} completion:completion];
}
}
}
#pragma mark - Private properties
- (void)performTransitionCompletionAnimated:(BOOL)animated
{
switch (self.transitionAttributes.targetScope) {
case FSCalendarScopeWeek: {
self.collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.calendar.calendarHeaderView.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.needsAdjustingViewFrame = YES;
[self.collectionView reloadData];
[self.calendar.calendarHeaderView reloadData];
break;
}
case FSCalendarScopeMonth: {
self.calendar.needsAdjustingViewFrame = YES;
break;
}
default:
break;
}
self.state = FSCalendarTransitionStateIdle;
self.transitionAttributes = nil;
[self.calendar setNeedsLayout];
[self.calendar layoutIfNeeded];
}
- (FSCalendarTransitionAttributes *)createTransitionAttributesTargetingScope:(FSCalendarScope)targetScope
{
FSCalendarTransitionAttributes *attributes = [[FSCalendarTransitionAttributes alloc] init];
attributes.sourceBounds = self.calendar.bounds;
attributes.sourcePage = self.calendar.currentPage;
attributes.targetScope = targetScope;
attributes.focusedDate = ({
NSArray<NSDate *> *candidates = ({
NSMutableArray *dates = self.calendar.selectedDates.reverseObjectEnumerator.allObjects.mutableCopy;
if (self.calendar.today) {
[dates addObject:self.calendar.today];
}
if (targetScope == FSCalendarScopeWeek) {
[dates addObject:self.calendar.currentPage];
} else {
[dates addObject:[self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:3 toDate:self.calendar.currentPage options:0]];
}
dates.copy;
});
NSArray<NSDate *> *visibleCandidates = [candidates filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSDate * _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:evaluatedObject scope:1-targetScope];
NSInteger currentSection = [self.calendar.calculator indexPathForDate:self.calendar.currentPage scope:1-targetScope].section;
return indexPath.section == currentSection;
}]];
NSDate *date = visibleCandidates.firstObject;
date;
});
attributes.focusedRow = ({
NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:attributes.focusedDate scope:FSCalendarScopeMonth];
FSCalendarCoordinate coordinate = [self.calendar.calculator coordinateForIndexPath:indexPath];
coordinate.row;
});
attributes.targetPage = ({
NSDate *targetPage = targetScope == FSCalendarScopeMonth ? [self.calendar.gregorian fs_firstDayOfMonth:attributes.focusedDate] : [self.calendar.gregorian fs_middleDayOfWeek:attributes.focusedDate];
targetPage;
});
attributes.targetBounds = [self boundingRectForScope:attributes.targetScope page:attributes.targetPage];
return attributes;
}
#pragma mark - Private properties
- (FSCalendarScope)representingScope
{
switch (self.state) {
case FSCalendarTransitionStateIdle: {
return self.calendar.scope;
}
case FSCalendarTransitionStateChanging:
case FSCalendarTransitionStateFinishing: {
return FSCalendarScopeMonth;
}
}
}
#pragma mark - Private methods
- (CGRect)boundingRectForScope:(FSCalendarScope)scope page:(NSDate *)page
{
CGSize contentSize;
switch (scope) {
case FSCalendarScopeMonth: {
contentSize = self.calendar.adjustsBoundingRectWhenChangingMonths ? [self.calendar sizeThatFits:self.calendar.frame.size scope:scope] : self.cachedMonthSize;
break;
}
case FSCalendarScopeWeek: {
contentSize = [self.calendar sizeThatFits:self.calendar.frame.size scope:scope];
break;
}
}
return (CGRect){CGPointZero, contentSize};
}
- (void)boundingRectWillChange:(CGRect)targetBounds animated:(BOOL)animated
{
self.calendar.contentView.fs_height = CGRectGetHeight(targetBounds);
self.calendar.daysContainer.fs_height = CGRectGetHeight(targetBounds)-self.calendar.preferredHeaderHeight-self.calendar.preferredWeekdayHeight;
[[self.calendar valueForKey:@"delegateProxy"] calendar:self.calendar boundingRectWillChange:targetBounds animated:animated];
}
- (void)performTransition:(FSCalendarScope)targetScope fromProgress:(CGFloat)fromProgress toProgress:(CGFloat)toProgress animated:(BOOL)animated
{
FSCalendarTransitionAttributes *attr = self.transitionAttributes;
[self.calendar willChangeValueForKey:@"scope"];
[self.calendar fs_setUnsignedIntegerVariable:targetScope forKey:@"_scope"];
if (targetScope == FSCalendarScopeWeek) {
[self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"];
}
[self.calendar didChangeValueForKey:@"scope"];
if (animated) {
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)])) {
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self performAlphaAnimationWithProgress:toProgress];
self.collectionView.fs_top = [self calculateOffsetForProgress:toProgress];
[self boundingRectWillChange:attr.targetBounds animated:YES];
} completion:^(BOOL finished) {
[self performTransitionCompletionAnimated:YES];
}];
}
} else {
[self performTransitionCompletionAnimated:animated];
[self boundingRectWillChange:attr.targetBounds animated:animated];
}
}
- (void)performAlphaAnimationWithProgress:(CGFloat)progress
{
CGFloat opacity = self.transitionAttributes.targetScope == FSCalendarScopeWeek ? MAX((1-progress*1.1), 0) : progress;
NSArray<FSCalendarCell *> *surroundingCells = [self.calendar.visibleCells filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FSCalendarCell * _Nullable cell, NSDictionary<NSString *,id> * _Nullable bindings) {
if (!CGRectContainsPoint(self.collectionView.bounds, cell.center)) {
return NO;
}
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
NSInteger row = [self.calendar.calculator coordinateForIndexPath:indexPath].row;
return row != self.transitionAttributes.focusedRow;
}]];
[surroundingCells setValue:@(opacity) forKey:@"alpha"];
}
- (void)performPathAnimationWithProgress:(CGFloat)progress
{
CGFloat targetHeight = CGRectGetHeight(self.transitionAttributes.targetBounds);
CGFloat sourceHeight = CGRectGetHeight(self.transitionAttributes.sourceBounds);
CGFloat currentHeight = sourceHeight - (sourceHeight-targetHeight)*progress;
CGRect currentBounds = CGRectMake(0, 0, CGRectGetWidth(self.transitionAttributes.targetBounds), currentHeight);
self.collectionView.fs_top = [self calculateOffsetForProgress:progress];
[self boundingRectWillChange:currentBounds animated:NO];
if (self.transitionAttributes.targetScope == FSCalendarScopeMonth) {
self.calendar.contentView.fs_height = targetHeight;
}
}
- (CGFloat)calculateOffsetForProgress:(CGFloat)progress
{
NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:self.transitionAttributes.focusedDate scope:FSCalendarScopeMonth];
CGRect frame = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath].frame;
CGFloat ratio = self.transitionAttributes.targetScope == FSCalendarScopeWeek ? progress : (1 - progress);
CGFloat offset = (-frame.origin.y + self.collectionViewLayout.sectionInsets.top) * ratio;
return offset;
}
- (void)prepareWeekToMonthTransition
{
[self.calendar fs_setVariable:self.transitionAttributes.targetPage forKey:@"_currentPage"];
self.calendar.contentView.fs_height = CGRectGetHeight(self.transitionAttributes.targetBounds);
self.collectionViewLayout.scrollDirection = (UICollectionViewScrollDirection)self.calendar.scrollDirection;
self.calendar.calendarHeaderView.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.needsAdjustingViewFrame = YES;
[CATransaction begin];
[CATransaction setDisableActions:NO];
[self.collectionView reloadData];
[self.calendar.calendarHeaderView reloadData];
[self.calendar layoutIfNeeded];
[CATransaction commit];
self.collectionView.fs_top = [self calculateOffsetForProgress:0];
}
@end
@implementation FSCalendarTransitionAttributes
- (void)revert
{
CGRect tempRect = self.sourceBounds;
self.sourceBounds = self.targetBounds;
self.targetBounds = tempRect;
NSDate *tempDate = self.sourcePage;
self.sourcePage = self.targetPage;
self.targetPage = tempDate;
self.targetScope = 1 - self.targetScope;
}
@end
//
// FSCalendarWeekdayView.h
// FSCalendar
//
// Created by dingwenchao on 03/11/2016.
// Copyright © 2016 dingwenchao. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FSCalendar;
@interface FSCalendarWeekdayView : UIView
/**
An array of UILabel objects displaying the weekday symbols.
*/
@property (readonly, nonatomic) NSArray<UILabel *> *weekdayLabels;
- (void)configureAppearance;
@end
NS_ASSUME_NONNULL_END
//
// FSCalendarWeekdayView.m
// FSCalendar
//
// Created by dingwenchao on 03/11/2016.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarWeekdayView.h"
#import "FSCalendar.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarWeekdayView()
@property (strong, nonatomic) NSPointerArray *weekdayPointers;
@property (weak , nonatomic) UIView *contentView;
@property (weak , nonatomic) FSCalendar *calendar;
- (void)commonInit;
@end
@implementation FSCalendarWeekdayView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit
{
UIView *contentView = [[UIView alloc] initWithFrame:CGRectZero];
[self addSubview:contentView];
_contentView = contentView;
_weekdayPointers = [NSPointerArray weakObjectsPointerArray];
for (int i = 0; i < 7; i++) {
UILabel *weekdayLabel = [[UILabel alloc] initWithFrame:CGRectZero];
weekdayLabel.textAlignment = NSTextAlignmentCenter;
[self.contentView addSubview:weekdayLabel];
[_weekdayPointers addPointer:(__bridge void * _Nullable)(weekdayLabel)];
}
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.contentView.frame = self.bounds;
// Position Calculation
NSInteger count = self.weekdayPointers.count;
size_t size = sizeof(CGFloat)*count;
CGFloat *widths = malloc(size);
CGFloat contentWidth = self.contentView.fs_width;
FSCalendarSliceCake(contentWidth, count, widths);
BOOL opposite = NO;
if (@available(iOS 9.0, *)) {
UIUserInterfaceLayoutDirection direction = [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.calendar.semanticContentAttribute];
opposite = (direction == UIUserInterfaceLayoutDirectionRightToLeft);
}
CGFloat x = 0;
for (NSInteger i = 0; i < count; i++) {
CGFloat width = widths[i];
NSInteger labelIndex = opposite ? count-1-i : i;
UILabel *label = [self.weekdayPointers pointerAtIndex:labelIndex];
label.frame = CGRectMake(x, 0, width, self.contentView.fs_height);
x = CGRectGetMaxX(label.frame);
}
free(widths);
}
- (void)setCalendar:(FSCalendar *)calendar
{
_calendar = calendar;
[self configureAppearance];
}
- (NSArray<UILabel *> *)weekdayLabels
{
return self.weekdayPointers.allObjects;
}
- (void)configureAppearance
{
BOOL useVeryShortWeekdaySymbols = (self.calendar.appearance.caseOptions & (15<<4) ) == FSCalendarCaseOptionsWeekdayUsesSingleUpperCase;
NSArray *weekdaySymbols = useVeryShortWeekdaySymbols ? self.calendar.gregorian.veryShortStandaloneWeekdaySymbols : self.calendar.gregorian.shortStandaloneWeekdaySymbols;
BOOL useDefaultWeekdayCase = (self.calendar.appearance.caseOptions & (15<<4) ) == FSCalendarCaseOptionsWeekdayUsesDefaultCase;
for (NSInteger i = 0; i < self.weekdayPointers.count; i++) {
NSInteger index = (i + self.calendar.firstWeekday-1) % 7;
UILabel *label = [self.weekdayPointers pointerAtIndex:i];
label.font = self.calendar.appearance.weekdayFont;
label.textColor = self.calendar.appearance.weekdayTextColor;
label.text = useDefaultWeekdayCase ? weekdaySymbols[index] : [weekdaySymbols[index] uppercaseString];
}
}
@end
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_5" orientation="portrait" appearance="light"/>
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
<capability name="Named colors" minToolsVersion="9.0"/>
......@@ -27,16 +27,7 @@
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<constraints>
<constraint firstAttribute="width" constant="165" id="6rk-H5-BDU">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isAdaptateScreen" value="NO"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="height" constant="165" id="vdi-T6-Rva">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isAdaptateScreen" value="NO"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="width" secondItem="MEL-J8-S3c" secondAttribute="height" multiplier="1:1" id="uqe-4D-VvN"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="gradientStartColor">
......@@ -54,16 +45,7 @@
<rect key="frame" x="0.0" y="0.0" width="165" height="165"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="width" constant="165" id="Evb-BF-yL7">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isAdaptateScreen" value="NO"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="height" constant="165" id="Fct-ke-bms">
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="isAdaptateScreen" value="NO"/>
</userDefinedRuntimeAttributes>
</constraint>
<constraint firstAttribute="width" secondItem="T8c-YA-14K" secondAttribute="height" multiplier="1:1" id="hU7-HF-s5b"/>
</constraints>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4Xi-Jf-NQw">
......@@ -118,7 +100,7 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="13" translatesAutoresizingMaskIntoConstraints="NO" id="50H-P6-uG6">
<rect key="frame" x="10.000000000000004" y="218" width="52.333333333333343" height="24"/>
<rect key="frame" x="10" y="218" width="52.5" height="24"/>
<attributedString key="attributedText">
<fragment content="¥0.00">
<attributes>
......@@ -140,7 +122,7 @@
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tHK-Kt-m55">
<rect key="frame" x="10" y="175" width="145" height="36.666666666666657"/>
<rect key="frame" x="10" y="175" width="145" height="36.5"/>
<attributedString key="attributedText">
<fragment content="六和川香鸡柳2.5kg*多发点嘎嘎大放送公司的嘎嘎搭嘎">
<attributes>
......@@ -229,7 +211,7 @@
</collectionViewCell>
</objects>
<resources>
<image name="二维码" width="45.666667938232422" height="30"/>
<image name="二维码" width="45.5" height="30"/>
<namedColor name="商品图背景渐变底色">
<color red="0.98039215686274506" green="0.98039215686274506" blue="0.98039215686274506" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
......@@ -240,7 +222,7 @@
<color red="0.92941176470588238" green="0.92941176470588238" blue="0.92941176470588238" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="灰色字体颜色">
<color red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="白色背景色">
<color red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
......
//
// DIYCalendarCell.swift
// FSCalendarSwiftExample
//
// Created by dingwenchao on 06/11/2016.
// Copyright © 2016 wenchao. All rights reserved.
//
import Foundation
import UIKit
enum SelectionType : Int {
case none
case single
case leftBorder
case middle
case rightBorder
}
class DIYCalendarCell: FSCalendarCell {
weak var selectionLayer: CAShapeLayer!
var selectionType: SelectionType = .none {
didSet {
setNeedsLayout()
}
}
required init!(coder aDecoder: NSCoder!) {
fatalError("init(coder:) has not been implemented")
}
override init(frame: CGRect) {
super.init(frame: frame)
let color = UIColor.init(named: "蓝色字体颜色")
// //选中layer
let selectionLayer = CAShapeLayer()
selectionLayer.fillColor = color?.cgColor
selectionLayer.actions = ["hidden": NSNull()]
self.contentView.layer.insertSublayer(selectionLayer, below: self.titleLabel!.layer)
self.selectionLayer = selectionLayer
self.shapeLayer.isHidden = true
}
override func layoutSubviews() {
super.layoutSubviews()
self.selectionLayer.frame = self.contentView.bounds
let color = UIColor.white
self.selectionLayer.fillColor = color.cgColor
if selectionType == .middle {
print("middle--middle")
self.titleLabel.textColor = UIColor.init(named: "蓝色字体颜色")
let color = UIColor.white
self.selectionLayer.fillColor = color.cgColor
self.selectionLayer.path = UIBezierPath(rect: self.selectionLayer.bounds).cgPath
}
else if selectionType == .leftBorder {
self.titleLabel.textColor = UIColor.white
let color = UIColor.init(named: "蓝色字体颜色")
self.selectionLayer.fillColor = color?.cgColor
let diameter: CGFloat = min(self.selectionLayer.frame.height, self.selectionLayer.frame.width)
self.selectionLayer.path = UIBezierPath(ovalIn: CGRect(x: self.bounds.width / 2 - diameter / 2 + 2, y: self.bounds.height / 2 - diameter / 2 - 1, width: diameter - 4, height: diameter - 4)).cgPath
print("leftBorder--leftBorder")
}
else if selectionType == .rightBorder {
print("rightBorder--rightBorder")
self.titleLabel.textColor = UIColor.white
let color = UIColor.init(named: "蓝色字体颜色")
self.selectionLayer.fillColor = color?.cgColor
let diameter: CGFloat = min(self.selectionLayer.frame.height, self.selectionLayer.frame.width)
self.selectionLayer.path = UIBezierPath(ovalIn: CGRect(x: self.bounds.width / 2 - diameter / 2 + 2, y: self.bounds.height / 2 - diameter / 2 - 1, width: diameter - 4, height: diameter - 4)).cgPath
}else if selectionType == .single {
let color = UIColor.init(named: "蓝色字体颜色")
self.selectionLayer.fillColor = color?.cgColor
let diameter: CGFloat = min(self.selectionLayer.frame.height, self.selectionLayer.frame.width)
self.selectionLayer.path = UIBezierPath(ovalIn: CGRect(x: self.bounds.width / 2 - diameter / 2 + 2, y: self.bounds.height / 2 - diameter / 2 - 1, width: diameter - 4, height: diameter - 4)).cgPath
print("single--single")
self.titleLabel.textColor = UIColor.white
}
}
override func configureAppearance() {
super.configureAppearance()
}
}
//
// TimeSelectView.swift
// GeliBusinessPlatform
//
// Created by 刘俊宏 on 2020/4/23.
// Copyright © 2020 junye lu. All rights reserved.
//
import UIKit
class TimeSelectView: UIView, FSCalendarDataSource, FSCalendarDelegate {
fileprivate weak var calendarC: FSCalendar!
var moreSelection:Bool = false {
didSet{
calendarC.allowsMultipleSelection = moreSelection;
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.white
let calendar = FSCalendar(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
calendar.dataSource = self
calendar.delegate = self
self.addSubview(calendar)
self.calendarC = calendar
self.calendarC.today = nil;
self.calendarC.appearance.headerMinimumDissolvedAlpha = 0.0;
calendar.appearance.headerDateFormat = "yyyy年 M 月"
calendar.allowsMultipleSelection = moreSelection;
calendar.locale = Locale.init(identifier: "zh-CN")
calendar.appearance.useVeryShortWeekdaySymbols = true
calendar.appearance.headerTitleColor = UIColor.init(named: "标题字颜色")
calendar.appearance.headerTitleFont = UIFont.systemFont(ofSize: 15)
calendar.appearance.weekdayTextColor = UIColor.init(named: "灰色字体颜色")
calendar.appearance.titleDefaultColor = UIColor.init(named: "标题字颜色")
calendar.appearance.titlePlaceholderColor = UIColor.init(named: "灰色字体颜色")
calendar.register(DIYCalendarCell.self, forCellReuseIdentifier: "cell")
addChangeBtnUI()
}
//切换年
@objc func changeYear(sender:UIButton) {
print("切换年\(calendarC.currentPage)")
if sender.tag == 1 {
let dateA = calendarC.currentPage.addingTimeInterval(TimeInterval(365*60*60*24))
let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: dateA)!
calendarC.setCurrentPage(nextDate, animated: false)
}else if sender.tag == 0{
let dateA = calendarC.currentPage.addingTimeInterval(TimeInterval(-365*60*60*24))
let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: dateA)!
calendarC.setCurrentPage(nextDate, animated: false)
}
}
@objc func changeMoth(sender:UIButton) {
print("切换月\(calendarC.currentPage)")
if sender.tag == 1 {
let dateA = calendarC.currentPage.addingTimeInterval(TimeInterval(30*60*60*24))
let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: dateA)!
calendarC.setCurrentPage(nextDate, animated: true)
}else if sender.tag == 0{
let dateA = calendarC.currentPage.addingTimeInterval(TimeInterval(-30*60*60*24))
let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: dateA)!
calendarC.setCurrentPage(nextDate, animated: true)
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var BeginDate: Date!
var EndDate: Date!
var SelectAction: Bool = false
//选中某一天进行相关操作
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
let dateA = self.gregorian.date(byAdding: .day, value: 1, to: date)!
print("第一次选中\(dateA)")
if moreSelection {
if !SelectAction {
let selectArr = calendar.selectedDates
for select in selectArr{
calendar.deselect(select)
}
calendarC.select(date)
BeginDate = date
SelectAction = true
EndDate = nil
let previousDate = self.gregorian.date(byAdding: .day, value: 1, to: BeginDate)!
print("选中开始时间\(previousDate)")
}else{
let number = numberOfDaysWithFromDate(fromDate: BeginDate, toDate: date)!
if number < 0 {
print("小于开始日期,请重新选择")
let selectArr = calendar.selectedDates
for select in selectArr{
calendar.deselect(select)
}
calendarC.select(date)
BeginDate = date
SelectAction = true
EndDate = nil
}else{
SelectAction = false
EndDate = date
for i in 0 ..< number {
let dateA = date.addingTimeInterval(TimeInterval(-i*60*60*24))
calendarC.select(dateA)
}
let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: EndDate)!
print("选中结束时间\(nextDate)")
}
}
}
self.configureVisibleCells()
}
func calendar(_ calendar: FSCalendar, didDeselect date: Date, at monthPosition: FSCalendarMonthPosition) {
let dateA = self.gregorian.date(byAdding: .day, value: 1, to: date)!
print("再次点击选择中范围\(dateA)")
if moreSelection {
let selectArr = calendar.selectedDates
for select in selectArr {
calendar.deselect(select)
}
calendarC.select(date)
SelectAction = true
BeginDate = date
EndDate = nil
}
self.configureVisibleCells()
}
// func calendar(_ calendar: FSCalendar, shouldSelect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool {
// return monthPosition == .current
// }
//
// func calendar(_ calendar: FSCalendar, shouldDeselect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool {
// return monthPosition == .current
// }
//计算日期差
func numberOfDaysWithFromDate(fromDate:Date,toDate:Date) -> Int? {
let calendar = Calendar.init(identifier: .gregorian)
let comp = calendar.dateComponents([.day], from: fromDate, to: toDate)
return comp.day
}
// MARK:- FSCalendarDataSource
func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
let cell = calendar.dequeueReusableCell(withIdentifier: "cell", for: date, at: position)
return cell
}
func calendar(_ calendar: FSCalendar, willDisplay cell: FSCalendarCell, for date: Date, at position: FSCalendarMonthPosition) {
self.configure(cell: cell, for: date, at: position)
}
fileprivate let gregorian = Calendar(identifier: .gregorian)
private func configureVisibleCells() {
calendarC.visibleCells().forEach { (cell) in
let date = calendarC.date(for: cell)
let position = calendarC.monthPosition(for: cell)
self.configure(cell: cell, for: date!, at: position)
}
}
private func configure(cell: FSCalendarCell, for date: Date, at position: FSCalendarMonthPosition) {
let diyCell = (cell as! DIYCalendarCell)
// if position == .current {
var selectionType = SelectionType.none
if calendarC.selectedDates.contains(date) {
let previousDate = self.gregorian.date(byAdding: .day, value: -1, to: date)!
let nextDate = self.gregorian.date(byAdding: .day, value: 1, to: date)!
if calendarC.selectedDates.contains(date) {
if calendarC.selectedDates.contains(previousDate) && calendarC.selectedDates.contains(nextDate) {
selectionType = .middle
}
else if calendarC.selectedDates.contains(previousDate) && calendarC.selectedDates.contains(date) {
selectionType = .rightBorder
}
else if calendarC.selectedDates.contains(nextDate) {
selectionType = .leftBorder
}
else {
selectionType = .single
}
diyCell.selectionLayer.isHidden = false
}
}
else {
selectionType = .none
diyCell.selectionType = selectionType
diyCell.selectionLayer.isHidden = true
}
if selectionType == .none {
diyCell.selectionLayer.isHidden = true
diyCell.selectionType = selectionType
return
}
diyCell.selectionLayer.isHidden = false
diyCell.selectionType = selectionType
// }
// else {
// diyCell.selectionType = .none
// diyCell.selectionLayer.isHidden = true
// }
}
//MARK:--自定义上下月和上下年UI
func addChangeBtnUI() {
//添加上下月和上下年UI
let previousYbtn = UIButton(type: .custom)
previousYbtn.backgroundColor = UIColor.white
self.addSubview(previousYbtn)
previousYbtn.snp.makeConstraints { (make) in
make.left.top.equalTo(0)
make.height.equalTo(30*glscale)
make.width.equalTo(65*glscale)
}
previousYbtn.tag = 0
previousYbtn.addTarget(self, action: #selector(changeYear(sender:)), for: .touchUpInside)
let previousYImg = UIImageView.init(image: UIImage.init(named: "wangnian"))
self.addSubview(previousYImg)
previousYImg.snp.makeConstraints { (make) in
make.top.equalTo(14)
make.left.equalTo((15*glscale))
}
let previousMbtn = UIButton(type: .custom)
previousMbtn.backgroundColor = UIColor.white
self.addSubview(previousMbtn)
previousMbtn.snp.makeConstraints { (make) in
make.top.equalTo(0)
make.left.equalTo(previousYbtn.snp_right)
make.height.equalTo(30*glscale)
make.width.equalTo(65*glscale)
}
previousMbtn.tag = 0
previousMbtn.addTarget(self, action: #selector(changeMoth(sender:)), for: .touchUpInside)
let previousMImg = UIImageView.init(image: UIImage.init(named: "wangyue"))
self.addSubview(previousMImg)
previousMImg.snp.makeConstraints { (make) in
make.top.equalTo(14)
make.left.equalTo((90*glscale))
}
let nextYbtn = UIButton(type: .custom)
nextYbtn.backgroundColor = UIColor.white
self.addSubview(nextYbtn)
nextYbtn.snp.makeConstraints { (make) in
make.right.top.equalTo(0)
make.height.equalTo(30*glscale)
make.width.equalTo(65*glscale)
}
nextYbtn.tag = 1
nextYbtn.addTarget(self, action: #selector(changeYear(sender:)), for: .touchUpInside)
let nextYImg = UIImageView.init(image: UIImage.init(named: "xiayinian"))
self.addSubview(nextYImg)
nextYImg.snp.makeConstraints { (make) in
make.top.equalTo(14)
make.right.equalTo((-15*glscale))
}
let nextMbtn = UIButton(type: .custom)
nextMbtn.backgroundColor = UIColor.white
self.addSubview(nextMbtn)
nextMbtn.snp.makeConstraints { (make) in
make.top.equalTo(0)
make.right.equalTo(nextYbtn.snp_left)
make.height.equalTo(30*glscale)
make.width.equalTo(65*glscale)
}
nextMbtn.tag = 1
nextMbtn.addTarget(self, action: #selector(changeMoth(sender:)), for: .touchUpInside)
let nextMImg = UIImageView.init(image: UIImage.init(named: "xiageyue"))
self.addSubview(nextMImg)
nextMImg.snp.makeConstraints { (make) in
make.top.equalTo(14)
make.right.equalTo((-90*glscale))
}
}
}
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