Increase fps

This commit is contained in:
Wenchao Ding 2016-10-31 22:08:10 +08:00
parent 7968899471
commit 2c3bcbbcb2
14 changed files with 453 additions and 159 deletions

View File

@ -49,6 +49,9 @@
30CEF9011C950C1F008EAFB1 /* FSCalendarAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 30CEF8FF1C950C1F008EAFB1 /* FSCalendarAnimator.m */; };
30CEF9021C950C1F008EAFB1 /* FSCalendarAnimator.m in Sources */ = {isa = PBXBuildFile; fileRef = 30CEF8FF1C950C1F008EAFB1 /* FSCalendarAnimator.m */; };
30D55B101C90240000BB43D5 /* HidePlaceholderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 30D55B0F1C90240000BB43D5 /* HidePlaceholderViewController.m */; };
30DBE3C41DC641AD005A22B7 /* FSCalendarCalculator.h in Headers */ = {isa = PBXBuildFile; fileRef = 30DBE3C21DC641AD005A22B7 /* FSCalendarCalculator.h */; settings = {ATTRIBUTES = (Private, ); }; };
30DBE3C51DC641AD005A22B7 /* FSCalendarCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 30DBE3C31DC641AD005A22B7 /* FSCalendarCalculator.m */; };
30DBE3C61DC641AD005A22B7 /* FSCalendarCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 30DBE3C31DC641AD005A22B7 /* FSCalendarCalculator.m */; };
30F5D8561B9FC33400C1C201 /* FSCalendar.m in Sources */ = {isa = PBXBuildFile; fileRef = 30B0BAC01B8D8E22004B9476 /* FSCalendar.m */; };
30F5D85A1B9FC4BB00C1C201 /* MultipleSelectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 30F5D8591B9FC4BB00C1C201 /* MultipleSelectionViewController.m */; };
30FCB3961BAAD112002B87AD /* FSCalendarStickyHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 30FCB3941BAAD112002B87AD /* FSCalendarStickyHeader.h */; settings = {ATTRIBUTES = (Private, ); }; };
@ -135,6 +138,8 @@
30CEF8FF1C950C1F008EAFB1 /* FSCalendarAnimator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarAnimator.m; sourceTree = "<group>"; };
30D55B0E1C90240000BB43D5 /* HidePlaceholderViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HidePlaceholderViewController.h; sourceTree = SOURCE_ROOT; };
30D55B0F1C90240000BB43D5 /* HidePlaceholderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HidePlaceholderViewController.m; sourceTree = SOURCE_ROOT; };
30DBE3C21DC641AD005A22B7 /* FSCalendarCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarCalculator.h; sourceTree = "<group>"; };
30DBE3C31DC641AD005A22B7 /* FSCalendarCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSCalendarCalculator.m; sourceTree = "<group>"; };
30F5D8581B9FC4BB00C1C201 /* MultipleSelectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipleSelectionViewController.h; sourceTree = SOURCE_ROOT; };
30F5D8591B9FC4BB00C1C201 /* MultipleSelectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipleSelectionViewController.m; sourceTree = SOURCE_ROOT; };
30FCB3941BAAD112002B87AD /* FSCalendarStickyHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSCalendarStickyHeader.h; sourceTree = "<group>"; };
@ -226,6 +231,8 @@
30B0BAC21B8D8E22004B9476 /* FSCalendarAppearance.m */,
303105F11DC241D9002F802F /* FSCalendarDelegateProxy.h */,
303105F21DC241D9002F802F /* FSCalendarDelegateProxy.m */,
30DBE3C21DC641AD005A22B7 /* FSCalendarCalculator.h */,
30DBE3C31DC641AD005A22B7 /* FSCalendarCalculator.m */,
309225381B905C4300123031 /* FSCalendarConstants.h */,
309225391B905C4300123031 /* FSCalendarConstants.m */,
3065CA941CD31B81006C218D /* FSCalendarScopeHandle.h */,
@ -405,6 +412,7 @@
3065CA961CD31B81006C218D /* FSCalendarScopeHandle.h in Headers */,
3095398F1C38D66C00BD37AA /* FSCalendarCollectionViewLayout.h in Headers */,
3055B1C21DA9323A002AFA13 /* FSCalendarExtensions.h in Headers */,
30DBE3C41DC641AD005A22B7 /* FSCalendarCalculator.h in Headers */,
30CEF9001C950C1F008EAFB1 /* FSCalendarAnimator.h in Headers */,
30B0BB031B8D9B6D004B9476 /* FSCalendarDynamicHeader.h in Headers */,
);
@ -558,6 +566,7 @@
files = (
EE638CCE1B89DBD80006DD1A /* StoryboardExampleViewController.m in Sources */,
EECA10F81BA9C0E400945B83 /* FullScreenExampleViewController.m in Sources */,
30DBE3C51DC641AD005A22B7 /* FSCalendarCalculator.m in Sources */,
EE638CC91B89DB940006DD1A /* CalendarConfigViewController.m in Sources */,
30B0BAD01B8D8E23004B9476 /* FSCalendar.m in Sources */,
3065CAA81CD3506A006C218D /* FSCalendar+Deprecated.m in Sources */,
@ -595,6 +604,7 @@
EEC9C03B1BDC9E7000383A07 /* FSCalendarCollectionView.m in Sources */,
3065CAA91CD3506A006C218D /* FSCalendar+Deprecated.m in Sources */,
30B0BAF71B8D9AC1004B9476 /* FSCalendarCell.m in Sources */,
30DBE3C61DC641AD005A22B7 /* FSCalendarCalculator.m in Sources */,
30B0BAF81B8D9AC1004B9476 /* FSCalendarHeader.m in Sources */,
3092253C1B905C4300123031 /* FSCalendarConstants.m in Sources */,
3055B1C41DA9323A002AFA13 /* FSCalendarExtensions.m in Sources */,

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>classNames</key>
<dict>
<key>FSCalendarTests</key>
<dict>
<key>testIndexPathForDatePerformance</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
</dict>
</dict>
</dict>
</dict>
</plist>

View File

@ -35,6 +35,18 @@
<string>com.apple.platform.iphonesimulator</string>
</dict>
</dict>
<key>621090AF-4CF3-4245-99AD-46AD956ACA82</key>
<dict>
<key>targetArchitecture</key>
<string>arm64</string>
<key>targetDevice</key>
<dict>
<key>modelCode</key>
<string>iPhone8,4</string>
<key>platformIdentifier</key>
<string>com.apple.platform.iphoneos</string>
</dict>
</dict>
<key>A165CA85-3EFC-487B-954E-5AD1DC2EB21D</key>
<dict>
<key>localComputer</key>

View File

@ -9,12 +9,14 @@
#import <XCTest/XCTest.h>
#import "FSCalendar.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarTests : XCTestCase
@interface FSCalendarTests : XCTestCase <FSCalendarDataSource,FSCalendarDelegate>
@property (strong, nonatomic) FSCalendar *calendar;
@property (strong, nonatomic) NSDate *date;
@property (strong, nonatomic) NSIndexPath *indexPath;
@property (strong, nonatomic) NSMutableArray<NSIndexPath *> *indexPaths;
@property (strong, nonatomic) NSDateFormatter *formatter;
@end
@ -25,13 +27,29 @@
{
[super setUp];
self.calendar = [[FSCalendar alloc] initWithFrame:CGRectMake(0, 0, 320, 300)];
[self.calendar.calculator reloadSections];
self.indexPath = [NSIndexPath indexPathForItem:25 inSection:0];
self.date = [self.calendar dateForIndexPath:self.indexPath];
self.indexPaths = [NSMutableArray array];
for (int i = 0; i < 42; i++) {
[self.indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:0]];
}
self.formatter = [[NSDateFormatter alloc] init];
self.formatter.dateFormat = @"yyyy-MM-dd";
self.date = [self.formatter dateFromString:@"1900-11-11"];
}
- (NSDate *)minimumDateForCalendar:(FSCalendar *)calendar
{
return [self.formatter dateFromString:@"1900-01-01"];
}
- (NSDate *)maximumDateForCalendar:(FSCalendar *)calendar
{
return [self.formatter dateFromString:@"2300-01-01"];
}
- (void)tearDown
{
[super tearDown];
@ -48,13 +66,15 @@
- (void)testIndexPathForDatePerformance {
[self measureBlock:^{
[self.calendar indexPathForDate:self.date scope:FSCalendarScopeMonth];
[self.calendar.calculator indexPathForDate:self.date scope:FSCalendarScopeMonth];
}];
}
- (void)testDateForIndexPathPerformance {
[self measureBlock:^{
[self.calendar dateForIndexPath:self.indexPath];
[self.indexPaths enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self.calendar.calculator dateForIndexPath:obj];
}];
}];
}

View File

@ -19,6 +19,7 @@
#import "FSCalendarAnimator.h"
#import "FSCalendarDelegateProxy.h"
#import "FSCalendarCalculator.h"
NS_ASSUME_NONNULL_BEGIN
@ -65,8 +66,9 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
@property (weak , nonatomic) FSCalendarCollectionView *collectionView;
@property (weak , nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout;
@property (strong, nonatomic) FSCalendarAnimator *animator;
@property (strong, nonatomic) FSCalendarDelegateProxy *proxy;
@property (strong, nonatomic) FSCalendarAnimator *animator;
@property (strong, nonatomic) FSCalendarDelegateProxy *proxy;
@property (strong, nonatomic) FSCalendarCalculator *calculator;
@property (weak , nonatomic) FSCalendarHeader *header;
@property (weak , nonatomic) FSCalendarHeaderTouchDeliver *deliver;
@ -92,13 +94,8 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (void)orientationDidChange:(NSNotification *)notification;
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath;
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath scope:(FSCalendarScope)scope;
- (NSIndexPath *)indexPathForDate:(NSDate *)date;
- (NSIndexPath *)indexPathForDate:(NSDate *)date scope:(FSCalendarScope)scope;
- (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope;
- (NSInteger)numberOfHeadPlaceholdersForMonth:(NSDate *)month;
- (NSInteger)numberOfRowsInMonth:(NSDate *)month;
- (void)scrollToDate:(NSDate *)date;
@ -133,8 +130,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (void)requestBoundingDatesIfNecessary;
- (NSDate *)safeDateForDate:(NSDate *)date;
@end
@implementation FSCalendar
@ -264,6 +259,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
// Assistants
self.animator = [[FSCalendarAnimator alloc] initWithCalendar:self];
self.proxy = [[FSCalendarDelegateProxy alloc] initWithCalendar:self];
self.calculator = [[FSCalendarCalculator alloc] initWithCalendar:self];
// Gestures
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self.animator action:@selector(handlePan:)];
@ -483,20 +479,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
[self requestBoundingDatesIfNecessary];
if (self.animator.transition == FSCalendarTransitionWeekToMonth) {
NSInteger sections = [self.gregorian components:NSCalendarUnitMonth fromDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] toDate:self.maximumDate options:0].month + 1;
return sections;
}
switch (_scope) {
case FSCalendarScopeMonth: {
NSInteger sections = [self.gregorian components:NSCalendarUnitMonth fromDate:[self.gregorian fs_firstDayOfMonth:self.minimumDate] toDate:self.maximumDate options:0].month + 1;
return sections;
}
case FSCalendarScopeWeek: {
NSInteger sections = [self.gregorian components:NSCalendarUnitWeekOfYear fromDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] toDate:self.maximumDate options:0].weekOfYear + 1;
return sections;
}
}
return self.calculator.numberOfSections;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
@ -556,7 +539,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
FSCalendarCell *cell = (FSCalendarCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.dateIsSelected = YES;
[cell performSelecting];
NSDate *selectedDate = [self dateForIndexPath:indexPath];
NSDate *selectedDate = [self.calculator dateForIndexPath:indexPath];
if (!_supressEvent) {
[self.proxy didSelectDate:selectedDate];
}
@ -573,7 +556,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
}
return NO;
}
NSDate *targetDate = [self dateForIndexPath:indexPath];
NSDate *targetDate = [self.calculator dateForIndexPath:indexPath];
if ([self isDateSelected:targetDate]) {
// Click on a selected date in multiple-selection mode
if (self.allowsMultipleSelection) {
@ -597,7 +580,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.allowsMultipleSelection && self.selectedDate) {
NSIndexPath *selectedIndexPath = [self indexPathForDate:self.selectedDate];
NSIndexPath *selectedIndexPath = [self.calculator indexPathForDate:self.selectedDate];
if (![indexPath isEqual:selectedIndexPath]) {
[self collectionView:collectionView didDeselectItemAtIndexPath:selectedIndexPath];
return;
@ -608,7 +591,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
cell.dateIsSelected = NO;
[cell setNeedsLayout];
}
NSDate *selectedDate = cell.date ?: [self dateForIndexPath:indexPath];
NSDate *selectedDate = cell.date ?: [self.calculator dateForIndexPath:indexPath];
[_selectedDates removeObject:selectedDate];
[self deselectCounterpartDate:selectedDate];
[self.proxy didDeselectDate:selectedDate];
@ -617,13 +600,13 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.allowsMultipleSelection) {
NSIndexPath *selectedIndexPath = [self indexPathForDate:self.selectedDate];
NSIndexPath *selectedIndexPath = [self.calculator indexPathForDate:self.selectedDate];
if (![indexPath isEqual:selectedIndexPath]) {
return [self collectionView:collectionView shouldDeselectItemAtIndexPath:selectedIndexPath];
}
}
FSCalendarCell *cell = (FSCalendarCell *)[collectionView cellForItemAtIndexPath:indexPath];
return [self.proxy shouldDeselectDate:(cell.date?:[self dateForIndexPath:indexPath])];
return [self.proxy shouldDeselectDate:(cell.date?:[self.calculator dateForIndexPath:indexPath])];
}
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
@ -816,7 +799,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
}
if (self.hasValidateVisibleLayout) {
[_collectionView.visibleCells makeObjectsPerformSelector:@selector(setDateIsToday:) withObject:@NO];
[[_collectionView cellForItemAtIndexPath:[self indexPathForDate:today]] setValue:@YES forKey:@"dateIsToday"];
[[_collectionView cellForItemAtIndexPath:[self.calculator indexPathForDate:today]] setValue:@YES forKey:@"dateIsToday"];
[_collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
@ -842,7 +825,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
if (!self.superview) {
return CGRectZero;
}
CGRect frame = [_collectionViewLayout layoutAttributesForItemAtIndexPath:[self indexPathForDate:date]].frame;
CGRect frame = [_collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calculator indexPathForDate:date]].frame;
frame = [self.superview convertRect:frame fromView:_collectionView];
return frame;
}
@ -1132,7 +1115,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
return;
}
[_selectedDates removeObject:date];
NSIndexPath *indexPath = [self indexPathForDate:date];
NSIndexPath *indexPath = [self.calculator indexPathForDate:date];
if ([_collectionView.indexPathsForSelectedItems containsObject:indexPath]) {
[_collectionView deselectItemAtIndexPath:indexPath animated:YES];
FSCalendarCell *cell = (FSCalendarCell *)[_collectionView cellForItemAtIndexPath:indexPath];
@ -1152,7 +1135,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
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 indexPathForDate:targetDate];
NSIndexPath *targetIndexPath = [self.calculator indexPathForDate:targetDate];
BOOL shouldSelect = !_supressEvent;
//
@ -1284,7 +1267,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
{
if (!date) return;
if (![self isDateInRange:date]) {
date = [self safeDateForDate:date];
date = [self.calculator safeDateForDate:date];
if (!date) return;
}
@ -1318,87 +1301,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
}
}
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath scope:(FSCalendarScope)scope
{
if (!indexPath) return nil;
switch (scope) {
case FSCalendarScopeMonth: {
NSDate *currentPage = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:indexPath.section toDate:[self.gregorian fs_firstDayOfMonth:_minimumDate] options:0];
NSInteger numberOfHeadPlaceholders = [self numberOfHeadPlaceholdersForMonth:currentPage];
NSDate *firstDateOfPage = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:-numberOfHeadPlaceholders toDate:currentPage options:0];
switch (_collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
NSUInteger rows = indexPath.item % 6;
NSUInteger columns = indexPath.item / 6;
NSUInteger daysOffset = 7*rows + columns;
NSDate *date = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:daysOffset toDate:firstDateOfPage options:0];
return date;
}
case UICollectionViewScrollDirectionVertical: {
NSUInteger daysOffset = indexPath.item;
NSDate *date = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:daysOffset toDate:firstDateOfPage options:0];
return date;
}
}
break;
}
case FSCalendarScopeWeek: {
NSDate *currentPage = [self.gregorian dateByAddingUnit:NSCalendarUnitWeekOfYear value:indexPath.section toDate:[self.gregorian fs_firstDayOfWeek:_minimumDate] options:0];
NSDate *date = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:indexPath.item toDate:currentPage options:0];
return date;
}
}
return nil;
}
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath
{
if (self.animator.transition == FSCalendarTransitionWeekToMonth && self.animator.state == FSCalendarTransitionStateInProgress) {
return [self dateForIndexPath:indexPath scope:FSCalendarScopeMonth];
}
return [self dateForIndexPath:indexPath scope:_scope];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date 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:date options:0].month;
NSDate *firstDayOfMonth = [self.gregorian fs_firstDayOfMonth:date];
NSInteger numberOfHeadPlaceholders = [self numberOfHeadPlaceholdersForMonth:firstDayOfMonth];
NSDate *firstDateOfPage = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:-numberOfHeadPlaceholders toDate:firstDayOfMonth options:0];
switch (_collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
NSInteger vItem = [self.gregorian components:NSCalendarUnitDay fromDate:firstDateOfPage toDate:date options:0].day;
NSInteger rows = vItem/7;
NSInteger columns = vItem%7;
item = columns*6 + rows;
break;
}
case UICollectionViewScrollDirectionVertical: {
item = [self.gregorian components:NSCalendarUnitDay fromDate:firstDateOfPage toDate:date options:0].day;
break;
}
}
break;
}
case FSCalendarScopeWeek: {
section = [self.gregorian components:NSCalendarUnitWeekOfYear fromDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] toDate:date options:0].weekOfYear;
item = (([self.gregorian component:NSCalendarUnitWeekday fromDate:date] - _firstWeekday) + 7) % 7;
break;
}
}
return [NSIndexPath indexPathForItem:item inSection:section];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date
{
return [self indexPathForDate:date scope:_scope];
}
- (BOOL)isDateInRange:(NSDate *)date
{
@ -1436,7 +1338,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (BOOL)isDateSelected:(NSDate *)date
{
return [_selectedDates containsObject:date] || [_collectionView.indexPathsForSelectedItems containsObject:[self indexPathForDate:date]];
return [_selectedDates containsObject:date] || [_collectionView.indexPathsForSelectedItems containsObject:[self.calculator indexPathForDate:date]];
}
- (BOOL)isDateInDifferentPage:(NSDate *)date
@ -1620,7 +1522,7 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
- (void)reloadDataForCell:(FSCalendarCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
cell.calendar = self;
cell.date = [self dateForIndexPath:indexPath];
cell.date = [self.calculator dateForIndexPath:indexPath];
cell.image = [self.proxy imageForDate:cell.date];
cell.numberOfEvents = [self.proxy numberOfEventsForDate:cell.date];
cell.title = [self.proxy titleForDate:cell.date];
@ -1778,13 +1680,6 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
return orientation;
}
- (NSInteger)numberOfHeadPlaceholdersForMonth:(NSDate *)month
{
NSInteger currentWeekday = [self.gregorian component:NSCalendarUnitWeekday fromDate:month];
NSInteger number = ((currentWeekday- _firstWeekday) + 7) % 7 ?: (7 * (!self.floatingMode&&(self.placeholderType == FSCalendarPlaceholderTypeFillSixRows)));
return number;
}
- (NSInteger)numberOfRowsInMonth:(NSDate *)month
{
if (!month) return 0;
@ -1805,19 +1700,11 @@ typedef NS_ENUM(NSUInteger, FSCalendarOrientation) {
_minimumDate = self.proxy.minimumDateForCalendar;
_maximumDate = self.proxy.maximumDateForCalendar;
NSAssert([self.gregorian compareDate:self.minimumDate toDate:self.maximumDate toUnitGranularity:NSCalendarUnitDay] != NSOrderedDescending, @"The minimum date of calendar should be earlier than the maximum.");
[self.calculator reloadSections];
}
}
- (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;
}
@end

View File

@ -313,8 +313,8 @@
- (void)performBoundingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration
{
if (self.calendarScope != FSCalendarScopeMonth) return;
NSInteger lastRowCount = [self.calendar numberOfRowsInMonth:fromMonth];
NSInteger currentRowCount = [self.calendar numberOfRowsInMonth:toMonth];
NSInteger lastRowCount = [self.calendar.calculator numberOfRowsInMonth:fromMonth];
NSInteger currentRowCount = [self.calendar.calculator numberOfRowsInMonth:toMonth];
if (lastRowCount != currentRowCount) {
CGFloat animationDuration = duration;
CGRect bounds = (CGRect){CGPointZero,[self.calendar sizeThatFits:self.calendar.frame.size scope:FSCalendarScopeMonth]};
@ -399,7 +399,7 @@
#define kCalculateRowNumber \
do { \
UICollectionViewLayoutAttributes *itemAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar indexPathForDate:focusedDate scope:FSCalendarScopeMonth]]; \
UICollectionViewLayoutAttributes *itemAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar.calculator indexPathForDate:focusedDate scope:FSCalendarScopeMonth]]; \
CGPoint focuedCenter = itemAttributes.center; \
if (CGRectContainsPoint(self.collectionView.bounds, focuedCenter)) { \
switch (self.collectionViewLayout.scrollDirection) { \
@ -434,7 +434,7 @@
NSDate *minimumPage = [self.calendar.gregorian fs_firstDayOfMonth:self.calendar.minimumDate];
NSInteger visibleSection = [self.calendar.gregorian components:NSCalendarUnitMonth fromDate:minimumPage toDate:currentPage options:0].month;
NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:visibleSection];
NSDate *firstDate = [self.calendar dateForIndexPath:firstIndexPath scope:FSCalendarScopeMonth];
NSDate *firstDate = [self.calendar.calculator dateForIndexPath:firstIndexPath scope:FSCalendarScopeMonth];
currentPage = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:focusedRowNumber*7 toDate:firstDate options:0];
attributes.focusedRowNumber = focusedRowNumber;
@ -455,7 +455,7 @@
NSDate *focusedDate = self.calendar.selectedDate ?: self.calendar.today;
if (focusedDate) {
UICollectionViewLayoutAttributes *itemAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar indexPathForDate:focusedDate scope:FSCalendarScopeWeek]];
UICollectionViewLayoutAttributes *itemAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar.calculator indexPathForDate:focusedDate scope:FSCalendarScopeWeek]];
CGPoint focuedCenter = itemAttributes.center;
if (!CGRectContainsPoint(self.calendar.collectionView.bounds, focuedCenter)) {
focusedDate = nil;
@ -468,7 +468,7 @@
NSDate *firstDayOfMonth = [self.calendar.gregorian fs_firstDayOfMonth:focusedDate];
attributes.focusedDate = focusedDate;
firstDayOfMonth = firstDayOfMonth ?: [self.calendar.gregorian fs_firstDayOfMonth:currentPage];
NSInteger numberOfPlaceholdersForPrev = [self.calendar numberOfHeadPlaceholdersForMonth:firstDayOfMonth];
NSInteger numberOfPlaceholdersForPrev = [self.calendar.calculator numberOfHeadPlaceholdersForMonth:firstDayOfMonth];
NSDate *firstDateOfPage = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:-numberOfPlaceholdersForPrev toDate:firstDayOfMonth options:0];
for (int i = 0; i < 6; i++) {
@ -534,7 +534,7 @@
self.calendar.preferredHeaderHeight+
self.calendar.preferredWeekdayHeight+
self.calendar.preferredPadding*2+
([self.calendar numberOfRowsInMonth:page]*self.calendar.preferredRowHeight)+
([self.calendar.calculator numberOfRowsInMonth:page]*self.calendar.preferredRowHeight)+
self.calendar.scopeHandle.fs_height);
}
break;

View File

@ -657,11 +657,13 @@
- (void)invalidateTitleFont
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
_calendar.calculator.titleHeight = -1;
}
- (void)invalidateSubtitleFont
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
_calendar.calculator.subtitleHeight = -1;
}
- (void)invalidateTitleTextColor

View File

@ -0,0 +1,39 @@
//
// FSCalendarCalculator.h
// FSCalendar
//
// Created by dingwenchao on 30/10/2016.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
@class FSCalendar;
@interface FSCalendarCalculator : NSObject
@property (weak , nonatomic) FSCalendar *calendar;
@property (assign, nonatomic) CGFloat monthHeight;
@property (assign, nonatomic) CGFloat titleHeight;
@property (assign, nonatomic) CGFloat subtitleHeight;
@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;
- (NSInteger)numberOfHeadPlaceholdersForMonth:(NSDate *)month;
- (NSInteger)numberOfRowsInMonth:(NSDate *)month;
- (NSInteger)numberOfRowsInSection:(NSInteger)section;
- (void)reloadSections;
@end

View File

@ -0,0 +1,278 @@
//
// FSCalendarCalculator.m
// FSCalendar
//
// Created by dingwenchao on 30/10/2016.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import "FSCalendar.h"
#import "FSCalendarCalculator.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarCalculator () <NSCacheDelegate>
@property (assign, nonatomic) NSInteger numberOfMonths;
@property (strong, nonatomic) NSCache<NSNumber *, NSDate *> *months;
@property (strong, nonatomic) NSCache<NSNumber *, NSDate *> *monthHeads;
@property (assign, nonatomic) NSInteger numberOfWeeks;
@property (strong, nonatomic) NSCache<NSNumber *, NSDate *> *weeks;
@property (strong, nonatomic) NSCache<NSDate *, NSNumber *> *rowNumbers;
@property (readonly, nonatomic) NSCalendar *gregorian;
@property (readonly, nonatomic) NSDate *minimumDate;
@property (readonly, nonatomic) NSDate *maximumDate;
- (NSDate *)weekForSection:(NSInteger)section;
- (NSDate *)monthForSection:(NSInteger)section;
- (NSDate *)monthHeadForSection:(NSInteger)section;
@end
@implementation FSCalendarCalculator
- (instancetype)initWithCalendar:(FSCalendar *)calendar
{
self = [super init];
if (self) {
self.calendar = calendar;
self.monthHeight = -1;
self.titleHeight = -1;
self.subtitleHeight = -1;
self.months = [[NSCache alloc] init];
self.months.countLimit = 40;
self.months.delegate = self;
self.monthHeads = [[NSCache alloc] init];
self.monthHeads.countLimit = 40;
self.monthHeads.delegate = self;
self.weeks = [[NSCache alloc] init];
self.weeks.countLimit = 30;
self.weeks.delegate = self;
self.rowNumbers = [[NSCache alloc] init];
self.rowNumbers.countLimit = 40;
self.rowNumbers.delegate = self;
}
return self;
}
#pragma mark - <NSCacheDelegate>
- (void)cache:(NSCache *)cache willEvictObject:(id)obj { }
#pragma mark - Public methods
- (CGFloat)titleHeight
{
if (_titleHeight == -1) {
_titleHeight = [@"1" sizeWithAttributes:@{NSFontAttributeName:self.calendar.appearance.titleFont}].height;
}
return _titleHeight;
}
- (CGFloat)subtitleHeight
{
if (_subtitleHeight == -1) {
_subtitleHeight = [@"1" sizeWithAttributes:@{NSFontAttributeName:self.calendar.appearance.subtitleFont}].height;
}
return _subtitleHeight;
}
- (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];
switch (self.calendar.collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
NSUInteger rows = indexPath.item % 6;
NSUInteger columns = indexPath.item / 6;
NSUInteger daysOffset = 7*rows + columns;
NSDate *date = [self.gregorian dateByAddingUnit:NSCalendarUnitDay value:daysOffset toDate:head options:0];
return date;
}
case UICollectionViewScrollDirectionVertical: {
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 (self.calendar.animator.transition == FSCalendarTransitionWeekToMonth && self.calendar.animator.state == FSCalendarTransitionStateInProgress) {
return [self dateForIndexPath:indexPath scope:FSCalendarScopeMonth];
}
return [self dateForIndexPath:indexPath scope:self.calendar.scope];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date 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:date options:0].month;
NSDate *head = [self monthHeadForSection:section];
switch (self.calendar.collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
NSInteger vItem = [self.gregorian components:NSCalendarUnitDay fromDate:head toDate:date options:0].day;
NSInteger rows = vItem/7;
NSInteger columns = vItem%7;
item = columns*6 + rows;
break;
}
case UICollectionViewScrollDirectionVertical: {
item = [self.gregorian components:NSCalendarUnitDay fromDate:head toDate:date options:0].day;
break;
}
}
break;
}
case FSCalendarScopeWeek: {
section = [self.gregorian components:NSCalendarUnitWeekOfYear fromDate:[self.gregorian fs_firstDayOfWeek:self.minimumDate] toDate:date options:0].weekOfYear;
item = (([self.gregorian component:NSCalendarUnitWeekday fromDate:date] - self.gregorian.firstWeekday) + 7) % 7;
break;
}
}
return [NSIndexPath indexPathForItem:item inSection:section];
}
- (NSIndexPath *)indexPathForDate:(NSDate *)date
{
return [self indexPathForDate:date scope:self.calendar.scope];
}
- (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.months removeAllObjects];
[self.monthHeads removeAllObjects];
[self.weeks removeAllObjects];
[self.rowNumbers removeAllObjects];
}
- (NSInteger)numberOfSections
{
if (self.calendar.animator.transition == FSCalendarTransitionWeekToMonth) {
return self.numberOfMonths;
} else {
switch (self.calendar.scope) {
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 *rowNumber = self.rowNumbers[month];
if (!rowNumber) {
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);
self.rowNumbers[month] = @(numberOfRows);
}
return rowNumber.integerValue;
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
if (self.calendar.scope == FSCalendarScopeWeek) return 1;
NSDate *month = [self monthForSection:section];
return [self numberOfRowsInMonth:month];
}
#pragma mark - Private methods
- (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;
}
- (NSCalendar *)gregorian { return self.calendar.gregorian; }
- (NSDate *)minimumDate { return self.calendar.minimumDate; }
- (NSDate *)maximumDate { return self.calendar.maximumDate; }
@end

View File

@ -153,7 +153,7 @@
NSIndexPath *indexPath = [self.calendar.collectionView indexPathForCell:self];
NSInteger lineCount = [self.calendar numberOfRowsInMonth:self.month];
NSInteger lineCount = [self.calendar.calculator numberOfRowsInMonth:self.month];
if (lineCount == 6) {
self.contentView.hidden = NO;
} else {
@ -187,8 +187,8 @@
if (_needsAdjustingViewFrame || CGSizeEqualToSize(_titleLabel.frame.size, CGSizeZero)) {
_needsAdjustingViewFrame = NO;
if (_subtitle) {
CGFloat titleHeight = [@"1" sizeWithAttributes:@{NSFontAttributeName:_titleLabel.font}].height;
CGFloat subtitleHeight = [@"1" sizeWithAttributes:@{NSFontAttributeName:_subtitleLabel.font}].height;
CGFloat titleHeight = self.calendar.calculator.titleHeight;
CGFloat subtitleHeight = self.calendar.calculator.subtitleHeight;
CGFloat height = titleHeight + subtitleHeight;
_titleLabel.frame = CGRectMake(

View File

@ -12,7 +12,6 @@
#import "FSCalendarCollectionView.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarConstants.h"
#import <objc/runtime.h>
#define kFSCalendarSeparatorInterRows @"FSCalendarSeparatorInterRows"
#define kFSCalendarSeparatorInterColumns @"FSCalendarSeparatorInterColumns"
@ -110,15 +109,16 @@
return NO;
}
NSDate *currentPage = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitMonth value:evaluatedObject.indexPath.section toDate:[self.calendar.gregorian fs_firstDayOfMonth:self.calendar.minimumDate] options:0];
NSInteger numberOfRows = [self.calendar numberOfRowsInMonth:currentPage];
NSInteger numberOfRows = [self.calendar.calculator numberOfRowsInSection:evaluatedObject.indexPath.section];
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
return evaluatedObject.indexPath.item < numberOfRows-1;
}
case UICollectionViewScrollDirectionVertical: {
return evaluatedObject.indexPath.item%7==0 && evaluatedObject.indexPath.item/7<numberOfRows-1;
BOOL isValid = evaluatedObject.indexPath.item == 0;
isValid |= (evaluatedObject.indexPath.item%7==0 && evaluatedObject.indexPath.item/7<numberOfRows-1);
return isValid;
}
}
return NO;

View File

@ -18,7 +18,9 @@
#import "FSCalendarCollectionView.h"
#import "FSCalendarCollectionViewLayout.h"
#import "FSCalendarScopeHandle.h"
#import "FSCalendarCalculator.h"
#import "FSCalendarAnimator.h"
#import "FSCalendarDelegateProxy.h"
@interface FSCalendar (Dynamic)
@ -28,6 +30,8 @@
@property (readonly, nonatomic) FSCalendarScopeHandle *scopeHandle;
@property (readonly, nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout;
@property (readonly, nonatomic) FSCalendarAnimator *animator;
@property (readonly, nonatomic) FSCalendarCalculator *calculator;
@property (readonly, nonatomic) FSCalendarDelegateProxy *proxy;
@property (readonly, nonatomic) NSArray<UILabel *> *weekdays;
@property (readonly, nonatomic) BOOL floatingMode;
@property (readonly, nonatomic) NSArray *visibleStickyHeaders;
@ -57,13 +61,6 @@
- (BOOL)isPageInRange:(NSDate *)page;
- (BOOL)isDateInRange:(NSDate *)date;
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath;
- (NSDate *)dateForIndexPath:(NSIndexPath *)indexPath scope:(FSCalendarScope)scope;
- (NSIndexPath *)indexPathForDate:(NSDate *)date;
- (NSIndexPath *)indexPathForDate:(NSDate *)date scope:(FSCalendarScope)scope;
- (NSInteger)numberOfHeadPlaceholdersForMonth:(NSDate *)month;
- (NSInteger)numberOfRowsInMonth:(NSDate *)month;
- (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope;

View File

@ -48,6 +48,13 @@ NS_ASSUME_NONNULL_BEGIN
@end
@interface NSCache (FSCalendarExtensions)
- (void)setObject:(nullable id)obj forKeyedSubscript:(id<NSCopying>)key;
- (id)objectForKeyedSubscript:(id<NSCopying>)key;
@end
@interface NSObject (FSCalendarExtensions)

View File

@ -222,6 +222,26 @@
@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)
- (void)fs_setVariable:(id)variable forKey:(NSString *)key