Versión estable 2018

This commit is contained in:
quique 2019-01-02 08:33:54 +01:00
parent e2649847ef
commit 459227bf4d
216 changed files with 20327 additions and 5233 deletions

View File

@ -7,7 +7,7 @@
<key>BaseLibrary.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>6</integer>
</dict>
</dict>
</dict>

View File

@ -2,5 +2,8 @@ use_frameworks!
platform :ios, '8.0'
pod 'FSCalendar'
target "Verdnaturaventas" do
pod 'Firebase/Core'
end

View File

@ -1,10 +1,51 @@
PODS:
- FSCalendar (2.0.1)
- Firebase/Core (5.3.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 5.0.1)
- Firebase/CoreOnly (5.3.0):
- FirebaseCore (= 5.0.4)
- FirebaseAnalytics (5.0.1):
- FirebaseCore (~> 5.0)
- FirebaseInstanceID (~> 3.0)
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- nanopb (~> 0.3)
- FirebaseCore (5.0.4):
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
- FirebaseInstanceID (3.1.1):
- FirebaseCore (~> 5.0)
- FSCalendar (2.7.9)
- GoogleToolboxForMac/Defines (2.1.4)
- "GoogleToolboxForMac/NSData+zlib (2.1.4)":
- GoogleToolboxForMac/Defines (= 2.1.4)
- nanopb (0.3.8):
- nanopb/decode (= 0.3.8)
- nanopb/encode (= 0.3.8)
- nanopb/decode (0.3.8)
- nanopb/encode (0.3.8)
DEPENDENCIES:
- Firebase/Core
- FSCalendar
SPEC CHECKSUMS:
FSCalendar: 18117308f5876a8f6f0fe572085d543580c2c5e5
SPEC REPOS:
https://github.com/cocoapods/specs.git:
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseInstanceID
- FSCalendar
- GoogleToolboxForMac
- nanopb
COCOAPODS: 0.39.0
SPEC CHECKSUMS:
Firebase: 68afeeb05461db02d7c9e3215cda28068670f4aa
FirebaseAnalytics: b3628aea54c50464c32c393fb2ea032566e7ecc2
FirebaseCore: 62f1b792a49bb9e8b4073f24606d2c93ffc352f0
FirebaseInstanceID: f3f0657372592ecdfdfe2cac604a5a75758376a6
FSCalendar: a04b09f16f811bc92e82f3cf852a15225233b9d5
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3
PODFILE CHECKSUM: 0645cd0766eb63ba97b1572c6e02dd27818977f2
COCOAPODS: 1.5.3

View File

@ -1,21 +0,0 @@
//
// CALayer+FSExtension.h
// FSCalendar
//
// Created by dingwenchao on 2/3/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import <QuartzCore/QuartzCore.h>
@interface CALayer (FSExtension)
@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

View File

@ -1,73 +0,0 @@
//
// CALayer+FSExtension.m
// FSCalendar
//
// Created by dingwenchao on 2/3/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import "CALayer+FSExtension.h"
@implementation CALayer (FSExtension)
- (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

View File

@ -0,0 +1,247 @@
//
// FSCalendar+Deprecated.m
// FSCalendar
//
// Created by dingwenchao on 4/29/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendar.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarDynamicHeader.h"
#pragma mark - Deprecate
@implementation FSCalendar (Deprecated)
@dynamic identifier, lineHeightMultiplier;
- (void)setShowsPlaceholders:(BOOL)showsPlaceholders
{
self.placeholderType = showsPlaceholders ? FSCalendarPlaceholderTypeFillSixRows : FSCalendarPlaceholderTypeNone;
}
- (BOOL)showsPlaceholders
{
return self.placeholderType == FSCalendarPlaceholderTypeFillSixRows;
}
#pragma mark - Public methods
- (NSInteger)yearOfDate:(NSDate *)date
{
if (!date) return NSNotFound;
NSDateComponents *component = [self.gregorian components:NSCalendarUnitYear fromDate:date];
return component.year;
}
- (NSInteger)monthOfDate:(NSDate *)date
{
if (!date) return NSNotFound;
NSDateComponents *component = [self.gregorian components:NSCalendarUnitMonth
fromDate:date];
return component.month;
}
- (NSInteger)dayOfDate:(NSDate *)date
{
if (!date) return NSNotFound;
NSDateComponents *component = [self.gregorian components:NSCalendarUnitDay
fromDate:date];
return component.day;
}
- (NSInteger)weekdayOfDate:(NSDate *)date
{
if (!date) return NSNotFound;
NSDateComponents *component = [self.gregorian components:NSCalendarUnitWeekday fromDate:date];
return component.weekday;
}
- (NSInteger)weekOfDate:(NSDate *)date
{
if (!date) return NSNotFound;
NSDateComponents *component = [self.gregorian components:NSCalendarUnitWeekOfYear fromDate:date];
return component.weekOfYear;
}
- (NSDate *)dateByIgnoringTimeComponentsOfDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = [self.gregorian components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:date];
return [self.gregorian dateFromComponents:components];
}
- (NSDate *)tomorrowOfDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = [self.gregorian components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour fromDate:date];
components.day++;
components.hour = FSCalendarDefaultHourComponent;
return [self.gregorian dateFromComponents:components];
}
- (NSDate *)yesterdayOfDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = [self.gregorian components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour fromDate:date];
components.day--;
components.hour = FSCalendarDefaultHourComponent;
return [self.gregorian dateFromComponents:components];
}
- (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day
{
NSDateComponents *components = self.components;
components.year = year;
components.month = month;
components.day = day;
components.hour = FSCalendarDefaultHourComponent;
NSDate *date = [self.gregorian dateFromComponents:components];
components.year = NSIntegerMax;
components.month = NSIntegerMax;
components.day = NSIntegerMax;
components.hour = NSIntegerMax;
return date;
}
- (NSDate *)dateByAddingYears:(NSInteger)years toDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = self.components;
components.year = years;
NSDate *d = [self.gregorian dateByAddingComponents:components toDate:date options:0];
components.year = NSIntegerMax;
return d;
}
- (NSDate *)dateBySubstractingYears:(NSInteger)years fromDate:(NSDate *)date
{
if (!date) return nil;
return [self dateByAddingYears:-years toDate:date];
}
- (NSDate *)dateByAddingMonths:(NSInteger)months toDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = self.components;
components.month = months;
NSDate *d = [self.gregorian dateByAddingComponents:components toDate:date options:0];
components.month = NSIntegerMax;
return d;
}
- (NSDate *)dateBySubstractingMonths:(NSInteger)months fromDate:(NSDate *)date
{
return [self dateByAddingMonths:-months toDate:date];
}
- (NSDate *)dateByAddingWeeks:(NSInteger)weeks toDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = self.components;
components.weekOfYear = weeks;
NSDate *d = [self.gregorian dateByAddingComponents:components toDate:date options:0];
components.weekOfYear = NSIntegerMax;
return d;
}
- (NSDate *)dateBySubstractingWeeks:(NSInteger)weeks fromDate:(NSDate *)date
{
return [self dateByAddingWeeks:-weeks toDate:date];
}
- (NSDate *)dateByAddingDays:(NSInteger)days toDate:(NSDate *)date
{
if (!date) return nil;
NSDateComponents *components = self.components;
components.day = days;
NSDate *d = [self.gregorian dateByAddingComponents:components toDate:date options:0];
components.day = NSIntegerMax;
return d;
}
- (NSDate *)dateBySubstractingDays:(NSInteger)days fromDate:(NSDate *)date
{
return [self dateByAddingDays:-days toDate:date];
}
- (NSInteger)yearsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate
{
NSDateComponents *components = [self.gregorian components:NSCalendarUnitYear
fromDate:fromDate
toDate:toDate
options:0];
return components.year;
}
- (NSInteger)monthsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate
{
NSDateComponents *components = [self.gregorian components:NSCalendarUnitMonth
fromDate:fromDate
toDate:toDate
options:0];
return components.month;
}
- (NSInteger)weeksFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate
{
NSDateComponents *components = [self.gregorian components:NSCalendarUnitWeekOfYear
fromDate:fromDate
toDate:toDate
options:0];
return components.weekOfYear;
}
- (NSInteger)daysFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate
{
NSDateComponents *components = [self.gregorian components:NSCalendarUnitDay
fromDate:fromDate
toDate:toDate
options:0];
return components.day;
}
- (BOOL)isDate:(NSDate *)date1 equalToDate:(NSDate *)date2 toCalendarUnit:(FSCalendarUnit)unit
{
switch (unit) {
case FSCalendarUnitMonth:
return [self.gregorian isDate:date1 equalToDate:date2 toUnitGranularity:NSCalendarUnitMonth];
case FSCalendarUnitWeekOfYear:
return [self.gregorian isDate:date1 equalToDate:date2 toUnitGranularity:NSCalendarUnitYear];
case FSCalendarUnitDay:
return [self.gregorian isDate:date1 inSameDayAsDate:date2];
}
return NO;
}
- (BOOL)isDateInToday:(NSDate *)date
{
return [self isDate:date equalToDate:[NSDate date] toCalendarUnit:FSCalendarUnitDay];
}
- (void)setIdentifier:(NSString *)identifier
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:identifier];
[self setValue:gregorian forKey:@"gregorian"];
[self fs_performSelector:NSSelectorFromString(@"invalidateDateTools") withObjects:nil, nil];
if ([[self valueForKey:@"hasValidateVisibleLayout"] boolValue]) {
[self reloadData];
}
[self fs_setVariable:[self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:self.minimumDate options:0] forKey:@"_minimumDate"];
[self fs_setVariable:[self.gregorian dateBySettingHour:0 minute:0 second:0 ofDate:self.currentPage options:0] forKey:@"_currentPage"];
[self fs_performSelector:NSSelectorFromString(@"scrollToPageForDate:animated") withObjects:self.today, @NO, nil];
}
- (NSString *)identifier
{
return self.gregorian.calendarIdentifier;
}
- (void)setLineHeightMultiplier:(CGFloat)lineHeightMultiplier
{
self.rowHeight = FSCalendarStandardRowHeight*MAX(1, FSCalendarDeviceIsIPad*1.5);
}
@end

View File

@ -1,55 +0,0 @@
//
// FSCalendar+IBExtension.h
// FSCalendar
//
// Created by Wenchao Ding on 8/14/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
// 注意: 这些方法仅仅为了在IB中使用不建议直接调用。这些方法在calendar.appearance中使用。如: calendar.appearance.eventColor
// Warning: For IB usage only. Directly calling these methods is deprecated. Use calendar.appearance(FSCalendarAppearance) instead
#import "FSCalendar.h"
#import "FSCalendarConstance.h"
IB_DESIGNABLE
@interface FSCalendar (IBExtension)
@property (assign, nonatomic) IBInspectable BOOL adjustsFontSizeToFitContentSize;
@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 *eventColor;
@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 FSCalendarCellShape cellShape;
@property (assign, nonatomic) IBInspectable BOOL useVeryShortWeekdaySymbols;
@property (assign, nonatomic) IBInspectable BOOL fakeSubtitles;
@property (assign, nonatomic) IBInspectable NSInteger fakedSelectedDay;
@end

View File

@ -1,378 +0,0 @@
//
// FSCalendar+IBExtension.m
// FSCalendar
//
// Created by Wenchao Ding on 8/14/15.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendar+IBExtension.h"
@implementation FSCalendar (IBExtension)
#if !TARGET_INTERFACE_BUILDER
@dynamic fakedSelectedDay,fakeSubtitles;
#endif
#pragma mark - adjustsFontSizeToFitContentSize
- (void)setAdjustsFontSizeToFitContentSize:(BOOL)adjustsFontSizeToFitContentSize
{
self.appearance.adjustsFontSizeToFitContentSize = adjustsFontSizeToFitContentSize;
}
- (BOOL)adjustsFontSizeToFitContentSize
{
return self.appearance.adjustsFontSizeToFitContentSize;
}
#pragma mark - eventColor
- (void)setEventColor:(UIColor *)eventColor
{
self.appearance.eventColor = eventColor;
}
- (UIColor *)eventColor
{
return self.appearance.eventColor;
}
#pragma mark - weekdayTextColor
- (void)setWeekdayTextColor:(UIColor *)weekdayTextColor
{
self.appearance.weekdayTextColor = weekdayTextColor;
}
- (UIColor *)weekdayTextColor
{
return self.appearance.weekdayTextColor;
}
#pragma mark - headerTitleColor
- (void)setHeaderTitleColor:(UIColor *)headerTitleColor
{
self.appearance.headerTitleColor = headerTitleColor;
}
- (UIColor *)headerTitleColor
{
return self.appearance.headerTitleColor;
}
#pragma mark - headerDateFormat
- (void)setHeaderDateFormat:(NSString *)headerDateFormat
{
self.appearance.headerDateFormat = headerDateFormat;
}
- (NSString *)headerDateFormat
{
return self.appearance.headerDateFormat;
}
#pragma mark - headerMinimumDissolvedAlpha
- (void)setHeaderMinimumDissolvedAlpha:(CGFloat)headerMinimumDissolvedAlpha
{
self.appearance.headerMinimumDissolvedAlpha = headerMinimumDissolvedAlpha;
}
- (CGFloat)headerMinimumDissolvedAlpha
{
return self.appearance.headerMinimumDissolvedAlpha;
}
#pragma mark - titleDefaultColor
- (void)setTitleDefaultColor:(UIColor *)titleDefaultColor
{
self.appearance.titleDefaultColor = titleDefaultColor;
}
- (UIColor *)titleDefaultColor
{
return self.appearance.titleDefaultColor;
}
#pragma mark - titleSelectionColor
- (void)setTitleSelectionColor:(UIColor *)titleSelectionColor
{
self.appearance.titleSelectionColor = titleSelectionColor;
}
- (UIColor *)titleSelectionColor
{
return self.appearance.titleSelectionColor;
}
#pragma mark - titleTodayColor
- (void)setTitleTodayColor:(UIColor *)titleTodayColor
{
self.appearance.titleTodayColor = titleTodayColor;
}
- (UIColor *)titleTodayColor
{
return self.appearance.titleTodayColor;
}
#pragma mark - titlePlaceholderColor
- (void)setTitlePlaceholderColor:(UIColor *)titlePlaceholderColor
{
self.appearance.titlePlaceholderColor = titlePlaceholderColor;
}
- (UIColor *)titlePlaceholderColor
{
return self.appearance.titlePlaceholderColor;
}
#pragma mark - titleWeekendColor
- (void)setTitleWeekendColor:(UIColor *)titleWeekendColor
{
self.appearance.titleWeekendColor = titleWeekendColor;
}
- (UIColor *)titleWeekendColor
{
return self.appearance.titleWeekendColor;
}
#pragma mark - subtitleDefaultColor
- (void)setSubtitleDefaultColor:(UIColor *)subtitleDefaultColor
{
self.appearance.subtitleDefaultColor = subtitleDefaultColor;
}
- (UIColor *)subtitleDefaultColor
{
return self.appearance.subtitleDefaultColor;
}
#pragma mark - subtitleSelectionColor
- (void)setSubtitleSelectionColor:(UIColor *)subtitleSelectionColor
{
self.appearance.subtitleSelectionColor = subtitleSelectionColor;
}
- (UIColor *)subtitleSelectionColor
{
return self.appearance.subtitleSelectionColor;
}
#pragma mark - subtitleTodayColor
- (void)setSubtitleTodayColor:(UIColor *)subtitleTodayColor
{
self.appearance.subtitleTodayColor = subtitleTodayColor;
}
- (UIColor *)subtitleTodayColor
{
return self.appearance.subtitleTodayColor;
}
#pragma mark - subtitlePlaceholderColor
- (void)setSubtitlePlaceholderColor:(UIColor *)subtitlePlaceholderColor
{
self.appearance.subtitlePlaceholderColor = subtitlePlaceholderColor;
}
- (UIColor *)subtitlePlaceholderColor
{
return self.appearance.subtitlePlaceholderColor;
}
#pragma mark - subtitleWeekendColor
- (void)setSubtitleWeekendColor:(UIColor *)subtitleWeekendColor
{
self.appearance.subtitleWeekendColor = subtitleWeekendColor;
}
- (UIColor *)subtitleWeekendColor
{
return self.appearance.subtitleWeekendColor;
}
#pragma mark - selectionColor
- (void)setSelectionColor:(UIColor *)selectionColor
{
self.appearance.selectionColor = selectionColor;
}
- (UIColor *)selectionColor
{
return self.appearance.selectionColor;
}
#pragma mark - todayColor
- (void)setTodayColor:(UIColor *)todayColor
{
self.appearance.todayColor = todayColor;
}
- (UIColor *)todayColor
{
return self.appearance.todayColor;
}
#pragma mark - todaySelectionColor
- (void)setTodaySelectionColor:(UIColor *)todaySelectionColor
{
self.appearance.todaySelectionColor = todaySelectionColor;
}
- (UIColor *)todaySelectionColor
{
return self.appearance.todaySelectionColor;
}
#pragma mark - borderDefaultColor
- (void)setBorderDefaultColor:(UIColor *)borderDefaultColor
{
self.appearance.borderDefaultColor = borderDefaultColor;
}
- (UIColor *)borderDefaultColor
{
return self.appearance.borderDefaultColor;
}
#pragma mark - borderSelectionColor
- (void)setBorderSelectionColor:(UIColor *)borderSelectionColor
{
self.appearance.borderSelectionColor = borderSelectionColor;
}
- (UIColor *)borderSelectionColor
{
return self.appearance.borderSelectionColor;
}
#pragma mark - cellStyle
- (void)setCellShape:(FSCalendarCellShape)cellShape
{
self.appearance.cellShape = cellShape;
}
- (FSCalendarCellShape)cellShape
{
return self.appearance.cellShape;
}
#pragma mark - useVeryShortWeekdaySymbols
- (void)setUseVeryShortWeekdaySymbols:(BOOL)useVeryShortWeekdaySymbols
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
self.appearance.useVeryShortWeekdaySymbols = useVeryShortWeekdaySymbols;
#pragma GCC diagnostic pop
}
- (BOOL)useVeryShortWeekdaySymbols
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
return self.appearance.useVeryShortWeekdaySymbols;
#pragma GCC diagnostic pop
}
#pragma mark - fakeSubtitles
- (void)setFakeSubtitles:(BOOL)fakeSubtitles
{
#if TARGET_INTERFACE_BUILDER
self.appearance.fakeSubtitles = fakeSubtitles;
#endif
}
#if TARGET_INTERFACE_BUILDER
- (BOOL)fakeSubtitles
{
return self.appearance.fakeSubtitles;
}
#endif
#pragma mark - fakedSelectedDay
- (void)setFakedSelectedDay:(NSInteger)fakedSelectedDay
{
#if TARGET_INTERFACE_BUILDER
self.appearance.fakedSelectedDay = fakedSelectedDay;
#endif
}
#if TARGET_INTERFACE_BUILDER
- (NSInteger)fakedSelectedDay
{
return self.appearance.fakedSelectedDay;
}
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)setTitleTextSize:(CGFloat)titleTextSize
{
self.appearance.titleTextSize = titleTextSize;
}
- (CGFloat)titleTextSize
{
return self.appearance.titleTextSize;
}
- (void)setSubtitleTextSize:(CGFloat)subtitleTextSize
{
self.appearance.subtitleTextSize = subtitleTextSize;
}
- (CGFloat)subtitleTextSize
{
return self.appearance.subtitleTextSize;
}
- (void)setWeekdayTextSize:(CGFloat)weekdayTextSize
{
self.appearance.weekdayTextSize = weekdayTextSize;
}
- (CGFloat)weekdayTextSize
{
return self.appearance.weekdayTextSize;
}
- (void)setHeaderTitleTextSize:(CGFloat)headerTitleTextSize
{
self.appearance.headerTitleTextSize = headerTitleTextSize;
}
- (CGFloat)headerTitleTextSize
{
return self.appearance.headerTitleTextSize;
}
#pragma GCC diagnostic pop
@end

View File

@ -7,22 +7,20 @@
//
// 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 "FSCalendarConstance.h"
/**
* FSCalendar is a superior awesome calendar control with high performance, high customizablility and very simple usage.
*
* @warning All NSDate instances used in the calendar should be managed by the DateTools category. See FSCalendar+DateTools.
*
* @see FSCalendarDataSource
* @see FSCalendarDelegate
* @see FSCalendarDelegateAppearance
* @see FSCalendarAppearance
* @see FSCalendar+DateTools
*/
#import "FSCalendarConstants.h"
#import "FSCalendarCell.h"
#import "FSCalendarWeekdayView.h"
#import "FSCalendarHeaderView.h"
//! Project version number for FSCalendar.
FOUNDATION_EXPORT double FSCalendarVersionNumber;
@ -40,10 +38,18 @@ typedef NS_ENUM(NSUInteger, FSCalendarScrollDirection) {
FSCalendarScrollDirectionHorizontal
};
typedef NS_ENUM(NSUInteger, FSCalendarUnit) {
FSCalendarUnitMonth = NSCalendarUnitMonth,
FSCalendarUnitWeekOfYear = NSCalendarUnitWeekOfYear,
FSCalendarUnitDay = NSCalendarUnitDay
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
@ -51,12 +57,17 @@ NS_ASSUME_NONNULL_BEGIN
@class FSCalendar;
/**
* FSCalendarDataSource is a source set of FSCalendar. The basic job is to provide eventsubtitle and min/max day to display for calendar.
* FSCalendarDataSource is a source set of FSCalendar. The basic role is to provide eventsubtitle 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.
*/
@ -77,11 +88,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (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;
*/
@ -103,56 +118,70 @@ NS_ASSUME_NONNULL_BEGIN
@optional
/**
* Asks the delegate whether the specific date is allowed to be selected by tapping.
Asks the delegate whether the specific date is allowed to be selected by tapping.
*/
- (BOOL)calendar:(FSCalendar *)calendar shouldSelectDate:(NSDate *)date;
- (BOOL)calendar:(FSCalendar *)calendar shouldSelectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
* Tells the delegate a date in the calendar is selected by tapping.
Tells the delegate a date in the calendar is selected by tapping.
*/
- (void)calendar:(FSCalendar *)calendar didSelectDate:(NSDate *)date;
- (void)calendar:(FSCalendar *)calendar didSelectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
* Asks the delegate whether the specific date is allowed to be deselected by tapping.
Asks the delegate whether the specific date is allowed to be deselected by tapping.
*/
- (BOOL)calendar:(FSCalendar *)calendar shouldDeselectDate:(NSDate *)date;
- (BOOL)calendar:(FSCalendar *)calendar shouldDeselectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
* Tells the delegate a date in the calendar is deselected by tapping.
Tells the delegate a date in the calendar is deselected by tapping.
*/
- (void)calendar:(FSCalendar *)calendar didDeselectDate:(NSDate *)date;
- (void)calendar:(FSCalendar *)calendar didDeselectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition;
/**
* Tells the delegate the calendar is about to change the bounding rect.
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 the calendar is about to change the current page.
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;
/**
* These functions are deprecated
These functions are deprecated
*/
- (void)calendarCurrentScopeWillChange:(FSCalendar *)calendar animated:(BOOL)animated FSCalendarDeprecated(-calendar:boundingRectWillChange:animated:);
- (void)calendarCurrentMonthDidChange:(FSCalendar *)calendar FSCalendarDeprecated(-calendarCurrentPageDidChange:);
- (BOOL)calendar:(FSCalendar *)calendar shouldSelectDate:(NSDate *)date FSCalendarDeprecated(-calendar:shouldSelectDate:atMonthPosition:);- (void)calendar:(FSCalendar *)calendar didSelectDate:(NSDate *)date FSCalendarDeprecated(-calendar:didSelectDate:atMonthPosition:);
- (BOOL)calendar:(FSCalendar *)calendar shouldDeselectDate:(NSDate *)date FSCalendarDeprecated(-calendar:shouldDeselectDate:atMonthPosition:);
- (void)calendar:(FSCalendar *)calendar didDeselectDate:(NSDate *)date FSCalendarDeprecated(-calendar:didDeselectDate:atMonthPosition:);
@end
/**
* FSCalendarDelegateAppearance determines the fonts and colors of components in the calendar, but more specificly. Basely, if you need to make a global customization of appearance of the calendar, use FSCalendarAppearance. But if you need different appearance for different day, use FSCalendarDelegateAppearance.
* 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 <NSObject>
@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 selectionColorForDate:(NSDate *)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.
@ -175,14 +204,14 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance subtitleSelectionColorForDate:(NSDate *)date;
/**
* Asks the delegate for single event color for the specific date.
* Asks the delegate for event colors for the specific date.
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventColorForDate:(NSDate *)date;
- (nullable NSArray<UIColor *> *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventDefaultColorsForDate:(NSDate *)date;
/**
* Asks the delegate for multiple event colors for the specific date.
* Asks the delegate for multiple event colors in selected state for the specific date.
*/
- (nullable NSArray *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventColorsForDate:(NSDate *)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.
@ -195,15 +224,39 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance borderSelectionColorForDate:(NSDate *)date;
/**
* Asks the delegate for a shape for the specific date.
* Asks the delegate for an offset for day text for the specific date.
*/
- (FSCalendarCellShape)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance cellShapeForDate:(NSDate *)date;
- (CGPoint)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance titleOffsetForDate:(NSDate *)date;
/**
* This function is deprecated
* Asks the delegate for an offset for subtitle for the specific date.
*/
- (FSCalendarCellStyle)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance cellStyleForDate:(NSDate *)date FSCalendarDeprecated(-calendar:appearance:cellShapeForDate:);
- (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;
/**
* These functions are deprecated
*/
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance fillColorForDate:(NSDate *)date FSCalendarDeprecated(-calendar:appearance:fillDefaultColorForDate:);
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance selectionColorForDate:(NSDate *)date FSCalendarDeprecated(-calendar:appearance:fillSelectionColorForDate:);
- (nullable UIColor *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventColorForDate:(NSDate *)date FSCalendarDeprecated(-calendar:appearance:eventDefaultColorsForDate:);
- (nullable NSArray *)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance eventColorsForDate:(NSDate *)date FSCalendarDeprecated(-calendar:appearance:eventDefaultColorsForDate:);
- (FSCalendarCellShape)calendar:(FSCalendar *)calendar appearance:(FSCalendarAppearance *)appearance cellShapeForDate:(NSDate *)date FSCalendarDeprecated(-calendar:appearance:borderRadiusForDate:);
@end
#pragma mark - Primary
@ -222,9 +275,9 @@ IB_DESIGNABLE
@property (weak, nonatomic) IBOutlet id<FSCalendarDataSource> dataSource;
/**
* A special mark will be put on today of the calendar
* A special mark will be put on 'today' of the calendar.
*/
@property (strong, nonatomic) NSDate *today;
@property (nullable, strong, nonatomic) NSDate *today;
/**
* The current page of calendar
@ -240,16 +293,7 @@ IB_DESIGNABLE
*
* calendar.locale = [NSLocale localeWithLocaleIdentifier:@"zh-CN"];
*/
@property (strong, nonatomic) NSLocale *locale;
/**
* Represents the NSCalendarIdentifier of calendar. Default is NSCalendarIdentifierGregorian.
*
* e.g. To display a Persian calendar
*
* calendar.identifier = NSCalendarIdentifierPersian;
*/
@property (strong, nonatomic) NSString *identifier;
@property (copy, nonatomic) NSLocale *locale;
/**
* The scroll direction of FSCalendar.
@ -268,330 +312,319 @@ IB_DESIGNABLE
@property (assign, nonatomic) FSCalendarScope scope;
/**
* The index of the first weekday of the calendar. Give a '2' to make Monday in the first column.
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;
*/
#if TARGET_INTERFACE_BUILDER
@property (assign, nonatomic) IBInspectable NSUInteger placeholderType;
#else
@property (assign, nonatomic) FSCalendarPlaceholderType placeholderType;
#endif
/**
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.
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.
The height of weekday header of the calendar.
*/
@property (assign, nonatomic) IBInspectable CGFloat weekdayHeight;
/**
* A Boolean value that determines whether users can select a date.
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.
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 paging is enabled for the calendar.
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.
A Boolean value that determines whether scrolling is enabled for the calendar.
*/
@property (assign, nonatomic) IBInspectable BOOL scrollEnabled;
/**
* A Boolean value that determines whether scoping animation is centered a visible selected date. Default is YES.
A Boolean value that determines whether the calendar should show a handle for control the scope. Default is NO;
@deprecated Use -handleScopeGesture: instead
e.g.
UIPanGestureRecognizer *scopeGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self.calendar action:@selector(handleScopeGesture:)];
scopeGesture.delegate = ...
[anyOtherView addGestureRecognizer:scopeGesture];
@see FSCalendarScopeExample
*/
@property (assign, nonatomic) IBInspectable BOOL focusOnSingleSelectedDate;
@property (assign, nonatomic) IBInspectable BOOL showsScopeHandle FSCalendarDeprecated(handleScopeGesture:);
/**
* A Boolean value that determines whether the calendar should show days out of month. Default is YES.
The row height of the calendar if paging enabled is NO.;
*/
@property (assign, nonatomic) IBInspectable BOOL showsPlaceholders;
@property (assign, nonatomic) IBInspectable CGFloat rowHeight;
/**
The calendar appearance used to control the global fontscolors .etc
*/
@property (readonly, nonatomic) FSCalendarAppearance *appearance;
/**
* A date object representing the minimum day enablevisible and selectable. (read-only)
A date object representing the minimum day enablevisible and selectable. (read-only)
*/
@property (readonly, nonatomic) NSDate *minimumDate;
/**
* A date object representing the maximum day enablevisible and selectable. (read-only)
A date object representing the maximum day enablevisible and selectable. (read-only)
*/
@property (readonly, nonatomic) NSDate *maximumDate;
/**
* A date object identifying the section of the selected date. (read-only)
A date object identifying the section of the selected date. (read-only)
*/
@property (readonly, nonatomic) NSDate *selectedDate;
@property (nullable, readonly, nonatomic) NSDate *selectedDate;
/**
* The dates representing the selected dates. (read-only)
The dates representing the selected dates. (read-only)
*/
@property (readonly, nonatomic) NSArray *selectedDates;
@property (readonly, nonatomic) NSArray<NSDate *> *selectedDates;
/**
* Reload the dates and appearance of the calendar.
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.
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.
Selects a given date in the calendar.
@param date A date in the calendar.
*/
- (void)selectDate:(NSDate *)date;
- (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.
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:(NSDate *)date scrollToDate:(BOOL)scrollToDate;
- (void)selectDate:(nullable NSDate *)date scrollToDate:(BOOL)scrollToDate;
/**
* Deselects a given date of the calendar.
* @param date A date in the calendar.
Deselects a given date of the calendar.
@param date A date in the calendar.
*/
- (void)deselectDate:(NSDate *)date;
/**
* Change 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.
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
#pragma mark - DateTools
/**
* Job for this category:
*
* 1. Manage date object simplierfaster
* 2. Bring date object into a no-timezone system.
*
* @warning All NSDate instances used in the calendar should be created by:
*
* - (NSDate *)dateFromString:(NSString *)string format:(NSString *)format;
* - (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day;
*
*/
@interface FSCalendar (DateTools)
IB_DESIGNABLE
@interface FSCalendar (IBExtension)
/**
* Returns the number of year of the given date
*/
- (NSInteger)yearOfDate:(NSDate *)date;
#if TARGET_INTERFACE_BUILDER
/**
* Returns the number of month of the given date
*/
- (NSInteger)monthOfDate:(NSDate *)date;
@property (assign, nonatomic) IBInspectable CGFloat titleTextSize;
@property (assign, nonatomic) IBInspectable CGFloat subtitleTextSize;
@property (assign, nonatomic) IBInspectable CGFloat weekdayTextSize;
@property (assign, nonatomic) IBInspectable CGFloat headerTitleTextSize;
/**
* Returns the number of day of the given date
*/
- (NSInteger)dayOfDate:(NSDate *)date;
@property (strong, nonatomic) IBInspectable UIColor *eventDefaultColor;
@property (strong, nonatomic) IBInspectable UIColor *eventSelectionColor;
@property (strong, nonatomic) IBInspectable UIColor *weekdayTextColor;
/**
* Returns the number of weekday of the given date
*/
- (NSInteger)weekdayOfDate:(NSDate *)date;
@property (strong, nonatomic) IBInspectable UIColor *headerTitleColor;
@property (strong, nonatomic) IBInspectable NSString *headerDateFormat;
@property (assign, nonatomic) IBInspectable CGFloat headerMinimumDissolvedAlpha;
/**
* Returns the number of weekOfYear of the given date
*/
- (NSInteger)weekOfDate:(NSDate *)date;
@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;
/**
* Returns the number of hour of the given date
*/
- (NSInteger)hourOfDate:(NSDate *)date;
@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;
/**
* Returns the number of minite of the given date
*/
- (NSInteger)miniuteOfDate:(NSDate *)date;
@property (strong, nonatomic) IBInspectable UIColor *selectionColor;
@property (strong, nonatomic) IBInspectable UIColor *todayColor;
@property (strong, nonatomic) IBInspectable UIColor *todaySelectionColor;
/**
* Returns the number of seconds of the given date
*/
- (NSInteger)secondOfDate:(NSDate *)date;
@property (strong, nonatomic) IBInspectable UIColor *borderDefaultColor;
@property (strong, nonatomic) IBInspectable UIColor *borderSelectionColor;
/**
* Returns the number of rows of the given month
*/
- (NSInteger)numberOfRowsInMonth:(NSDate *)month;
@property (assign, nonatomic) IBInspectable CGFloat borderRadius;
@property (assign, nonatomic) IBInspectable BOOL useVeryShortWeekdaySymbols;
/**
* Zeronizing hourminute and second components of the given date
*/
- (NSDate *)dateByIgnoringTimeComponentsOfDate:(NSDate *)date;
@property (assign, nonatomic) IBInspectable BOOL fakeSubtitles;
@property (assign, nonatomic) IBInspectable BOOL fakeEventDots;
@property (assign, nonatomic) IBInspectable NSInteger fakedSelectedDay;
/**
* Returns the first day of month of the given date
*/
- (NSDate *)beginingOfMonthOfDate:(NSDate *)date;
/**
* Returns the last day of month of the given date
*/
- (NSDate *)endOfMonthOfDate:(NSDate *)date;
/**
* Returns the first day of week of the given date
*/
- (NSDate *)beginingOfWeekOfDate:(NSDate *)date;
/**
* Returns the middle day of week of the given date
*/
- (NSDate *)middleOfWeekFromDate:(NSDate *)date;
/**
* Returns the next day of the given date
*/
- (NSDate *)tomorrowOfDate:(NSDate *)date;
/**
* Returns the previous day of the given date
*/
- (NSDate *)yesterdayOfDate:(NSDate *)date;
/**
* Returns the number of days in the month of the given date
*/
- (NSInteger)numberOfDatesInMonthOfDate:(NSDate *)date;
/**
* Instantiating a date by given string and date format.
*
* e.g.
*
* NSDate *date = [calendar dateFromString:@"2000-10-10" format:@"yyyy-MM-dd"];
*/
- (NSDate *)dateFromString:(NSString *)string format:(NSString *)format;
/**
* Instantiating a date by given numbers of yearmonth and day.
*
* e.g.
*
* NSDate *date = [calendar dateWithYear:2000 month:10 day:10];
*/
- (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day;
/**
* Returns a new NSDate object representing the time calculated by adding given number of year to a given date.
*/
- (NSDate *)dateByAddingYears:(NSInteger)years toDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by substracting given number of year from a given date.
*/
- (NSDate *)dateBySubstractingYears:(NSInteger)years fromDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by adding given number of month to a given date.
*/
- (NSDate *)dateByAddingMonths:(NSInteger)months toDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by substracting given number of month from a given date.
*/
- (NSDate *)dateBySubstractingMonths:(NSInteger)months fromDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by adding given number of week to a given date.
*/
- (NSDate *)dateByAddingWeeks:(NSInteger)weeks toDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by substracting given number of week from a given date.
*/
- (NSDate *)dateBySubstractingWeeks:(NSInteger)weeks fromDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by adding given number of day to a given date.
*/
- (NSDate *)dateByAddingDays:(NSInteger)days toDate:(NSDate *)date;
/**
* Returns a new NSDate object representing the time calculated by substracting given number of day from a given date.
*/
- (NSDate *)dateBySubstractingDays:(NSInteger)days fromDate:(NSDate *)date;
/**
* Returns the year-difference between the given dates
*/
- (NSInteger)yearsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate;
/**
* Returns the month-difference between the given dates
*/
- (NSInteger)monthsFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate;
/**
* Returns the day-difference between the given dates
*/
- (NSInteger)daysFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate;
/**
* Returns the week-difference between the given dates
*/
- (NSInteger)weeksFromDate:(NSDate *)fromDate toDate:(NSDate *)toDate;
/**
* Returns whether two dates are equal to a given unit of calendar.
*/
- (BOOL)isDate:(NSDate *)date1 equalToDate:(NSDate *)date2 toCalendarUnit:(FSCalendarUnit)unit;
/**
* Returns whether the given date is in 'today' of the calendar.
*/
- (BOOL)isDateInToday:(NSDate *)date;
/**
* Returns a string representation of a given date formatted using a specific date format.
*/
- (NSString *)stringFromDate:(NSDate *)date format:(NSString *)format;
/**
* Returns a string representation of a given date formatted using a yyyy-MM-dd.
*/
- (NSString *)stringFromDate:(NSDate *)date;
#endif
@end
#pragma mark - Deprecate
/**
* These attributes and functions are deprecated.
*/
@interface FSCalendar (Deprecated)
@property (strong, nonatomic) NSDate *currentMonth FSCalendarDeprecated('currentPage');
@property (assign, nonatomic) FSCalendarFlow flow FSCalendarDeprecated('scrollDirection');
- (void)setSelectedDate:(NSDate *)selectedDate FSCalendarDeprecated(-selectDate:);
- (void)setSelectedDate:(NSDate *)selectedDate animate:(BOOL)animate FSCalendarDeprecated(-selectDate:scrollToDate:);
- (BOOL)date:(NSDate *)date sharesSameMonthWithDate:(NSDate *)anotherDate FSCalendarDeprecated(-isDate:equalToDate:toCalendarUnit);
- (BOOL)date:(NSDate *)date sharesSameWeekWithDate:(NSDate *)anotherDate FSCalendarDeprecated(-isDate:equalToDate:toCalendarUnit);
- (BOOL)date:(NSDate *)date sharesSameDayWithDate:(NSDate *)anotherDate FSCalendarDeprecated(-isDate:equalToDate:toCalendarUnit);
@property (assign, nonatomic) CGFloat lineHeightMultiplier FSCalendarDeprecated(rowHeight);
@property (assign, nonatomic) IBInspectable BOOL showsPlaceholders FSCalendarDeprecated('placeholderType');
@property (strong, nonatomic) NSString *identifier DEPRECATED_MSG_ATTRIBUTE("Changing calendar identifier is NOT RECOMMENDED. ");
// Use NSCalendar.
- (NSDate *)dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day FSCalendarDeprecated([NSDateFormatter dateFromString:]);
- (NSInteger)yearOfDate:(NSDate *)date FSCalendarDeprecated(NSCalendar component:fromDate:]);
- (NSInteger)monthOfDate:(NSDate *)date FSCalendarDeprecated(NSCalendar component:fromDate:]);
- (NSInteger)dayOfDate:(NSDate *)date FSCalendarDeprecated(NSCalendar component:fromDate:]);
- (NSInteger)weekdayOfDate:(NSDate *)date FSCalendarDeprecated(NSCalendar component:fromDate:]);
- (NSInteger)weekOfDate:(NSDate *)date FSCalendarDeprecated(NSCalendar component:fromDate:]);
- (NSDate *)dateByAddingYears:(NSInteger)years toDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateBySubstractingYears:(NSInteger)years fromDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateByAddingMonths:(NSInteger)months toDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateBySubstractingMonths:(NSInteger)months fromDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateByAddingWeeks:(NSInteger)weeks toDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateBySubstractingWeeks:(NSInteger)weeks fromDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateByAddingDays:(NSInteger)days toDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (NSDate *)dateBySubstractingDays:(NSInteger)days fromDate:(NSDate *)date FSCalendarDeprecated([NSCalendar dateByAddingUnit:value:toDate:options:]);
- (BOOL)isDate:(NSDate *)date1 equalToDate:(NSDate *)date2 toCalendarUnit:(FSCalendarUnit)unit FSCalendarDeprecated([NSCalendar -isDate:equalToDate:toUnitGranularity:]);
- (BOOL)isDateInToday:(NSDate *)date FSCalendarDeprecated([NSCalendar -isDateInToday:]);
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +0,0 @@
//
// FSCalendarAnimator.h
// FSCalendar
//
// Created by dingwenchao on 3/13/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "FSCalendar.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarFlowLayout.h"
#import "FSCalendarDynamicHeader.h"
typedef NS_ENUM(NSUInteger, FSCalendarTransition) {
FSCalendarTransitionNone,
FSCalendarTransitionMonthToWeek,
FSCalendarTransitionWeekToMonth
};
typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) {
FSCalendarTransitionStateIdle,
FSCalendarTransitionStateInProgress
};
@interface FSCalendarAnimator : NSObject
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarCollectionView *collectionView;
@property (weak, nonatomic) FSCalendarFlowLayout *collectionViewLayout;
@property (assign, nonatomic) FSCalendarTransition transition;
@property (assign, nonatomic) FSCalendarTransitionState state;
- (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated;
- (void)performBoudingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration;
@end

View File

@ -1,394 +0,0 @@
//
// FSCalendarAnimator.m
// FSCalendar
//
// Created by Wenchao Ding on 3/13/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarAnimator.h"
#import <objc/runtime.h>
#import "UIView+FSExtension.h"
@implementation FSCalendarAnimator
#pragma mark - Public methods
- (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated
{
if (fromScope == toScope) {
self.transition = FSCalendarTransitionNone;
return;
}
if (fromScope == FSCalendarScopeMonth && toScope == FSCalendarScopeWeek) {
self.transition = FSCalendarTransitionMonthToWeek;
} else if (fromScope == FSCalendarScopeWeek && toScope == FSCalendarScopeMonth) {
self.transition = FSCalendarTransitionWeekToMonth;
}
// Start transition
self.state = FSCalendarTransitionStateInProgress;
switch (self.transition) {
case FSCalendarTransitionMonthToWeek: {
CGSize contentSize = [self.calendar sizeThatFits:self.calendar.frame.size scope:FSCalendarScopeWeek];
CGRect targetBounds = (CGRect){CGPointZero,contentSize};
NSInteger focusedRowNumber = 0;
if (self.calendar.focusOnSingleSelectedDate) {
NSDate *focusedDate = self.calendar.selectedDate;
if (focusedDate) {
UICollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar indexPathForDate:focusedDate scope:FSCalendarScopeMonth]];
CGPoint focuedCenter = attributes.center;
if (CGRectContainsPoint(self.collectionView.bounds, focuedCenter)) {
switch (self.collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
focusedRowNumber = attributes.indexPath.item%6;
break;
}
case UICollectionViewScrollDirectionVertical: {
focusedRowNumber = attributes.indexPath.item/7;
break;
}
}
} else {
focusedDate = nil;
}
}
if (!focusedDate) {
focusedDate = self.calendar.today;
if (focusedDate) {
UICollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar indexPathForDate:focusedDate scope:FSCalendarScopeMonth]];
CGPoint focuedCenter = attributes.center;
if (CGRectContainsPoint(self.collectionView.bounds, focuedCenter)) {
switch (self.collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
focusedRowNumber = attributes.indexPath.item%6;
break;
}
case UICollectionViewScrollDirectionVertical: {
focusedRowNumber = attributes.indexPath.item/7;
break;
}
}
}
}
}
}
NSDate *currentPage = self.calendar.currentPage;
NSDate *minimumPage = [self.calendar beginingOfMonthOfDate:self.calendar.minimumDate];
NSInteger visibleSection = [self.calendar monthsFromDate:minimumPage toDate:currentPage];
NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:visibleSection];
NSDate *firstDate = [self.calendar dateForIndexPath:firstIndexPath scope:FSCalendarScopeMonth];
currentPage = [self.calendar dateByAddingDays:focusedRowNumber*7 toDate:firstDate];
Ivar currentPageIvar = class_getInstanceVariable(FSCalendar.class, "_currentPage");
object_setIvar(self.calendar, currentPageIvar, currentPage);
self.calendar.contentView.clipsToBounds = YES;
self.calendar.daysContainer.clipsToBounds = YES;
if (animated) {
CGFloat duration = 0.3;
// Perform alpha animation
CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacity.duration = duration*0.6;
opacity.removedOnCompletion = NO;
opacity.fillMode = kCAFillModeForwards;
opacity.toValue = @0;
[self.collectionView.visibleCells enumerateObjectsUsingBlock:^(FSCalendarCell *cell, NSUInteger idx, BOOL *stop) {
if (CGRectContainsPoint(self.collectionView.bounds, cell.center)) {
BOOL shouldPerformAlpha = NO;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
switch (self.collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
shouldPerformAlpha = indexPath.item%6 != focusedRowNumber;
break;
}
case UICollectionViewScrollDirectionVertical: {
shouldPerformAlpha = indexPath.item/7 != focusedRowNumber;
break;
}
}
if (shouldPerformAlpha) {
[cell.contentView.layer addAnimation:opacity forKey:@"opacity"];
}
}
}];
// Perform path and frame animation
CABasicAnimation *path = [CABasicAnimation animationWithKeyPath:@"path"];
path.fromValue = (id)self.calendar.maskLayer.path;
path.toValue = (id)[UIBezierPath bezierPathWithRect:targetBounds].CGPath;
path.duration = duration;
path.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
self.state = FSCalendarTransitionStateIdle;
self.transition = FSCalendarTransitionNone;
self.collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.calendar.header.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.maskLayer.path = [UIBezierPath bezierPathWithRect:targetBounds].CGPath;
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
[self.calendar.header reloadData];
[self.calendar.header layoutIfNeeded];
self.calendar.needsAdjustingMonthPosition = YES;
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar setNeedsLayout];
self.calendar.contentView.clipsToBounds = NO;
self.calendar.daysContainer.clipsToBounds = NO;
}];
[CATransaction setAnimationDuration:duration];
[self.calendar.maskLayer addAnimation:path forKey:@"path"];
[CATransaction commit];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
[UIView beginAnimations:@"delegateTranslation" context:"translation"];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:duration];
self.collectionView.fs_top = -focusedRowNumber*self.calendar.preferredRowHeight;
self.calendar.bottomBorder.fs_top = CGRectGetMaxY(targetBounds);
if ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)]) {
[self.calendar.delegate calendar:self.calendar boundingRectWillChange:targetBounds animated:animated];
} else {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.calendar.delegate calendarCurrentScopeWillChange:self.calendar animated:animated];
#pragma GCC diagnostic pop
}
[UIView commitAnimations];
}
} else {
self.state = FSCalendarTransitionStateIdle;
self.transition = FSCalendarTransitionNone;
self.collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.calendar.header.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.needsAdjustingViewFrame = YES;
self.calendar.bottomBorder.frame = CGRectMake(0, contentSize.height, self.calendar.fs_width, 1);
self.calendar.maskLayer.path = [UIBezierPath bezierPathWithRect:targetBounds].CGPath;
self.calendar.bottomBorder.fs_top = CGRectGetMaxY(targetBounds);
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
[self.calendar.header reloadData];
[self.calendar.header layoutIfNeeded];
self.calendar.needsAdjustingMonthPosition = YES;
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar setNeedsLayout];
self.calendar.contentView.clipsToBounds = NO;
self.calendar.daysContainer.clipsToBounds = NO;
if (self.calendar.delegate && [self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)]) {
[self.calendar.delegate calendar:self.calendar boundingRectWillChange:targetBounds animated:animated];
} else if (self.calendar.delegate && [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.calendar.delegate calendarCurrentScopeWillChange:self.calendar animated:animated];
#pragma GCC diagnostic pop
}
}
break;
}
case FSCalendarTransitionWeekToMonth: {
CGSize contentSize = [self.calendar sizeThatFits:self.calendar.frame.size scope:FSCalendarScopeMonth];
CGRect targetBounds = (CGRect){CGPointZero,contentSize};
NSInteger focusedRowNumber = 0;
NSDate *currentPage = self.calendar.currentPage;
NSDate *firstDayOfMonth = nil;
if (self.calendar.focusOnSingleSelectedDate) {
NSDate *focusedDate = self.calendar.selectedDate;
if (focusedDate) {
UICollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar indexPathForDate:focusedDate scope:FSCalendarScopeWeek]];
CGPoint focuedCenter = attributes.center;
if (CGRectContainsPoint(self.collectionView.bounds, focuedCenter)) {
firstDayOfMonth = [self.calendar beginingOfMonthOfDate:focusedDate];
} else {
focusedDate = nil;
}
}
if (!focusedDate) {
focusedDate = self.calendar.today;
if (focusedDate) {
UICollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:[self.calendar indexPathForDate:focusedDate scope:FSCalendarScopeWeek]];
CGPoint focuedCenter = attributes.center;
if (CGRectContainsPoint(self.collectionView.bounds, focuedCenter)) {
firstDayOfMonth = [self.calendar beginingOfMonthOfDate:focusedDate];
}
}
};
}
firstDayOfMonth = firstDayOfMonth ?: [self.calendar beginingOfMonthOfDate:currentPage];
NSInteger numberOfPlaceholdersForPrev = [self.calendar numberOfHeadPlaceholdersForMonth:firstDayOfMonth];
NSDate *firstDateOfPage = [self.calendar dateBySubstractingDays:numberOfPlaceholdersForPrev fromDate:firstDayOfMonth];
for (int i = 0; i < 6; i++) {
NSDate *currentRow = [self.calendar dateByAddingWeeks:i toDate:firstDateOfPage];
if ([self.calendar isDate:currentRow equalToDate:currentPage toCalendarUnit:FSCalendarUnitDay]) {
focusedRowNumber = i;
currentPage = firstDayOfMonth;
break;
}
}
Ivar currentPageIvar = class_getInstanceVariable(FSCalendar.class, "_currentPage");
object_setIvar(self.calendar, currentPageIvar, currentPage);
self.collectionViewLayout.scrollDirection = (UICollectionViewScrollDirection)self.calendar.scrollDirection;
self.calendar.header.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.needsAdjustingMonthPosition = YES;
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar layoutSubviews];
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
[self.calendar.header reloadData];
[self.calendar.header layoutIfNeeded];
self.calendar.contentView.clipsToBounds = YES;
self.calendar.daysContainer.clipsToBounds = YES;
if (animated) {
// Perform alpha animation
CGFloat duration = 0.3;
CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacity.duration = duration;
opacity.fromValue = @0;
opacity.toValue = @1;
opacity.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[self.collectionView.visibleCells enumerateObjectsUsingBlock:^(FSCalendarCell *cell, NSUInteger idx, BOOL *stop) {
if (CGRectContainsPoint(self.collectionView.bounds, cell.center)) {
BOOL shouldPerformAlpha = NO;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
switch (self.collectionViewLayout.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
shouldPerformAlpha = indexPath.item%6 != focusedRowNumber;
break;
}
case UICollectionViewScrollDirectionVertical: {
shouldPerformAlpha = indexPath.item/7 != focusedRowNumber;
break;
}
}
if (shouldPerformAlpha) {
[cell.contentView.layer addAnimation:opacity forKey:@"opacity"];
}
}
}];
// Perform path and frame animation
BOOL oldDisableActions = [CATransaction disableActions];
[CATransaction setDisableActions:NO];
CABasicAnimation *path = [CABasicAnimation animationWithKeyPath:@"path"];
path.fromValue = (id)self.calendar.maskLayer.path;
path.toValue = (id)[UIBezierPath bezierPathWithRect:targetBounds].CGPath;
path.duration = duration;
path.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[CATransaction begin];
[CATransaction setCompletionBlock:^{
self.state = FSCalendarTransitionStateIdle;
self.transition = FSCalendarTransitionNone;
self.calendar.maskLayer.path = [UIBezierPath bezierPathWithRect:targetBounds].CGPath;
self.calendar.contentView.clipsToBounds = NO;
self.calendar.daysContainer.clipsToBounds = NO;
}];
[CATransaction setAnimationDuration:duration];
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar.maskLayer addAnimation:path forKey:@"path"];
[CATransaction commit];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
self.collectionView.fs_top = -focusedRowNumber*self.calendar.preferredRowHeight;
[UIView setAnimationsEnabled:YES];
[UIView beginAnimations:@"delegateTranslation" context:"translation"];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:duration];
self.collectionView.fs_top = 0;
self.self.calendar.bottomBorder.frame = CGRectMake(0, contentSize.height, self.calendar.fs_width, 1);
if ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)]) {
[self.calendar.delegate calendar:self.calendar boundingRectWillChange:targetBounds animated:animated];
} else {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.calendar.delegate calendarCurrentScopeWillChange:self.calendar animated:animated];
#pragma GCC diagnostic pop
}
[UIView commitAnimations];
}
[CATransaction setDisableActions:oldDisableActions];
} else {
self.state = FSCalendarTransitionStateIdle;
self.transition = FSCalendarTransitionNone;
self.calendar.needsAdjustingViewFrame = YES;
self.calendar.bottomBorder.frame = CGRectMake(0, contentSize.height, self.calendar.fs_width, 1);
self.calendar.maskLayer.path = [UIBezierPath bezierPathWithRect:targetBounds].CGPath;
self.calendar.contentView.clipsToBounds = NO;
self.calendar.daysContainer.clipsToBounds = NO;
if (self.calendar.delegate && [self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)]) {
[self.calendar.delegate calendar:self.calendar boundingRectWillChange:targetBounds animated:animated];
} else if (self.calendar.delegate && [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)]) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.calendar.delegate calendarCurrentScopeWillChange:self.calendar animated:animated];
#pragma GCC diagnostic pop
}
}
break;
}
default:
break;
}
}
- (void)performBoudingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration
{
NSInteger lastRowCount = [self.calendar numberOfRowsInMonth:fromMonth];
NSInteger currentRowCount = [self.calendar numberOfRowsInMonth:toMonth];
if (lastRowCount != currentRowCount) {
CGFloat animationDuration = duration;
CGRect bounds = (CGRect){CGPointZero,[self.calendar sizeThatFits:self.calendar.frame.size]};
self.state = FSCalendarTransitionStateInProgress;
[UIView animateWithDuration:animationDuration delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{
if (self.calendar.delegate && [self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)]) {
if (!CGRectEqualToRect((CGRect){CGPointZero,self.calendar.frame.size}, bounds)) {
[self.calendar.delegate calendar:self.calendar boundingRectWillChange:bounds animated:YES];
}
}
self.calendar.bottomBorder.fs_top = CGRectGetMaxY(bounds);
} completion:^(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;
});
}];
CABasicAnimation *path = [CABasicAnimation animationWithKeyPath:@"path"];
path.fromValue = (id)self.calendar.maskLayer.path;
path.toValue = (id)[UIBezierPath bezierPathWithRect:bounds].CGPath;
path.duration = animationDuration*(currentRowCount>lastRowCount?1.25:0.75);
path.removedOnCompletion = NO;
path.fillMode = kCAFillModeForwards;
[self.calendar.maskLayer addAnimation:path forKey:@"path"];
}
}
@end

View File

@ -8,7 +8,7 @@
// https://github.com/WenchaoD
//
#import "FSCalendarConstance.h"
#import "FSCalendarConstants.h"
@class FSCalendar;
@ -22,9 +22,9 @@ typedef NS_ENUM(NSInteger, FSCalendarCellState) {
FSCalendarCellStateTodaySelected = FSCalendarCellStateToday|FSCalendarCellStateSelected
};
typedef NS_ENUM(NSUInteger, FSCalendarCellShape) {
FSCalendarCellShapeCircle = 0,
FSCalendarCellShapeRectangle = 1
typedef NS_ENUM(NSUInteger, FSCalendarSeparators) {
FSCalendarSeparatorNone = 0,
FSCalendarSeparatorInterRows = 1
};
typedef NS_OPTIONS(NSUInteger, FSCalendarCaseOptions) {
@ -45,46 +45,53 @@ typedef NS_OPTIONS(NSUInteger, FSCalendarCaseOptions) {
/**
* The font of the day text.
*
* @warning The size of font is adjusted by calendar size. To turn it off, set adjustsFontSizeToFitContentSize to NO;
*/
@property (strong, nonatomic) UIFont *titleFont;
/**
* The font of the subtitle text.
*
* @warning The size of font is adjusted by calendar size. To turn it off, set adjustsFontSizeToFitContentSize to NO;
*/
@property (strong, nonatomic) UIFont *subtitleFont;
/**
* The font of the weekday text.
*
* @warning The size of font is adjusted by calendar size. To turn it off, set adjustsFontSizeToFitContentSize to NO;
*/
@property (strong, nonatomic) UIFont *weekdayFont;
/**
* The font of the month text.
*
* @warning The size of font is adjusted by calendar size. To turn it off, set adjustsFontSizeToFitContentSize to NO;
*/
@property (strong, nonatomic) UIFont *headerTitleFont;
/**
* The vertical offset of the day text from default position.
* The offset of the day text from default position.
*/
@property (assign, nonatomic) CGFloat titleVerticalOffset;
@property (assign, nonatomic) CGPoint titleOffset;
/**
* The vertical offset of the suntitle text from default position.
* The offset of the day text from default position.
*/
@property (assign, nonatomic) CGFloat subtitleVerticalOffset;
@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 *eventColor;
@property (strong, nonatomic) UIColor *eventDefaultColor;
/**
* The color of event dots.
*/
@property (strong, nonatomic) UIColor *eventSelectionColor;
/**
* The color of weekday text.
@ -182,11 +189,9 @@ typedef NS_OPTIONS(NSUInteger, FSCalendarCaseOptions) {
@property (strong, nonatomic) UIColor *borderSelectionColor;
/**
* The shape appears when a day is selected or today.
*
* @see FSCalendarCellShape
* 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) FSCalendarCellShape cellShape;
@property (assign, nonatomic) CGFloat borderRadius;
/**
* The case options manage the case of month label and weekday symbols.
@ -196,28 +201,20 @@ typedef NS_OPTIONS(NSUInteger, FSCalendarCaseOptions) {
@property (assign, nonatomic) FSCalendarCaseOptions caseOptions;
/**
* A Boolean value indicates whether the calendar should adjust font size by its content size.
* The line integrations for calendar.
*
* @see titleFont
* @see subtitleFont
* @see weekdayFont
* @see headerTitleFont
*/
@property (assign, nonatomic) BOOL adjustsFontSizeToFitContentSize;
@property (assign, nonatomic) FSCalendarSeparators separators;
#if TARGET_INTERFACE_BUILDER
// For preview only
@property (assign, nonatomic) BOOL fakeSubtitles;
@property (assign, nonatomic) BOOL fakeEventDots;
@property (assign, nonatomic) NSInteger fakedSelectedDay;
#endif
/**
* Triggers an appearance update.
*/
- (void)invalidateAppearance;
@end
/**
@ -225,15 +222,13 @@ typedef NS_OPTIONS(NSUInteger, FSCalendarCaseOptions) {
*/
@interface FSCalendarAppearance (Deprecated)
@property (assign, nonatomic) FSCalendarCellStyle cellStyle FSCalendarDeprecated('cellShape');
@property (assign, nonatomic) BOOL useVeryShortWeekdaySymbols FSCalendarDeprecated('caseOptions');
@property (assign, nonatomic) BOOL autoAdjustTitleSize FSCalendarDeprecated('adjustFontSizeToFitContentSize');
@property (assign, nonatomic) BOOL adjustsFontSizeToFitCellSize FSCalendarDeprecated('adjustFontSizeToFitContentSize');
@property (assign, nonatomic) CGFloat titleTextSize FSCalendarDeprecated('titleFont');
@property (assign, nonatomic) CGFloat subtitleTextSize FSCalendarDeprecated('subtitleFont');
@property (assign, nonatomic) CGFloat weekdayTextSize FSCalendarDeprecated('weekdayFont');
@property (assign, nonatomic) CGFloat headerTitleTextSize FSCalendarDeprecated('headerTitleFont');
@property (assign, nonatomic) CGFloat titleVerticalOffset FSCalendarDeprecated('titleOffset');
@property (assign, nonatomic) CGFloat subtitleVerticalOffset FSCalendarDeprecated('subtitleOffset');
@property (strong, nonatomic) UIColor *eventColor FSCalendarDeprecated('eventDefaultColor');
@property (assign, nonatomic) FSCalendarCellShape cellShape FSCalendarDeprecated('borderRadius');
@property (assign, nonatomic) BOOL adjustsFontSizeToFitContentSize DEPRECATED_MSG_ATTRIBUTE("The attribute \'adjustsFontSizeToFitContentSize\' is not neccesary anymore.");
- (void)invalidateAppearance FSCalendarDeprecated('FSCalendar setNeedsConfigureAppearance');
@end

View File

@ -10,7 +10,7 @@
#import "FSCalendarAppearance.h"
#import "FSCalendarDynamicHeader.h"
#import "UIView+FSExtension.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarAppearance ()
@ -21,44 +21,6 @@
@property (strong, nonatomic) NSMutableDictionary *subtitleColors;
@property (strong, nonatomic) NSMutableDictionary *borderColors;
@property (strong, nonatomic) NSString *titleFontName;
@property (strong, nonatomic) NSString *subtitleFontName;
@property (strong, nonatomic) NSString *weekdayFontName;
@property (strong, nonatomic) NSString *headerTitleFontName;
@property (assign, nonatomic) CGFloat titleFontSize;
@property (assign, nonatomic) CGFloat subtitleFontSize;
@property (assign, nonatomic) CGFloat weekdayFontSize;
@property (assign, nonatomic) CGFloat headerTitleFontSize;
@property (assign, nonatomic) CGFloat preferredTitleFontSize;
@property (assign, nonatomic) CGFloat preferredSubtitleFontSize;
@property (assign, nonatomic) CGFloat preferredWeekdayFontSize;
@property (assign, nonatomic) CGFloat preferredHeaderTitleFontSize;
@property (readonly, nonatomic) UIFont *preferredTitleFont;
@property (readonly, nonatomic) UIFont *preferredSubtitleFont;
@property (readonly, nonatomic) UIFont *preferredWeekdayFont;
@property (readonly, nonatomic) UIFont *preferredHeaderTitleFont;
- (void)adjustTitleIfNecessary;
- (void)invalidateFonts;
- (void)invalidateTextColors;
- (void)invalidateTitleFont;
- (void)invalidateSubtitleFont;
- (void)invalidateWeekdayFont;
- (void)invalidateHeaderFont;
- (void)invalidateTitleTextColor;
- (void)invalidateSubtitleTextColor;
- (void)invalidateWeekdayTextColor;
- (void)invalidateHeaderTextColor;
- (void)invalidateBorderColors;
- (void)invalidateBackgroundColors;
- (void)invalidateEventColors;
- (void)invalidateCellShapes;
@end
@implementation FSCalendarAppearance
@ -68,17 +30,10 @@
self = [super init];
if (self) {
_adjustsFontSizeToFitContentSize = YES;
_titleFontSize = _preferredTitleFontSize = FSCalendarStandardTitleTextSize;
_subtitleFontSize = _preferredSubtitleFontSize = FSCalendarStandardSubtitleTextSize;
_weekdayFontSize = _preferredWeekdayFontSize = FSCalendarStandardWeekdayTextSize;
_headerTitleFontSize = _preferredHeaderTitleFontSize = FSCalendarStandardHeaderTextSize;
_titleFontName = [UIFont systemFontOfSize:1].fontName;
_subtitleFontName = [UIFont systemFontOfSize:1].fontName;
_weekdayFontName = [UIFont systemFontOfSize:1].fontName;
_headerTitleFontName = [UIFont systemFontOfSize:1].fontName;
_titleFont = [UIFont systemFontOfSize:FSCalendarStandardTitleTextSize];
_subtitleFont = [UIFont systemFontOfSize:FSCalendarStandardSubtitleTextSize];
_weekdayFont = [UIFont systemFontOfSize:FSCalendarStandardWeekdayTextSize];
_headerTitleFont = [UIFont systemFontOfSize:FSCalendarStandardHeaderTextSize];
_headerTitleColor = FSCalendarStandardTitleTextColor;
_headerDateFormat = @"MMMM yyyy";
@ -110,113 +65,82 @@
_borderColors[@(FSCalendarCellStateSelected)] = [UIColor clearColor];
_borderColors[@(FSCalendarCellStateNormal)] = [UIColor clearColor];
_cellShape = FSCalendarCellShapeCircle;
_eventColor = FSCalendarStandardEventDotColor;
_borderRadius = 1.0;
_eventDefaultColor = FSCalendarStandardEventDotColor;
_eventSelectionColor = FSCalendarStandardEventDotColor;
_borderColors = [NSMutableDictionary dictionaryWithCapacity:2];
#if TARGET_INTERFACE_BUILDER
_fakeEventDots = YES;
#endif
}
return self;
}
- (void)setTitleFont:(UIFont *)titleFont
{
BOOL needsInvalidating = NO;
if (![_titleFontName isEqualToString:titleFont.fontName]) {
_titleFontName = titleFont.fontName;
needsInvalidating = YES;
if (![_titleFont isEqual:titleFont]) {
_titleFont = titleFont;
[self.calendar configureAppearance];
}
if (_titleFontSize != titleFont.pointSize) {
_titleFontSize = titleFont.pointSize;
needsInvalidating = YES;
}
if (needsInvalidating) {
[self invalidateTitleFont];
}
}
- (UIFont *)titleFont
{
return [UIFont fontWithName:_titleFontName size:_titleFontSize];
}
- (void)setSubtitleFont:(UIFont *)subtitleFont
{
BOOL needsInvalidating = NO;
if (![_subtitleFontName isEqualToString:subtitleFont.fontName]) {
_subtitleFontName = subtitleFont.fontName;
needsInvalidating = YES;
if (![_subtitleFont isEqual:subtitleFont]) {
_subtitleFont = subtitleFont;
[self.calendar configureAppearance];
}
if (_subtitleFontSize != subtitleFont.pointSize) {
_subtitleFontSize = subtitleFont.pointSize;
needsInvalidating = YES;
}
if (needsInvalidating) {
[self invalidateSubtitleFont];
}
}
- (UIFont *)subtitleFont
{
return [UIFont fontWithName:_subtitleFontName size:_subtitleFontSize];
}
- (void)setWeekdayFont:(UIFont *)weekdayFont
{
BOOL needsInvalidating = NO;
if (![_weekdayFontName isEqualToString:weekdayFont.fontName]) {
_weekdayFontName = weekdayFont.fontName;
needsInvalidating = YES;
if (![_weekdayFont isEqual:weekdayFont]) {
_weekdayFont = weekdayFont;
[self.calendar configureAppearance];
}
if (_weekdayFontSize != weekdayFont.pointSize) {
_weekdayFontSize = weekdayFont.pointSize;
needsInvalidating = YES;
}
if (needsInvalidating) {
[self invalidateWeekdayFont];
}
}
- (UIFont *)weekdayFont
{
return [UIFont fontWithName:_weekdayFontName size:_weekdayFontSize];
}
- (void)setHeaderTitleFont:(UIFont *)headerTitleFont
{
BOOL needsInvalidating = NO;
if (![_headerTitleFontName isEqualToString:headerTitleFont.fontName]) {
_headerTitleFontName = headerTitleFont.fontName;
needsInvalidating = YES;
}
if (_headerTitleFontSize != headerTitleFont.pointSize) {
_headerTitleFontSize = headerTitleFont.pointSize;
needsInvalidating = YES;
}
if (needsInvalidating) {
[self invalidateHeaderFont];
if (![_headerTitleFont isEqual:headerTitleFont]) {
_headerTitleFont = headerTitleFont;
[self.calendar configureAppearance];
}
}
- (void)setTitleVerticalOffset:(CGFloat)titleVerticalOffset
- (void)setTitleOffset:(CGPoint)titleOffset
{
if (_titleVerticalOffset != titleVerticalOffset) {
_titleVerticalOffset = titleVerticalOffset;
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
if (!CGPointEqualToPoint(_titleOffset, titleOffset)) {
_titleOffset = titleOffset;
[_calendar.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
- (void)setSubtitleVerticalOffset:(CGFloat)subtitleVerticalOffset
- (void)setSubtitleOffset:(CGPoint)subtitleOffset
{
if (_subtitleVerticalOffset != subtitleVerticalOffset) {
_subtitleVerticalOffset = subtitleVerticalOffset;
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
if (!CGPointEqualToPoint(_subtitleOffset, subtitleOffset)) {
_subtitleOffset = subtitleOffset;
[_calendar.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
}
- (UIFont *)headerTitleFont
- (void)setImageOffset:(CGPoint)imageOffset
{
return [UIFont fontWithName:_headerTitleFontName size:_headerTitleFontSize];
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
@ -226,7 +150,7 @@
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateNormal)];
}
[self invalidateTitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)titleDefaultColor
@ -241,7 +165,7 @@
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self invalidateTitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)titleSelectionColor
@ -256,7 +180,7 @@
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateToday)];
}
[self invalidateTitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)titleTodayColor
@ -271,7 +195,7 @@
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStatePlaceholder)];
}
[self invalidateTitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)titlePlaceholderColor
@ -286,7 +210,7 @@
} else {
[_titleColors removeObjectForKey:@(FSCalendarCellStateWeekend)];
}
[self invalidateTitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)titleWeekendColor
@ -301,7 +225,7 @@
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateNormal)];
}
[self invalidateSubtitleTextColor];
[self.calendar configureAppearance];
}
-(UIColor *)subtitleDefaultColor
@ -316,7 +240,7 @@
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self invalidateSubtitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)subtitleSelectionColor
@ -331,7 +255,7 @@
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateToday)];
}
[self invalidateSubtitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)subtitleTodayColor
@ -346,7 +270,7 @@
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStatePlaceholder)];
}
[self invalidateSubtitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)subtitlePlaceholderColor
@ -361,7 +285,7 @@
} else {
[_subtitleColors removeObjectForKey:@(FSCalendarCellStateWeekend)];
}
[self invalidateSubtitleTextColor];
[self.calendar configureAppearance];
}
- (UIColor *)subtitleWeekendColor
@ -376,7 +300,7 @@
} else {
[_backgroundColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self invalidateBackgroundColors];
[self.calendar configureAppearance];
}
- (UIColor *)selectionColor
@ -391,7 +315,7 @@
} else {
[_backgroundColors removeObjectForKey:@(FSCalendarCellStateToday)];
}
[self invalidateBackgroundColors];
[self.calendar configureAppearance];
}
- (UIColor *)todayColor
@ -406,7 +330,7 @@
} else {
[_backgroundColors removeObjectForKey:@(FSCalendarCellStateToday|FSCalendarCellStateSelected)];
}
[self invalidateBackgroundColors];
[self.calendar configureAppearance];
}
- (UIColor *)todaySelectionColor
@ -414,11 +338,11 @@
return _backgroundColors[@(FSCalendarCellStateToday|FSCalendarCellStateSelected)];
}
- (void)setEventColor:(UIColor *)eventColor
- (void)setEventDefaultColor:(UIColor *)eventDefaultColor
{
if (![_eventColor isEqual:eventColor]) {
_eventColor = eventColor;
[self invalidateEventColors];
if (![_eventDefaultColor isEqual:eventDefaultColor]) {
_eventDefaultColor = eventDefaultColor;
[self.calendar configureAppearance];
}
}
@ -429,7 +353,7 @@
} else {
[_borderColors removeObjectForKey:@(FSCalendarCellStateNormal)];
}
[self invalidateBorderColors];
[self.calendar configureAppearance];
}
- (UIColor *)borderDefaultColor
@ -444,7 +368,7 @@
} else {
[_borderColors removeObjectForKey:@(FSCalendarCellStateSelected)];
}
[self invalidateBorderColors];
[self.calendar configureAppearance];
}
- (UIColor *)borderSelectionColor
@ -452,11 +376,13 @@
return _borderColors[@(FSCalendarCellStateSelected)];
}
- (void)setCellShape:(FSCalendarCellShape)cellShape
- (void)setBorderRadius:(CGFloat)borderRadius
{
if (_cellShape != cellShape) {
_cellShape = cellShape;
[self invalidateCellShapes];
borderRadius = MAX(0.0, borderRadius);
borderRadius = MIN(1.0, borderRadius);
if (_borderRadius != borderRadius) {
_borderRadius = borderRadius;
[self.calendar configureAppearance];
}
}
@ -464,7 +390,7 @@
{
if (![_weekdayTextColor isEqual:weekdayTextColor]) {
_weekdayTextColor = weekdayTextColor;
[self invalidateWeekdayTextColor];
[self.calendar configureAppearance];
}
}
@ -472,7 +398,7 @@
{
if (![_headerTitleColor isEqual:color]) {
_headerTitleColor = color;
[self invalidateHeaderTextColor];
[self.calendar configureAppearance];
}
}
@ -480,8 +406,7 @@
{
if (_headerMinimumDissolvedAlpha != headerMinimumDissolvedAlpha) {
_headerMinimumDissolvedAlpha = headerMinimumDissolvedAlpha;
[_calendar.header.collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
[_calendar.visibleStickyHeaders makeObjectsPerformSelector:@selector(setNeedsLayout)];
[self.calendar configureAppearance];
}
}
@ -489,169 +414,24 @@
{
if (![_headerDateFormat isEqual:headerDateFormat]) {
_headerDateFormat = headerDateFormat;
[_calendar invalidateHeaders];
[self.calendar configureAppearance];
}
}
- (void)setAdjustsFontSizeToFitContentSize:(BOOL)adjustsFontSizeToFitContentSize
{
if (_adjustsFontSizeToFitContentSize != adjustsFontSizeToFitContentSize) {
_adjustsFontSizeToFitContentSize = adjustsFontSizeToFitContentSize;
if (adjustsFontSizeToFitContentSize) {
[self invalidateFonts];
}
}
}
- (UIFont *)preferredTitleFont
{
return [UIFont fontWithName:_titleFontName size:_adjustsFontSizeToFitContentSize?_preferredTitleFontSize:_titleFontSize];
}
- (UIFont *)preferredSubtitleFont
{
return [UIFont fontWithName:_subtitleFontName size:_adjustsFontSizeToFitContentSize?_preferredSubtitleFontSize:_subtitleFontSize];
}
- (UIFont *)preferredWeekdayFont
{
return [UIFont fontWithName:_weekdayFontName size:_adjustsFontSizeToFitContentSize?_preferredWeekdayFontSize:_weekdayFontSize];
}
- (UIFont *)preferredHeaderTitleFont
{
return [UIFont fontWithName:_headerTitleFontName size:_adjustsFontSizeToFitContentSize?_preferredHeaderTitleFontSize:_headerTitleFontSize];
}
- (void)adjustTitleIfNecessary
{
if (!self.calendar.floatingMode) {
if (_adjustsFontSizeToFitContentSize) {
CGFloat factor = (_calendar.scope==FSCalendarScopeMonth) ? 6 : 1.1;
_preferredTitleFontSize = _calendar.collectionView.fs_height/3/factor;
_preferredTitleFontSize -= (_preferredTitleFontSize-FSCalendarStandardTitleTextSize)*0.5;
_preferredSubtitleFontSize = _calendar.collectionView.fs_height/4.5/factor;
_preferredSubtitleFontSize -= (_preferredSubtitleFontSize-FSCalendarStandardSubtitleTextSize)*0.75;
_preferredHeaderTitleFontSize = _preferredTitleFontSize * 1.25;
_preferredWeekdayFontSize = _preferredTitleFontSize;
}
} else {
_preferredHeaderTitleFontSize = 20;
if (FSCalendarDeviceIsIPad) {
_preferredHeaderTitleFontSize = FSCalendarStandardHeaderTextSize * 1.5;
_preferredTitleFontSize = FSCalendarStandardTitleTextSize * 1.3;
_preferredSubtitleFontSize = FSCalendarStandardSubtitleTextSize * 1.15;
_preferredWeekdayFontSize = _preferredTitleFontSize;
}
}
// reload appearance
[self invalidateFonts];
}
- (void)setCaseOptions:(FSCalendarCaseOptions)caseOptions
{
if (_caseOptions != caseOptions) {
_caseOptions = caseOptions;
[_calendar invalidateWeekdaySymbols];
[_calendar invalidateHeaders];
[self.calendar configureAppearance];
}
}
- (void)invalidateAppearance
- (void)setSeparators:(FSCalendarSeparators)separators
{
[self invalidateFonts];
[self invalidateTextColors];
[self invalidateBorderColors];
[self invalidateBackgroundColors];
/*
[_calendar.collectionView.visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[_calendar invalidateAppearanceForCell:obj];
}];
[_calendar.header.collectionView.visibleCells makeObjectsPerformSelector:@selector(setNeedsLayout)];
[_calendar.visibleStickyHeaders makeObjectsPerformSelector:@selector(setNeedsLayout)];
*/
}
- (void)invalidateFonts
{
[self invalidateTitleFont];
[self invalidateSubtitleFont];
[self invalidateWeekdayFont];
[self invalidateHeaderFont];
}
- (void)invalidateTextColors
{
[self invalidateTitleTextColor];
[self invalidateSubtitleTextColor];
[self invalidateWeekdayTextColor];
[self invalidateHeaderTextColor];
}
- (void)invalidateBorderColors
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateBackgroundColors
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateEventColors
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateCellShapes
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateTitleFont
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateSubtitleFont
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateTitleTextColor
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateSubtitleTextColor
{
[_calendar.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
}
- (void)invalidateWeekdayFont
{
[_calendar invalidateWeekdayFont];
[_calendar.visibleStickyHeaders makeObjectsPerformSelector:_cmd];
}
- (void)invalidateWeekdayTextColor
{
[_calendar invalidateWeekdayTextColor];
[_calendar.visibleStickyHeaders makeObjectsPerformSelector:_cmd];
}
- (void)invalidateHeaderFont
{
[_calendar.header.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
[_calendar.visibleStickyHeaders makeObjectsPerformSelector:_cmd];
}
- (void)invalidateHeaderTextColor
{
[_calendar.header.collectionView.visibleCells makeObjectsPerformSelector:_cmd];
[_calendar.visibleStickyHeaders makeObjectsPerformSelector:_cmd];
if (_separators != separators) {
_separators = separators;
[_calendar.collectionView.collectionViewLayout invalidateLayout];
}
}
@end
@ -659,16 +439,6 @@
@implementation FSCalendarAppearance (Deprecated)
- (void)setCellStyle:(FSCalendarCellStyle)cellStyle
{
self.cellShape = (FSCalendarCellShape)cellStyle;
}
- (FSCalendarCellStyle)cellStyle
{
return (FSCalendarCellStyle)self.cellShape;
}
- (void)setUseVeryShortWeekdaySymbols:(BOOL)useVeryShortWeekdaySymbols
{
_caseOptions &= 15;
@ -680,65 +450,73 @@
return (_caseOptions & (15<<4) ) == FSCalendarCaseOptionsWeekdayUsesSingleUpperCase;
}
- (void)setAutoAdjustTitleSize:(BOOL)autoAdjustTitleSize
- (void)setTitleVerticalOffset:(CGFloat)titleVerticalOffset
{
self.adjustsFontSizeToFitContentSize = autoAdjustTitleSize;
self.titleOffset = CGPointMake(0, titleVerticalOffset);
}
- (BOOL)autoAdjustTitleSize
- (CGFloat)titleVerticalOffset
{
return self.adjustsFontSizeToFitContentSize;
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)setCellShape:(FSCalendarCellShape)cellShape
{
self.borderRadius = 1-cellShape;
}
- (FSCalendarCellShape)cellShape
{
return self.borderRadius==1.0?FSCalendarCellShapeCircle:FSCalendarCellShapeRectangle;
}
- (void)setTitleTextSize:(CGFloat)titleTextSize
{
self.titleFont = [UIFont fontWithName:_titleFontName size:titleTextSize];
}
- (CGFloat)titleTextSize
{
return _titleFontSize;
self.titleFont = [UIFont fontWithName:self.titleFont.fontName size:titleTextSize];
}
- (void)setSubtitleTextSize:(CGFloat)subtitleTextSize
{
self.subtitleFont = [UIFont fontWithName:_subtitleFontName size:subtitleTextSize];
}
- (CGFloat)subtitleTextSize
{
return _subtitleFontSize;
self.subtitleFont = [UIFont fontWithName:self.subtitleFont.fontName size:subtitleTextSize];
}
- (void)setWeekdayTextSize:(CGFloat)weekdayTextSize
{
self.weekdayFont = [UIFont fontWithName:_weekdayFontName size:weekdayTextSize];
}
- (CGFloat)weekdayTextSize
{
return _weekdayFontSize;
self.weekdayFont = [UIFont fontWithName:self.weekdayFont.fontName size:weekdayTextSize];
}
- (void)setHeaderTitleTextSize:(CGFloat)headerTitleTextSize
{
self.headerTitleFont = [UIFont fontWithName:_headerTitleFontName size:headerTitleTextSize];
self.headerTitleFont = [UIFont fontWithName:self.headerTitleFont.fontName size:headerTitleTextSize];
}
- (CGFloat)headerTitleTextSize
- (void)invalidateAppearance
{
return _headerTitleFontSize;
[self.calendar configureAppearance];
}
- (void)setAdjustsFontSizeToFitCellSize:(BOOL)adjustsFontSizeToFitCellSize
{
self.adjustsFontSizeToFitContentSize = adjustsFontSizeToFitCellSize;
}
- (BOOL)adjustsFontSizeToFitCellSize
{
return self.adjustsFontSizeToFitContentSize;
}
- (void)setAdjustsFontSizeToFitContentSize:(BOOL)adjustsFontSizeToFitContentSize {}
- (BOOL)adjustsFontSizeToFitContentSize { return YES; }
@end

View File

@ -0,0 +1,49 @@
//
// 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

View File

@ -0,0 +1,299 @@
//
// 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
{
if (self.calendar.transitionCoordinator.transition == FSCalendarTransitionWeekToMonth) {
return self.numberOfMonths;
} else {
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) {
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

View File

@ -7,57 +7,101 @@
//
#import <UIKit/UIKit.h>
#import "FSCalendar.h"
#import "FSCalendarEventIndicator.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 (weak, nonatomic) UILabel *titleLabel;
@property (weak, nonatomic) UILabel *subtitleLabel;
@property (weak, nonatomic) UIImageView *imageView;
@property (weak, nonatomic) CAShapeLayer *backgroundLayer;
@property (weak, nonatomic) FSCalendarEventIndicator *eventIndicator;
@property (strong, nonatomic) NSDate *date;
@property (strong, nonatomic) NSString *subtitle;
@property (strong, nonatomic) UIImage *image;
@property (assign, nonatomic) FSCalendarMonthPosition monthPosition;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
@property (assign, nonatomic) NSInteger numberOfEvents;
@property (assign, nonatomic) BOOL dateIsPlaceholder;
@property (assign, nonatomic) BOOL dateIsSelected;
@property (assign, nonatomic) BOOL dateIsToday;
@property (assign, nonatomic) BOOL weekend;
@property (readonly, nonatomic) BOOL weekend;
@property (strong, nonatomic) UIColor *preferredSelectionColor;
@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 (strong, nonatomic) id preferredEventColor;
@property (assign, nonatomic) FSCalendarCellShape preferredCellShape;
@property (assign, nonatomic) CGPoint preferredTitleOffset;
@property (assign, nonatomic) CGPoint preferredSubtitleOffset;
@property (assign, nonatomic) CGPoint preferredImageOffset;
@property (assign, nonatomic) CGPoint preferredEventOffset;
- (void)invalidateTitleFont;
- (void)invalidateSubtitleFont;
- (void)invalidateTitleTextColor;
- (void)invalidateSubtitleTextColor;
@property (strong, nonatomic) NSArray<UIColor *> *preferredEventDefaultColors;
@property (strong, nonatomic) NSArray<UIColor *> *preferredEventSelectionColors;
@property (assign, nonatomic) CGFloat preferredBorderRadius;
- (void)invalidateBorderColors;
- (void)invalidateBackgroundColors;
- (void)invalidateEventColors;
- (void)invalidateCellShapes;
// Add subviews to self.contentView and set up constraints
- (instancetype)initWithFrame:(CGRect)frame NS_REQUIRES_SUPER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_REQUIRES_SUPER;
- (void)invalidateImage;
// 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

View File

@ -8,17 +8,18 @@
#import "FSCalendarCell.h"
#import "FSCalendar.h"
#import "UIView+FSExtension.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarConstance.h"
#import "FSCalendarConstants.h"
@interface FSCalendarCell ()
@property (readonly, nonatomic) UIColor *colorForBackgroundLayer;
@property (readonly, nonatomic) UIColor *colorForCellFill;
@property (readonly, nonatomic) UIColor *colorForTitleLabel;
@property (readonly, nonatomic) UIColor *colorForSubtitleLabel;
@property (readonly, nonatomic) UIColor *colorForCellBorder;
@property (readonly, nonatomic) FSCalendarCellShape cellShape;
@property (readonly, nonatomic) NSArray<UIColor *> *colorsForEvents;
@property (readonly, nonatomic) CGFloat borderRadius;
@end
@ -30,79 +31,137 @@
{
self = [super initWithFrame:frame];
if (self) {
_needsAdjustingViewFrame = YES;
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.hidden = YES;
[self.contentView.layer insertSublayer:shapeLayer below:_titleLabel.layer];
self.backgroundLayer = 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;
[self commonInit];
}
return self;
}
- (void)setBounds:(CGRect)bounds
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
[super setBounds:bounds];
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;
_backgroundLayer.frame = CGRectMake((self.bounds.size.width-diameter)/2,
(titleHeight-diameter)/2,
diameter,
diameter);
_backgroundLayer.borderWidth = 1.0;
_backgroundLayer.borderColor = [UIColor clearColor].CGColor;
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;
CGFloat eventSize = _backgroundLayer.frame.size.height/6.0;
_eventIndicator.frame = CGRectMake(0, CGRectGetMaxY(_backgroundLayer.frame)+eventSize*0.17, bounds.size.width, eventSize*0.83);
_imageView.frame = self.contentView.bounds;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self configureCell];
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];
[CATransaction setDisableActions:YES];
_backgroundLayer.hidden = YES;
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"];
}
@ -110,7 +169,7 @@
- (void)performSelecting
{
_backgroundLayer.hidden = NO;
_shapeLayer.opacity = 1;
#define kAnimationDuration FSCalendarDefaultBounceAnimationDuration
@ -126,8 +185,8 @@
zoomIn.duration = kAnimationDuration/4;
group.duration = kAnimationDuration;
group.animations = @[zoomOut, zoomIn];
[_backgroundLayer addAnimation:group forKey:@"bounce"];
[self configureCell];
[_shapeLayer addAnimation:group forKey:@"bounce"];
[self configureAppearance];
#undef kAnimationDuration
@ -135,103 +194,72 @@
#pragma mark - Private
- (void)configureCell
- (void)configureAppearance
{
self.contentView.hidden = self.dateIsPlaceholder && !self.calendar.showsPlaceholders;
if (self.contentView.hidden) {
return;
}
_titleLabel.text = [NSString stringWithFormat:@"%@",@([_calendar dayOfDate:_date])];
if (_subtitle) {
_subtitleLabel.text = _subtitle;
if (_subtitleLabel.hidden) {
_subtitleLabel.hidden = NO;
}
} else {
if (!_subtitleLabel.hidden) {
_subtitleLabel.hidden = YES;
}
}
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 height = titleHeight + subtitleHeight;
_titleLabel.frame = CGRectMake(0,
(self.contentView.fs_height*5.0/6.0-height)*0.5+_appearance.titleVerticalOffset,
self.fs_width,
titleHeight);
_subtitleLabel.frame = CGRectMake(0,
_titleLabel.fs_bottom - (_titleLabel.fs_height-_titleLabel.font.pointSize)+_appearance.subtitleVerticalOffset,
self.fs_width,
subtitleHeight);
} else {
_titleLabel.frame = CGRectMake(0, _appearance.titleVerticalOffset, self.contentView.fs_width, floor(self.contentView.fs_height*5.0/6.0));
}
}
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;
BOOL shouldHiddenBackgroundLayer = !self.selected && !self.dateIsToday && !self.dateIsSelected && !borderColor;
UIColor *fillColor = self.colorForCellFill;
if (_backgroundLayer.hidden != shouldHiddenBackgroundLayer) {
_backgroundLayer.hidden = shouldHiddenBackgroundLayer;
BOOL shouldHideShapeLayer = !self.selected && !self.dateIsToday && !borderColor && !fillColor;
if (_shapeLayer.opacity == shouldHideShapeLayer) {
_shapeLayer.opacity = !shouldHideShapeLayer;
}
if (!shouldHiddenBackgroundLayer) {
if (!shouldHideShapeLayer) {
CGPathRef path = self.cellShape == FSCalendarCellShapeCircle ?
[UIBezierPath bezierPathWithOvalInRect:_backgroundLayer.bounds].CGPath :
[UIBezierPath bezierPathWithRect:_backgroundLayer.bounds].CGPath;
if (!CGPathEqualToPath(_backgroundLayer.path,path)) {
_backgroundLayer.path = path;
CGColorRef cellFillColor = self.colorForCellFill.CGColor;
if (!CGColorEqualToColor(_shapeLayer.fillColor, cellFillColor)) {
_shapeLayer.fillColor = cellFillColor;
}
CGColorRef backgroundColor = self.colorForBackgroundLayer.CGColor;
if (!CGColorEqualToColor(_backgroundLayer.fillColor, backgroundColor)) {
_backgroundLayer.fillColor = backgroundColor;
CGColorRef cellBorderColor = self.colorForCellBorder.CGColor;
if (!CGColorEqualToColor(_shapeLayer.strokeColor, cellBorderColor)) {
_shapeLayer.strokeColor = cellBorderColor;
}
CGColorRef borderColor = self.colorForCellBorder.CGColor;
if (!CGColorEqualToColor(_backgroundLayer.strokeColor, borderColor)) {
_backgroundLayer.strokeColor = borderColor;
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]) {
[self invalidateImage];
_imageView.image = _image;
_imageView.hidden = !_image;
}
if (_eventIndicator.hidden == (_numberOfEvents > 0)) {
_eventIndicator.hidden = !_numberOfEvents;
}
_eventIndicator.numberOfEvents = self.numberOfEvents;
_eventIndicator.color = self.preferredEventColor ?: _appearance.eventColor;
}
_eventIndicator.color = self.colorsForEvents;
- (BOOL)isWeekend
{
return _date && ([_calendar weekdayOfDate:_date] == 1 || [_calendar weekdayOfDate:_date] == 7);
}
- (UIColor *)colorForCurrentStateInDictionary:(NSDictionary *)dictionary
{
if (self.isSelected || self.dateIsSelected) {
if (self.isSelected) {
if (self.dateIsToday) {
return dictionary[@(FSCalendarCellStateSelected|FSCalendarCellStateToday)] ?: dictionary[@(FSCalendarCellStateSelected)];
}
@ -240,77 +268,28 @@
if (self.dateIsToday && [[dictionary allKeys] containsObject:@(FSCalendarCellStateToday)]) {
return dictionary[@(FSCalendarCellStateToday)];
}
if (self.dateIsPlaceholder && [[dictionary allKeys] containsObject:@(FSCalendarCellStatePlaceholder)]) {
if (self.placeholder && [[dictionary allKeys] containsObject:@(FSCalendarCellStatePlaceholder)]) {
return dictionary[@(FSCalendarCellStatePlaceholder)];
}
if (self.isWeekend && [[dictionary allKeys] containsObject:@(FSCalendarCellStateWeekend)]) {
if (self.weekend && [[dictionary allKeys] containsObject:@(FSCalendarCellStateWeekend)]) {
return dictionary[@(FSCalendarCellStateWeekend)];
}
return dictionary[@(FSCalendarCellStateNormal)];
}
- (void)invalidateTitleFont
{
_titleLabel.font = self.appearance.preferredTitleFont;
}
- (void)invalidateTitleTextColor
{
_titleLabel.textColor = self.colorForTitleLabel;
}
- (void)invalidateSubtitleFont
{
_subtitleLabel.font = self.appearance.preferredSubtitleFont;
}
- (void)invalidateSubtitleTextColor
{
_subtitleLabel.textColor = self.colorForSubtitleLabel;
}
- (void)invalidateBorderColors
{
_backgroundLayer.strokeColor = self.colorForCellBorder.CGColor;
}
- (void)invalidateBackgroundColors
{
_backgroundLayer.fillColor = self.colorForBackgroundLayer.CGColor;
}
- (void)invalidateEventColors
{
_eventIndicator.color = self.preferredEventColor ?: _appearance.eventColor;
}
- (void)invalidateCellShapes
{
CGPathRef path = self.cellShape == FSCalendarCellShapeCircle ?
[UIBezierPath bezierPathWithOvalInRect:_backgroundLayer.bounds].CGPath :
[UIBezierPath bezierPathWithRect:_backgroundLayer.bounds].CGPath;
_backgroundLayer.path = path;
}
- (void)invalidateImage
{
_imageView.image = _image;
_imageView.hidden = !_image;
}
#pragma mark - Properties
- (UIColor *)colorForBackgroundLayer
- (UIColor *)colorForCellFill
{
if (self.dateIsSelected || self.isSelected) {
return self.preferredSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.backgroundColors];
if (self.selected) {
return self.preferredFillSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.backgroundColors];
}
return [self colorForCurrentStateInDictionary:_appearance.backgroundColors];
return self.preferredFillDefaultColor ?: [self colorForCurrentStateInDictionary:_appearance.backgroundColors];
}
- (UIColor *)colorForTitleLabel
{
if (self.dateIsSelected || self.isSelected) {
if (self.selected) {
return self.preferredTitleSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.titleColors];
}
return self.preferredTitleDefaultColor ?: [self colorForCurrentStateInDictionary:_appearance.titleColors];
@ -318,7 +297,7 @@
- (UIColor *)colorForSubtitleLabel
{
if (self.dateIsSelected || self.isSelected) {
if (self.selected) {
return self.preferredSubtitleSelectionColor ?: [self colorForCurrentStateInDictionary:_appearance.subtitleColors];
}
return self.preferredSubtitleDefaultColor ?: [self colorForCurrentStateInDictionary:_appearance.subtitleColors];
@ -326,51 +305,168 @@
- (UIColor *)colorForCellBorder
{
if (self.dateIsSelected || self.isSelected) {
if (self.selected) {
return _preferredBorderSelectionColor ?: _appearance.borderSelectionColor;
}
return _preferredBorderDefaultColor ?: _appearance.borderDefaultColor;
}
- (FSCalendarCellShape)cellShape
- (NSArray<UIColor *> *)colorsForEvents
{
return _preferredCellShape ?: _appearance.cellShape;
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;
}
if (![_appearance isEqual:calendar.appearance]) {
_appearance = calendar.appearance;
[self invalidateTitleFont];
[self invalidateSubtitleFont];
[self invalidateTitleTextColor];
[self invalidateSubtitleTextColor];
[self invalidateEventColors];
[self configureAppearance];
}
}
- (void)setSubtitle:(NSString *)subtitle
{
if (![_subtitle isEqualToString:subtitle]) {
_needsAdjustingViewFrame = !(_subtitle.length && subtitle.length);
BOOL diff = (subtitle.length && !_subtitle.length) || (_subtitle.length && !subtitle.length);
_subtitle = subtitle;
if (_needsAdjustingViewFrame) {
if (diff) {
[self setNeedsLayout];
}
}
}
- (void)setNeedsAdjustingViewFrame:(BOOL)needsAdjustingViewFrame
{
if (_needsAdjustingViewFrame != needsAdjustingViewFrame) {
_needsAdjustingViewFrame = needsAdjustingViewFrame;
_eventIndicator.needsAdjustingViewFrame = needsAdjustingViewFrame;
}
}
@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 < 3; 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),3);
[self setNeedsLayout];
}
}
@end
@implementation FSCalendarBlankCell
- (void)configureAppearance {}
@end

View File

@ -3,7 +3,7 @@
// FSCalendar
//
// Created by Wenchao Ding on 10/25/15.
// Copyright (c) 2015 wenchaoios. All rights reserved.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
@ -11,3 +11,8 @@
@interface FSCalendarCollectionView : UICollectionView
@end
@interface FSCalendarSeparator : UICollectionReusableView
@end

View File

@ -3,11 +3,14 @@
// FSCalendar
//
// Created by Wenchao Ding on 10/25/15.
// Copyright (c) 2015 wenchaoios. All rights reserved.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
// Reject -[UIScrollView(UIScrollViewInternal) _adjustContentOffsetIfNecessary]
#import "FSCalendarCollectionView.h"
#import "FSCalendarCell.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarConstants.h"
@interface FSCalendarCollectionView ()
@ -41,16 +44,27 @@
{
self.scrollsToTop = NO;
self.contentInset = UIEdgeInsetsZero;
#ifdef __IPHONE_9_0
if ([self respondsToSelector:@selector(setSemanticContentAttribute:)]) {
self.semanticContentAttribute = UISemanticContentAttributeForceLeftToRight;
}
#endif
#ifdef __IPHONE_10_0
SEL selector = NSSelectorFromString(@"setPrefetchingEnabled:");
if (selector && [self respondsToSelector:selector]) {
[self fs_performSelector:selector withObjects:@NO, nil];
}
#endif
}
- (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
@ -60,3 +74,24 @@
@end
@implementation FSCalendarSeparator
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = FSCalendarStandardSeparatorColor;
}
return self;
}
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
self.frame = layoutAttributes.frame;
}
@end

View File

@ -0,0 +1,22 @@
//
// 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) CGFloat interitemSpacing;
@property (assign, nonatomic) UIEdgeInsets sectionInsets;
@property (assign, nonatomic) UICollectionViewScrollDirection scrollDirection;
@property (assign, nonatomic) CGSize headerReferenceSize;
@end

View File

@ -0,0 +1,561 @@
//
// 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"
#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) 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;
@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:UIScreenDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveNotifications:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[self registerClass:[FSCalendarSeparator 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;
UICollectionViewLayoutAttributes *attributes = self.itemAttributes[indexPath];
if (!attributes) {
attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect frame = ({
CGFloat x, y;
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
x = self.lefts[column] + indexPath.section * self.collectionView.fs_width;
y = self.tops[row];
break;
}
case UICollectionViewScrollDirectionVertical: {
x = self.lefts[column];
if (!self.calendar.floatingMode) {
y = self.tops[row] + indexPath.section * self.collectionView.fs_height;
} else {
y = self.sectionTops[indexPath.section] + self.headerReferenceSize.height + self.tops[row];
}
break;
}
default:
break;
}
CGFloat width = self.widths[column];
CGFloat height = self.heights[row];
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) {
switch (self.scrollDirection) {
case UICollectionViewScrollDirectionHorizontal: {
x = self.lefts[coordinate.column] + indexPath.section * self.collectionView.fs_width;
y = self.tops[coordinate.row]+self.heights[coordinate.row];
break;
}
case UICollectionViewScrollDirectionVertical: {
x = 0;
y = self.tops[coordinate.row]+self.heights[coordinate.row] + indexPath.section * self.collectionView.fs_height;
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;
}
#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
- (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

View File

@ -1,61 +0,0 @@
//
// 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 - Constance
UIKIT_EXTERN CGFloat const FSCalendarStandardHeaderHeight;
UIKIT_EXTERN CGFloat const FSCalendarStandardWeekdayHeight;
UIKIT_EXTERN CGFloat const FSCalendarStandardMonthlyPageHeight;
UIKIT_EXTERN CGFloat const FSCalendarStandardWeeklyPageHeight;
UIKIT_EXTERN CGFloat const FSCalendarStandardCellDiameter;
UIKIT_EXTERN CGFloat const FSCalendarAutomaticDimension;
UIKIT_EXTERN CGFloat const FSCalendarDefaultBounceAnimationDuration;
UIKIT_EXTERN CGFloat const FSCalendarStandardRowHeight;
UIKIT_EXTERN CGFloat const FSCalendarStandardTitleTextSize;
UIKIT_EXTERN CGFloat const FSCalendarStandardSubtitleTextSize;
UIKIT_EXTERN CGFloat const FSCalendarStandardWeekdayTextSize;
UIKIT_EXTERN CGFloat const FSCalendarStandardHeaderTextSize;
UIKIT_EXTERN CGFloat const FSCalendarMaximumEventDotDiameter;
UIKIT_EXTERN NSInteger const FSCalendarDefaultHourComponent;
#define FSCalendarDeviceIsIPad [[UIDevice currentDevice].model hasPrefix:@"iPad"]
#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 FSColorRGBA(r,g,b,a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]
#if CGFLOAT_IS_DOUBLE
#define FSCalendarFloor(c) floor(c)
#else
#define FSCalendarFloor(c) floorf(c)
#endif
#pragma mark - Deprecated
#define FSCalendarDeprecated(instead) DEPRECATED_MSG_ATTRIBUTE(" Use " # instead " instead")
FSCalendarDeprecated('FSCalendarCellShape')
typedef NS_ENUM(NSInteger, FSCalendarCellStyle) {
FSCalendarCellStyleCircle = 0,
FSCalendarCellStyleRectangle = 1
};
FSCalendarDeprecated('FSCalendarScrollDirection')
typedef NS_ENUM(NSInteger, FSCalendarFlow) {
FSCalendarFlowVertical,
FSCalendarFlowHorizontal
};

View File

@ -0,0 +1,102 @@
//
// 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;
CG_EXTERN CGFloat const FSCalendarStandardScopeHandleHeight;
UIKIT_EXTERN NSInteger const FSCalendarDefaultHourComponent;
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 FSCalendarStandardScopeHandleColor [[UIColor lightGrayColor] colorWithAlphaComponent:0.50]
#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"]
#if CGFLOAT_IS_DOUBLE
#define FSCalendarFloor(c) floor(c)
#define FSCalendarRound(c) round(c)
#define FSCalendarCeil(c) ceil(c)
#define FSCalendarMod(c1,c2) fmod(c1,c2)
#else
#define FSCalendarFloor(c) floorf(c)
#define FSCalendarRound(c) roundf(c)
#define FSCalendarCeil(c) ceilf(c)
#define FSCalendarMod(c1,c2) fmodf(c1,c2)
#endif
#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")
FSCalendarDeprecated('borderRadius')
typedef NS_ENUM(NSUInteger, FSCalendarCellShape) {
FSCalendarCellShapeCircle = 0,
FSCalendarCellShapeRectangle = 1
};
typedef NS_ENUM(NSUInteger, FSCalendarUnit) {
FSCalendarUnitMonth = NSCalendarUnitMonth,
FSCalendarUnitWeekOfYear = NSCalendarUnitWeekOfYear,
FSCalendarUnitDay = NSCalendarUnitDay
};
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;
}
}

View File

@ -8,20 +8,39 @@
// https://github.com/WenchaoD
//
#import "FSCalendarConstance.h"
#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+1.0/3;
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;
CGFloat const FSCalendarStandardScopeHandleHeight = 26;
NSInteger const FSCalendarDefaultHourComponent = 0;
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
};
NSInteger const FSCalendarDefaultHourComponent = 0;

View File

@ -0,0 +1,17 @@
//
// 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

View File

@ -0,0 +1,40 @@
//
// FSCalendarDelegationFactory.m
// FSCalendar
//
// Created by dingwenchao on 19/12/2016.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import "FSCalendarDelegationFactory.h"
#define FSCalendarSelectorEntry(SEL1,SEL2) NSStringFromSelector(@selector(SEL1)):NSStringFromSelector(@selector(SEL2))
@implementation FSCalendarDelegationFactory
+ (FSCalendarDelegationProxy *)dataSourceProxy
{
FSCalendarDelegationProxy *delegation = [[FSCalendarDelegationProxy alloc] init];
delegation.protocol = @protocol(FSCalendarDataSource);
delegation.deprecations = @{FSCalendarSelectorEntry(calendar:numberOfEventsForDate:, calendar:hasEventForDate:)};
return delegation;
}
+ (FSCalendarDelegationProxy *)delegateProxy
{
FSCalendarDelegationProxy *delegation = [[FSCalendarDelegationProxy alloc] init];
delegation.protocol = @protocol(FSCalendarDelegateAppearance);
delegation.deprecations = @{
FSCalendarSelectorEntry(calendarCurrentPageDidChange:, calendarCurrentMonthDidChange:),
FSCalendarSelectorEntry(calendar:shouldSelectDate:atMonthPosition:, calendar:shouldSelectDate:),
FSCalendarSelectorEntry(calendar:didSelectDate:atMonthPosition:, calendar:didSelectDate:),
FSCalendarSelectorEntry(calendar:shouldDeselectDate:atMonthPosition:, calendar:shouldDeselectDate:),
FSCalendarSelectorEntry(calendar:didDeselectDate:atMonthPosition:, calendar:didDeselectDate:)
};
return delegation;
}
@end
#undef FSCalendarSelectorEntry

View File

@ -0,0 +1,31 @@
//
// 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

View File

@ -0,0 +1,68 @@
//
// 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)];
#endif
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)];
}
- (SEL)deprecatedSelectorOfSelector:(SEL)selector
{
NSString *selectorString = NSStringFromSelector(selector);
selectorString = self.deprecations[selectorString];
return NSSelectorFromString(selectorString);
}
@end

View File

@ -13,47 +13,43 @@
#import "FSCalendar.h"
#import "FSCalendarCell.h"
#import "FSCalendarHeader.h"
#import "FSCalendarHeaderView.h"
#import "FSCalendarStickyHeader.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarCollectionViewLayout.h"
#import "FSCalendarScopeHandle.h"
#import "FSCalendarCalculator.h"
#import "FSCalendarTransitionCoordinator.h"
#import "FSCalendarDelegationProxy.h"
@interface FSCalendar (Dynamic)
@property (readonly, nonatomic) CAShapeLayer *maskLayer;
@property (readonly, nonatomic) FSCalendarHeader *header;
@property (readonly, nonatomic) UICollectionView *collectionView;
@property (readonly, nonatomic) UICollectionViewFlowLayout *collectionViewLayout;
@property (readonly, nonatomic) NSArray *weekdays;
@property (readonly, nonatomic) BOOL ibEditing;
@property (readonly, nonatomic) FSCalendarCollectionView *collectionView;
@property (readonly, nonatomic) FSCalendarScopeHandle *scopeHandle;
@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) CGFloat preferredRowHeight;
@property (readonly, nonatomic) UIView *bottomBorder;
@property (readonly, nonatomic) NSCalendar *calendar;
@property (readonly, nonatomic) NSCalendar *gregorian;
@property (readonly, nonatomic) NSDateComponents *components;
@property (readonly, nonatomic) NSDateFormatter *formatter;
@property (readonly, nonatomic) UIView *contentView;
@property (readonly, nonatomic) UIView *daysContainer;
@property (assign, nonatomic) BOOL needsAdjustingMonthPosition;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
- (void)invalidateWeekdayFont;
- (void)invalidateWeekdayTextColor;
- (void)invalidateHeaders;
- (void)invalidateWeekdaySymbols;
- (void)invalidateAppearanceForCell:(FSCalendarCell *)cell;
- (void)adjustMonthPosition;
- (void)configureAppearance;
- (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;
- (BOOL)isPageInRange:(NSDate *)page;
- (BOOL)isDateInRange:(NSDate *)date;
- (CGSize)sizeThatFits:(CGSize)size scope:(FSCalendarScope)scope;
@ -68,21 +64,21 @@
@property (readonly, nonatomic) NSDictionary *subtitleColors;
@property (readonly, nonatomic) NSDictionary *borderColors;
@property (readonly, nonatomic) UIFont *preferredTitleFont;
@property (readonly, nonatomic) UIFont *preferredSubtitleFont;
@property (readonly, nonatomic) UIFont *preferredWeekdayFont;
@property (readonly, nonatomic) UIFont *preferredHeaderTitleFont;
@end
- (void)adjustTitleIfNecessary;
- (void)invalidateFonts;
@interface FSCalendarWeekdayView (Dynamic)
@property (readwrite, nonatomic) FSCalendar *calendar;
@end
@interface FSCalendarCollectionViewLayout (Dynamic)
@interface FSCalendarHeader (Dynamic)
@property (readonly, nonatomic) UICollectionView *collectionView;
@property (readonly, nonatomic) CGSize estimatedItemSize;
@end
@interface FSCalendarDelegationProxy()<FSCalendarDataSource,FSCalendarDelegate,FSCalendarDelegateAppearance>
@end

View File

@ -1,17 +0,0 @@
//
// FSCalendarEventView.h
// FSCalendar
//
// Created by dingwenchao on 2/3/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface FSCalendarEventIndicator : UIView
@property (assign, nonatomic) NSInteger numberOfEvents;
@property (strong, nonatomic) id color;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
@end

View File

@ -1,143 +0,0 @@
//
// FSCalendarEventView.m
// FSCalendar
//
// Created by dingwenchao on 2/3/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import "FSCalendarEventIndicator.h"
#import "FSCalendarConstance.h"
#import "UIView+FSExtension.h"
#import "CALayer+FSExtension.h"
@interface FSCalendarEventIndicator ()
@property (weak, nonatomic) UIView *contentView;
@property (strong, nonatomic) NSMutableArray *eventLayers;
@property (assign, nonatomic) BOOL needsInvalidatingColor;
- (UIImage *)dotImageWithColor:(UIColor *)color diameter:(CGFloat)diameter;
@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 = [NSMutableArray arrayWithCapacity:3];
for (int i = 0; i < 3; i++) {
CALayer *layer = [CALayer layer];
layer.masksToBounds = YES;
layer.backgroundColor = FSCalendarStandardEventDotColor.CGColor;
[self.eventLayers addObject:layer];
[self.contentView.layer addSublayer:layer];
}
_needsInvalidatingColor = YES;
_needsAdjustingViewFrame = YES;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (_needsAdjustingViewFrame) {
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) {
if (_needsAdjustingViewFrame) {
_needsAdjustingViewFrame = NO;
CGFloat diameter = MIN(MIN(self.fs_width, self.fs_height),FSCalendarMaximumEventDotDiameter);
for (int i = 0; i < self.eventLayers.count; i++) {
CALayer *layer = self.eventLayers[i];
layer.hidden = i >= self.numberOfEvents;
if (!layer.hidden) {
layer.frame = CGRectMake(2*i*diameter, (self.fs_height-diameter)*0.5, diameter, diameter);
layer.cornerRadius = diameter * 0.5;
}
}
}
if (_needsInvalidatingColor) {
_needsInvalidatingColor = NO;
CGFloat diameter = MIN(MIN(self.fs_width, self.fs_height),FSCalendarMaximumEventDotDiameter);
if ([_color isKindOfClass:[UIColor class]]) {
UIImage *dotImage = [self dotImageWithColor:_color diameter:diameter];
[self.eventLayers makeObjectsPerformSelector:@selector(setContents:) withObject:(id)dotImage.CGImage];
} else if ([_color isKindOfClass:[NSArray class]]) {
NSArray *colors = (NSArray *)_color;
if (colors.count) {
UIColor *lastColor = colors.firstObject;
for (int i = 0; i < self.numberOfEvents; i++) {
if (i < colors.count) {
lastColor = colors[i];
}
CALayer *layer = self.eventLayers[i];
UIImage *dotImage = [self dotImageWithColor:lastColor diameter:diameter];
layer.contents = (id)dotImage.CGImage;
}
}
}
}
}
}
- (void)setColor:(id)color
{
if (![_color isEqual:color]) {
_color = color;
_needsInvalidatingColor = YES;
[self setNeedsLayout];
}
}
- (void)setNumberOfEvents:(NSInteger)numberOfEvents
{
if (_numberOfEvents != numberOfEvents) {
_numberOfEvents = MIN(MAX(numberOfEvents,0),3);
_needsAdjustingViewFrame = YES;
[self setNeedsLayout];
}
}
- (void)setNeedsAdjustingViewFrame:(BOOL)needsAdjustingViewFrame
{
if (_needsAdjustingViewFrame != needsAdjustingViewFrame) {
_needsAdjustingViewFrame = needsAdjustingViewFrame;
if (needsAdjustingViewFrame) {
[self setNeedsLayout];
}
}
}
- (UIImage *)dotImageWithColor:(UIColor *)color diameter:(CGFloat)diameter
{
CGRect bounds = CGRectMake(0, 0, diameter, diameter);
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillEllipseInRect(context, bounds);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end

View File

@ -0,0 +1,84 @@
//
// 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

View File

@ -0,0 +1,440 @@
//
// 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;
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

View File

@ -1,20 +0,0 @@
//
// FSCalendarAnimationLayout.h
// FSCalendar
//
// Created by dingwenchao on 1/3/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FSCalendar;
typedef NS_ENUM(NSUInteger, FSCalendarScope);
@interface FSCalendarFlowLayout : UICollectionViewFlowLayout <UICollectionViewDelegateFlowLayout>
@property (weak, nonatomic) FSCalendar *calendar;
@end

View File

@ -1,86 +0,0 @@
//
// FSCalendarAnimationLayout.m
// FSCalendar
//
// Created by dingwenchao on 1/3/16.
// Copyright © 2016 wenchaoios. All rights reserved.
//
#import "FSCalendarFlowLayout.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendar.h"
#import "UIView+FSExtension.h"
#import <objc/runtime.h>
@implementation FSCalendarFlowLayout
- (instancetype)init
{
self = [super init];
if (self) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumInteritemSpacing = 0;
self.minimumLineSpacing = 0;
self.itemSize = CGSizeMake(1, 1);
self.sectionInset = UIEdgeInsetsZero;
}
return self;
}
- (void)prepareLayout
{
[super prepareLayout];
CGFloat rowHeight = self.calendar.preferredRowHeight;
if (!self.calendar.floatingMode) {
self.headerReferenceSize = CGSizeZero;
CGFloat padding = self.calendar.preferredWeekdayHeight*0.1;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
padding = FSCalendarFloor(padding);
rowHeight = FSCalendarFloor(rowHeight*2)*0.5; // Round to nearest multiple of 0.5. e.g. (16.8->16.5),(16.2->16.0)
}
self.sectionInset = UIEdgeInsetsMake(padding, 0, padding, 0);
switch (self.calendar.scope) {
case FSCalendarScopeMonth: {
CGSize itemSize = CGSizeMake(
self.collectionView.fs_width/7.0-(self.scrollDirection == UICollectionViewScrollDirectionVertical)*0.1,
rowHeight
);
self.itemSize = itemSize;
break;
}
case FSCalendarScopeWeek: {
CGSize itemSize = CGSizeMake(self.collectionView.fs_width/7.0, rowHeight);
self.itemSize = itemSize;
break;
}
}
} else {
CGFloat headerHeight = self.calendar.preferredWeekdayHeight*1.5+self.calendar.preferredHeaderHeight;
self.headerReferenceSize = CGSizeMake(self.collectionView.fs_width, headerHeight);
CGSize itemSize = CGSizeMake(
self.collectionView.fs_width/7-(self.scrollDirection == UICollectionViewScrollDirectionVertical)*0.1,
rowHeight
);
self.itemSize = itemSize;
self.sectionInset = UIEdgeInsetsZero;
}
}
@end

View File

@ -7,22 +7,25 @@
//
#import <UIKit/UIKit.h>
#import "FSCalendarCollectionView.h"
@class FSCalendar,FSCalendarAppearance;
@interface FSCalendarHeader : UIView
@class FSCalendar, FSCalendarAppearance, FSCalendarHeaderLayout, FSCalendarCollectionView;
@interface FSCalendarHeaderView : UIView
@property (weak, nonatomic) FSCalendarCollectionView *collectionView;
@property (weak, nonatomic) FSCalendarHeaderLayout *collectionViewLayout;
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarAppearance *appearance;
@property (assign, nonatomic) CGFloat scrollOffset;
@property (assign, nonatomic) UICollectionViewScrollDirection scrollDirection;
@property (assign, nonatomic) BOOL scrollEnabled;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
@property (assign, nonatomic) BOOL needsAdjustingMonthPosition;
- (void)setScrollOffset:(CGFloat)scrollOffset animated:(BOOL)animated;
- (void)reloadData;
- (void)configureAppearance;
@end
@ -30,17 +33,17 @@
@interface FSCalendarHeaderCell : UICollectionViewCell
@property (weak, nonatomic) UILabel *titleLabel;
@property (weak, nonatomic) FSCalendarHeader *header;
- (void)invalidateHeaderFont;
- (void)invalidateHeaderTextColor;
@property (weak, nonatomic) FSCalendarHeaderView *header;
@end
@interface FSCalendarHeaderLayout : UICollectionViewFlowLayout
@end
@interface FSCalendarHeaderTouchDeliver : UIView
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarHeader *header;
@property (weak, nonatomic) FSCalendarHeaderView *header;
@end

View File

@ -7,20 +7,19 @@
//
#import "FSCalendar.h"
#import "UIView+FSExtension.h"
#import "FSCalendarHeader.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarHeaderView.h"
#import "FSCalendarCollectionView.h"
#import "FSCalendarDynamicHeader.h"
@interface FSCalendarHeader ()<UICollectionViewDataSource,UICollectionViewDelegate>
@interface FSCalendarHeaderView ()<UICollectionViewDataSource,UICollectionViewDelegate>
@property (weak, nonatomic) UICollectionViewFlowLayout *collectionViewLayout;
@property (assign, nonatomic) BOOL needsAdjustingMonthPosition;
- (void)scrollToOffset:(CGFloat)scrollOffset animated:(BOOL)animated;
- (void)configureCell:(FSCalendarHeaderCell *)cell atIndexPath:(NSIndexPath *)indexPath;
@end
@implementation FSCalendarHeader
@implementation FSCalendarHeaderView
#pragma mark - Life cycle
@ -44,17 +43,12 @@
- (void)initialize
{
_needsAdjustingViewFrame = YES;
_needsAdjustingMonthPosition = YES;
_scrollDirection = UICollectionViewScrollDirectionHorizontal;
_scrollEnabled = YES;
_needsAdjustingMonthPosition = YES;
_needsAdjustingViewFrame = YES;
UICollectionViewFlowLayout *collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
collectionViewLayout.minimumInteritemSpacing = 0;
collectionViewLayout.minimumLineSpacing = 0;
collectionViewLayout.sectionInset = UIEdgeInsetsZero;
collectionViewLayout.itemSize = CGSizeMake(1, 1);
FSCalendarHeaderLayout *collectionViewLayout = [[FSCalendarHeaderLayout alloc] init];
self.collectionViewLayout = collectionViewLayout;
FSCalendarCollectionView *collectionView = [[FSCalendarCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:collectionViewLayout];
@ -77,21 +71,14 @@
if (_needsAdjustingViewFrame) {
_needsAdjustingViewFrame = NO;
_collectionViewLayout.itemSize = CGSizeMake(1, 1);
[_collectionViewLayout invalidateLayout];
_collectionView.frame = CGRectMake(0, self.fs_height*0.1, self.fs_width, self.fs_height*0.9);
_collectionViewLayout.itemSize = CGSizeMake(
_collectionView.fs_width*((_scrollDirection==UICollectionViewScrollDirectionHorizontal)?0.5:1),
_collectionView.fs_height
);
}
if (_needsAdjustingMonthPosition) {
_needsAdjustingMonthPosition = NO;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
[_collectionView setContentOffset:CGPointMake((_scrollOffset+0.5)*_collectionViewLayout.itemSize.width, 0) animated:NO];
} else {
[_collectionView setContentOffset:CGPointMake(0, _scrollOffset * _collectionViewLayout.itemSize.height) animated:NO];
}
};
[self scrollToOffset:_scrollOffset animated:NO];
}
}
@ -110,81 +97,18 @@
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
switch (self.calendar.scope) {
case FSCalendarScopeMonth: {
switch (_scrollDirection) {
case UICollectionViewScrollDirectionVertical: {
NSDate *minimumPage = [_calendar beginingOfMonthOfDate:_calendar.minimumDate];
NSInteger count = [_calendar monthsFromDate:minimumPage toDate:_calendar.maximumDate] + 1;
return count;
}
case UICollectionViewScrollDirectionHorizontal: {
// contentOffset
// 2 more pages to prevent scrollView from auto bouncing while push/present to other UIViewController
NSDate *minimumPage = [_calendar beginingOfMonthOfDate:_calendar.minimumDate];
NSInteger count = [_calendar monthsFromDate:minimumPage toDate:_calendar.maximumDate] + 1;
return count + 2;
}
default: {
break;
}
}
break;
}
case FSCalendarScopeWeek: {
NSDate *minimumPage = [_calendar beginingOfMonthOfDate:_calendar.minimumDate];
NSInteger count = [_calendar weeksFromDate:minimumPage toDate:_calendar.maximumDate] + 1;
return count + 2;
}
default: {
break;
}
NSInteger numberOfSections = self.calendar.collectionView.numberOfSections;
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
return numberOfSections;
}
return 0;
return numberOfSections + 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
FSCalendarHeaderCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.header = self;
cell.titleLabel.font = _appearance.preferredHeaderTitleFont;
cell.titleLabel.textColor = _appearance.headerTitleColor;
_calendar.formatter.dateFormat = _appearance.headerDateFormat;
BOOL usesUpperCase = (_appearance.caseOptions & 15) == FSCalendarCaseOptionsHeaderUsesUpperCase;
NSString *text = nil;
switch (self.calendar.scope) {
case FSCalendarScopeMonth: {
if (_scrollDirection == UICollectionViewScrollDirectionHorizontal) {
//
if ((indexPath.item == 0 || indexPath.item == [collectionView numberOfItemsInSection:0] - 1)) {
text = nil;
} else {
NSDate *date = [_calendar dateByAddingMonths:indexPath.item-1 toDate:_calendar.minimumDate];
text = [_calendar.formatter stringFromDate:date];
}
} else {
NSDate *date = [_calendar dateByAddingMonths:indexPath.item toDate:_calendar.minimumDate];
text = [_calendar.formatter stringFromDate:date];
}
break;
}
case FSCalendarScopeWeek: {
if ((indexPath.item == 0 || indexPath.item == [collectionView numberOfItemsInSection:0] - 1)) {
text = nil;
} else {
NSDate *firstPage = [_calendar middleOfWeekFromDate:_calendar.minimumDate];
NSDate *date = [_calendar dateByAddingWeeks:indexPath.item-1 toDate:firstPage];
text = [_calendar.formatter stringFromDate:date];
}
break;
}
default: {
break;
}
}
text = usesUpperCase ? text.uppercaseString : text;
cell.titleLabel.text = text;
[cell setNeedsLayout];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
@ -200,23 +124,34 @@
#pragma mark - Properties
- (void)setCalendar:(FSCalendar *)calendar
{
if (![_calendar isEqual:calendar]) {
_calendar = calendar;
_appearance = calendar.appearance;
}
_calendar = calendar;
[self configureAppearance];
}
- (void)setScrollOffset:(CGFloat)scrollOffset
{
[self setScrollOffset:scrollOffset animated:NO];
}
- (void)setScrollOffset:(CGFloat)scrollOffset animated:(BOOL)animated
{
if (_scrollOffset != scrollOffset) {
_scrollOffset = scrollOffset;
}
_needsAdjustingMonthPosition = YES;
[self setNeedsLayout];
[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
@ -225,7 +160,6 @@
_scrollDirection = scrollDirection;
_collectionViewLayout.scrollDirection = scrollDirection;
_needsAdjustingMonthPosition = YES;
_needsAdjustingViewFrame = YES;
[self setNeedsLayout];
}
}
@ -245,6 +179,56 @@
[_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
@ -280,31 +264,49 @@
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.appearance.headerMinimumDissolvedAlpha)*ABS(center-position)/self.fs_width;
self.contentView.alpha = 1.0 - (1.0-self.header.calendar.appearance.headerMinimumDissolvedAlpha)*ABS(center-position)/self.fs_width;
} else {
self.contentView.alpha = (position > 0 && position < self.header.fs_width*0.75);
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.appearance.headerMinimumDissolvedAlpha)*ABS(center-position)/self.fs_height;
self.contentView.alpha = 1.0 - (1.0-self.header.calendar.appearance.headerMinimumDissolvedAlpha)*ABS(center-position)/self.fs_height;
}
}
- (void)invalidateHeaderFont
@end
@implementation FSCalendarHeaderLayout
- (instancetype)init
{
_titleLabel.font = self.header.appearance.preferredHeaderTitleFont;
self = [super init];
if (self) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumInteritemSpacing = 0;
self.minimumLineSpacing = 0;
self.sectionInset = UIEdgeInsetsZero;
self.itemSize = CGSizeMake(1, 1);
}
return self;
}
- (void)invalidateHeaderTextColor
- (void)prepareLayout
{
_titleLabel.textColor = self.header.appearance.headerTitleColor;
[super prepareLayout];
self.itemSize = CGSizeMake(
self.collectionView.fs_width*((self.scrollDirection==UICollectionViewScrollDirectionHorizontal)?0.5:1),
self.collectionView.fs_height
);
}
@end
@implementation FSCalendarHeaderTouchDeliver
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

View File

@ -0,0 +1,20 @@
//
// FSCalendarScopeHandle.h
// FSCalendar
//
// Created by dingwenchao on 4/29/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
@class FSCalendar;
@interface FSCalendarScopeHandle : UIView <UIGestureRecognizerDelegate>
@property (weak, nonatomic) UIPanGestureRecognizer *panGesture;
@property (weak, nonatomic) FSCalendar *calendar;
- (void)handlePan:(id)sender;
@end

View File

@ -0,0 +1,78 @@
//
// FSCalendarScopeHandle.m
// FSCalendar
//
// Created by dingwenchao on 4/29/16.
// Copyright © 2016 Wenchao Ding. All rights reserved.
//
#import "FSCalendarScopeHandle.h"
#import "FSCalendar.h"
#import "FSCalendarTransitionCoordinator.h"
#import "FSCalendarDynamicHeader.h"
#import "FSCalendarExtensions.h"
@interface FSCalendarScopeHandle () <UIGestureRecognizerDelegate>
@property (weak, nonatomic) UIView *topBorder;
@property (weak, nonatomic) UIView *handleIndicator;
@property (weak, nonatomic) FSCalendarAppearance *appearance;
@property (assign, nonatomic) CGFloat lastTranslation;
@end
@implementation FSCalendarScopeHandle
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIView *view;
view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 1)];
view.backgroundColor = FSCalendarStandardLineColor;
[self addSubview:view];
self.topBorder = view;
view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 30, 6)];
view.layer.shouldRasterize = YES;
view.layer.masksToBounds = YES;
view.layer.cornerRadius = 3;
view.layer.backgroundColor = FSCalendarStandardScopeHandleColor.CGColor;
[self addSubview:view];
self.handleIndicator = view;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
panGesture.minimumNumberOfTouches = 1;
panGesture.maximumNumberOfTouches = 2;
[self addGestureRecognizer:panGesture];
self.panGesture = panGesture;
self.exclusiveTouch = YES;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.topBorder.frame = CGRectMake(0, 0, self.fs_width, 1);
self.handleIndicator.center = CGPointMake(self.fs_width/2, self.fs_height/2-0.5);
}
- (void)handlePan:(id)sender
{
[self.calendar.transitionCoordinator handleScopeGesture:sender];
}
- (void)setCalendar:(FSCalendar *)calendar
{
_calendar = calendar;
self.panGesture.delegate = self.calendar.transitionCoordinator;
}
@end

View File

@ -3,7 +3,7 @@
// FSCalendar
//
// Created by dingwenchao on 9/17/15.
// Copyright (c) 2015 wenchaoios. All rights reserved.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
#import <UIKit/UIKit.h>
@ -13,18 +13,11 @@
@interface FSCalendarStickyHeader : UICollectionReusableView
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarAppearance *appearance;
@property (weak, nonatomic) UILabel *titleLabel;
@property (strong, nonatomic) NSArray *weekdayLabels;
@property (strong, nonatomic) NSDate *month;
- (void)invalidateHeaderFont;
- (void)invalidateHeaderTextColor;
- (void)invalidateWeekdayFont;
- (void)invalidateWeekdayTextColor;
- (void)configureAppearance;
- (void)invalidateWeekdaySymbols;
@end
@end

View File

@ -3,21 +3,21 @@
// FSCalendar
//
// Created by dingwenchao on 9/17/15.
// Copyright (c) 2015 wenchaoios. All rights reserved.
// Copyright (c) 2015 Wenchao Ding. All rights reserved.
//
#import "FSCalendarStickyHeader.h"
#import "FSCalendar.h"
#import "UIView+FSExtension.h"
#import "FSCalendarConstance.h"
#import "FSCalendarWeekdayView.h"
#import "FSCalendarExtensions.h"
#import "FSCalendarConstants.h"
#import "FSCalendarDynamicHeader.h"
@interface FSCalendarStickyHeader ()
@property (weak, nonatomic) UIView *contentView;
@property (weak, nonatomic) UIView *separator;
@property (assign, nonatomic) BOOL needsAdjustingViewFrame;
@property (weak , nonatomic) UIView *contentView;
@property (weak , nonatomic) UIView *bottomBorder;
@property (weak , nonatomic) FSCalendarWeekdayView *weekdayView;
@end
@ -28,8 +28,6 @@
self = [super initWithFrame:frame];
if (self) {
_needsAdjustingViewFrame = YES;
UIView *view;
UILabel *label;
@ -45,18 +43,13 @@
self.titleLabel = label;
view = [[UIView alloc] initWithFrame:CGRectZero];
view.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.25];
view.backgroundColor = FSCalendarStandardLineColor;
[_contentView addSubview:view];
self.separator = view;
self.bottomBorder = view;
NSMutableArray *weekdayLabels = [NSMutableArray arrayWithCapacity:7];
for (int i = 0; i < 7; i++) {
label = [[UILabel alloc] initWithFrame:CGRectZero];
label.textAlignment = NSTextAlignmentCenter;
[_contentView addSubview:label];
[weekdayLabels addObject:label];
}
self.weekdayLabels = weekdayLabels.copy;
FSCalendarWeekdayView *weekdayView = [[FSCalendarWeekdayView alloc] init];
[self.contentView addSubview:weekdayView];
self.weekdayView = weekdayView;
}
return self;
}
@ -65,28 +58,19 @@
{
[super layoutSubviews];
if (_needsAdjustingViewFrame) {
_needsAdjustingViewFrame = NO;
_contentView.frame = self.bounds;
CGFloat weekdayWidth = self.fs_width / 7.0;
CGFloat weekdayHeight = _calendar.preferredWeekdayHeight;
CGFloat weekdayMargin = weekdayHeight * 0.1;
CGFloat titleWidth = _contentView.fs_width;
[_weekdayLabels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger index, BOOL *stop) { \
label.frame = CGRectMake(index*weekdayWidth, _contentView.fs_height-weekdayHeight-weekdayMargin, weekdayWidth, weekdayHeight);
}];
CGFloat titleHeight = [@"1" sizeWithAttributes:@{NSFontAttributeName:_appearance.preferredHeaderTitleFont}].height*1.5 + weekdayMargin*3;
_separator.frame = CGRectMake(0, _contentView.fs_height-weekdayHeight-weekdayMargin*2, _contentView.fs_width, 1.0);
_titleLabel.frame = CGRectMake(0, _separator.fs_bottom-titleHeight-weekdayMargin, titleWidth,titleHeight);
}
_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);
[self reloadData];
}
#pragma mark - Properties
@ -95,63 +79,30 @@
{
if (![_calendar isEqual:calendar]) {
_calendar = calendar;
_weekdayView.calendar = calendar;
[self configureAppearance];
}
if (![_appearance isEqual:calendar.appearance]) {
_appearance = calendar.appearance;
[self invalidateHeaderFont];
[self invalidateHeaderTextColor];
[self invalidateWeekdayFont];
[self invalidateWeekdayTextColor];
}
}
#pragma mark - Public methods
- (void)reloadData
{
[self invalidateWeekdaySymbols];
_calendar.formatter.dateFormat = _appearance.headerDateFormat;
BOOL usesUpperCase = (_appearance.caseOptions & 15) == FSCalendarCaseOptionsHeaderUsesUpperCase;
NSString *text = [_calendar.formatter stringFromDate:_month];
text = usesUpperCase ? text.uppercaseString : text;
_titleLabel.text = text;
}
#pragma mark - Private methods
- (void)invalidateHeaderFont
- (void)configureAppearance
{
_titleLabel.font = _appearance.headerTitleFont;
_titleLabel.font = self.calendar.appearance.headerTitleFont;
_titleLabel.textColor = self.calendar.appearance.headerTitleColor;
[self.weekdayView configureAppearance];
}
- (void)invalidateHeaderTextColor
- (void)setMonth:(NSDate *)month
{
_titleLabel.textColor = _appearance.headerTitleColor;
_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;
}
- (void)invalidateWeekdayFont
{
[_weekdayLabels makeObjectsPerformSelector:@selector(setFont:) withObject:_appearance.weekdayFont];
}
- (void)invalidateWeekdayTextColor
{
[_weekdayLabels makeObjectsPerformSelector:@selector(setTextColor:) withObject:_appearance.weekdayTextColor];
}
- (void)invalidateWeekdaySymbols
{
BOOL useVeryShortWeekdaySymbols = (_appearance.caseOptions & (15<<4) ) == FSCalendarCaseOptionsWeekdayUsesSingleUpperCase;
NSArray *weekdaySymbols = useVeryShortWeekdaySymbols ? _calendar.calendar.veryShortStandaloneWeekdaySymbols : _calendar.calendar.shortStandaloneWeekdaySymbols;
BOOL useDefaultWeekdayCase = (_appearance.caseOptions & (15<<4) ) == FSCalendarCaseOptionsWeekdayUsesDefaultCase;
[_weekdayLabels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger index, BOOL *stop) {
index += _calendar.firstWeekday-1;
index %= 7;
label.text = useDefaultWeekdayCase ? weekdaySymbols[index] : [weekdaySymbols[index] uppercaseString];
}];
}
@end

View File

@ -0,0 +1,59 @@
//
// 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"
#import "FSCalendarScopeHandle.h"
typedef NS_ENUM(NSUInteger, FSCalendarTransition) {
FSCalendarTransitionNone,
FSCalendarTransitionMonthToWeek,
FSCalendarTransitionWeekToMonth
};
typedef NS_ENUM(NSUInteger, FSCalendarTransitionState) {
FSCalendarTransitionStateIdle,
FSCalendarTransitionStateChanging,
FSCalendarTransitionStateFinishing,
};
@interface FSCalendarTransitionCoordinator : NSObject <UIGestureRecognizerDelegate>
@property (weak, nonatomic) FSCalendar *calendar;
@property (weak, nonatomic) FSCalendarCollectionView *collectionView;
@property (weak, nonatomic) FSCalendarCollectionViewLayout *collectionViewLayout;
@property (assign, nonatomic) FSCalendarTransition transition;
@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;
- (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 focusedRowNumber;
@property (assign, nonatomic) NSDate *focusedDate;
@property (strong, nonatomic) NSDate *firstDayOfMonth;
@end

View File

@ -0,0 +1,752 @@
//
// 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"
#import <objc/runtime.h>
@interface FSCalendarTransitionCoordinator ()
@property (readonly, nonatomic) FSCalendarTransitionAttributes *transitionAttributes;
@property (strong , nonatomic) FSCalendarTransitionAttributes *pendingAttributes;
@property (assign , nonatomic) CGFloat lastTranslation;
- (void)performTransitionCompletionAnimated:(BOOL)animated;
- (void)performTransitionCompletion:(FSCalendarTransition)transition animated:(BOOL)animated;
- (void)performAlphaAnimationFrom:(CGFloat)fromAlpha to:(CGFloat)toAlpha duration:(CGFloat)duration exception:(NSInteger)exception completion:(void(^)())completion;
- (void)performForwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress;
- (void)performBackwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress;
- (void)performAlphaAnimationWithProgress:(CGFloat)progress;
- (void)performPathAnimationWithProgress:(CGFloat)progress;
- (void)scopeTransitionDidBegin:(UIPanGestureRecognizer *)panGesture;
- (void)scopeTransitionDidUpdate:(UIPanGestureRecognizer *)panGesture;
- (void)scopeTransitionDidEnd:(UIPanGestureRecognizer *)panGesture;
- (CGRect)boundingRectForScope:(FSCalendarScope)scope page:(NSDate *)page;
- (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: {
[self scopeTransitionDidEnd:sender];
break;
}
case UIGestureRecognizerStateCancelled: {
[self scopeTransitionDidEnd:sender];
break;
}
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 == self.calendar.scopeHandle.panGesture) {
CGFloat velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:gestureRecognizer.view].y;
return self.calendar.scope == FSCalendarScopeWeek ? velocity >= 0 : velocity <= 0;
}
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];
switch (self.calendar.scope) {
case FSCalendarScopeMonth: {
if (velocity.y < 0) {
self.state = FSCalendarTransitionStateChanging;
self.transition = FSCalendarTransitionMonthToWeek;
}
break;
}
case FSCalendarScopeWeek: {
if (velocity.y > 0) {
self.state = FSCalendarTransitionStateChanging;
self.transition = FSCalendarTransitionWeekToMonth;
}
break;
}
default:
break;
}
if (self.state != FSCalendarTransitionStateChanging) return;
self.pendingAttributes = self.transitionAttributes;
self.lastTranslation = [panGesture translationInView:panGesture.view].y;
if (self.transition == FSCalendarTransitionWeekToMonth) {
[self.calendar fs_setVariable:self.pendingAttributes.targetPage forKey:@"_currentPage"];
[self prelayoutForWeekToMonthTransition];
self.collectionView.fs_top = -self.pendingAttributes.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height;
}
}
- (void)scopeTransitionDidUpdate:(UIPanGestureRecognizer *)panGesture
{
if (self.state != FSCalendarTransitionStateChanging) return;
CGFloat translation = [panGesture translationInView:panGesture.view].y;
[CATransaction begin];
[CATransaction setDisableActions:YES];
switch (self.transition) {
case FSCalendarTransitionMonthToWeek: {
CGFloat progress = ({
CGFloat minTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds);
translation = MAX(minTranslation, translation);
translation = MIN(0, translation);
CGFloat progress = translation/minTranslation;
progress;
});
[self performAlphaAnimationWithProgress:progress];
[self performPathAnimationWithProgress:progress];
break;
}
case FSCalendarTransitionWeekToMonth: {
CGFloat progress = ({
CGFloat maxTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds);
translation = MIN(maxTranslation, translation);
translation = MAX(0, translation);
CGFloat progress = translation/maxTranslation;
progress;
});
[self performAlphaAnimationWithProgress:progress];
[self performPathAnimationWithProgress:progress];
break;
}
default:
break;
}
[CATransaction commit];
self.lastTranslation = translation;
}
- (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;
switch (self.transition) {
case FSCalendarTransitionMonthToWeek: {
CGFloat progress = ({
CGFloat minTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds);
translation = MAX(minTranslation, translation);
translation = MIN(0, translation);
CGFloat progress = translation/minTranslation;
progress;
});
if (velocity >= 0) {
[self performBackwardTransition:self.transition fromProgress:progress];
} else {
[self performForwardTransition:self.transition fromProgress:progress];
}
break;
}
case FSCalendarTransitionWeekToMonth: {
CGFloat progress = ({
CGFloat maxTranslation = CGRectGetHeight(self.pendingAttributes.targetBounds) - CGRectGetHeight(self.pendingAttributes.sourceBounds);
translation = MAX(0, translation);
translation = MIN(maxTranslation, translation);
CGFloat progress = translation/maxTranslation;
progress;
});
if (velocity >= 0) {
[self performForwardTransition:self.transition fromProgress:progress];
} else {
[self performBackwardTransition:self.transition fromProgress:progress];
}
break;
}
default:
break;
}
}
#pragma mark - Public methods
- (void)performScopeTransitionFromScope:(FSCalendarScope)fromScope toScope:(FSCalendarScope)toScope animated:(BOOL)animated
{
if (fromScope == toScope) return;
self.transition = ({
FSCalendarTransition transition = FSCalendarTransitionNone;
if (fromScope == FSCalendarScopeMonth && toScope == FSCalendarScopeWeek) {
transition = FSCalendarTransitionMonthToWeek;
} else if (fromScope == FSCalendarScopeWeek && toScope == FSCalendarScopeMonth) {
transition = FSCalendarTransitionWeekToMonth;
}
transition;
});
// Start transition
self.state = FSCalendarTransitionStateFinishing;
FSCalendarTransitionAttributes *attr = self.transitionAttributes;
self.pendingAttributes = attr;
switch (self.transition) {
case FSCalendarTransitionMonthToWeek: {
[self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"];
self.calendar.contentView.clipsToBounds = YES;
if (animated) {
CGFloat duration = 0.3;
[self performAlphaAnimationFrom:1 to:0 duration:0.22 exception:attr.focusedRowNumber completion:^{
[self performTransitionCompletionAnimated:animated];
}];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
[UIView beginAnimations:nil context:nil];
[UIView setAnimationsEnabled:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:duration];
self.collectionView.fs_top = -attr.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height;
[self boundingRectWillChange:attr.targetBounds animated:animated];
[UIView commitAnimations];
}
} else {
[self performTransitionCompletionAnimated:animated];
[self boundingRectWillChange:attr.targetBounds animated:animated];
}
break;
}
case FSCalendarTransitionWeekToMonth: {
[self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"];
[self prelayoutForWeekToMonthTransition];
if (animated) {
CGFloat duration = 0.3;
[self performAlphaAnimationFrom:0 to:1 duration:duration exception:attr.focusedRowNumber completion:^{
[self performTransitionCompletionAnimated:animated];
}];
[CATransaction begin];
[CATransaction setDisableActions:NO];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
self.collectionView.fs_top = -attr.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationsEnabled:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:duration];
self.collectionView.fs_top = 0;
[self boundingRectWillChange:attr.targetBounds animated:animated];
[UIView commitAnimations];
}
[CATransaction commit];
} else {
[self performTransitionCompletionAnimated:animated];
[self boundingRectWillChange:attr.targetBounds animated:animated];
}
break;
}
default:
break;
}
}
- (void)performBoundingRectTransitionFromMonth:(NSDate *)fromMonth toMonth:(NSDate *)toMonth duration:(CGFloat)duration
{
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 = (CGRect){CGPointZero,[self.calendar sizeThatFits:self.calendar.frame.size scope:FSCalendarScopeMonth]};
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
{
[self performTransitionCompletion:self.transition animated:animated];
}
- (void)performTransitionCompletion:(FSCalendarTransition)transition animated:(BOOL)animated
{
switch (transition) {
case FSCalendarTransitionMonthToWeek: {
[self.calendar.visibleCells enumerateObjectsUsingBlock:^(UICollectionViewCell *obj, NSUInteger idx, BOOL * stop) {
obj.contentView.layer.opacity = 1;
}];
self.collectionViewLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.calendar.calendarHeaderView.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.needsAdjustingViewFrame = YES;
[self.collectionView reloadData];
[self.calendar.calendarHeaderView reloadData];
break;
}
case FSCalendarTransitionWeekToMonth: {
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar.visibleCells enumerateObjectsUsingBlock:^(UICollectionViewCell *obj, NSUInteger idx, BOOL * stop) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
obj.contentView.layer.opacity = 1;
[CATransaction commit];
[obj.contentView.layer removeAnimationForKey:@"opacity"];
}];
break;
}
default:
break;
}
self.state = FSCalendarTransitionStateIdle;
self.transition = FSCalendarTransitionNone;
self.calendar.contentView.clipsToBounds = NO;
self.pendingAttributes = nil;
[self.calendar setNeedsLayout];
[self.calendar layoutIfNeeded];
}
- (FSCalendarTransitionAttributes *)transitionAttributes
{
FSCalendarTransitionAttributes *attributes = [[FSCalendarTransitionAttributes alloc] init];
attributes.sourceBounds = self.calendar.bounds;
attributes.sourcePage = self.calendar.currentPage;
switch (self.transition) {
case FSCalendarTransitionMonthToWeek: {
NSDate *focusedDate = ({
NSArray<NSDate *> *candidates = ({
NSMutableArray *dates = self.calendar.selectedDates.reverseObjectEnumerator.allObjects.mutableCopy;
if (self.calendar.today) {
[dates addObject:self.calendar.today];
}
if (self.calendar.currentPage) {
[dates addObject:self.calendar.currentPage];
}
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:FSCalendarScopeMonth];
NSInteger currentSection = [self.calendar.calculator indexPathForDate:self.calendar.currentPage scope:FSCalendarScopeMonth].section;
return indexPath.section == currentSection;
}]];
NSDate *date = visibleCandidates.firstObject;
date;
});
NSInteger focusedRow = [self.calendar.calculator coordinateForIndexPath:[self.calendar.calculator indexPathForDate:focusedDate scope:FSCalendarScopeMonth]].row;
NSDate *currentPage = self.calendar.currentPage;
NSIndexPath *indexPath = [self.calendar.calculator indexPathForDate:currentPage scope:FSCalendarScopeMonth];
NSDate *monthHead = [self.calendar.calculator monthHeadForSection:indexPath.section];
NSDate *targetPage = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitDay value:focusedRow*7 toDate:monthHead options:0];
attributes.focusedRowNumber = focusedRow;
attributes.focusedDate = focusedDate;
attributes.targetPage = targetPage;
attributes.targetBounds = [self boundingRectForScope:FSCalendarScopeWeek page:attributes.targetPage];
break;
}
case FSCalendarTransitionWeekToMonth: {
NSInteger focusedRow = 0;
NSDate *currentPage = self.calendar.currentPage;
NSDate *focusedDate = ({
NSArray<NSDate *> *candidates = ({
NSMutableArray *dates = self.calendar.selectedDates.reverseObjectEnumerator.allObjects.mutableCopy;
if (self.calendar.today) {
[dates addObject:self.calendar.today];
}
if (self.calendar.currentPage) {
[dates addObject:[self.calendar.gregorian fs_lastDayOfWeek:currentPage]];
}
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:FSCalendarScopeWeek];
NSInteger currentSection = [self.calendar.calculator indexPathForDate:self.calendar.currentPage scope:FSCalendarScopeWeek].section;
return indexPath.section == currentSection;
}]];
NSDate *date = visibleCandidates.firstObject;
date;
});
NSDate *firstDayOfMonth = [self.calendar.gregorian fs_firstDayOfMonth:focusedDate];
attributes.focusedDate = focusedDate;
firstDayOfMonth = firstDayOfMonth ?: [self.calendar.gregorian fs_firstDayOfMonth:currentPage];
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++) {
NSDate *currentRow = [self.calendar.gregorian dateByAddingUnit:NSCalendarUnitWeekOfYear value:i toDate:firstDateOfPage options:0];
if ([self.calendar.gregorian isDate:currentRow inSameDayAsDate:currentPage]) {
focusedRow = i;
currentPage = firstDayOfMonth;
break;
}
}
attributes.focusedRowNumber = focusedRow;
attributes.targetPage = currentPage;
attributes.firstDayOfMonth = firstDayOfMonth;
attributes.targetBounds = [self boundingRectForScope:FSCalendarScopeMonth page:attributes.targetPage];
break;
}
default:
break;
}
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: {
if (self.calendar.placeholderType == FSCalendarPlaceholderTypeFillSixRows) {
contentSize = self.cachedMonthSize;
} else {
CGFloat padding = self.calendar.collectionViewLayout.sectionInsets.top + self.calendar.collectionViewLayout.sectionInsets.bottom;
contentSize = CGSizeMake(self.calendar.fs_width,
self.calendar.preferredHeaderHeight+
self.calendar.preferredWeekdayHeight+
([self.calendar.calculator numberOfRowsInMonth:page]*self.calendar.collectionViewLayout.estimatedItemSize.height)+
self.calendar.scopeHandle.fs_height+padding);
}
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.scopeHandle.fs_bottom = CGRectGetMaxY(targetBounds);
self.calendar.contentView.fs_height = CGRectGetHeight(targetBounds)-self.calendar.scopeHandle.fs_height;
self.calendar.daysContainer.fs_height = CGRectGetHeight(targetBounds)-self.calendar.preferredHeaderHeight-self.calendar.preferredWeekdayHeight-self.calendar.scopeHandle.fs_height;
[[self.calendar valueForKey:@"delegateProxy"] calendar:self.calendar boundingRectWillChange:targetBounds animated:animated];
}
- (void)performForwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress
{
FSCalendarTransitionAttributes *attr = self.pendingAttributes;
switch (transition) {
case FSCalendarTransitionMonthToWeek: {
[self.calendar willChangeValueForKey:@"scope"];
[self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeWeek forKey:@"_scope"];
[self.calendar didChangeValueForKey:@"scope"];
[self.calendar fs_setVariable:attr.targetPage forKey:@"_currentPage"];
self.calendar.contentView.clipsToBounds = YES;
CGFloat currentAlpha = MAX(1-progress*1.1,0);
CGFloat duration = 0.3;
[self performAlphaAnimationFrom:currentAlpha to:0 duration:0.22 exception:attr.focusedRowNumber completion:^{
[self performTransitionCompletionAnimated:YES];
}];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
[UIView beginAnimations:@"delegateTranslation" context:"translation"];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:duration];
self.collectionView.fs_top = -attr.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height;
[self boundingRectWillChange:attr.targetBounds animated:YES];
[UIView commitAnimations];
}
break;
}
case FSCalendarTransitionWeekToMonth: {
[self.calendar willChangeValueForKey:@"scope"];
[self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeMonth forKey:@"_scope"];
[self.calendar didChangeValueForKey:@"scope"];
[self performAlphaAnimationFrom:progress to:1 duration:0.4 exception:attr.focusedRowNumber completion:^{
[self performTransitionCompletionAnimated:YES];
}];
CGFloat duration = 0.3;
[CATransaction begin];
[CATransaction setDisableActions:NO];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
[UIView beginAnimations:@"delegateTranslation" context:"translation"];
[UIView setAnimationsEnabled:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:duration];
self.collectionView.fs_top = 0;
[self boundingRectWillChange:attr.targetBounds animated:YES];
[UIView commitAnimations];
}
[CATransaction commit];
break;
}
default:
break;
}
}
- (void)performBackwardTransition:(FSCalendarTransition)transition fromProgress:(CGFloat)progress
{
switch (transition) {
case FSCalendarTransitionMonthToWeek: {
[self.calendar willChangeValueForKey:@"scope"];
[self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeMonth forKey:@"_scope"];
[self.calendar didChangeValueForKey:@"scope"];
[self performAlphaAnimationFrom:MAX(1-progress*1.1,0) to:1 duration:0.3 exception:self.pendingAttributes.focusedRowNumber completion:^{
[self.calendar.visibleCells enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.contentView.layer.opacity = 1;
[obj.contentView.layer removeAnimationForKey:@"opacity"];
}];
self.pendingAttributes = nil;
self.state = FSCalendarTransitionStateIdle;
}];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
[UIView beginAnimations:@"delegateTranslation" context:"translation"];
[UIView setAnimationsEnabled:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.3];
self.collectionView.fs_top = 0;
[self boundingRectWillChange:self.pendingAttributes.sourceBounds animated:YES];
[UIView commitAnimations];
}
break;
}
case FSCalendarTransitionWeekToMonth: {
[self.calendar willChangeValueForKey:@"scope"];
[self.calendar fs_setUnsignedIntegerVariable:FSCalendarScopeWeek forKey:@"_scope"];
[self.calendar didChangeValueForKey:@"scope"];
[self performAlphaAnimationFrom:progress to:0 duration:0.3 exception:self.pendingAttributes.focusedRowNumber completion:^{
[self.calendar fs_setVariable:self.pendingAttributes.sourcePage forKey:@"_currentPage"];
[self performTransitionCompletion:FSCalendarTransitionMonthToWeek animated:YES];
}];
if (self.calendar.delegate && ([self.calendar.delegate respondsToSelector:@selector(calendar:boundingRectWillChange:animated:)] || [self.calendar.delegate respondsToSelector:@selector(calendarCurrentScopeWillChange:animated:)])) {
[UIView beginAnimations:@"delegateTranslation" context:"translation"];
[UIView setAnimationsEnabled:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.3];
self.collectionView.fs_top = (-self.pendingAttributes.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height);
[self boundingRectWillChange:self.pendingAttributes.sourceBounds animated:YES];
[UIView commitAnimations];
}
break;
}
default:
break;
}
}
- (void)performAlphaAnimationFrom:(CGFloat)fromAlpha to:(CGFloat)toAlpha duration:(CGFloat)duration exception:(NSInteger)exception completion:(void(^)())completion;
{
[self.calendar.visibleCells enumerateObjectsUsingBlock:^(FSCalendarCell *cell, NSUInteger idx, BOOL *stop) {
if (CGRectContainsPoint(self.collectionView.bounds, cell.center)) {
BOOL shouldPerformAlpha = NO;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
NSInteger row = [self.calendar.calculator coordinateForIndexPath:indexPath].row;
shouldPerformAlpha = row != exception;
if (shouldPerformAlpha) {
CABasicAnimation *opacity = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacity.duration = duration;
opacity.fromValue = @(fromAlpha);
opacity.toValue = @(toAlpha);
opacity.removedOnCompletion = NO;
opacity.fillMode = kCAFillModeForwards;
[cell.contentView.layer addAnimation:opacity forKey:@"opacity"];
}
}
}];
if (completion) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
completion();
});
}
}
- (void)performAlphaAnimationWithProgress:(CGFloat)progress
{
CGFloat opacity = self.transition == FSCalendarTransitionMonthToWeek ? MAX((1-progress*1.1),0) : progress;
[self.calendar.visibleCells enumerateObjectsUsingBlock:^(FSCalendarCell *cell, NSUInteger idx, BOOL *stop) {
if (CGRectContainsPoint(self.collectionView.bounds, cell.center)) {
BOOL shouldPerformAlpha = NO;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
NSInteger row = [self.calendar.calculator coordinateForIndexPath:indexPath].row;
shouldPerformAlpha = row != self.pendingAttributes.focusedRowNumber;
if (shouldPerformAlpha) {
cell.contentView.layer.opacity = opacity;
}
}
}];
}
- (void)performPathAnimationWithProgress:(CGFloat)progress
{
CGFloat targetHeight = CGRectGetHeight(self.pendingAttributes.targetBounds);
CGFloat sourceHeight = CGRectGetHeight(self.pendingAttributes.sourceBounds);
CGFloat currentHeight = sourceHeight - (sourceHeight-targetHeight)*progress - self.calendar.scopeHandle.fs_height;
CGRect currentBounds = CGRectMake(0, 0, CGRectGetWidth(self.pendingAttributes.targetBounds), currentHeight+self.calendar.scopeHandle.fs_height);
self.collectionView.fs_top = (-self.pendingAttributes.focusedRowNumber*self.calendar.collectionViewLayout.estimatedItemSize.height)*(self.transition == FSCalendarTransitionMonthToWeek?progress:(1-progress));
[self boundingRectWillChange:currentBounds animated:NO];
if (self.transition == FSCalendarTransitionWeekToMonth) {
self.calendar.contentView.fs_height = targetHeight;
}
}
- (void)prelayoutForWeekToMonthTransition
{
self.calendar.contentView.clipsToBounds = YES;
self.calendar.contentView.fs_height = CGRectGetHeight(self.pendingAttributes.targetBounds)-self.calendar.scopeHandle.fs_height;
self.collectionViewLayout.scrollDirection = (UICollectionViewScrollDirection)self.calendar.scrollDirection;
self.calendar.calendarHeaderView.scrollDirection = self.collectionViewLayout.scrollDirection;
self.calendar.needsAdjustingViewFrame = YES;
[self.calendar setNeedsLayout];
[self.collectionView reloadData];
[self.calendar.calendarHeaderView reloadData];
[self.calendar layoutIfNeeded];
}
@end
@implementation FSCalendarTransitionAttributes
@end

View File

@ -0,0 +1,27 @@
//
// 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

View File

@ -0,0 +1,109 @@
//
// 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);
CGFloat x = 0;
for (NSInteger i = 0; i < count; i++) {
CGFloat width = widths[i];
UILabel *label = [self.weekdayPointers pointerAtIndex:i];
label.frame = CGRectMake(x, 0, width, self.contentView.fs_height);
x += width;
}
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

View File

@ -1,79 +0,0 @@
//
// NSDate+FSExtension.h
// Pods
//
// Created by Wenchao Ding on 29/1/15.
//
//
#import <Foundation/Foundation.h>
/**
* This category is deprecated in this framework as it premised that the calendar should be gregorian. But feel free to use it for gregorian-only.
*/
@interface NSDate (FSExtension)
@property (readonly, nonatomic) NSInteger fs_year;
@property (readonly, nonatomic) NSInteger fs_month;
@property (readonly, nonatomic) NSInteger fs_day;
@property (readonly, nonatomic) NSInteger fs_weekday;
@property (readonly, nonatomic) NSInteger fs_weekOfYear;
@property (readonly, nonatomic) NSInteger fs_hour;
@property (readonly, nonatomic) NSInteger fs_minute;
@property (readonly, nonatomic) NSInteger fs_second;
@property (readonly, nonatomic) NSDate *fs_dateByIgnoringTimeComponents;
@property (readonly, nonatomic) NSDate *fs_firstDayOfMonth;
@property (readonly, nonatomic) NSDate *fs_lastDayOfMonth;
@property (readonly, nonatomic) NSDate *fs_firstDayOfWeek;
@property (readonly, nonatomic) NSDate *fs_middleOfWeek;
@property (readonly, nonatomic) NSDate *fs_tomorrow;
@property (readonly, nonatomic) NSDate *fs_yesterday;
@property (readonly, nonatomic) NSInteger fs_numberOfDaysInMonth;
+ (instancetype)fs_dateFromString:(NSString *)string format:(NSString *)format;
+ (instancetype)fs_dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day;
- (NSDate *)fs_dateByAddingYears:(NSInteger)years;
- (NSDate *)fs_dateBySubtractingYears:(NSInteger)years;
- (NSDate *)fs_dateByAddingMonths:(NSInteger)months;
- (NSDate *)fs_dateBySubtractingMonths:(NSInteger)months;
- (NSDate *)fs_dateByAddingWeeks:(NSInteger)weeks;
- (NSDate *)fs_dateBySubtractingWeeks:(NSInteger)weeks;
- (NSDate *)fs_dateByAddingDays:(NSInteger)days;
- (NSDate *)fs_dateBySubtractingDays:(NSInteger)days;
- (NSInteger)fs_yearsFrom:(NSDate *)date;
- (NSInteger)fs_monthsFrom:(NSDate *)date;
- (NSInteger)fs_weeksFrom:(NSDate *)date;
- (NSInteger)fs_daysFrom:(NSDate *)date;
- (BOOL)fs_isEqualToDateForMonth:(NSDate *)date;
- (BOOL)fs_isEqualToDateForWeek:(NSDate *)date;
- (BOOL)fs_isEqualToDateForDay:(NSDate *)date;
- (NSString *)fs_stringWithFormat:(NSString *)format;
- (NSString *)fs_string;
@end
@interface NSCalendar (FSExtension)
+ (instancetype)fs_sharedCalendar;
@end
@interface NSDateFormatter (FSExtension)
+ (instancetype)fs_sharedDateFormatter;
@end
@interface NSDateComponents (FSExtension)
+ (instancetype)fs_sharedDateComponents;
@end

View File

@ -1,344 +0,0 @@
//
// NSDate+FSExtension.m
// Pods
//
// Created by Wenchao Ding on 29/1/15.
//
//
#import "NSDate+FSExtension.h"
@implementation NSDate (FSExtension)
- (NSInteger)fs_year
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitYear fromDate:self];
return component.year;
}
- (NSInteger)fs_month
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitMonth
fromDate:self];
return component.month;
}
- (NSInteger)fs_day
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitDay
fromDate:self];
return component.day;
}
- (NSInteger)fs_weekday
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitWeekday fromDate:self];
return component.weekday;
}
- (NSInteger)fs_weekOfYear
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitWeekOfYear fromDate:self];
return component.weekOfYear;
}
- (NSInteger)fs_hour
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitHour
fromDate:self];
return component.hour;
}
- (NSInteger)fs_minute
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitMinute
fromDate:self];
return component.minute;
}
- (NSInteger)fs_second
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *component = [calendar components:NSCalendarUnitSecond
fromDate:self];
return component.second;
}
- (NSDate *)fs_dateByIgnoringTimeComponents
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:self];
return [calendar dateFromComponents:components];
}
- (NSDate *)fs_firstDayOfMonth
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth| NSCalendarUnitDay fromDate:self];
components.day = 1;
return [calendar dateFromComponents:components];
}
- (NSDate *)fs_lastDayOfMonth
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:self];
components.month++;
components.day = 0;
return [calendar dateFromComponents:components];
}
- (NSDate *)fs_firstDayOfWeek
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *weekdayComponents = [calendar components:NSCalendarUnitWeekday fromDate:self];
NSDateComponents *componentsToSubtract = [NSDateComponents fs_sharedDateComponents];
componentsToSubtract.day = - (weekdayComponents.weekday - calendar.firstWeekday);
NSDate *beginningOfWeek = [calendar dateByAddingComponents:componentsToSubtract toDate:self options:0];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:beginningOfWeek];
beginningOfWeek = [calendar dateFromComponents:components];
componentsToSubtract.day = NSIntegerMax;
return beginningOfWeek;
}
- (NSDate *)fs_middleOfWeek
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *weekdayComponents = [calendar components:NSCalendarUnitWeekday fromDate:self];
NSDateComponents *componentsToSubtract = [NSDateComponents fs_sharedDateComponents];
componentsToSubtract.day = - (weekdayComponents.weekday - calendar.firstWeekday) + 3;
NSDate *middleOfWeek = [calendar dateByAddingComponents:componentsToSubtract toDate:self options:0];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:middleOfWeek];
middleOfWeek = [calendar dateFromComponents:components];
componentsToSubtract.day = NSIntegerMax;
return middleOfWeek;
}
- (NSDate *)fs_tomorrow
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:self];
components.day++;
return [calendar dateFromComponents:components];
}
- (NSDate *)fs_yesterday
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:self];
components.day--;
return [calendar dateFromComponents:components];
}
- (NSInteger)fs_numberOfDaysInMonth
{
NSCalendar *c = [NSCalendar fs_sharedCalendar];
NSRange days = [c rangeOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitMonth
forDate:self];
return days.length;
}
+ (instancetype)fs_dateFromString:(NSString *)string format:(NSString *)format
{
NSDateFormatter *formatter = [NSDateFormatter fs_sharedDateFormatter];
formatter.dateFormat = format;
return [formatter dateFromString:string];
}
+ (instancetype)fs_dateWithYear:(NSInteger)year month:(NSInteger)month day:(NSInteger)day
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [NSDateComponents fs_sharedDateComponents];
components.year = year;
components.month = month;
components.day = day;
NSDate *date = [calendar dateFromComponents:components];
components.year = NSIntegerMax;
components.month = NSIntegerMax;
components.day = NSIntegerMax;
return date;
}
- (NSDate *)fs_dateByAddingYears:(NSInteger)years
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [NSDateComponents fs_sharedDateComponents];
components.year = years;
NSDate *date = [calendar dateByAddingComponents:components toDate:self options:0];
components.year = NSIntegerMax;
return date;
}
- (NSDate *)fs_dateBySubtractingYears:(NSInteger)years
{
return [self fs_dateByAddingYears:-years];
}
- (NSDate *)fs_dateByAddingMonths:(NSInteger)months
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [NSDateComponents fs_sharedDateComponents];
components.month = months;
NSDate *date = [calendar dateByAddingComponents:components toDate:self options:0];
components.month = NSIntegerMax;
return date;
}
- (NSDate *)fs_dateBySubtractingMonths:(NSInteger)months
{
return [self fs_dateByAddingMonths:-months];
}
- (NSDate *)fs_dateByAddingWeeks:(NSInteger)weeks
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [NSDateComponents fs_sharedDateComponents];
components.weekOfYear = weeks;
NSDate *date = [calendar dateByAddingComponents:components toDate:self options:0];
components.weekOfYear = NSIntegerMax;
return date;
}
-(NSDate *)fs_dateBySubtractingWeeks:(NSInteger)weeks
{
return [self fs_dateByAddingWeeks:-weeks];
}
- (NSDate *)fs_dateByAddingDays:(NSInteger)days
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [NSDateComponents fs_sharedDateComponents];
components.day = days;
NSDate *date = [calendar dateByAddingComponents:components toDate:self options:0];
components.day = NSIntegerMax;
return date;
}
- (NSDate *)fs_dateBySubtractingDays:(NSInteger)days
{
return [self fs_dateByAddingDays:-days];
}
- (NSInteger)fs_yearsFrom:(NSDate *)date
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitYear
fromDate:date
toDate:self
options:0];
return components.year;
}
- (NSInteger)fs_monthsFrom:(NSDate *)date
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitMonth
fromDate:date
toDate:self
options:0];
return components.month;
}
- (NSInteger)fs_weeksFrom:(NSDate *)date
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitWeekOfYear
fromDate:date
toDate:self
options:0];
return components.weekOfYear;
}
- (NSInteger)fs_daysFrom:(NSDate *)date
{
NSCalendar *calendar = [NSCalendar fs_sharedCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitDay
fromDate:date
toDate:self
options:0];
return components.day;
}
- (NSString *)fs_stringWithFormat:(NSString *)format
{
NSDateFormatter *formatter = [NSDateFormatter fs_sharedDateFormatter];
formatter.dateFormat = format;
return [formatter stringFromDate:self];
}
- (NSString *)fs_string
{
return [self fs_stringWithFormat:@"yyyyMMdd"];
}
- (BOOL)fs_isEqualToDateForMonth:(NSDate *)date
{
return self.fs_year == date.fs_year && self.fs_month == date.fs_month;
}
- (BOOL)fs_isEqualToDateForWeek:(NSDate *)date
{
return self.fs_year == date.fs_year && self.fs_weekOfYear == date.fs_weekOfYear;
}
- (BOOL)fs_isEqualToDateForDay:(NSDate *)date
{
return self.fs_year == date.fs_year && self.fs_month == date.fs_month && self.fs_day == date.fs_day;
}
@end
@implementation NSCalendar (FSExtension)
+ (instancetype)fs_sharedCalendar
{
static id instance;
static dispatch_once_t fs_sharedCalendar_onceToken;
dispatch_once(&fs_sharedCalendar_onceToken, ^{
instance = [NSCalendar currentCalendar];
});
return instance;
}
@end
@implementation NSDateFormatter (FSExtension)
+ (instancetype)fs_sharedDateFormatter
{
static id instance;
static dispatch_once_t fs_sharedDateFormatter_onceToken;
dispatch_once(&fs_sharedDateFormatter_onceToken, ^{
instance = [[NSDateFormatter alloc] init];
});
return instance;
}
@end
@implementation NSDateComponents (FSExtension)
+ (instancetype)fs_sharedDateComponents
{
static id instance;
static dispatch_once_t fs_sharedDateFormatter_onceToken;
dispatch_once(&fs_sharedDateFormatter_onceToken, ^{
instance = [[NSDateComponents alloc] init];
});
return instance;
}
@end

View File

@ -1,16 +0,0 @@
//
// NSString+FSExtension.h
// FSCalendar
//
// Created by Wenchao Ding on 8/29/15.
// Copyright (c) 2015 wenchaoios. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSString (FSExtension)
- (NSDate *)fs_dateWithFormat:(NSString *)format;
- (NSDate *)fs_date;
@end

View File

@ -1,26 +0,0 @@
//
// NSString+FSExtension.m
// FSCalendar
//
// Created by Wenchao Ding on 8/29/15.
// Copyright (c) 2015 wenchaoios. All rights reserved.
//
#import "NSString+FSExtension.h"
#import "NSDate+FSExtension.h"
@implementation NSString (FSExtension)
- (NSDate *)fs_dateWithFormat:(NSString *)format
{
NSDateFormatter *formatter = [NSDateFormatter fs_sharedDateFormatter];
formatter.dateFormat = format;
return [formatter dateFromString:self];
}
- (NSDate *)fs_date
{
return [self fs_dateWithFormat:@"yyyyMMdd"];
}
@end

View File

@ -1,21 +0,0 @@
//
// UIView+FSExtension.h
// Pods
//
// Created by Wenchao Ding on 29/1/15.
//
//
#import <UIKit/UIKit.h>
@interface UIView (FSExtension)
@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

View File

@ -1,73 +0,0 @@
//
// UIView+FSExtension.m
// Pods
//
// Created by Wenchao Ding on 29/1/15.
//
//
#import "UIView+FSExtension.h"
@implementation UIView (FSExtension)
- (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

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2016 FSCalendar (https://github.com/WenchaoIOS/FSCalendar)
Copyright (c) 2013-2016 FSCalendar (https://github.com/WenchaoD/FSCalendar)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,45 +1,90 @@
![fscalendar](https://cloud.githubusercontent.com/assets/5186464/6655324/213a814a-cb36-11e4-9add-f80515a83291.png)<br/><br/>
![logo](https://cloud.githubusercontent.com/assets/5186464/16540124/efc51f72-408b-11e6-934a-4e750b8b55bb.png)
<br/><br/>
[![Apps Using](https://img.shields.io/badge/Apps%20Using-%3E%2010,000-00BFFF.svg?style=plastic)](https://cocoapods.org/pods/FSCalendar)
[![Total Downloads](https://img.shields.io/badge/Total%20Downloads-%3E%20500,000-00BFFF.svg?style=plastic)](https://cocoapods.org/pods/FSCalendar)
<br>
[![Travis](https://travis-ci.org/WenchaoD/FSCalendar.svg?branch=master)](https://travis-ci.org/WenchaoD/FSCalendar)
[![Version](https://img.shields.io/cocoapods/v/FSCalendar.svg?style=flat)](http://cocoadocs.org/docsets/FSCalendar)
[![Platform](https://img.shields.io/badge/platform-iOS%207%2B-blue.svg?style=flat)](http://cocoadocs.org/docsets/FSCalendar)
[![Swift2 compatible](https://img.shields.io/badge/swift2-compatible-4BC51D.svg?style=flat)](https://developer.apple.com/swift/)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/FSCalendar.svg?style=flat)](http://cocoadocs.org/docsets/FSCalendar)
[![Join the chat at https://gitter.im/WenchaoD/FSCalendar](https://badges.gitter.im/WenchaoD/FSCalendar.svg)](https://gitter.im/WenchaoD/FSCalendar?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<br>
[![Languages](https://img.shields.io/badge/language-objc%20|%20swift-FF69B4.svg?style=plastic)](#)
# Screenshots
# Table of contents
* [Screenshots](#screenshots)
* [Installation](#installation)
* [Pre-knowledge](#pre-knowledge)
* [Support](#support)
* [Contact](#contact)
## iPhone
## <a id="screenshots"></a>Screenshots
### iPhone
![fscalendar](https://cloud.githubusercontent.com/assets/5186464/10262249/4fabae40-69f2-11e5-97ab-afbacd0a3da2.jpg)
## iPad
### iPad
![fscalendar-ipad](https://cloud.githubusercontent.com/assets/5186464/10927681/d2448cb6-82dc-11e5-9d11-f664a06698a7.jpg)
## Working with AutoLayout and Orientation
![fscalendar-scope-orientation-autolayout](https://cloud.githubusercontent.com/assets/5186464/13728798/59855e3e-e95e-11e5-84db-60f843427ef3.gif)
### Safe Orientation
![fscalendar-scope-orientation-autolayout](https://cloud.githubusercontent.com/assets/5186464/20325758/ea125e1e-abc0-11e6-9e29-491acbcb0d07.gif)
# Installation
### Today Extension
| iOS8/9 | iOS10 |
|--------------|-------------|
|![today1](https://cloud.githubusercontent.com/assets/5186464/20288375/ed3fba0e-ab0d-11e6-8b15-43d3dc656f22.gif)|![today2](https://cloud.githubusercontent.com/assets/5186464/20288378/f11e318c-ab0d-11e6-8d1d-9d89b563e9d7.gif)|
### Interactive Scope Gesture
| ![1](https://cloud.githubusercontent.com/assets/5186464/21559640/e92a9ccc-ce8a-11e6-8c60-e52204f33249.gif) |
| ---- |
### DIY support
| ![1](https://cloud.githubusercontent.com/assets/5186464/20026983/22354a0e-a342-11e6-8ae6-0614ea7f35ae.gif) |
| ------------- |
> To customize your own cell, view DIY Example in `Example-Swift` or `Example-Objc`
### Swipe-To-Choose
|Single-Selection<br/>Swipe-To-Choose|Multiple-Selection<br/>Swipe-To-Choose|DIY<br/>Swipe-To-Choose|
|----------|--------|--------|
|![1](https://cloud.githubusercontent.com/assets/5186464/20257768/cb1905d4-aa86-11e6-9ef7-af76f9caa024.gif)|![2](https://cloud.githubusercontent.com/assets/5186464/20257826/254070ec-aa87-11e6-81b1-1815453fd464.gif)|![3](https://cloud.githubusercontent.com/assets/5186464/20257836/2ffa3252-aa87-11e6-8ff9-3b40f5b2307b.gif)|
## Achievement of Users <a id="achievement"></a>
| ![1](https://cloud.githubusercontent.com/assets/5186464/21747193/3111e4ee-d59a-11e6-8e4d-ca695b53e421.png) | ![2](https://cloud.githubusercontent.com/assets/5186464/21747393/42a753fa-d5a0-11e6-9cb2-de7cc642e69e.png) | ![3](https://cloud.githubusercontent.com/assets/5186464/21897255/ff78fcdc-d923-11e6-9d59-62119bc4343f.png) | ![4](https://cloud.githubusercontent.com/assets/5186464/21747192/3111cacc-d59a-11e6-8626-44cd75ebd794.png) |
| ------------- | ------------- | ------------- | ------------- |
#### [***More Achievements***](https://github.com/WenchaoD/FSCalendar/wiki/) are available in [***FSCalendar Gallery***](https://github.com/WenchaoD/FSCalendar/wiki/)
===
# <a id="installation"></a>Installation
## CocoaPods:
* For iOS8+: 👍
```ruby
use_frameworks!
pod 'FSCalendar'
target '<Your Target Name>' do
pod 'FSCalendar'
end
```
* For iOS7+:
```ruby
pod 'FSCalendar'
target '<Your Target Name>' do
pod 'FSCalendar'
end
```
* Alternatively to give it a test run, run the command:
```ruby
pod try FSCalendar
```
> [NSCalendarExtension](https://github.com/WenchaoD/NSCalendarExtension) is required to get iOS7 compatibility.
## Carthage:
* For iOS8+
```ruby
github "WenchaoD/FSCalendar"
```
@ -47,20 +92,21 @@ github "WenchaoD/FSCalendar"
## Manually:
* Drag all files under `FSCalendar` folder into your project. 👍
## Support IBInspectable / IBDesignable
Only the methods marked "👍" support IBInspectable / IBDesignable feature. [Have fun with Interface builder](#roll_with_interface_builder)
> Alternatively to give it a test run, simply press `command+u` in `Example-Objc` or `Example-Swift` and launch the ***UITest Target***. <br>
> Only the methods marked "👍" support IBInspectable / IBDesignable feature. [Have fun with Interface builder](#roll_with_interface_builder)
# Setup
## Use Interface Builder
1. Drag an UIView object to ViewController Scene
2. Change the `Custom Class` to `FSCalendar`<br/>
3. Link `dataSource` and `delegate` to the ViewController <br/>
1 Drag an UIView object to ViewController Scene
2 Change the `Custom Class` to `FSCalendar`<br/>
3 Link `dataSource` and `delegate` to the ViewController <br/>
![fscalendar-ib](https://cloud.githubusercontent.com/assets/5186464/9488580/a360297e-4c0d-11e5-8548-ee9274e7c4af.jpg)
4. Finally, you should implement `FSCalendarDataSource` and `FSCalendarDelegate` in ViewController.m
4、 Finally, implement `FSCalendarDataSource` and `FSCalendarDelegate` in your `ViewController`
## Or use code
@ -83,7 +129,7 @@ self.calendar = calendar;
```swift
private weak var calendar: FSCalendar!
fileprivate weak var calendar: FSCalendar!
```
```swift
// In loadView or viewDidLoad
@ -93,57 +139,190 @@ calendar.delegate = self
view.addSubview(calendar)
self.calendar = calendar
```
<br/>
> To use **FSCalendar** in Swift3, see `Example-Swift` for details.
## Hide placeholder dates
![fscalendar-showsplaceholder](https://cloud.githubusercontent.com/assets/5186464/13727902/21a90042-e940-11e5-9b9f-392f38cf007d.gif)
## <a id='adjusts_frame_dynamicly' /></a>Warning
`FSCalendar` ***doesn't*** update frame by itself, Please implement
1. Set `calendar.showsPlaceholders = NO`;
2. <a id="implement_bounding_rect_will_change"></a> Implement `-calendar:boundingRectWillChange:animated:`
* For ***AutoLayout***
```objc
// For autoLayout
- (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated
{
_calendarHeightConstraint.constant = CGRectGetHeight(bounds);
self.calendarHeightConstraint.constant = CGRectGetHeight(bounds);
// Do other updates here
[self.view layoutIfNeeded];
}
```
* For ***Manual Layout***
```objc
// For manual layout
- (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated
{
calendar.frame = (CGRect){calendar.frame.origin,bounds.size};
// Do other updates here
}
```
* If you are using ***Masonry***
```objc
- (void)calendar:(FSCalendar *)calendar boundingRectWillChange:(CGRect)bounds animated:(BOOL)animated
{
[calendar mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.equalTo(@(bounds.size.height));
// Do other updates
}];
[self.view layoutIfNeeded];
}
```
* If you are using ***SnapKit***
```swift
func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
calendar.snp.updateConstraints { (make) in
make.height.equalTo(bounds.height)
// Do other updates
}
self.view.layoutIfNeeded()
}
```
### <a id="roll_with_interface_builder"></a> Roll with Interface Builder
![fscalendar - ibdesignable](https://cloud.githubusercontent.com/assets/5186464/9301716/2e76a2ca-4503-11e5-8450-1fa7aa93e9fd.gif)
## More Usage
* To view more usage, download the zip file and read the example.
* Or you could refer to [this document](https://github.com/WenchaoD/FSCalendar/blob/master/MOREUSAGE.md)
* To view the full documentation, see [CocoaPods Documentation](http://cocoadocs.org/docsets/FSCalendar/2.0.1/)
# <a id="pre-knowledge"></a>Pre-knowledge
# If you like this repo
* ***Star*** this repo.
* Send your calendar screenshot or `itunes link address` [here](https://github.com/WenchaoD/FSCalendar/issues/2).
> In `Swift3`, `NSDate` and `NSDateFormatter` have been renamed to ***Date*** and ***DateFormatter*** , see `Example-Swift` for details.
## How to create NSDate object
* By **NSCalendar**.
```objc
self.gregorian = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
```
Then:
```objc
NSDate *date = [gregorian dateWithEra:1 year:2016 month:9 day:10 hour:0 minute:0 second:0 nanosecond:0];
// 2016-09-10 00:00:00
```
* Or by **NSDateFormatter**
```objc
self.formatter = [[NSDateFormatter alloc] init];
self.formatter.dateFormat = @"yyyy-MM-dd";
```
Then:
```objc
NSDate *date = [self.formatter dateFromString:@"2016-09-10"];
```
## How to print out NSDate object
* Use **NSDateFormatter**
```objc
self.formatter = [[NSDateFormatter alloc] init];
self.formatter.dateFormat = @"yyyy/MM/dd";
```
```objc
NSString *string = [self.formatter stringFromDate:date];
NSLog(@"Date is %@", string);
```
## How to manipulate NSDate with NSCalendar
```objc
self.gregorian = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
```
* Get component of NSDate
```objc
NSInteger era = [self.gregorian component:NSCalendarUnitEra fromDate:date];
NSInteger year = [self.gregorian component:NSCalendarUnitYear fromDate:date];
NSInteger month = [self.gregorian component:NSCalendarUnitMonth fromDate:date];
NSInteger day = [self.gregorian component:NSCalendarUnitDay fromDate:date];
NSInteger hour = [self.gregorian component:NSCalendarUnitHour fromDate:date];
NSInteger minute = [self.gregorian component:NSCalendarUnitMinute fromDate:date];
...
```
* Get next **month**
```objc
NSDate *nextMonth = [self.gregorain dateByAddingUnit:NSCalendarUnitMonth value:1 toDate:date options:0];
```
* Get next **day**
```objc
NSDate *nextDay = [self.gregorain dateByAddingUnit:NSCalendarUnitDay value:1 toDate:date options:0];
```
* Is date in today/tomorrow/yesterday/weekend
```objc
BOOL isToday = [self.gregorian isDateInToday:date];
BOOL isYesterday = [self.gregorian isDateInYesterday:date];
BOOL isTomorrow = [self.gregorian isDateInTomorrow:date];
BOOL isWeekend = [self.gregorian isDateInWeekend:date];
```
* Compare two dates
```objc
BOOL sameDay = [self.gregorian isDate:date1 inSameDayAsDate:date2];
// Yes if the date1 and date2 are in same day
[self.gregorian compareDate:date1 toDate:date2 toUnitGranularity:unit];
// compare the era/year/month/day/hour/minute .etc ...
// return NSOrderAscending/NSOrderSame/NSOrderDecending
BOOL inSameUnit = [self.gregorian isDate:date1 equalToDate:date2 toUnitGranularity:unit];
// if the given unit (era/year/month/day/hour/minute .etc) are the same
```
## <a id="support"></a>Support this repo
* [**★Star**](#) this repo
<br/>
* Support with &nbsp; <a href="https://www.paypal.me/WenchaoD" target="_blank"><img src="https://www.paypalobjects.com/webstatic/i/logo/rebrand/ppcom.svg" width="100" height="40" style="margin-bottom:-15px;"></a>
<br/>
* Support with <a href="https://cloud.githubusercontent.com/assets/5186464/15096775/bacc0506-1539-11e6-91b7-b1a7a773622b.png" target="_blank"><img src="http://a1.mzstatic.com/us/r30/Purple49/v4/50/16/b3/5016b341-39c1-b47b-2994-d7e23823baed/icon175x175.png" width="40" height="40" style="margin-bottom:-15px;-webkit-border-radius:10px;border:1px solid rgba(30, 154, 236, 1);"></a> or
<a href="https://cloud.githubusercontent.com/assets/5186464/15096872/b06f3a3a-153c-11e6-89f9-2e9c7b88ef42.png" target="_blank"><img src="http://a4.mzstatic.com/us/r30/Purple49/v4/23/31/14/233114f8-2e8d-7b63-8dc5-85d29893061e/icon175x175.jpeg" height="40" width="40" style="margin-bottom:-15px; -webkit-border-radius: 10px;border:1px solid rgba(43, 177, 0, 1)"></a>
<br/>
## <a id='contact'/></a> Contact
* 微博: [**@WenchaoD**](http://weibo.com/WenchaoD)
* Twitter[**@WenchaoD**](https://twitter.com/WenchaoD)
* <a id='qq_group'/></a>QQ支持群: <br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
![fscalendar](https://cloud.githubusercontent.com/assets/5186464/18407011/8e4b6e48-7738-11e6-9fad-0e23cc881516.JPG)
> If your made a beautiful calendar with this library in your app, please take a screen shot and [@me](https://twitter.com/WenchaoD) in twitter. Your help really means a lot to me! <br/>
# Support me via [![paypal](https://www.paypalobjects.com/webstatic/i/logo/rebrand/ppcom.svg)](https://www.paypalobjects.com/webstatic/i/logo/rebrand/ppcom.svg)
* ☕️ [This coffee is on me!](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=Z84P82H3V4Q26&lc=C2&item_name=This%20coffee%20is%20on%20me%21&item_number=Support%20FSCalendar%20%2d%20WenchaoIOS&amount=5%2e00&currency_code=USD&button_subtype=services&bn=PP%2dBuyNowBF%3abtn_buynowCC_LG%2egif%3aNonHosted)
* [Lunch is on me!](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=Z84P82H3V4Q26&lc=C2&item_name=Lunch%20is%20on%20me%21&item_number=Support%20FSCalendar&amount=10%2e00&currency_code=USD&button_subtype=services&bn=PP%2dBuyNowBF%3abtn_buynowCC_LG%2egif%3aNonHosted)
* [Have a nice dinner!](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=Z84P82H3V4Q26&lc=C2&item_name=Tonight%27s%20dinner%20is%20on%20me%21&item_number=Support%20FSCalendar%20%2d%20WenchaoIOS&amount=25%2e00&currency_code=USD&button_subtype=services&bn=PP%2dBuyNowBF%3abtn_buynowCC_LG%2egif%3aNonHosted)
* [Greate work! Keep the change!](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=Z84P82H3V4Q26&lc=C2&item_name=Great%20work%21%20Keep%20the%20change%21&item_number=Support%20FSCalendar%20%2d%20WenchaoIOS&amount=100%2e00&currency_code=USD&button_subtype=services&bn=PP%2dBuyNowBF%3abtn_buynowCC_LG%2egif%3aNonHosted)
# License
FSCalendar is available under the MIT license. See the LICENSE file for more info.
# Contributions
* Issues and pull requests are absolutely welcome.
* For code contributions, please follow [Coding Guidelines for Cocoa](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/CodingGuidelines.html)
# Contact
* Email: `f33chobits@gmail.com`
### [Documentation](http://cocoadocs.org/docsets/FSCalendar/) | [More Usage](https://github.com/WenchaoD/FSCalendar/blob/master/MOREUSAGE.md) | [简书](http://www.jianshu.com/notebooks/4276521/latest)

View File

@ -0,0 +1,101 @@
#import <FirebaseCore/FirebaseCore.h>
#if !defined(__has_include)
#error "Firebase.h won't import anything if your compiler doesn't support __has_include. Please \
import the headers individually."
#else
#if __has_include(<FirebaseAnalytics/FirebaseAnalytics.h>)
#import <FirebaseAnalytics/FirebaseAnalytics.h>
#else
#ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#warning "FirebaseAnalytics.framework is not included in your target. Please add \
`Firebase/Core` to your Podfile or add FirebaseAnalytics.framework to your project to ensure \
Firebase services work as intended."
#endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#endif
#if __has_include(<FirebaseAuth/FirebaseAuth.h>)
#import <FirebaseAuth/FirebaseAuth.h>
#endif
#if __has_include(<FirebaseCrash/FirebaseCrash.h>)
#import <FirebaseCrash/FirebaseCrash.h>
#endif
#if __has_include(<FirebaseDatabase/FirebaseDatabase.h>)
#import <FirebaseDatabase/FirebaseDatabase.h>
#endif
#if __has_include(<FirebaseDynamicLinks/FirebaseDynamicLinks.h>)
#import <FirebaseDynamicLinks/FirebaseDynamicLinks.h>
#endif
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <FirebaseFirestore/FirebaseFirestore.h>
#endif
#if __has_include(<FirebaseFunctions/FirebaseFunctions.h>)
#import <FirebaseFunctions/FirebaseFunctions.h>
#endif
#if __has_include(<FirebaseInstanceID/FirebaseInstanceID.h>)
#import <FirebaseInstanceID/FirebaseInstanceID.h>
#endif
#if __has_include(<FirebaseInvites/FirebaseInvites.h>)
#import <FirebaseInvites/FirebaseInvites.h>
#endif
#if __has_include(<FirebaseMessaging/FirebaseMessaging.h>)
#import <FirebaseMessaging/FirebaseMessaging.h>
#endif
#if __has_include(<FirebaseMLModelInterpreter/FirebaseMLModelInterpreter.h>)
#import <FirebaseMLModelInterpreter/FirebaseMLModelInterpreter.h>
#endif
#if __has_include(<FirebaseMLVision/FirebaseMLVision.h>)
#import <FirebaseMLVision/FirebaseMLVision.h>
#endif
#if __has_include(<FirebaseMLVisionBarcodeModel/FirebaseMLVisionBarcodeModel.h>)
#import <FirebaseMLVisionBarcodeModel/FirebaseMLVisionBarcodeModel.h>
#endif
#if __has_include(<FirebaseMLVisionFaceModel/FirebaseMLVisionFaceModel.h>)
#import <FirebaseMLVisionFaceModel/FirebaseMLVisionFaceModel.h>
#endif
#if __has_include(<FirebaseMLVisionLabelModel/FirebaseMLVisionLabelModel.h>)
#import <FirebaseMLVisionLabelModel/FirebaseMLVisionLabelModel.h>
#endif
#if __has_include(<FirebaseMLVisionTextModel/FirebaseMLVisionTextModel.h>)
#import <FirebaseMLVisionTextModel/FirebaseMLVisionTextModel.h>
#endif
#if __has_include(<FirebasePerformance/FirebasePerformance.h>)
#import <FirebasePerformance/FirebasePerformance.h>
#endif
#if __has_include(<FirebaseRemoteConfig/FirebaseRemoteConfig.h>)
#import <FirebaseRemoteConfig/FirebaseRemoteConfig.h>
#endif
#if __has_include(<FirebaseStorage/FirebaseStorage.h>)
#import <FirebaseStorage/FirebaseStorage.h>
#endif
#if __has_include(<GoogleMobileAds/GoogleMobileAds.h>)
#import <GoogleMobileAds/GoogleMobileAds.h>
#endif
#if __has_include(<Fabric/Fabric.h>)
#import <Fabric/Fabric.h>
#endif
#if __has_include(<Crashlytics/Crashlytics.h>)
#import <Crashlytics/Crashlytics.h>
#endif
#endif // defined(__has_include)

View File

@ -0,0 +1,4 @@
module Firebase {
export *
header "Firebase.h"
}

87
Verdnaturaventas/Pods/Firebase/README.md generated Executable file
View File

@ -0,0 +1,87 @@
# Firebase APIs for iOS
Simplify your iOS development, grow your user base, and monetize more
effectively with Firebase services.
Much more information can be found at [https://firebase.google.com](https://firebase.google.com).
## Install a Firebase SDK using CocoaPods
Firebase distributes several iOS specific APIs and SDKs via CocoaPods.
You can install the CocoaPods tool on OS X by running the following command from
the terminal. Detailed information is available in the [Getting Started
guide](https://guides.cocoapods.org/using/getting-started.html#getting-started).
```
$ sudo gem install cocoapods
```
## Try out an SDK
You can try any of the SDKs with `pod try`. Run the following command and select
the SDK you are interested in when prompted:
```
$ pod try Firebase
```
Note that some SDKs may require credentials. More information is available in
the SDK-specific documentation at [https://firebase.google.com/docs/](https://firebase.google.com/docs/).
## Add a Firebase SDK to your iOS app
CocoaPods is used to install and manage dependencies in existing Xcode projects.
1. Create an Xcode project, and save it to your local machine.
2. Create a file named `Podfile` in your project directory. This file defines
your project's dependencies, and is commonly referred to as a Podspec.
3. Open `Podfile`, and add your dependencies. A simple Podspec is shown here:
```
platform :ios, '8.0'
pod 'Firebase'
```
4. Save the file.
5. Open a terminal and `cd` to the directory containing the Podfile.
```
$ cd <path-to-project>/project/
```
6. Run the `pod install` command. This will install the SDKs specified in the
Podspec, along with any dependencies they may have.
```
$ pod install
```
7. Open your app's `.xcworkspace` file to launch Xcode. Use this file for all
development on your app.
8. You can also install other Firebase SDKs by adding the subspecs in the
Podfile.
```
pod 'Firebase/AdMob'
pod 'Firebase/Analytics'
pod 'Firebase/Auth'
pod 'Firebase/Crash'
pod 'Firebase/Database'
pod 'Firebase/DynamicLinks'
pod 'Firebase/Firestore'
pod 'Firebase/Functions'
pod 'Firebase/Invites'
pod 'Firebase/Messaging'
pod 'Firebase/MLCommon'
pod 'Firebase/MLModelInterpreter'
pod 'Firebase/MLVision'
pod 'Firebase/MLVisionBarcodeModel'
pod 'Firebase/MLVisionFaceModel'
pod 'Firebase/MLVisionLabelModel'
pod 'Firebase/MLVisionTextModel'
pod 'Firebase/Performance'
pod 'Firebase/RemoteConfig'
pod 'Firebase/Storage'
```

View File

@ -0,0 +1,62 @@
#import <Foundation/Foundation.h>
#import "FIRAnalytics.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Provides App Delegate handlers to be used in your App Delegate.
*
* To save time integrating Firebase Analytics in an application, Firebase Analytics does not
* require delegation implementation from the AppDelegate. Instead this is automatically done by
* Firebase Analytics. Should you choose instead to delegate manually, you can turn off the App
* Delegate Proxy by adding FirebaseAppDelegateProxyEnabled into your app's Info.plist and setting
* it to NO, and adding the methods in this category to corresponding delegation handlers.
*
* To handle Universal Links, you must return YES in
* [UIApplicationDelegate application:didFinishLaunchingWithOptions:].
*/
@interface FIRAnalytics (AppDelegate)
/**
* Handles events related to a URL session that are waiting to be processed.
*
* For optimal use of Firebase Analytics, call this method from the
* [UIApplicationDelegate application:handleEventsForBackgroundURLSession:completionHandler]
* method of the app delegate in your app.
*
* @param identifier The identifier of the URL session requiring attention.
* @param completionHandler The completion handler to call when you finish processing the events.
* Calling this completion handler lets the system know that your app's user interface is
* updated and a new snapshot can be taken.
*/
+ (void)handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(nullable void (^)(void))completionHandler;
/**
* Handles the event when the app is launched by a URL.
*
* Call this method from [UIApplicationDelegate application:openURL:options:] &#40;on iOS 9.0 and
* above&#41;, or [UIApplicationDelegate application:openURL:sourceApplication:annotation:] &#40;on
* iOS 8.x and below&#41; in your app.
*
* @param url The URL resource to open. This resource can be a network resource or a file.
*/
+ (void)handleOpenURL:(NSURL *)url;
/**
* Handles the event when the app receives data associated with user activity that includes a
* Universal Link (on iOS 9.0 and above).
*
* Call this method from [UIApplication continueUserActivity:restorationHandler:] in your app
* delegate (on iOS 9.0 and above).
*
* @param userActivity The activity object containing the data associated with the task the user
* was performing.
*/
+ (void)handleUserActivity:(id)userActivity;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,119 @@
#import <Foundation/Foundation.h>
#import "FIREventNames.h"
#import "FIRParameterNames.h"
#import "FIRUserPropertyNames.h"
NS_ASSUME_NONNULL_BEGIN
/// The top level Firebase Analytics singleton that provides methods for logging events and setting
/// user properties. See <a href="http://goo.gl/gz8SLz">the developer guides</a> for general
/// information on using Firebase Analytics in your apps.
NS_SWIFT_NAME(Analytics)
@interface FIRAnalytics : NSObject
/// Logs an app event. The event can have up to 25 parameters. Events with the same name must have
/// the same parameters. Up to 500 event names are supported. Using predefined events and/or
/// parameters is recommended for optimal reporting.
///
/// The following event names are reserved and cannot be used:
/// <ul>
/// <li>ad_activeview</li>
/// <li>ad_click</li>
/// <li>ad_exposure</li>
/// <li>ad_impression</li>
/// <li>ad_query</li>
/// <li>adunit_exposure</li>
/// <li>app_clear_data</li>
/// <li>app_remove</li>
/// <li>app_update</li>
/// <li>error</li>
/// <li>first_open</li>
/// <li>in_app_purchase</li>
/// <li>notification_dismiss</li>
/// <li>notification_foreground</li>
/// <li>notification_open</li>
/// <li>notification_receive</li>
/// <li>os_update</li>
/// <li>screen_view</li>
/// <li>session_start</li>
/// <li>user_engagement</li>
/// </ul>
///
/// @param name The name of the event. Should contain 1 to 40 alphanumeric characters or
/// underscores. The name must start with an alphabetic character. Some event names are
/// reserved. See FIREventNames.h for the list of reserved event names. The "firebase_",
/// "google_", and "ga_" prefixes are reserved and should not be used. Note that event names are
/// case-sensitive and that logging two events whose names differ only in case will result in
/// two distinct events.
/// @param parameters The dictionary of event parameters. Passing nil indicates that the event has
/// no parameters. Parameter names can be up to 40 characters long and must start with an
/// alphabetic character and contain only alphanumeric characters and underscores. Only NSString
/// and NSNumber (signed 64-bit integer and 64-bit floating-point number) parameter types are
/// supported. NSString parameter values can be up to 100 characters long. The "firebase_",
/// "google_", and "ga_" prefixes are reserved and should not be used for parameter names.
+ (void)logEventWithName:(NSString *)name
parameters:(nullable NSDictionary<NSString *, id> *)parameters
NS_SWIFT_NAME(logEvent(_:parameters:));
/// Sets a user property to a given value. Up to 25 user property names are supported. Once set,
/// user property values persist throughout the app lifecycle and across sessions.
///
/// The following user property names are reserved and cannot be used:
/// <ul>
/// <li>first_open_time</li>
/// <li>last_deep_link_referrer</li>
/// <li>user_id</li>
/// </ul>
///
/// @param value The value of the user property. Values can be up to 36 characters long. Setting the
/// value to nil removes the user property.
/// @param name The name of the user property to set. Should contain 1 to 24 alphanumeric characters
/// or underscores and must start with an alphabetic character. The "firebase_", "google_", and
/// "ga_" prefixes are reserved and should not be used for user property names.
+ (void)setUserPropertyString:(nullable NSString *)value forName:(NSString *)name
NS_SWIFT_NAME(setUserProperty(_:forName:));
/// Sets the user ID property. This feature must be used in accordance with
/// <a href="https://www.google.com/policies/privacy">Google's Privacy Policy</a>
///
/// @param userID The user ID to ascribe to the user of this app on this device, which must be
/// non-empty and no more than 256 characters long. Setting userID to nil removes the user ID.
+ (void)setUserID:(nullable NSString *)userID;
/// Sets the current screen name, which specifies the current visual context in your app. This helps
/// identify the areas in your app where users spend their time and how they interact with your app.
/// Must be called on the main thread.
///
/// Note that screen reporting is enabled automatically and records the class name of the current
/// UIViewController for you without requiring you to call this method. If you implement
/// viewDidAppear in your UIViewController but do not call [super viewDidAppear:], that screen class
/// will not be automatically tracked. The class name can optionally be overridden by calling this
/// method in the viewDidAppear callback of your UIViewController and specifying the
/// screenClassOverride parameter. setScreenName:screenClass: must be called after
/// [super viewDidAppear:].
///
/// If your app does not use a distinct UIViewController for each screen, you should call this
/// method and specify a distinct screenName each time a new screen is presented to the user.
///
/// The screen name and screen class remain in effect until the current UIViewController changes or
/// a new call to setScreenName:screenClass: is made.
///
/// @param screenName The name of the current screen. Should contain 1 to 100 characters. Set to nil
/// to clear the current screen name.
/// @param screenClassOverride The name of the screen class. Should contain 1 to 100 characters. By
/// default this is the class name of the current UIViewController. Set to nil to revert to the
/// default class name.
+ (void)setScreenName:(nullable NSString *)screenName
screenClass:(nullable NSString *)screenClassOverride;
/// The unique ID for this instance of the application.
+ (NSString *)appInstanceID;
/// Clears all analytics data for this instance from the device and resets the app instance ID.
/// FIRAnalyticsConfiguration values will be reset to the default values.
+ (void)resetAnalyticsData;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,13 @@
#ifndef FIR_SWIFT_NAME
#import <Foundation/Foundation.h>
// NS_SWIFT_NAME can only translate factory methods before the iOS 9.3 SDK.
// Wrap it in our own macro if it's a non-compatible SDK.
#ifdef __IPHONE_9_3
#define FIR_SWIFT_NAME(X) NS_SWIFT_NAME(X)
#else
#define FIR_SWIFT_NAME(X) // Intentionally blank.
#endif // #ifdef __IPHONE_9_3
#endif // FIR_SWIFT_NAME

View File

@ -0,0 +1,407 @@
/// @file FIREventNames.h
///
/// Predefined event names.
///
/// An Event is an important occurrence in your app that you want to measure. You can report up to
/// 500 different types of Events per app and you can associate up to 25 unique parameters with each
/// Event type. Some common events are suggested below, but you may also choose to specify custom
/// Event types that are associated with your specific app. Each event type is identified by a
/// unique name. Event names can be up to 40 characters long, may only contain alphanumeric
/// characters and underscores ("_"), and must start with an alphabetic character. The "firebase_",
/// "google_", and "ga_" prefixes are reserved and should not be used.
#import <Foundation/Foundation.h>
/// Add Payment Info event. This event signifies that a user has submitted their payment information
/// to your app.
static NSString *const kFIREventAddPaymentInfo NS_SWIFT_NAME(AnalyticsEventAddPaymentInfo) =
@"add_payment_info";
/// E-Commerce Add To Cart event. This event signifies that an item was added to a cart for
/// purchase. Add this event to a funnel with kFIREventEcommercePurchase to gauge the effectiveness
/// of your checkout process. Note: If you supply the @c kFIRParameterValue parameter, you must
/// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed
/// accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventAddToCart NS_SWIFT_NAME(AnalyticsEventAddToCart) = @"add_to_cart";
/// E-Commerce Add To Wishlist event. This event signifies that an item was added to a wishlist.
/// Use this event to identify popular gift items in your app. Note: If you supply the
/// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency
/// parameter so that revenue metrics can be computed accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// </ul>
static NSString *const kFIREventAddToWishlist NS_SWIFT_NAME(AnalyticsEventAddToWishlist) =
@"add_to_wishlist";
/// App Open event. By logging this event when an App becomes active, developers can understand how
/// often users leave and return during the course of a Session. Although Sessions are automatically
/// reported, this event can provide further clarification around the continuous engagement of
/// app-users.
static NSString *const kFIREventAppOpen NS_SWIFT_NAME(AnalyticsEventAppOpen) = @"app_open";
/// E-Commerce Begin Checkout event. This event signifies that a user has begun the process of
/// checking out. Add this event to a funnel with your kFIREventEcommercePurchase event to gauge the
/// effectiveness of your checkout process. Note: If you supply the @c kFIRParameterValue
/// parameter, you must also supply the @c kFIRParameterCurrency parameter so that revenue
/// metrics can be computed accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterTransactionID (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventBeginCheckout NS_SWIFT_NAME(AnalyticsEventBeginCheckout) =
@"begin_checkout";
/// Campaign Detail event. Log this event to supply the referral details of a re-engagement
/// campaign. Note: you must supply at least one of the required parameters kFIRParameterSource,
/// kFIRParameterMedium or kFIRParameterCampaign. Params:
///
/// <ul>
/// <li>@c kFIRParameterSource (NSString)</li>
/// <li>@c kFIRParameterMedium (NSString)</li>
/// <li>@c kFIRParameterCampaign (NSString)</li>
/// <li>@c kFIRParameterTerm (NSString) (optional)</li>
/// <li>@c kFIRParameterContent (NSString) (optional)</li>
/// <li>@c kFIRParameterAdNetworkClickID (NSString) (optional)</li>
/// <li>@c kFIRParameterCP1 (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventCampaignDetails NS_SWIFT_NAME(AnalyticsEventCampaignDetails) =
@"campaign_details";
/// Checkout progress. Params:
///
/// <ul>
/// <li>@c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterCheckoutOption (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventCheckoutProgress NS_SWIFT_NAME(AnalyticsEventCheckoutProgress) =
@"checkout_progress";
/// Earn Virtual Currency event. This event tracks the awarding of virtual currency in your app. Log
/// this along with @c kFIREventSpendVirtualCurrency to better understand your virtual economy.
/// Params:
///
/// <ul>
/// <li>@c kFIRParameterVirtualCurrencyName (NSString)</li>
/// <li>@c kFIRParameterValue (signed 64-bit integer or double as NSNumber)</li>
/// </ul>
static NSString *const kFIREventEarnVirtualCurrency
NS_SWIFT_NAME(AnalyticsEventEarnVirtualCurrency) = @"earn_virtual_currency";
/// E-Commerce Purchase event. This event signifies that an item was purchased by a user. Note:
/// This is different from the in-app purchase event, which is reported automatically for App
/// Store-based apps. Note: If you supply the @c kFIRParameterValue parameter, you must also
/// supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed
/// accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterTransactionID (NSString) (optional)</li>
/// <li>@c kFIRParameterTax (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterShipping (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCoupon (NSString) (optional)</li>
/// <li>@c kFIRParameterLocation (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventEcommercePurchase NS_SWIFT_NAME(AnalyticsEventEcommercePurchase) =
@"ecommerce_purchase";
/// Generate Lead event. Log this event when a lead has been generated in the app to understand the
/// efficacy of your install and re-engagement campaigns. Note: If you supply the
/// @c kFIRParameterValue parameter, you must also supply the @c kFIRParameterCurrency
/// parameter so that revenue metrics can be computed accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// </ul>
static NSString *const kFIREventGenerateLead NS_SWIFT_NAME(AnalyticsEventGenerateLead) =
@"generate_lead";
/// Join Group event. Log this event when a user joins a group such as a guild, team or family. Use
/// this event to analyze how popular certain groups or social features are in your app. Params:
///
/// <ul>
/// <li>@c kFIRParameterGroupID (NSString)</li>
/// </ul>
static NSString *const kFIREventJoinGroup NS_SWIFT_NAME(AnalyticsEventJoinGroup) = @"join_group";
/// Level Up event. This event signifies that a player has leveled up in your gaming app. It can
/// help you gauge the level distribution of your userbase and help you identify certain levels that
/// are difficult to pass. Params:
///
/// <ul>
/// <li>@c kFIRParameterLevel (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterCharacter (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventLevelUp NS_SWIFT_NAME(AnalyticsEventLevelUp) = @"level_up";
/// Login event. Apps with a login feature can report this event to signify that a user has logged
/// in.
static NSString *const kFIREventLogin NS_SWIFT_NAME(AnalyticsEventLogin) = @"login";
/// Post Score event. Log this event when the user posts a score in your gaming app. This event can
/// help you understand how users are actually performing in your game and it can help you correlate
/// high scores with certain audiences or behaviors. Params:
///
/// <ul>
/// <li>@c kFIRParameterScore (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterLevel (signed 64-bit integer as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCharacter (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventPostScore NS_SWIFT_NAME(AnalyticsEventPostScore) = @"post_score";
/// Present Offer event. This event signifies that the app has presented a purchase offer to a user.
/// Add this event to a funnel with the kFIREventAddToCart and kFIREventEcommercePurchase to gauge
/// your conversion process. Note: If you supply the @c kFIRParameterValue parameter, you must
/// also supply the @c kFIRParameterCurrency parameter so that revenue metrics can be computed
/// accurately. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// </ul>
static NSString *const kFIREventPresentOffer NS_SWIFT_NAME(AnalyticsEventPresentOffer) =
@"present_offer";
/// E-Commerce Purchase Refund event. This event signifies that an item purchase was refunded.
/// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the
/// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately.
/// Params:
///
/// <ul>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterTransactionID (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventPurchaseRefund NS_SWIFT_NAME(AnalyticsEventPurchaseRefund) =
@"purchase_refund";
/// Remove from cart event. Params:
///
/// <ul>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// </ul>
static NSString *const kFIREventRemoveFromCart NS_SWIFT_NAME(AnalyticsEventRemoveFromCart) =
@"remove_from_cart";
/// Search event. Apps that support search features can use this event to contextualize search
/// operations by supplying the appropriate, corresponding parameters. This event can help you
/// identify the most popular content in your app. Params:
///
/// <ul>
/// <li>@c kFIRParameterSearchTerm (NSString)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// hotel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventSearch NS_SWIFT_NAME(AnalyticsEventSearch) = @"search";
/// Select Content event. This general purpose event signifies that a user has selected some content
/// of a certain type in an app. The content can be any object in your app. This event can help you
/// identify popular content and categories of content in your app. Params:
///
/// <ul>
/// <li>@c kFIRParameterContentType (NSString)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// </ul>
static NSString *const kFIREventSelectContent NS_SWIFT_NAME(AnalyticsEventSelectContent) =
@"select_content";
/// Set checkout option. Params:
///
/// <ul>
/// <li>@c kFIRParameterCheckoutStep (unsigned 64-bit integer as NSNumber)</li>
/// <li>@c kFIRParameterCheckoutOption (NSString)</li>
/// </ul>
static NSString *const kFIREventSetCheckoutOption NS_SWIFT_NAME(AnalyticsEventSetCheckoutOption) =
@"set_checkout_option";
/// Share event. Apps with social features can log the Share event to identify the most viral
/// content. Params:
///
/// <ul>
/// <li>@c kFIRParameterContentType (NSString)</li>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// </ul>
static NSString *const kFIREventShare NS_SWIFT_NAME(AnalyticsEventShare) = @"share";
/// Sign Up event. This event indicates that a user has signed up for an account in your app. The
/// parameter signifies the method by which the user signed up. Use this event to understand the
/// different behaviors between logged in and logged out users. Params:
///
/// <ul>
/// <li>@c kFIRParameterSignUpMethod (NSString)</li>
/// </ul>
static NSString *const kFIREventSignUp NS_SWIFT_NAME(AnalyticsEventSignUp) = @"sign_up";
/// Spend Virtual Currency event. This event tracks the sale of virtual goods in your app and can
/// help you identify which virtual goods are the most popular objects of purchase. Params:
///
/// <ul>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterVirtualCurrencyName (NSString)</li>
/// <li>@c kFIRParameterValue (signed 64-bit integer or double as NSNumber)</li>
/// </ul>
static NSString *const kFIREventSpendVirtualCurrency
NS_SWIFT_NAME(AnalyticsEventSpendVirtualCurrency) = @"spend_virtual_currency";
/// Tutorial Begin event. This event signifies the start of the on-boarding process in your app. Use
/// this in a funnel with kFIREventTutorialComplete to understand how many users complete this
/// process and move on to the full app experience.
static NSString *const kFIREventTutorialBegin NS_SWIFT_NAME(AnalyticsEventTutorialBegin) =
@"tutorial_begin";
/// Tutorial End event. Use this event to signify the user's completion of your app's on-boarding
/// process. Add this to a funnel with kFIREventTutorialBegin to gauge the completion rate of your
/// on-boarding process.
static NSString *const kFIREventTutorialComplete NS_SWIFT_NAME(AnalyticsEventTutorialComplete) =
@"tutorial_complete";
/// Unlock Achievement event. Log this event when the user has unlocked an achievement in your
/// game. Since achievements generally represent the breadth of a gaming experience, this event can
/// help you understand how many users are experiencing all that your game has to offer. Params:
///
/// <ul>
/// <li>@c kFIRParameterAchievementID (NSString)</li>
/// </ul>
static NSString *const kFIREventUnlockAchievement NS_SWIFT_NAME(AnalyticsEventUnlockAchievement) =
@"unlock_achievement";
/// View Item event. This event signifies that some content was shown to the user. This content may
/// be a product, a webpage or just a simple image or text. Use the appropriate parameters to
/// contextualize the event. Use this event to discover the most popular items viewed in your app.
/// Note: If you supply the @c kFIRParameterValue parameter, you must also supply the
/// @c kFIRParameterCurrency parameter so that revenue metrics can be computed accurately.
/// Params:
///
/// <ul>
/// <li>@c kFIRParameterItemID (NSString)</li>
/// <li>@c kFIRParameterItemName (NSString)</li>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// <li>@c kFIRParameterItemLocationID (NSString) (optional)</li>
/// <li>@c kFIRParameterPrice (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterQuantity (signed 64-bit integer as NSNumber) (optional)</li>
/// <li>@c kFIRParameterCurrency (NSString) (optional)</li>
/// <li>@c kFIRParameterValue (double as NSNumber) (optional)</li>
/// <li>@c kFIRParameterStartDate (NSString) (optional)</li>
/// <li>@c kFIRParameterEndDate (NSString) (optional)</li>
/// <li>@c kFIRParameterFlightNumber (NSString) (optional) for travel bookings</li>
/// <li>@c kFIRParameterNumberOfPassengers (signed 64-bit integer as NSNumber) (optional)
/// for travel bookings</li>
/// <li>@c kFIRParameterNumberOfNights (signed 64-bit integer as NSNumber) (optional) for
/// travel bookings</li>
/// <li>@c kFIRParameterNumberOfRooms (signed 64-bit integer as NSNumber) (optional) for
/// travel bookings</li>
/// <li>@c kFIRParameterOrigin (NSString) (optional)</li>
/// <li>@c kFIRParameterDestination (NSString) (optional)</li>
/// <li>@c kFIRParameterSearchTerm (NSString) (optional) for travel bookings</li>
/// <li>@c kFIRParameterTravelClass (NSString) (optional) for travel bookings</li>
/// </ul>
static NSString *const kFIREventViewItem NS_SWIFT_NAME(AnalyticsEventViewItem) = @"view_item";
/// View Item List event. Log this event when the user has been presented with a list of items of a
/// certain category. Params:
///
/// <ul>
/// <li>@c kFIRParameterItemCategory (NSString)</li>
/// </ul>
static NSString *const kFIREventViewItemList NS_SWIFT_NAME(AnalyticsEventViewItemList) =
@"view_item_list";
/// View Search Results event. Log this event when the user has been presented with the results of a
/// search. Params:
///
/// <ul>
/// <li>@c kFIRParameterSearchTerm (NSString)</li>
/// </ul>
static NSString *const kFIREventViewSearchResults NS_SWIFT_NAME(AnalyticsEventViewSearchResults) =
@"view_search_results";
/// Level Start event. Log this event when the user starts a new level. Params:
///
/// <ul>
/// <li>@c kFIRParameterLevelName (NSString)</li>
/// </ul>
static NSString *const kFIREventLevelStart NS_SWIFT_NAME(AnalyticsEventLevelStart) =
@"level_start";
/// Level End event. Log this event when the user finishes a level. Params:
///
/// <ul>
/// <li>@c kFIRParameterLevelName (NSString)</li>
/// <li>@c kFIRParameterSuccess (NSString)</li>
/// </ul>
static NSString *const kFIREventLevelEnd NS_SWIFT_NAME(AnalyticsEventLevelEnd) = @"level_end";

View File

@ -0,0 +1,507 @@
/// @file FIRParameterNames.h
///
/// Predefined event parameter names.
///
/// Params supply information that contextualize Events. You can associate up to 25 unique Params
/// with each Event type. Some Params are suggested below for certain common Events, but you are
/// not limited to these. You may supply extra Params for suggested Events or custom Params for
/// Custom events. Param names can be up to 40 characters long, may only contain alphanumeric
/// characters and underscores ("_"), and must start with an alphabetic character. Param values can
/// be up to 100 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and
/// should not be used.
#import <Foundation/Foundation.h>
/// Game achievement ID (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterAchievementID : @"10_matches_won",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterAchievementID NS_SWIFT_NAME(AnalyticsParameterAchievementID) =
@"achievement_id";
/// Ad Network Click ID (NSString). Used for network-specific click IDs which vary in format.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterAdNetworkClickID : @"1234567",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterAdNetworkClickID
NS_SWIFT_NAME(AnalyticsParameterAdNetworkClickID) = @"aclid";
/// The store or affiliation from which this transaction occurred (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterAffiliation : @"Google Store",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterAffiliation NS_SWIFT_NAME(AnalyticsParameterAffiliation) =
@"affiliation";
/// The individual campaign name, slogan, promo code, etc. Some networks have pre-defined macro to
/// capture campaign information, otherwise can be populated by developer. Highly Recommended
/// (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCampaign : @"winter_promotion",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCampaign NS_SWIFT_NAME(AnalyticsParameterCampaign) =
@"campaign";
/// Character used in game (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCharacter : @"beat_boss",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCharacter NS_SWIFT_NAME(AnalyticsParameterCharacter) =
@"character";
/// The checkout step (1..N) (unsigned 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCheckoutStep : @"1",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCheckoutStep NS_SWIFT_NAME(AnalyticsParameterCheckoutStep) =
@"checkout_step";
/// Some option on a step in an ecommerce flow (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCheckoutOption : @"Visa",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCheckoutOption
NS_SWIFT_NAME(AnalyticsParameterCheckoutOption) = @"checkout_option";
/// Campaign content (NSString).
static NSString *const kFIRParameterContent NS_SWIFT_NAME(AnalyticsParameterContent) = @"content";
/// Type of content selected (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterContentType : @"news article",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterContentType NS_SWIFT_NAME(AnalyticsParameterContentType) =
@"content_type";
/// Coupon code for a purchasable item (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCoupon : @"zz123",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCoupon NS_SWIFT_NAME(AnalyticsParameterCoupon) = @"coupon";
/// Campaign custom parameter (NSString). Used as a method of capturing custom data in a campaign.
/// Use varies by network.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCP1 : @"custom_data",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCP1 NS_SWIFT_NAME(AnalyticsParameterCP1) = @"cp1";
/// The name of a creative used in a promotional spot (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCreativeName : @"Summer Sale",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCreativeName NS_SWIFT_NAME(AnalyticsParameterCreativeName) =
@"creative_name";
/// The name of a creative slot (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCreativeSlot : @"summer_banner2",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCreativeSlot NS_SWIFT_NAME(AnalyticsParameterCreativeSlot) =
@"creative_slot";
/// Purchase currency in 3-letter <a href="http://en.wikipedia.org/wiki/ISO_4217#Active_codes">
/// ISO_4217</a> format (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterCurrency : @"USD",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterCurrency NS_SWIFT_NAME(AnalyticsParameterCurrency) =
@"currency";
/// Flight or Travel destination (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterDestination : @"Mountain View, CA",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterDestination NS_SWIFT_NAME(AnalyticsParameterDestination) =
@"destination";
/// The arrival date, check-out date or rental end date for the item. This should be in
/// YYYY-MM-DD format (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterEndDate : @"2015-09-14",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterEndDate NS_SWIFT_NAME(AnalyticsParameterEndDate) = @"end_date";
/// Flight number for travel events (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterFlightNumber : @"ZZ800",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterFlightNumber NS_SWIFT_NAME(AnalyticsParameterFlightNumber) =
@"flight_number";
/// Group/clan/guild ID (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterGroupID : @"g1",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterGroupID NS_SWIFT_NAME(AnalyticsParameterGroupID) = @"group_id";
/// Index of an item in a list (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterIndex : @(1),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterIndex NS_SWIFT_NAME(AnalyticsParameterIndex) = @"index";
/// Item brand (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemBrand : @"Google",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemBrand NS_SWIFT_NAME(AnalyticsParameterItemBrand) =
@"item_brand";
/// Item category (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemCategory : @"t-shirts",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemCategory NS_SWIFT_NAME(AnalyticsParameterItemCategory) =
@"item_category";
/// Item ID (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemID : @"p7654",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemID NS_SWIFT_NAME(AnalyticsParameterItemID) = @"item_id";
/// The Google <a href="https://developers.google.com/places/place-id">Place ID</a> (NSString) that
/// corresponds to the associated item. Alternatively, you can supply your own custom Location ID.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemLocationID : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemLocationID
NS_SWIFT_NAME(AnalyticsParameterItemLocationID) = @"item_location_id";
/// Item name (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemName : @"abc",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemName NS_SWIFT_NAME(AnalyticsParameterItemName) =
@"item_name";
/// The list in which the item was presented to the user (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemList : @"Search Results",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemList NS_SWIFT_NAME(AnalyticsParameterItemList) =
@"item_list";
/// Item variant (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterItemVariant : @"Red",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterItemVariant NS_SWIFT_NAME(AnalyticsParameterItemVariant) =
@"item_variant";
/// Level in game (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterLevel : @(42),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterLevel NS_SWIFT_NAME(AnalyticsParameterLevel) = @"level";
/// Location (NSString). The Google <a href="https://developers.google.com/places/place-id">Place ID
/// </a> that corresponds to the associated event. Alternatively, you can supply your own custom
/// Location ID.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterLocation : @"ChIJiyj437sx3YAR9kUWC8QkLzQ",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterLocation NS_SWIFT_NAME(AnalyticsParameterLocation) =
@"location";
/// The advertising or marketing medium, for example: cpc, banner, email, push. Highly recommended
/// (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterMedium : @"email",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterMedium NS_SWIFT_NAME(AnalyticsParameterMedium) = @"medium";
/// Number of nights staying at hotel (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterNumberOfNights : @(3),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterNumberOfNights
NS_SWIFT_NAME(AnalyticsParameterNumberOfNights) = @"number_of_nights";
/// Number of passengers traveling (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterNumberOfPassengers : @(11),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterNumberOfPassengers
NS_SWIFT_NAME(AnalyticsParameterNumberOfPassengers) = @"number_of_passengers";
/// Number of rooms for travel events (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterNumberOfRooms : @(2),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterNumberOfRooms NS_SWIFT_NAME(AnalyticsParameterNumberOfRooms) =
@"number_of_rooms";
/// Flight or Travel origin (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterOrigin : @"Mountain View, CA",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterOrigin NS_SWIFT_NAME(AnalyticsParameterOrigin) = @"origin";
/// Purchase price (double as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterPrice : @(1.0),
/// kFIRParameterCurrency : @"USD", // e.g. $1.00 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterPrice NS_SWIFT_NAME(AnalyticsParameterPrice) = @"price";
/// Purchase quantity (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterQuantity : @(1),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterQuantity NS_SWIFT_NAME(AnalyticsParameterQuantity) =
@"quantity";
/// Score in game (signed 64-bit integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterScore : @(4200),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterScore NS_SWIFT_NAME(AnalyticsParameterScore) = @"score";
/// The search string/keywords used (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSearchTerm : @"periodic table",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSearchTerm NS_SWIFT_NAME(AnalyticsParameterSearchTerm) =
@"search_term";
/// Shipping cost (double as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterShipping : @(9.50),
/// kFIRParameterCurrency : @"USD", // e.g. $9.50 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterShipping NS_SWIFT_NAME(AnalyticsParameterShipping) =
@"shipping";
/// Sign up method (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSignUpMethod : @"google",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSignUpMethod NS_SWIFT_NAME(AnalyticsParameterSignUpMethod) =
@"sign_up_method";
/// The origin of your traffic, such as an Ad network (for example, google) or partner (urban
/// airship). Identify the advertiser, site, publication, etc. that is sending traffic to your
/// property. Highly recommended (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSource : @"InMobi",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSource NS_SWIFT_NAME(AnalyticsParameterSource) = @"source";
/// The departure date, check-in date or rental start date for the item. This should be in
/// YYYY-MM-DD format (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterStartDate : @"2015-09-14",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterStartDate NS_SWIFT_NAME(AnalyticsParameterStartDate) =
@"start_date";
/// Tax amount (double as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTax : @(1.0),
/// kFIRParameterCurrency : @"USD", // e.g. $1.00 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTax NS_SWIFT_NAME(AnalyticsParameterTax) = @"tax";
/// If you're manually tagging keyword campaigns, you should use utm_term to specify the keyword
/// (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTerm : @"game",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTerm NS_SWIFT_NAME(AnalyticsParameterTerm) = @"term";
/// A single ID for a ecommerce group transaction (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTransactionID : @"ab7236dd9823",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTransactionID NS_SWIFT_NAME(AnalyticsParameterTransactionID) =
@"transaction_id";
/// Travel class (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterTravelClass : @"business",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterTravelClass NS_SWIFT_NAME(AnalyticsParameterTravelClass) =
@"travel_class";
/// A context-specific numeric value which is accumulated automatically for each event type. This is
/// a general purpose parameter that is useful for accumulating a key metric that pertains to an
/// event. Examples include revenue, distance, time and points. Value should be specified as signed
/// 64-bit integer or double as NSNumber. Notes: Values for pre-defined currency-related events
/// (such as @c kFIREventAddToCart) should be supplied using double as NSNumber and must be
/// accompanied by a @c kFIRParameterCurrency parameter. The valid range of accumulated values is
/// [-9,223,372,036,854.77, 9,223,372,036,854.77]. Supplying a non-numeric value, omitting the
/// corresponding @c kFIRParameterCurrency parameter, or supplying an invalid
/// <a href="https://goo.gl/qqX3J2">currency code</a> for conversion events will cause that
/// conversion to be omitted from reporting.
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterValue : @(3.99),
/// kFIRParameterCurrency : @"USD", // e.g. $3.99 USD
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterValue NS_SWIFT_NAME(AnalyticsParameterValue) = @"value";
/// Name of virtual currency type (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterVirtualCurrencyName : @"virtual_currency_name",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterVirtualCurrencyName
NS_SWIFT_NAME(AnalyticsParameterVirtualCurrencyName) = @"virtual_currency_name";
/// The name of a level in a game (NSString).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterLevelName : @"room_1",
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterLevelName NS_SWIFT_NAME(AnalyticsParameterLevelName) =
@"level_name";
/// The result of an operation. Specify 1 to indicate success and 0 to indicate failure (unsigned
/// integer as NSNumber).
/// <pre>
/// NSDictionary *params = @{
/// kFIRParameterSuccess : @(1),
/// // ...
/// };
/// </pre>
static NSString *const kFIRParameterSuccess NS_SWIFT_NAME(AnalyticsParameterSuccess) = @"success";

View File

@ -0,0 +1,17 @@
/// @file FIRUserPropertyNames.h
///
/// Predefined user property names.
///
/// A UserProperty is an attribute that describes the app-user. By supplying UserProperties, you can
/// later analyze different behaviors of various segments of your userbase. You may supply up to 25
/// unique UserProperties per app, and you can use the name and value of your choosing for each one.
/// UserProperty names can be up to 24 characters long, may only contain alphanumeric characters and
/// underscores ("_"), and must start with an alphabetic character. UserProperty values can be up to
/// 36 characters long. The "firebase_", "google_", and "ga_" prefixes are reserved and should not
/// be used.
#import <Foundation/Foundation.h>
/// The method used to sign in. For example, "google", "facebook" or "twitter".
static NSString *const kFIRUserPropertySignUpMethod
NS_SWIFT_NAME(AnalyticsUserPropertySignUpMethod) = @"sign_up_method";

View File

@ -0,0 +1,6 @@
#import "FIRAnalytics+AppDelegate.h"
#import "FIRAnalytics.h"
#import "FIRAnalyticsSwiftNameSupport.h"
#import "FIREventNames.h"
#import "FIRParameterNames.h"
#import "FIRUserPropertyNames.h"

View File

@ -0,0 +1,10 @@
framework module FirebaseAnalytics {
umbrella header "FirebaseAnalytics.h"
export *
module * { export *}
link "sqlite3"
link "z"
link framework "Security"
link framework "StoreKit"
link framework "SystemConfiguration"
link framework "UIKit"}

View File

@ -0,0 +1,6 @@
framework module FirebaseCoreDiagnostics {
export *
module * { export *}
link "z"
link framework "Security"
link framework "SystemConfiguration"}

View File

@ -0,0 +1,69 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "FIRAnalyticsConfiguration.h"
#import "Private/FIRAnalyticsConfiguration+Internal.h"
@implementation FIRAnalyticsConfiguration
+ (FIRAnalyticsConfiguration *)sharedInstance {
static FIRAnalyticsConfiguration *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[FIRAnalyticsConfiguration alloc] init];
});
return sharedInstance;
}
- (void)postNotificationName:(NSString *)name value:(id)value {
if (!name.length || !value) {
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:name
object:self
userInfo:@{name : value}];
}
- (void)setMinimumSessionInterval:(NSTimeInterval)minimumSessionInterval {
[self postNotificationName:kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification
value:@(minimumSessionInterval)];
}
- (void)setSessionTimeoutInterval:(NSTimeInterval)sessionTimeoutInterval {
[self postNotificationName:kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification
value:@(sessionTimeoutInterval)];
}
- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled {
[self setAnalyticsCollectionEnabled:analyticsCollectionEnabled persistSetting:YES];
}
- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled
persistSetting:(BOOL)shouldPersist {
// Persist the measurementEnabledState. Use FIRAnalyticsEnabledState values instead of YES/NO.
FIRAnalyticsEnabledState analyticsEnabledState =
analyticsCollectionEnabled ? kFIRAnalyticsEnabledStateSetYes : kFIRAnalyticsEnabledStateSetNo;
if (shouldPersist) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@(analyticsEnabledState)
forKey:kFIRAPersistedConfigMeasurementEnabledStateKey];
[userDefaults synchronize];
}
[self postNotificationName:kFIRAnalyticsConfigurationSetEnabledNotification
value:@(analyticsCollectionEnabled)];
}
@end

View File

@ -0,0 +1,737 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sys/utsname.h>
#import "FIRApp.h"
#import "FIRConfiguration.h"
#import "Private/FIRAnalyticsConfiguration+Internal.h"
#import "Private/FIRAppInternal.h"
#import "Private/FIRBundleUtil.h"
#import "Private/FIRLogger.h"
#import "Private/FIROptionsInternal.h"
#import "third_party/FIRAppEnvironmentUtil.h"
NSString *const kFIRServiceAdMob = @"AdMob";
NSString *const kFIRServiceAuth = @"Auth";
NSString *const kFIRServiceAuthUI = @"AuthUI";
NSString *const kFIRServiceCrash = @"Crash";
NSString *const kFIRServiceDatabase = @"Database";
NSString *const kFIRServiceDynamicLinks = @"DynamicLinks";
NSString *const kFIRServiceFirestore = @"Firestore";
NSString *const kFIRServiceFunctions = @"Functions";
NSString *const kFIRServiceInstanceID = @"InstanceID";
NSString *const kFIRServiceInvites = @"Invites";
NSString *const kFIRServiceMessaging = @"Messaging";
NSString *const kFIRServiceMeasurement = @"Measurement";
NSString *const kFIRServicePerformance = @"Performance";
NSString *const kFIRServiceRemoteConfig = @"RemoteConfig";
NSString *const kFIRServiceStorage = @"Storage";
NSString *const kGGLServiceAnalytics = @"Analytics";
NSString *const kGGLServiceSignIn = @"SignIn";
NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT";
NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification";
NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification";
NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
NSString *const kFIRAppNameKey = @"FIRAppNameKey";
NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat =
@"/google/firebase/global_data_collection_enabled:%@";
NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey =
@"FirebaseAutomaticDataCollectionEnabled";
NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
NSString *const kFIRAppDiagnosticsErrorKey = @"Error";
NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp";
NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName";
NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion";
// Auth internal notification notification and key.
NSString *const FIRAuthStateDidChangeInternalNotification =
@"FIRAuthStateDidChangeInternalNotification";
NSString *const FIRAuthStateDidChangeInternalNotificationAppKey =
@"FIRAuthStateDidChangeInternalNotificationAppKey";
NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey =
@"FIRAuthStateDidChangeInternalNotificationTokenKey";
NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey =
@"FIRAuthStateDidChangeInternalNotificationUIDKey";
/**
* The URL to download plist files.
*/
static NSString *const kPlistURL = @"https://console.firebase.google.com/";
@interface FIRApp ()
@property(nonatomic) BOOL alreadySentConfigureNotification;
@property(nonatomic) BOOL alreadySentDeleteNotification;
@end
@implementation FIRApp
// This is necessary since our custom getter prevents `_options` from being created.
@synthesize options = _options;
static NSMutableDictionary *sAllApps;
static FIRApp *sDefaultApp;
static NSMutableDictionary *sLibraryVersions;
+ (void)configure {
FIROptions *options = [FIROptions defaultOptions];
if (!options) {
[[NSNotificationCenter defaultCenter]
postNotificationName:kFIRAppDiagnosticsNotification
object:nil
userInfo:@{
kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
kFIRAppDiagnosticsErrorKey : [FIRApp errorForMissingOptions]
}];
[NSException raise:kFirebaseCoreErrorDomain
format:
@"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) could not find "
@"a valid GoogleService-Info.plist in your project. Please download one "
@"from %@.",
kPlistURL];
}
[FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
#if TARGET_OS_OSX || TARGET_OS_TV
FIRLogNotice(kFIRLoggerCore, @"I-COR000028",
@"tvOS and macOS SDK support is not part of the official Firebase product. "
@"Instead they are community supported. Details at "
@"https://github.com/firebase/firebase-ios-sdk/blob/master/README.md.");
#endif
}
+ (void)configureWithOptions:(FIROptions *)options {
if (!options) {
[NSException raise:kFirebaseCoreErrorDomain
format:@"Options is nil. Please pass a valid options."];
}
[FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
}
+ (void)configureDefaultAppWithOptions:(FIROptions *)options
sendingNotifications:(BOOL)sendNotifications {
if (sDefaultApp) {
// FIRApp sets up FirebaseAnalytics and does plist validation, but does not cause it
// to fire notifications. So, if the default app already exists, but has not sent out
// configuration notifications, then continue re-initializing it.
if (!sendNotifications || sDefaultApp.alreadySentConfigureNotification) {
[NSException raise:kFirebaseCoreErrorDomain
format:@"Default app has already been configured."];
}
}
@synchronized(self) {
FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app.");
sDefaultApp = [[FIRApp alloc] initInstanceWithName:kFIRDefaultAppName options:options];
[FIRApp addAppToAppDictionary:sDefaultApp];
if (!sDefaultApp.alreadySentConfigureNotification && sendNotifications) {
[FIRApp sendNotificationsToSDKs:sDefaultApp];
sDefaultApp.alreadySentConfigureNotification = YES;
}
}
}
+ (void)configureWithName:(NSString *)name options:(FIROptions *)options {
if (!name || !options) {
[NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."];
}
if (name.length == 0) {
[NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."];
}
if ([name isEqualToString:kFIRDefaultAppName]) {
[NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be __FIRAPP_DEFAULT."];
}
for (NSInteger charIndex = 0; charIndex < name.length; charIndex++) {
char character = [name characterAtIndex:charIndex];
if (!((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') ||
(character >= '0' && character <= '9') || character == '_' || character == '-')) {
[NSException raise:kFirebaseCoreErrorDomain
format:
@"App name should only contain Letters, "
@"Numbers, Underscores, and Dashes."];
}
}
if (sAllApps && sAllApps[name]) {
[NSException raise:kFirebaseCoreErrorDomain
format:@"App named %@ has already been configured.", name];
}
@synchronized(self) {
FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name);
FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options];
[FIRApp addAppToAppDictionary:app];
if (!app.alreadySentConfigureNotification) {
[FIRApp sendNotificationsToSDKs:app];
app.alreadySentConfigureNotification = YES;
}
}
}
+ (FIRApp *)defaultApp {
if (sDefaultApp) {
return sDefaultApp;
}
FIRLogError(kFIRLoggerCore, @"I-COR000003",
@"The default Firebase app has not yet been "
@"configured. Add `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) to your "
@"application initialization. Read more: https://goo.gl/ctyzm8.");
return nil;
}
+ (FIRApp *)appNamed:(NSString *)name {
@synchronized(self) {
if (sAllApps) {
FIRApp *app = sAllApps[name];
if (app) {
return app;
}
}
FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name);
return nil;
}
}
+ (NSDictionary *)allApps {
@synchronized(self) {
if (!sAllApps) {
FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet.");
}
NSDictionary *dict = [NSDictionary dictionaryWithDictionary:sAllApps];
return dict;
}
}
// Public only for tests
+ (void)resetApps {
sDefaultApp = nil;
[sAllApps removeAllObjects];
sAllApps = nil;
[sLibraryVersions removeAllObjects];
sLibraryVersions = nil;
}
- (void)deleteApp:(FIRAppVoidBoolCallback)completion {
@synchronized([self class]) {
if (sAllApps && sAllApps[self.name]) {
FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
[sAllApps removeObjectForKey:self.name];
[self clearDataCollectionSwitchFromUserDefaults];
if ([self.name isEqualToString:kFIRDefaultAppName]) {
sDefaultApp = nil;
}
if (!self.alreadySentDeleteNotification) {
NSDictionary *appInfoDict = @{kFIRAppNameKey : self.name};
[[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification
object:[self class]
userInfo:appInfoDict];
self.alreadySentDeleteNotification = YES;
}
completion(YES);
} else {
FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist.");
completion(NO);
}
}
}
+ (void)addAppToAppDictionary:(FIRApp *)app {
if (!sAllApps) {
sAllApps = [NSMutableDictionary dictionary];
}
if ([app configureCore]) {
sAllApps[app.name] = app;
[[NSNotificationCenter defaultCenter]
postNotificationName:kFIRAppDiagnosticsNotification
object:nil
userInfo:@{
kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
kFIRAppDiagnosticsFIRAppKey : app
}];
} else {
[NSException raise:kFirebaseCoreErrorDomain
format:
@"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in "
@"GoogleService-Info.plist or set in the customized options."];
}
}
- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options {
self = [super init];
if (self) {
_name = [name copy];
_options = [options copy];
_options.editingLocked = YES;
FIRApp *app = sAllApps[name];
_alreadySentConfigureNotification = app.alreadySentConfigureNotification;
_alreadySentDeleteNotification = app.alreadySentDeleteNotification;
}
return self;
}
- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback {
if (!_getTokenImplementation) {
callback(nil, nil);
return;
}
_getTokenImplementation(forceRefresh, callback);
}
- (BOOL)configureCore {
[self checkExpectedBundleID];
if (![self isAppIDValid]) {
if (_options.usingOptionsFromDefaultPlist) {
[[NSNotificationCenter defaultCenter]
postNotificationName:kFIRAppDiagnosticsNotification
object:nil
userInfo:@{
kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
kFIRAppDiagnosticsErrorKey : [FIRApp errorForInvalidAppID],
}];
}
return NO;
}
// Initialize the Analytics once there is a valid options under default app. Analytics should
// always initialize first by itself before the other SDKs.
if ([self.name isEqualToString:kFIRDefaultAppName]) {
Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics");
if (!firAnalyticsClass) {
FIRLogError(kFIRLoggerCore, @"I-COR000022", @"Firebase Analytics is not available.");
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:);
#pragma clang diagnostic pop
if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[firAnalyticsClass performSelector:startWithConfigurationSelector
withObject:[FIRConfiguration sharedInstance].analyticsConfiguration
withObject:_options];
#pragma clang diagnostic pop
}
}
}
return YES;
}
- (FIROptions *)options {
return [_options copy];
}
- (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled {
NSString *key =
[NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
[[NSUserDefaults standardUserDefaults] setBool:automaticDataCollectionEnabled forKey:key];
// Core also controls the FirebaseAnalytics flag, so check if the Analytics flags are set
// within FIROptions and change the Analytics value if necessary. Analytics only works with the
// default app, so return if this isn't the default app.
if (self != sDefaultApp) {
return;
}
// Check if the Analytics flag is explicitly set. If so, no further actions are necessary.
if ([self.options isAnalyticsCollectionExpicitlySet]) {
return;
}
// The Analytics flag has not been explicitly set, so update with the value being set.
[[FIRAnalyticsConfiguration sharedInstance]
setAnalyticsCollectionEnabled:automaticDataCollectionEnabled
persistSetting:NO];
}
- (BOOL)isAutomaticDataCollectionEnabled {
// Check if it's been manually set before in code, and use that as the higher priority value.
NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self];
if (defaultsObject) {
return [defaultsObject boolValue];
}
// Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`.
// As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has
// no performance impact calling multiple times.
NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist];
if (collectionEnabledPlistValue) {
return [collectionEnabledPlistValue boolValue];
}
return YES;
}
#pragma mark - private
+ (void)sendNotificationsToSDKs:(FIRApp *)app {
NSNumber *isDefaultApp = [NSNumber numberWithBool:(app == sDefaultApp)];
NSDictionary *appInfoDict = @{
kFIRAppNameKey : app.name,
kFIRAppIsDefaultAppKey : isDefaultApp,
kFIRGoogleAppIDKey : app.options.googleAppID
};
[[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification
object:self
userInfo:appInfoDict];
}
+ (NSError *)errorForMissingOptions {
NSDictionary *errorDict = @{
NSLocalizedDescriptionKey :
@"Unable to parse GoogleService-Info.plist in order to configure services.",
NSLocalizedRecoverySuggestionErrorKey :
@"Check formatting and location of GoogleService-Info.plist."
};
return [NSError errorWithDomain:kFirebaseCoreErrorDomain
code:FIRErrorCodeInvalidPlistFile
userInfo:errorDict];
}
+ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
errorCode:(FIRErrorCode)code
service:(NSString *)service
reason:(NSString *)reason {
NSString *description =
[NSString stringWithFormat:@"Configuration failed for service %@.", service];
NSDictionary *errorDict =
@{NSLocalizedDescriptionKey : description, NSLocalizedFailureReasonErrorKey : reason};
return [NSError errorWithDomain:domain code:code userInfo:errorDict];
}
+ (NSError *)errorForInvalidAppID {
NSDictionary *errorDict = @{
NSLocalizedDescriptionKey : @"Unable to validate Google App ID",
NSLocalizedRecoverySuggestionErrorKey :
@"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the "
@"customized options."
};
return [NSError errorWithDomain:kFirebaseCoreErrorDomain
code:FIRErrorCodeInvalidAppID
userInfo:errorDict];
}
+ (BOOL)isDefaultAppConfigured {
return (sDefaultApp != nil);
}
+ (void)registerLibrary:(nonnull NSString *)library withVersion:(nonnull NSString *)version {
// Create the set of characters which aren't allowed, only if this feature is used.
NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet];
[allowedSet addCharactersInString:@"-_."];
NSCharacterSet *disallowedSet = [allowedSet invertedSet];
// Make sure the library name and version strings do not contain unexpected characters, and
// add the name/version pair to the dictionary.
if ([library rangeOfCharacterFromSet:disallowedSet].location == NSNotFound &&
[version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) {
if (!sLibraryVersions) {
sLibraryVersions = [[NSMutableDictionary alloc] init];
}
sLibraryVersions[library] = version;
} else {
FIRLogError(kFIRLoggerCore, @"I-COR000027",
@"The library name (%@) or version number (%@) contain illegal characters. "
@"Only alphanumeric, dash, underscore and period characters are allowed.",
library, version);
}
}
+ (NSString *)firebaseUserAgent {
NSMutableArray<NSString *> *libraries =
[[NSMutableArray<NSString *> alloc] initWithCapacity:sLibraryVersions.count];
for (NSString *libraryName in sLibraryVersions) {
[libraries
addObject:[NSString stringWithFormat:@"%@/%@", libraryName, sLibraryVersions[libraryName]]];
}
[libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
return [libraries componentsJoinedByString:@" "];
}
- (void)checkExpectedBundleID {
NSArray *bundles = [FIRBundleUtil relevantBundles];
NSString *expectedBundleID = [self expectedBundleID];
// The checking is only done when the bundle ID is provided in the serviceInfo dictionary for
// backward compatibility.
if (expectedBundleID != nil &&
![FIRBundleUtil hasBundleIdentifier:expectedBundleID inBundles:bundles]) {
FIRLogError(kFIRLoggerCore, @"I-COR000008",
@"The project's Bundle ID is inconsistent with "
@"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are "
@"using a customized options. To ensure that everything can be configured "
@"correctly, you may need to make the Bundle IDs consistent. To continue with this "
@"plist file, you may change your app's bundle identifier to '%@'. Or you can "
@"download a new configuration file that matches your bundle identifier from %@ "
@"and replace the current one.",
kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
}
}
- (nullable NSString *)getUID {
if (!_getUIDImplementation) {
FIRLogWarning(kFIRLoggerCore, @"I-COR000025", @"FIRAuth getUID implementation wasn't set.");
return nil;
}
return _getUIDImplementation();
}
#pragma mark - private - App ID Validation
/**
* Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file.
* This is the main method for validating app ID.
*
* @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise.
*/
- (BOOL)isAppIDValid {
NSString *appID = _options.googleAppID;
BOOL isValid = [FIRApp validateAppID:appID];
if (!isValid) {
NSString *expectedBundleID = [self expectedBundleID];
FIRLogError(kFIRLoggerCore, @"I-COR000009",
@"The GOOGLE_APP_ID either in the plist file "
@"'%@.%@' or the one set in the customized options is invalid. If you are using "
@"the plist file, use the iOS version of bundle identifier to download the file, "
@"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle "
@"identifier to '%@'. Or you can download a new configuration file that matches "
@"your bundle identifier from %@ and replace the current one.",
kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
};
return isValid;
}
+ (BOOL)validateAppID:(NSString *)appID {
// Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not
// have a valid fingerprint, otherwise we just warn about the potential issue.
if (!appID.length) {
return NO;
}
// All app IDs must start with at least "<version number>:".
NSString *const versionPattern = @"^\\d+:";
NSRegularExpression *versionRegex =
[NSRegularExpression regularExpressionWithPattern:versionPattern options:0 error:NULL];
if (!versionRegex) {
return NO;
}
NSRange appIDRange = NSMakeRange(0, appID.length);
NSArray *versionMatches = [versionRegex matchesInString:appID options:0 range:appIDRange];
if (versionMatches.count != 1) {
return NO;
}
NSRange versionRange = [(NSTextCheckingResult *)versionMatches.firstObject range];
NSString *appIDVersion = [appID substringWithRange:versionRange];
NSArray *knownVersions = @[ @"1:" ];
if (![knownVersions containsObject:appIDVersion]) {
// Permit unknown yet properly formatted app ID versions.
return YES;
}
if (![FIRApp validateAppIDFormat:appID withVersion:appIDVersion]) {
return NO;
}
if (![FIRApp validateAppIDFingerprint:appID withVersion:appIDVersion]) {
return NO;
}
return YES;
}
+ (NSString *)actualBundleID {
return [[NSBundle mainBundle] bundleIdentifier];
}
/**
* Validates that the format of the app ID string is what is expected based on the supplied version.
* The version must end in ":".
*
* For v1 app ids the format is expected to be
* '<version #>:<project number>:ios:<fingerprint of bundle id>'.
*
* This method does not verify that the contents of the app id are correct, just that they fulfill
* the expected format.
*
* @param appID Contents of GOOGLE_APP_ID from the plist file.
* @param version Indicates what version of the app id format this string should be.
* @return YES if provided string fufills the expected format, NO otherwise.
*/
+ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version {
if (!appID.length || !version.length) {
return NO;
}
if (![version hasSuffix:@":"]) {
return NO;
}
if (![appID hasPrefix:version]) {
return NO;
}
NSString *const pattern = @"^\\d+:ios:[a-f0-9]+$";
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
if (!regex) {
return NO;
}
NSRange localRange = NSMakeRange(version.length, appID.length - version.length);
NSUInteger numberOfMatches = [regex numberOfMatchesInString:appID options:0 range:localRange];
if (numberOfMatches != 1) {
return NO;
}
return YES;
}
/**
* Validates that the fingerprint of the app ID string is what is expected based on the supplied
* version. The version must end in ":".
*
* Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated.
*
* @param appID Contents of GOOGLE_APP_ID from the plist file.
* @param version Indicates what version of the app id format this string should be.
* @return YES if provided string fufills the expected fingerprint and the version is known, NO
* otherwise.
*/
+ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version {
if (!appID.length || !version.length) {
return NO;
}
if (![version hasSuffix:@":"]) {
return NO;
}
if (![appID hasPrefix:version]) {
return NO;
}
// Extract the supplied fingerprint from the supplied app ID.
// This assumes the app ID format is the same for all known versions below. If the app ID format
// changes in future versions, the tokenizing of the app ID format will need to take into account
// the version of the app ID.
NSArray *components = [appID componentsSeparatedByString:@":"];
if (components.count != 4) {
return NO;
}
NSString *suppliedFingerprintString = components[3];
if (!suppliedFingerprintString.length) {
return NO;
}
uint64_t suppliedFingerprint;
NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString];
if (![scanner scanHexLongLong:&suppliedFingerprint]) {
return NO;
}
if ([version isEqual:@"1:"]) {
// The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated.
return YES;
}
// Unknown version.
return NO;
}
- (NSString *)expectedBundleID {
return _options.bundleID;
}
// end App ID validation
#pragma mark - Reading From Plist & User Defaults
/**
* Clears the data collection switch from the standard NSUserDefaults for easier testing and
* readability.
*/
- (void)clearDataCollectionSwitchFromUserDefaults {
NSString *key =
[NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
/**
* Reads the data collection switch from the standard NSUserDefaults for easier testing and
* readability.
*/
+ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app {
// Read the object in user defaults, and only return if it's an NSNumber.
NSString *key =
[NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) {
return collectionEnabledDefaultsObject;
}
return nil;
}
/**
* Reads the data collection switch from the Info.plist for easier testing and readability. Will
* only read once from the plist and return the cached value.
*/
+ (nullable NSNumber *)readDataCollectionSwitchFromPlist {
static NSNumber *collectionEnabledPlistObject;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Read the data from the `Info.plist`, only assign it if it's there and an NSNumber.
id plistValue = [[NSBundle mainBundle]
objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey];
if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) {
collectionEnabledPlistObject = (NSNumber *)plistValue;
}
});
return collectionEnabledPlistObject;
}
#pragma mark - Sending Logs
- (void)sendLogsWithServiceName:(NSString *)serviceName
version:(NSString *)version
error:(NSError *)error {
// If the user has manually turned off data collection, return and don't send logs.
if (![self isAutomaticDataCollectionEnabled]) {
return;
}
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithDictionary:@{
kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeSDK),
kFIRAppDiagnosticsSDKNameKey : serviceName,
kFIRAppDiagnosticsSDKVersionKey : version,
kFIRAppDiagnosticsFIRAppKey : self
}];
if (error) {
userInfo[kFIRAppDiagnosticsErrorKey] = error;
}
[[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDiagnosticsNotification
object:nil
userInfo:userInfo];
}
@end

View File

@ -0,0 +1,47 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRAppAssociationRegistration.h"
#import <objc/runtime.h>
@implementation FIRAppAssociationRegistration
+ (nullable id)registeredObjectWithHost:(id)host
key:(NSString *)key
creationBlock:(id _Nullable (^)(void))creationBlock {
@synchronized(self) {
SEL dictKey = @selector(registeredObjectWithHost:key:creationBlock:);
NSMutableDictionary<NSString *, id> *objectsByKey = objc_getAssociatedObject(host, dictKey);
if (!objectsByKey) {
objectsByKey = [[NSMutableDictionary alloc] init];
objc_setAssociatedObject(host, dictKey, objectsByKey, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
id obj = objectsByKey[key];
NSValue *creationBlockBeingCalled = [NSValue valueWithPointer:dictKey];
if (obj) {
if ([creationBlockBeingCalled isEqual:obj]) {
[NSException raise:@"Reentering registeredObjectWithHost:key:creationBlock: not allowed"
format:@"host: %@ key: %@", host, key];
}
return obj;
}
objectsByKey[key] = creationBlockBeingCalled;
obj = creationBlock();
objectsByKey[key] = obj;
return obj;
}
}
@end

View File

@ -0,0 +1,57 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRBundleUtil.h"
@implementation FIRBundleUtil
+ (NSArray *)relevantBundles {
return @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ];
}
+ (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName
andFileType:(NSString *)fileType
inBundles:(NSArray *)bundles {
// Loop through all bundles to find the config dict.
for (NSBundle *bundle in bundles) {
NSString *path = [bundle pathForResource:resourceName ofType:fileType];
// Use the first one we find.
if (path) {
return path;
}
}
return nil;
}
+ (NSArray *)relevantURLSchemes {
NSMutableArray *result = [[NSMutableArray alloc] init];
for (NSBundle *bundle in [[self class] relevantBundles]) {
NSArray *urlTypes = [bundle objectForInfoDictionaryKey:@"CFBundleURLTypes"];
for (NSDictionary *urlType in urlTypes) {
[result addObjectsFromArray:urlType[@"CFBundleURLSchemes"]];
}
}
return result;
}
+ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles {
for (NSBundle *bundle in bundles) {
if ([bundle.bundleIdentifier isEqualToString:bundleIdentifier]) {
return YES;
}
}
return NO;
}
@end

View File

@ -0,0 +1,44 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "FIRConfiguration.h"
extern void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
@implementation FIRConfiguration
+ (instancetype)sharedInstance {
static FIRConfiguration *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[FIRConfiguration alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_analyticsConfiguration = [FIRAnalyticsConfiguration sharedInstance];
}
return self;
}
- (void)setLoggerLevel:(FIRLoggerLevel)loggerLevel {
NSAssert(loggerLevel <= FIRLoggerLevelMax && loggerLevel >= FIRLoggerLevelMin,
@"Invalid logger level, %ld", (long)loggerLevel);
FIRSetLoggerLevel(loggerLevel);
}
@end

View File

@ -0,0 +1,29 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRErrors.h"
NSString *const kFirebaseErrorDomain = @"com.firebase";
NSString *const kFirebaseAdMobErrorDomain = @"com.firebase.admob";
NSString *const kFirebaseAppInviteErrorDomain = @"com.firebase.appinvite";
NSString *const kFirebaseAuthErrorDomain = @"com.firebase.auth";
NSString *const kFirebaseCloudMessagingErrorDomain = @"com.firebase.cloudmessaging";
NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config";
NSString *const kFirebaseCoreErrorDomain = @"com.firebase.core";
NSString *const kFirebaseCrashReportingErrorDomain = @"com.firebase.crashreporting";
NSString *const kFirebaseDatabaseErrorDomain = @"com.firebase.database";
NSString *const kFirebaseDurableDeepLinkErrorDomain = @"com.firebase.durabledeeplink";
NSString *const kFirebaseInstanceIDErrorDomain = @"com.firebase.instanceid";
NSString *const kFirebasePerfErrorDomain = @"com.firebase.perf";
NSString *const kFirebaseStorageErrorDomain = @"com.firebase.storage";

View File

@ -0,0 +1,282 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRLogger.h"
#import "FIRLoggerLevel.h"
#import "Private/FIRVersion.h"
#import "third_party/FIRAppEnvironmentUtil.h"
#include <asl.h>
#include <assert.h>
#include <stdbool.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
FIRLoggerService kFIRLoggerABTesting = @"[Firebase/ABTesting]";
FIRLoggerService kFIRLoggerAdMob = @"[Firebase/AdMob]";
FIRLoggerService kFIRLoggerAnalytics = @"[Firebase/Analytics]";
FIRLoggerService kFIRLoggerAuth = @"[Firebase/Auth]";
FIRLoggerService kFIRLoggerCore = @"[Firebase/Core]";
FIRLoggerService kFIRLoggerCrash = @"[Firebase/Crash]";
FIRLoggerService kFIRLoggerDatabase = @"[Firebase/Database]";
FIRLoggerService kFIRLoggerDynamicLinks = @"[Firebase/DynamicLinks]";
FIRLoggerService kFIRLoggerFirestore = @"[Firebase/Firestore]";
FIRLoggerService kFIRLoggerInstanceID = @"[Firebase/InstanceID]";
FIRLoggerService kFIRLoggerInvites = @"[Firebase/Invites]";
FIRLoggerService kFIRLoggerMLKit = @"[Firebase/MLKit]";
FIRLoggerService kFIRLoggerMessaging = @"[Firebase/Messaging]";
FIRLoggerService kFIRLoggerPerf = @"[Firebase/Performance]";
FIRLoggerService kFIRLoggerRemoteConfig = @"[Firebase/RemoteConfig]";
FIRLoggerService kFIRLoggerStorage = @"[Firebase/Storage]";
FIRLoggerService kFIRLoggerSwizzler = @"[FirebaseSwizzlingUtilities]";
/// Arguments passed on launch.
NSString *const kFIRDisableDebugModeApplicationArgument = @"-FIRDebugDisabled";
NSString *const kFIREnableDebugModeApplicationArgument = @"-FIRDebugEnabled";
NSString *const kFIRLoggerForceSDTERRApplicationArgument = @"-FIRLoggerForceSTDERR";
/// Key for the debug mode bit in NSUserDefaults.
NSString *const kFIRPersistedDebugModeKey = @"/google/firebase/debug_mode";
/// ASL client facility name used by FIRLogger.
const char *kFIRLoggerASLClientFacilityName = "com.firebase.app.logger";
/// Message format used by ASL client that matches format of NSLog.
const char *kFIRLoggerCustomASLMessageFormat =
"$((Time)(J.3)) $(Sender)[$(PID)] <$((Level)(str))> $Message";
/// Keys for the number of errors and warnings logged.
NSString *const kFIRLoggerErrorCountKey = @"/google/firebase/count_of_errors_logged";
NSString *const kFIRLoggerWarningCountKey = @"/google/firebase/count_of_warnings_logged";
static dispatch_once_t sFIRLoggerOnceToken;
static aslclient sFIRLoggerClient;
static dispatch_queue_t sFIRClientQueue;
static BOOL sFIRLoggerDebugMode;
// The sFIRAnalyticsDebugMode flag is here to support the -FIRDebugEnabled/-FIRDebugDisabled
// flags used by Analytics. Users who use those flags expect Analytics to log verbosely,
// while the rest of Firebase logs at the default level. This flag is introduced to support
// that behavior.
static BOOL sFIRAnalyticsDebugMode;
static FIRLoggerLevel sFIRLoggerMaximumLevel;
#ifdef DEBUG
/// The regex pattern for the message code.
static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$";
static NSRegularExpression *sMessageCodeRegex;
#endif
void FIRLoggerInitializeASL() {
dispatch_once(&sFIRLoggerOnceToken, ^{
NSInteger majorOSVersion = [[FIRAppEnvironmentUtil systemVersion] integerValue];
uint32_t aslOptions = ASL_OPT_STDERR;
#if TARGET_OS_SIMULATOR
// The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag.
if (majorOSVersion >= 11) {
aslOptions = 0;
}
#else
// Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag.
if (majorOSVersion >= 10) {
aslOptions = 0;
}
#endif // TARGET_OS_SIMULATOR
// Override the aslOptions to ASL_OPT_STDERR if the override argument is passed in.
NSArray *arguments = [NSProcessInfo processInfo].arguments;
if ([arguments containsObject:kFIRLoggerForceSDTERRApplicationArgument]) {
aslOptions = ASL_OPT_STDERR;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated
// Initialize the ASL client handle.
sFIRLoggerClient = asl_open(NULL, kFIRLoggerASLClientFacilityName, aslOptions);
// Set the filter used by system/device log. Initialize in default mode.
asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE));
sFIRLoggerDebugMode = NO;
sFIRAnalyticsDebugMode = NO;
sFIRLoggerMaximumLevel = FIRLoggerLevelNotice;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL debugMode = [userDefaults boolForKey:kFIRPersistedDebugModeKey];
if ([arguments containsObject:kFIRDisableDebugModeApplicationArgument]) { // Default mode
[userDefaults removeObjectForKey:kFIRPersistedDebugModeKey];
} else if ([arguments containsObject:kFIREnableDebugModeApplicationArgument] ||
debugMode) { // Debug mode
[userDefaults setBool:YES forKey:kFIRPersistedDebugModeKey];
asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
sFIRLoggerDebugMode = YES;
}
// We should disable debug mode if we are running from App Store.
if (sFIRLoggerDebugMode && [FIRAppEnvironmentUtil isFromAppStore]) {
sFIRLoggerDebugMode = NO;
}
sFIRClientQueue = dispatch_queue_create("FIRLoggingClientQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(sFIRClientQueue,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
#ifdef DEBUG
sMessageCodeRegex =
[NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL];
#endif
});
}
void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode) {
FIRLoggerInitializeASL();
dispatch_async(sFIRClientQueue, ^{
// We should not enable debug mode if we are running from App Store.
if (analyticsDebugMode && [FIRAppEnvironmentUtil isFromAppStore]) {
return;
}
sFIRAnalyticsDebugMode = analyticsDebugMode;
asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
});
}
void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) {
if (loggerLevel < FIRLoggerLevelMin || loggerLevel > FIRLoggerLevelMax) {
FIRLogError(kFIRLoggerCore, @"I-COR000023", @"Invalid logger level, %ld", (long)loggerLevel);
return;
}
FIRLoggerInitializeASL();
// We should not raise the logger level if we are running from App Store.
if (loggerLevel >= FIRLoggerLevelNotice && [FIRAppEnvironmentUtil isFromAppStore]) {
return;
}
sFIRLoggerMaximumLevel = loggerLevel;
dispatch_async(sFIRClientQueue, ^{
asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel));
});
}
/**
* Check if the level is high enough to be loggable.
*
* Analytics can override the log level with an intentional race condition.
* Add the attribute to get a clean thread sanitizer run.
*/
__attribute__((no_sanitize("thread"))) BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel,
BOOL analyticsComponent) {
FIRLoggerInitializeASL();
if (sFIRLoggerDebugMode) {
return YES;
} else if (sFIRAnalyticsDebugMode && analyticsComponent) {
return YES;
}
return (BOOL)(loggerLevel <= sFIRLoggerMaximumLevel);
}
#ifdef DEBUG
void FIRResetLogger() {
sFIRLoggerOnceToken = 0;
[[NSUserDefaults standardUserDefaults] removeObjectForKey:kFIRPersistedDebugModeKey];
}
aslclient getFIRLoggerClient() {
return sFIRLoggerClient;
}
dispatch_queue_t getFIRClientQueue() {
return sFIRClientQueue;
}
BOOL getFIRLoggerDebugMode() {
return sFIRLoggerDebugMode;
}
#endif
void FIRLogBasic(FIRLoggerLevel level,
FIRLoggerService service,
NSString *messageCode,
NSString *message,
va_list args_ptr) {
FIRLoggerInitializeASL();
BOOL canLog = level <= sFIRLoggerMaximumLevel;
if (sFIRLoggerDebugMode) {
canLog = YES;
} else if (sFIRAnalyticsDebugMode && [kFIRLoggerAnalytics isEqualToString:service]) {
canLog = YES;
}
if (!canLog) {
return;
}
#ifdef DEBUG
NSCAssert(messageCode.length == 11, @"Incorrect message code length.");
NSRange messageCodeRange = NSMakeRange(0, messageCode.length);
NSUInteger numberOfMatches =
[sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange];
NSCAssert(numberOfMatches == 1, @"Incorrect message code format.");
#endif
NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr];
logMsg =
[NSString stringWithFormat:@"%s - %@[%@] %@", FIRVersionString, service, messageCode, logMsg];
dispatch_async(sFIRClientQueue, ^{
asl_log(sFIRLoggerClient, NULL, level, "%s", logMsg.UTF8String);
});
}
#pragma clang diagnostic pop
/**
* Generates the logging functions using macros.
*
* Calling FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows:
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Error> [Firebase/Core][I-COR000001] Configure blah failed.
* Calling FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configure succeed.") shows:
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [Firebase/Core][I-COR000001] Configure succeed.
*/
#define FIR_LOGGING_FUNCTION(level) \
void FIRLog##level(FIRLoggerService service, NSString *messageCode, NSString *message, ...) { \
va_list args_ptr; \
va_start(args_ptr, message); \
FIRLogBasic(FIRLoggerLevel##level, service, messageCode, message, args_ptr); \
va_end(args_ptr); \
}
FIR_LOGGING_FUNCTION(Error)
FIR_LOGGING_FUNCTION(Warning)
FIR_LOGGING_FUNCTION(Notice)
FIR_LOGGING_FUNCTION(Info)
FIR_LOGGING_FUNCTION(Debug)
#undef FIR_MAKE_LOGGER
#pragma mark - FIRLoggerWrapper
@implementation FIRLoggerWrapper
+ (void)logWithLevel:(FIRLoggerLevel)level
withService:(FIRLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args {
FIRLogBasic(level, service, messageCode, message, args);
}
@end

View File

@ -0,0 +1,97 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRMutableDictionary.h"
@implementation FIRMutableDictionary {
/// The mutable dictionary.
NSMutableDictionary *_objects;
/// Serial synchronization queue. All reads should use dispatch_sync, while writes use
/// dispatch_async.
dispatch_queue_t _queue;
}
- (instancetype)init {
self = [super init];
if (self) {
_objects = [[NSMutableDictionary alloc] init];
_queue = dispatch_queue_create("FIRMutableDictionary", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (NSString *)description {
__block NSString *description;
dispatch_sync(_queue, ^{
description = self->_objects.description;
});
return description;
}
- (id)objectForKey:(id)key {
__block id object;
dispatch_sync(_queue, ^{
object = self->_objects[key];
});
return object;
}
- (void)setObject:(id)object forKey:(id<NSCopying>)key {
dispatch_async(_queue, ^{
self->_objects[key] = object;
});
}
- (void)removeObjectForKey:(id)key {
dispatch_async(_queue, ^{
[self->_objects removeObjectForKey:key];
});
}
- (void)removeAllObjects {
dispatch_async(_queue, ^{
[self->_objects removeAllObjects];
});
}
- (NSUInteger)count {
__block NSUInteger count;
dispatch_sync(_queue, ^{
count = self->_objects.count;
});
return count;
}
- (id)objectForKeyedSubscript:(id<NSCopying>)key {
// The method this calls is already synchronized.
return [self objectForKey:key];
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
// The method this calls is already synchronized.
[self setObject:obj forKey:key];
}
- (NSDictionary *)dictionary {
__block NSDictionary *dictionary;
dispatch_sync(_queue, ^{
dictionary = [self->_objects copy];
});
return dictionary;
}
@end

View File

@ -0,0 +1,390 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRNetwork.h"
#import "Private/FIRNetworkMessageCode.h"
#import "Private/FIRLogger.h"
#import "Private/FIRMutableDictionary.h"
#import "Private/FIRNetworkConstants.h"
#import "Private/FIRReachabilityChecker.h"
#import <GoogleToolboxForMac/GTMNSData+zlib.h>
/// Constant string for request header Content-Encoding.
static NSString *const kFIRNetworkContentCompressionKey = @"Content-Encoding";
/// Constant string for request header Content-Encoding value.
static NSString *const kFIRNetworkContentCompressionValue = @"gzip";
/// Constant string for request header Content-Length.
static NSString *const kFIRNetworkContentLengthKey = @"Content-Length";
/// Constant string for request header Content-Type.
static NSString *const kFIRNetworkContentTypeKey = @"Content-Type";
/// Constant string for request header Content-Type value.
static NSString *const kFIRNetworkContentTypeValue = @"application/x-www-form-urlencoded";
/// Constant string for GET request method.
static NSString *const kFIRNetworkGETRequestMethod = @"GET";
/// Constant string for POST request method.
static NSString *const kFIRNetworkPOSTRequestMethod = @"POST";
/// Default constant string as a prefix for network logger.
static NSString *const kFIRNetworkLogTag = @"Firebase/Network";
@interface FIRNetwork () <FIRReachabilityDelegate, FIRNetworkLoggerDelegate>
@end
@implementation FIRNetwork {
/// Network reachability.
FIRReachabilityChecker *_reachability;
/// The dictionary of requests by session IDs { NSString : id }.
FIRMutableDictionary *_requests;
}
- (instancetype)init {
return [self initWithReachabilityHost:kFIRNetworkReachabilityHost];
}
- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost {
self = [super init];
if (self) {
// Setup reachability.
_reachability = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self
loggerDelegate:self
withHost:reachabilityHost];
if (![_reachability start]) {
return nil;
}
_requests = [[FIRMutableDictionary alloc] init];
_timeoutInterval = kFIRNetworkTimeOutInterval;
}
return self;
}
- (void)dealloc {
_reachability.reachabilityDelegate = nil;
[_reachability stop];
}
#pragma mark - External Methods
+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler {
[FIRNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID
completionHandler:completionHandler];
}
- (NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(FIRNetworkCompletionHandler)handler {
if (!url.absoluteString.length) {
[self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
return nil;
}
NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
NSMutableURLRequest *request =
[[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:timeOutInterval];
if (!request) {
[self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
queue:queue
withHandler:handler];
return nil;
}
NSError *compressError = nil;
NSData *compressedData = [NSData gtm_dataByGzippingData:payload error:&compressError];
if (!compressedData || compressError) {
if (compressError || payload.length > 0) {
// If the payload is not empty but it fails to compress the payload, something has been wrong.
[self handleErrorWithCode:FIRErrorCodeNetworkPayloadCompression
queue:queue
withHandler:handler];
return nil;
}
compressedData = [[NSData alloc] init];
}
NSString *postLength = @(compressedData.length).stringValue;
// Set up the request with the compressed data.
[request setValue:postLength forHTTPHeaderField:kFIRNetworkContentLengthKey];
request.HTTPBody = compressedData;
request.HTTPMethod = kFIRNetworkPOSTRequestMethod;
[request setValue:kFIRNetworkContentTypeValue forHTTPHeaderField:kFIRNetworkContentTypeKey];
[request setValue:kFIRNetworkContentCompressionValue
forHTTPHeaderField:kFIRNetworkContentCompressionKey];
FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
fetcher.backgroundNetworkEnabled = usingBackgroundSession;
__weak FIRNetwork *weakSelf = self;
NSString *requestID = [fetcher
sessionIDFromAsyncPOSTRequest:request
completionHandler:^(NSHTTPURLResponse *response, NSData *data,
NSString *sessionID, NSError *error) {
FIRNetwork *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
dispatch_async(queueToDispatch, ^{
if (sessionID.length) {
[strongSelf->_requests removeObjectForKey:sessionID];
}
if (handler) {
handler(response, data, error);
}
});
}];
if (!requestID) {
[self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
queue:queue
withHandler:handler];
return nil;
}
[self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeNetwork000
message:@"Uploading data. Host"
context:url];
_requests[requestID] = fetcher;
return requestID;
}
- (NSString *)getURL:(NSURL *)url
headers:(NSDictionary *)headers
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(FIRNetworkCompletionHandler)handler {
if (!url.absoluteString.length) {
[self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler];
return nil;
}
NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval;
NSMutableURLRequest *request =
[[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:timeOutInterval];
if (!request) {
[self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
queue:queue
withHandler:handler];
return nil;
}
request.HTTPMethod = kFIRNetworkGETRequestMethod;
request.allHTTPHeaderFields = headers;
FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self];
fetcher.backgroundNetworkEnabled = usingBackgroundSession;
__weak FIRNetwork *weakSelf = self;
NSString *requestID = [fetcher
sessionIDFromAsyncGETRequest:request
completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID,
NSError *error) {
FIRNetwork *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
dispatch_async(queueToDispatch, ^{
if (sessionID.length) {
[strongSelf->_requests removeObjectForKey:sessionID];
}
if (handler) {
handler(response, data, error);
}
});
}];
if (!requestID) {
[self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation
queue:queue
withHandler:handler];
return nil;
}
[self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeNetwork001
message:@"Downloading data. Host"
context:url];
_requests[requestID] = fetcher;
return requestID;
}
- (BOOL)hasUploadInProgress {
return _requests.count > 0;
}
#pragma mark - Network Reachability
/// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network
/// reachability has changed.
- (void)reachability:(FIRReachabilityChecker *)reachability
statusChanged:(FIRReachabilityStatus)status {
_networkConnected = (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi);
[_reachabilityDelegate reachabilityDidChange];
}
#pragma mark - Network logger delegate
- (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
// Explicitly check whether the delegate responds to the methods because conformsToProtocol does
// not work correctly even though the delegate does respond to the methods.
if (!loggerDelegate ||
![loggerDelegate
respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:contexts:)] ||
![loggerDelegate
respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:context:)] ||
!
[loggerDelegate respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:)]) {
FIRLogError(kFIRLoggerAnalytics,
[NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork002],
@"Cannot set the network logger delegate: delegate does not conform to the network "
"logger protocol.");
return;
}
_loggerDelegate = loggerDelegate;
}
#pragma mark - Private methods
/// Handles network error and calls completion handler with the error.
- (void)handleErrorWithCode:(NSInteger)code
queue:(dispatch_queue_t)queue
withHandler:(FIRNetworkCompletionHandler)handler {
NSDictionary *userInfo = @{kFIRNetworkErrorContext : @"Failed to create network request"};
NSError *error =
[[NSError alloc] initWithDomain:kFIRNetworkErrorDomain code:code userInfo:userInfo];
[self firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
messageCode:kFIRNetworkMessageCodeNetwork002
message:@"Failed to create network request. Code, error"
contexts:@[ @(code), error ]];
if (handler) {
dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue();
dispatch_async(queueToDispatch, ^{
handler(nil, nil, error);
});
}
}
#pragma mark - Network logger
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
messageCode:(FIRNetworkMessageCode)messageCode
message:(NSString *)message
contexts:(NSArray *)contexts {
// Let the delegate log the message if there is a valid logger delegate. Otherwise, just log
// errors/warnings/info messages to the console log.
if (_loggerDelegate) {
[_loggerDelegate firNetwork_logWithLevel:logLevel
messageCode:messageCode
message:message
contexts:contexts];
return;
}
if (_isDebugModeEnabled || logLevel == kFIRNetworkLogLevelError ||
logLevel == kFIRNetworkLogLevelWarning || logLevel == kFIRNetworkLogLevelInfo) {
NSString *formattedMessage = FIRStringWithLogMessage(message, logLevel, contexts);
NSLog(@"%@", formattedMessage);
FIRLogBasic((FIRLoggerLevel)logLevel, kFIRLoggerCore,
[NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage,
NULL);
}
}
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
messageCode:(FIRNetworkMessageCode)messageCode
message:(NSString *)message
context:(id)context {
if (_loggerDelegate) {
[_loggerDelegate firNetwork_logWithLevel:logLevel
messageCode:messageCode
message:message
context:context];
return;
}
NSArray *contexts = context ? @[ context ] : @[];
[self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts];
}
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
messageCode:(FIRNetworkMessageCode)messageCode
message:(NSString *)message {
if (_loggerDelegate) {
[_loggerDelegate firNetwork_logWithLevel:logLevel messageCode:messageCode message:message];
return;
}
[self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]];
}
/// Returns a string for the given log level (e.g. kFIRNetworkLogLevelError -> @"ERROR").
static NSString *FIRLogLevelDescriptionFromLogLevel(FIRNetworkLogLevel logLevel) {
static NSDictionary *levelNames = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
levelNames = @{
@(kFIRNetworkLogLevelError) : @"ERROR",
@(kFIRNetworkLogLevelWarning) : @"WARNING",
@(kFIRNetworkLogLevelInfo) : @"INFO",
@(kFIRNetworkLogLevelDebug) : @"DEBUG"
};
});
return levelNames[@(logLevel)];
}
/// Returns a formatted string to be used for console logging.
static NSString *FIRStringWithLogMessage(NSString *message,
FIRNetworkLogLevel logLevel,
NSArray *contexts) {
if (!message) {
message = @"(Message was nil)";
} else if (!message.length) {
message = @"(Message was empty)";
}
NSMutableString *result = [[NSMutableString alloc]
initWithFormat:@"<%@/%@> %@", kFIRNetworkLogTag, FIRLogLevelDescriptionFromLogLevel(logLevel),
message];
if (!contexts.count) {
return result;
}
NSMutableArray *formattedContexts = [[NSMutableArray alloc] init];
for (id item in contexts) {
[formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")];
}
[result appendString:@": "];
[result appendString:[formattedContexts componentsJoinedByString:@", "]];
return result;
}
@end

View File

@ -0,0 +1,39 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRNetworkConstants.h"
#import <Foundation/Foundation.h>
NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix =
@"com.firebase.network.background-upload";
NSString *const kFIRNetworkApplicationSupportSubdirectory = @"Firebase/Network";
NSString *const kFIRNetworkTempDirectoryName = @"FIRNetworkTemporaryDirectory";
const NSTimeInterval kFIRNetworkTempFolderExpireTime = 60 * 60; // 1 hour
const NSTimeInterval kFIRNetworkTimeOutInterval = 60; // 1 minute.
NSString *const kFIRNetworkReachabilityHost = @"app-measurement.com";
NSString *const kFIRNetworkErrorContext = @"Context";
const int kFIRNetworkHTTPStatusOK = 200;
const int kFIRNetworkHTTPStatusNoContent = 204;
const int kFIRNetworkHTTPStatusCodeMultipleChoices = 300;
const int kFIRNetworkHTTPStatusCodeMovedPermanently = 301;
const int kFIRNetworkHTTPStatusCodeFound = 302;
const int kFIRNetworkHTTPStatusCodeNotModified = 304;
const int kFIRNetworkHTTPStatusCodeMovedTemporarily = 307;
const int kFIRNetworkHTTPStatusCodeNotFound = 404;
const int kFIRNetworkHTTPStatusCodeCannotAcceptTraffic = 429;
const int kFIRNetworkHTTPStatusCodeUnavailable = 503;
NSString *const kFIRNetworkErrorDomain = @"com.firebase.network.ErrorDomain";

View File

@ -0,0 +1,669 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Private/FIRNetworkURLSession.h"
#import "Private/FIRLogger.h"
#import "Private/FIRMutableDictionary.h"
#import "Private/FIRNetworkConstants.h"
#import "Private/FIRNetworkMessageCode.h"
@implementation FIRNetworkURLSession {
/// The handler to be called when the request completes or error has occurs.
FIRNetworkURLSessionCompletionHandler _completionHandler;
/// Session ID generated randomly with a fixed prefix.
NSString *_sessionID;
/// The session configuration.
NSURLSessionConfiguration *_sessionConfig;
/// The path to the directory where all temporary files are stored before uploading.
NSURL *_networkDirectoryURL;
/// The downloaded data from fetching.
NSData *_downloadedData;
/// The path to the temporary file which stores the uploading data.
NSURL *_uploadingFileURL;
/// The current request.
NSURLRequest *_request;
}
#pragma mark - Init
- (instancetype)initWithNetworkLoggerDelegate:(id<FIRNetworkLoggerDelegate>)networkLoggerDelegate {
self = [super init];
if (self) {
// Create URL to the directory where all temporary files to upload have to be stored.
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *applicationSupportDirectory = paths.firstObject;
NSArray *tempPathComponents = @[
applicationSupportDirectory, kFIRNetworkApplicationSupportSubdirectory,
kFIRNetworkTempDirectoryName
];
_networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents];
_sessionID = [NSString stringWithFormat:@"%@-%@", kFIRNetworkBackgroundSessionConfigIDPrefix,
[[NSUUID UUID] UUIDString]];
_loggerDelegate = networkLoggerDelegate;
}
return self;
}
#pragma mark - External Methods
#pragma mark - To be called from AppDelegate
+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
completionHandler:
(FIRNetworkSystemCompletionHandler)systemCompletionHandler {
// The session may not be FIRAnalytics background. Ignore those that do not have the prefix.
if (![sessionID hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) {
return;
}
FIRNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID];
if (fetcher != nil) {
[fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID];
} else {
FIRLogError(kFIRLoggerCore,
[NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork003],
@"Failed to retrieve background session with ID %@ after app is relaunched.",
sessionID);
}
}
#pragma mark - External Methods
/// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the
/// connection.
- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
completionHandler:(FIRNetworkURLSessionCompletionHandler)handler {
// NSURLSessionUploadTask does not work with NSData in the background.
// To avoid this issue, write the data to a temporary file to upload it.
// Make a temporary file with the data subset.
_uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID];
NSError *writeError;
NSURLSessionUploadTask *postRequestTask;
NSURLSession *session;
BOOL didWriteFile = NO;
// Clean up the entire temp folder to avoid temp files that remain in case the previous session
// crashed and did not clean up.
[self maybeRemoveTempFilesAtURL:_networkDirectoryURL
expiringTime:kFIRNetworkTempFolderExpireTime];
// If there is no background network enabled, no need to write to file. This will allow default
// network session which runs on the foreground.
if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) {
didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path
options:NSDataWritingAtomic
error:&writeError];
if (writeError) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession000
message:@"Failed to write request data to file"
context:writeError];
}
}
if (didWriteFile) {
// Exclude this file from backing up to iTunes. There are conflicting reports that excluding
// directory from backing up does not excluding files of that directory from backing up.
[self excludeFromBackupForURL:_uploadingFileURL];
_sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
[self populateSessionConfig:_sessionConfig withRequest:request];
session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL];
} else {
// If we cannot write to file, just send it in the foreground.
_sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[self populateSessionConfig:_sessionConfig withRequest:request];
_sessionConfig.URLCache = nil;
session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody];
}
if (!session || !postRequestTask) {
NSError *error = [[NSError alloc]
initWithDomain:kFIRNetworkErrorDomain
code:FIRErrorCodeNetworkRequestCreation
userInfo:@{kFIRNetworkErrorContext : @"Cannot create network session"}];
[self callCompletionHandler:handler withResponse:nil data:nil error:error];
return nil;
}
// Save the session into memory.
NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
[sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
_request = [request copy];
// Store completion handler because background session does not accept handler block but custom
// delegate.
_completionHandler = [handler copy];
[postRequestTask resume];
return _sessionID;
}
/// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session.
- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
completionHandler:(FIRNetworkURLSessionCompletionHandler)handler {
if (_backgroundNetworkEnabled) {
_sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
} else {
_sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
}
[self populateSessionConfig:_sessionConfig withRequest:request];
// Do not cache the GET request.
_sessionConfig.URLCache = nil;
NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
if (!session || !downloadTask) {
NSError *error = [[NSError alloc]
initWithDomain:kFIRNetworkErrorDomain
code:FIRErrorCodeNetworkRequestCreation
userInfo:@{kFIRNetworkErrorContext : @"Cannot create network session"}];
[self callCompletionHandler:handler withResponse:nil data:nil error:error];
return nil;
}
// Save the session into memory.
NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
[sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
_request = [request copy];
_completionHandler = [handler copy];
[downloadTask resume];
return _sessionID;
}
#pragma mark - NSURLSessionTaskDelegate
/// Called by the NSURLSession once the download task is completed. The file is saved in the
/// provided URL so we need to read the data and store into _downloadedData. Once the session is
/// completed, URLSession:task:didCompleteWithError will be called and the completion handler will
/// be called with the downloaded data.
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)task
didFinishDownloadingToURL:(NSURL *)url {
if (!url.path) {
[_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession001
message:@"Unable to read downloaded data from empty temp path"];
_downloadedData = nil;
return;
}
NSError *error;
_downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error];
if (error) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession002
message:@"Cannot read the content of downloaded data"
context:error];
_downloadedData = nil;
}
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession003
message:@"Background session finished"
context:session.configuration.identifier];
[self callSystemCompletionHandler:session.configuration.identifier];
}
#endif
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
// Avoid any chance of recursive behavior leading to it being used repeatedly.
FIRNetworkURLSessionCompletionHandler handler = _completionHandler;
_completionHandler = nil;
if (task.response) {
// The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7.
NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP");
// The server responded so ignore the error created by the system.
error = nil;
} else if (!error) {
error = [[NSError alloc]
initWithDomain:kFIRNetworkErrorDomain
code:FIRErrorCodeNetworkInvalidResponse
userInfo:@{kFIRNetworkErrorContext : @"Network Error: Empty network response"}];
}
[self callCompletionHandler:handler
withResponse:(NSHTTPURLResponse *)task.response
data:_downloadedData
error:error];
// Remove the temp file to avoid trashing devices with lots of temp files.
[self removeTempItemAtURL:_uploadingFileURL];
// Try to clean up stale files again.
[self maybeRemoveTempFilesAtURL:_networkDirectoryURL
expiringTime:kFIRNetworkTempFolderExpireTime];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
// The handling is modeled after GTMSessionFetcher.
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
if (serverTrust == NULL) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession004
message:@"Received empty server trust for host. Host"
context:_request.URL];
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
return;
}
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
if (!credential) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
messageCode:kFIRNetworkMessageCodeURLSession005
message:@"Unable to verify server identity. Host"
context:_request.URL];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession006
message:@"Received SSL challenge for host. Host"
context:_request.URL];
void (^callback)(BOOL) = ^(BOOL allow) {
if (allow) {
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
[self->_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession007
message:@"Cancelling authentication challenge for host. Host"
context:self->_request.URL];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
};
// Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7.
CFRetain(serverTrust);
// Evaluate the certificate chain.
//
// The delegate queue may be the main thread. Trust evaluation could cause some
// blocking network activity, so we must evaluate async, as documented at
// https://developer.apple.com/library/ios/technotes/tn2232/
dispatch_queue_t evaluateBackgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(evaluateBackgroundQueue, ^{
SecTrustResultType trustEval = kSecTrustResultInvalid;
BOOL shouldAllow;
OSStatus trustError;
@synchronized([FIRNetworkURLSession class]) {
trustError = SecTrustEvaluate(serverTrust, &trustEval);
}
if (trustError != errSecSuccess) {
[self->_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession008
message:@"Cannot evaluate server trust. Error, host"
contexts:@[ @(trustError), self->_request.URL ]];
shouldAllow = NO;
} else {
// Having a trust level "unspecified" by the user is the usual result, described at
// https://developer.apple.com/library/mac/qa/qa1360
shouldAllow =
(trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed);
}
// Call the call back with the permission.
callback(shouldAllow);
CFRelease(serverTrust);
});
return;
}
// Default handling for other Auth Challenges.
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
#pragma mark - Internal Methods
/// Stores system completion handler with session ID as key.
- (void)addSystemCompletionHandler:(FIRNetworkSystemCompletionHandler)handler
forSession:(NSString *)identifier {
if (!handler) {
[_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession009
message:@"Cannot store nil system completion handler in network"];
return;
}
if (!identifier.length) {
[_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession010
message:
@"Cannot store system completion handler with empty network "
"session identifier"];
return;
}
FIRMutableDictionary *systemCompletionHandlers =
[[self class] sessionIDToSystemCompletionHandlerDictionary];
if (systemCompletionHandlers[identifier]) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
messageCode:kFIRNetworkMessageCodeURLSession011
message:@"Got multiple system handlers for a single session ID"
context:identifier];
}
systemCompletionHandlers[identifier] = handler;
}
/// Calls the system provided completion handler with the session ID stored in the dictionary.
/// The handler will be removed from the dictionary after being called.
- (void)callSystemCompletionHandler:(NSString *)identifier {
FIRMutableDictionary *systemCompletionHandlers =
[[self class] sessionIDToSystemCompletionHandlerDictionary];
FIRNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier];
if (handler) {
[systemCompletionHandlers removeObjectForKey:identifier];
dispatch_async(dispatch_get_main_queue(), ^{
handler();
});
}
}
/// Sets or updates the session ID of this session.
- (void)setSessionID:(NSString *)sessionID {
_sessionID = [sessionID copy];
}
/// Creates a background session configuration with the session ID using the supported method.
- (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID {
#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \
TARGET_OS_TV || \
(TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
// iOS 8/10.10 builds require the new backgroundSessionConfiguration method name.
return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
#elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \
(TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)
// Do a runtime check to avoid a deprecation warning about using
// +backgroundSessionConfiguration: on iOS 8.
if ([NSURLSessionConfiguration
respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) {
// Running on iOS 8+/OS X 10.10+.
return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
} else {
// Running on iOS 7/OS X 10.9.
return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
}
#else
// Building with an SDK earlier than iOS 8/OS X 10.10.
return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
#endif
}
- (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime {
if (!folderURL.absoluteString.length) {
return;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *properties = @[ NSURLCreationDateKey ];
NSArray *directoryContent =
[fileManager contentsOfDirectoryAtURL:folderURL
includingPropertiesForKeys:properties
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
error:&error];
if (error && error.code != NSFileReadNoSuchFileError) {
[_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeURLSession012
message:@"Cannot get files from the temporary network folder. Error"
context:error];
return;
}
if (!directoryContent.count) {
return;
}
NSTimeInterval now = [NSDate date].timeIntervalSince1970;
for (NSURL *tempFile in directoryContent) {
NSDate *creationDate;
BOOL getCreationDate =
[tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL];
if (!getCreationDate) {
continue;
}
NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970;
if (fabs(now - creationTimeInterval) > staleTime) {
[self removeTempItemAtURL:tempFile];
}
}
}
/// Removes the temporary file written to disk for sending the request. It has to be cleaned up
/// after the session is done.
- (void)removeTempItemAtURL:(NSURL *)fileURL {
if (!fileURL.absoluteString.length) {
return;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) {
[_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession013
message:@"Failed to remove temporary uploading data file. Error"
context:error.localizedDescription];
}
}
/// Gets the fetcher with the session ID.
+ (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier {
NSMapTable *sessionIdentifierToFetcherMap = [self sessionIDToFetcherMap];
FIRNetworkURLSession *session = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
if (!session && [sessionIdentifier hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) {
session = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil];
[session setSessionID:sessionIdentifier];
[sessionIdentifierToFetcherMap setObject:session forKey:sessionIdentifier];
}
return session;
}
/// Returns a map of the fetcher by session ID. Creates a map if it is not created.
+ (NSMapTable *)sessionIDToFetcherMap {
static NSMapTable *sessionIDToFetcherMap;
static dispatch_once_t sessionMapOnceToken;
dispatch_once(&sessionMapOnceToken, ^{
sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable];
});
return sessionIDToFetcherMap;
}
/// Returns a map of system provided completion handler by session ID. Creates a map if it is not
/// created.
+ (FIRMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary {
static FIRMutableDictionary *systemCompletionHandlers;
static dispatch_once_t systemCompletionHandlerOnceToken;
dispatch_once(&systemCompletionHandlerOnceToken, ^{
systemCompletionHandlers = [[FIRMutableDictionary alloc] init];
});
return systemCompletionHandlers;
}
- (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID {
NSString *tempName = [NSString stringWithFormat:@"FIRUpload_temp_%@", sessionID];
return [_networkDirectoryURL URLByAppendingPathComponent:tempName];
}
/// Makes sure that the directory to store temp files exists. If not, tries to create it and returns
/// YES. If there is anything wrong, returns NO.
- (BOOL)ensureTemporaryDirectoryExists {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
// Create a temporary directory if it does not exist or was deleted.
if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
return YES;
}
if (error && error.code != NSFileReadNoSuchFileError) {
[_loggerDelegate
firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
messageCode:kFIRNetworkMessageCodeURLSession014
message:@"Error while trying to access Network temp folder. Error"
context:error];
}
NSError *writeError = nil;
[fileManager createDirectoryAtURL:_networkDirectoryURL
withIntermediateDirectories:YES
attributes:nil
error:&writeError];
if (writeError) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession015
message:@"Cannot create temporary directory. Error"
context:writeError];
return NO;
}
// Set the iCloud exclusion attribute on the Documents URL.
[self excludeFromBackupForURL:_networkDirectoryURL];
return YES;
}
- (void)excludeFromBackupForURL:(NSURL *)url {
if (!url.path) {
return;
}
// Set the iCloud exclusion attribute on the Documents URL.
NSError *preventBackupError = nil;
[url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError];
if (preventBackupError) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession016
message:@"Cannot exclude temporary folder from iTunes backup"];
}
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler {
NSArray *nonAllowedRedirectionCodes = @[
@(kFIRNetworkHTTPStatusCodeFound), @(kFIRNetworkHTTPStatusCodeMovedPermanently),
@(kFIRNetworkHTTPStatusCodeMovedTemporarily), @(kFIRNetworkHTTPStatusCodeMultipleChoices)
];
// Allow those not in the non allowed list to be followed.
if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) {
completionHandler(request);
return;
}
// Do not allow redirection if the response code is in the non-allowed list.
NSURLRequest *newRequest = request;
if (response) {
newRequest = nil;
}
completionHandler(newRequest);
}
#pragma mark - Helper Methods
- (void)callCompletionHandler:(FIRNetworkURLSessionCompletionHandler)handler
withResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError *)error {
if (error) {
[_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeURLSession017
message:@"Encounter network error. Code, error"
contexts:@[ @(error.code), error ]];
}
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(response, data, self->_sessionID, error);
});
}
}
- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig
withRequest:(NSURLRequest *)request {
sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
sessionConfig.timeoutIntervalForResource = request.timeoutInterval;
sessionConfig.requestCachePolicy = request.cachePolicy;
}
@end

View File

@ -0,0 +1,445 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Private/FIRAppInternal.h"
#import "Private/FIRBundleUtil.h"
#import "Private/FIRErrors.h"
#import "Private/FIRLogger.h"
#import "Private/FIROptionsInternal.h"
// Keys for the strings in the plist file.
NSString *const kFIRAPIKey = @"API_KEY";
NSString *const kFIRTrackingID = @"TRACKING_ID";
NSString *const kFIRGoogleAppID = @"GOOGLE_APP_ID";
NSString *const kFIRClientID = @"CLIENT_ID";
NSString *const kFIRGCMSenderID = @"GCM_SENDER_ID";
NSString *const kFIRAndroidClientID = @"ANDROID_CLIENT_ID";
NSString *const kFIRDatabaseURL = @"DATABASE_URL";
NSString *const kFIRStorageBucket = @"STORAGE_BUCKET";
// The key to locate the expected bundle identifier in the plist file.
NSString *const kFIRBundleID = @"BUNDLE_ID";
// The key to locate the project identifier in the plist file.
NSString *const kFIRProjectID = @"PROJECT_ID";
NSString *const kFIRIsMeasurementEnabled = @"IS_MEASUREMENT_ENABLED";
NSString *const kFIRIsAnalyticsCollectionEnabled = @"FIREBASE_ANALYTICS_COLLECTION_ENABLED";
NSString *const kFIRIsAnalyticsCollectionDeactivated = @"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED";
NSString *const kFIRIsAnalyticsEnabled = @"IS_ANALYTICS_ENABLED";
NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED";
// Library version ID.
NSString *const kFIRLibraryVersionID =
@"5" // Major version (one or more digits)
@"00" // Minor version (exactly 2 digits)
@"04" // Build number (exactly 2 digits)
@"000"; // Fixed "000"
// Plist file name.
NSString *const kServiceInfoFileName = @"GoogleService-Info";
// Plist file type.
NSString *const kServiceInfoFileType = @"plist";
// Exception raised from attempting to modify a FIROptions after it's been copied to a FIRApp.
NSString *const kFIRExceptionBadModification =
@"Attempted to modify options after it's set on FIRApp. Please modify all properties before "
@"initializing FIRApp.";
@interface FIROptions ()
/**
* This property maintains the actual configuration key-value pairs.
*/
@property(nonatomic, readwrite) NSMutableDictionary *optionsDictionary;
/**
* Calls `analyticsOptionsDictionaryWithInfoDictionary:` using [NSBundle mainBundle].infoDictionary.
* It combines analytics options from both the infoDictionary and the GoogleService-Info.plist.
* Values which are present in the main plist override values from the GoogleService-Info.plist.
*/
@property(nonatomic, readonly) NSDictionary *analyticsOptionsDictionary;
/**
* Combination of analytics options from both the infoDictionary and the GoogleService-Info.plist.
* Values which are present in the infoDictionary override values from the GoogleService-Info.plist.
*/
- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary;
/**
* Throw exception if editing is locked when attempting to modify an option.
*/
- (void)checkEditingLocked;
@end
@implementation FIROptions {
/// Backing variable for self.analyticsOptionsDictionary.
NSDictionary *_analyticsOptionsDictionary;
dispatch_once_t _createAnalyticsOptionsDictionaryOnce;
}
static FIROptions *sDefaultOptions = nil;
static NSDictionary *sDefaultOptionsDictionary = nil;
#pragma mark - Public only for internal class methods
+ (FIROptions *)defaultOptions {
if (sDefaultOptions != nil) {
return sDefaultOptions;
}
NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary];
if (defaultOptionsDictionary == nil) {
return nil;
}
sDefaultOptions = [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary];
return sDefaultOptions;
}
#pragma mark - Private class methods
+ (void)initialize {
// Report FirebaseCore version for useragent string
NSRange major = NSMakeRange(0, 1);
NSRange minor = NSMakeRange(1, 2);
NSRange patch = NSMakeRange(3, 2);
[FIRApp
registerLibrary:@"fire-ios"
withVersion:[NSString stringWithFormat:@"%@.%d.%d",
[kFIRLibraryVersionID substringWithRange:major],
[[kFIRLibraryVersionID substringWithRange:minor]
intValue],
[[kFIRLibraryVersionID substringWithRange:patch]
intValue]]];
NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
NSString *xcodeVersion = info[@"DTXcodeBuild"];
NSString *sdkVersion = info[@"DTSDKBuild"];
if (xcodeVersion) {
[FIRApp registerLibrary:@"xcode" withVersion:xcodeVersion];
}
if (sdkVersion) {
[FIRApp registerLibrary:@"apple-sdk" withVersion:sdkVersion];
}
}
+ (NSDictionary *)defaultOptionsDictionary {
if (sDefaultOptionsDictionary != nil) {
return sDefaultOptionsDictionary;
}
NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName];
if (plistFilePath == nil) {
return nil;
}
sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
if (sDefaultOptionsDictionary == nil) {
FIRLogError(kFIRLoggerCore, @"I-COR000011",
@"The configuration file is not a dictionary: "
@"'%@.%@'.",
kServiceInfoFileName, kServiceInfoFileType);
}
return sDefaultOptionsDictionary;
}
// Returns the path of the plist file with a given file name.
+ (NSString *)plistFilePathWithName:(NSString *)fileName {
NSArray *bundles = [FIRBundleUtil relevantBundles];
NSString *plistFilePath =
[FIRBundleUtil optionsDictionaryPathWithResourceName:fileName
andFileType:kServiceInfoFileType
inBundles:bundles];
if (plistFilePath == nil) {
FIRLogError(kFIRLoggerCore, @"I-COR000012", @"Could not locate configuration file: '%@.%@'.",
fileName, kServiceInfoFileType);
}
return plistFilePath;
}
+ (void)resetDefaultOptions {
sDefaultOptions = nil;
sDefaultOptionsDictionary = nil;
}
#pragma mark - Private instance methods
- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)optionsDictionary {
self = [super init];
if (self) {
_optionsDictionary = [optionsDictionary mutableCopy];
_usingOptionsFromDefaultPlist = YES;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
FIROptions *newOptions = [[[self class] allocWithZone:zone] init];
if (newOptions) {
newOptions.optionsDictionary = self.optionsDictionary;
newOptions.deepLinkURLScheme = self.deepLinkURLScheme;
newOptions.editingLocked = self.isEditingLocked;
newOptions.usingOptionsFromDefaultPlist = self.usingOptionsFromDefaultPlist;
}
return newOptions;
}
#pragma mark - Public instance methods
- (instancetype)initWithContentsOfFile:(NSString *)plistPath {
self = [super init];
if (self) {
if (plistPath == nil) {
FIRLogError(kFIRLoggerCore, @"I-COR000013", @"The plist file path is nil.");
return nil;
}
_optionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:plistPath] mutableCopy];
if (_optionsDictionary == nil) {
FIRLogError(kFIRLoggerCore, @"I-COR000014",
@"The configuration file at %@ does not exist or "
@"is not a well-formed plist file.",
plistPath);
return nil;
}
// TODO: Do we want to validate the dictionary here? It says we do that already in
// the public header.
}
return self;
}
- (instancetype)initWithGoogleAppID:(NSString *)googleAppID GCMSenderID:(NSString *)GCMSenderID {
self = [super init];
if (self) {
NSMutableDictionary *mutableOptionsDict = [NSMutableDictionary dictionary];
[mutableOptionsDict setValue:googleAppID forKey:kFIRGoogleAppID];
[mutableOptionsDict setValue:GCMSenderID forKey:kFIRGCMSenderID];
[mutableOptionsDict setValue:[[NSBundle mainBundle] bundleIdentifier] forKey:kFIRBundleID];
self.optionsDictionary = mutableOptionsDict;
}
return self;
}
- (NSString *)APIKey {
return self.optionsDictionary[kFIRAPIKey];
}
- (void)checkEditingLocked {
if (self.isEditingLocked) {
[NSException raise:kFirebaseCoreErrorDomain format:kFIRExceptionBadModification];
}
}
- (void)setAPIKey:(NSString *)APIKey {
[self checkEditingLocked];
_optionsDictionary[kFIRAPIKey] = [APIKey copy];
}
- (NSString *)clientID {
return self.optionsDictionary[kFIRClientID];
}
- (void)setClientID:(NSString *)clientID {
[self checkEditingLocked];
_optionsDictionary[kFIRClientID] = [clientID copy];
}
- (NSString *)trackingID {
return self.optionsDictionary[kFIRTrackingID];
}
- (void)setTrackingID:(NSString *)trackingID {
[self checkEditingLocked];
_optionsDictionary[kFIRTrackingID] = [trackingID copy];
}
- (NSString *)GCMSenderID {
return self.optionsDictionary[kFIRGCMSenderID];
}
- (void)setGCMSenderID:(NSString *)GCMSenderID {
[self checkEditingLocked];
_optionsDictionary[kFIRGCMSenderID] = [GCMSenderID copy];
}
- (NSString *)projectID {
return self.optionsDictionary[kFIRProjectID];
}
- (void)setProjectID:(NSString *)projectID {
[self checkEditingLocked];
_optionsDictionary[kFIRProjectID] = [projectID copy];
}
- (NSString *)androidClientID {
return self.optionsDictionary[kFIRAndroidClientID];
}
- (void)setAndroidClientID:(NSString *)androidClientID {
[self checkEditingLocked];
_optionsDictionary[kFIRAndroidClientID] = [androidClientID copy];
}
- (NSString *)googleAppID {
return self.optionsDictionary[kFIRGoogleAppID];
}
- (void)setGoogleAppID:(NSString *)googleAppID {
[self checkEditingLocked];
_optionsDictionary[kFIRGoogleAppID] = [googleAppID copy];
}
- (NSString *)libraryVersionID {
return kFIRLibraryVersionID;
}
- (void)setLibraryVersionID:(NSString *)libraryVersionID {
_optionsDictionary[kFIRLibraryVersionID] = [libraryVersionID copy];
}
- (NSString *)databaseURL {
return self.optionsDictionary[kFIRDatabaseURL];
}
- (void)setDatabaseURL:(NSString *)databaseURL {
[self checkEditingLocked];
_optionsDictionary[kFIRDatabaseURL] = [databaseURL copy];
}
- (NSString *)storageBucket {
return self.optionsDictionary[kFIRStorageBucket];
}
- (void)setStorageBucket:(NSString *)storageBucket {
[self checkEditingLocked];
_optionsDictionary[kFIRStorageBucket] = [storageBucket copy];
}
- (void)setDeepLinkURLScheme:(NSString *)deepLinkURLScheme {
[self checkEditingLocked];
_deepLinkURLScheme = [deepLinkURLScheme copy];
}
- (NSString *)bundleID {
return self.optionsDictionary[kFIRBundleID];
}
- (void)setBundleID:(NSString *)bundleID {
[self checkEditingLocked];
_optionsDictionary[kFIRBundleID] = [bundleID copy];
}
#pragma mark - Internal instance methods
- (NSDictionary *)analyticsOptionsDictionaryWithInfoDictionary:(NSDictionary *)infoDictionary {
dispatch_once(&_createAnalyticsOptionsDictionaryOnce, ^{
NSMutableDictionary *tempAnalyticsOptions = [[NSMutableDictionary alloc] init];
NSArray *measurementKeys = @[
kFIRIsMeasurementEnabled, kFIRIsAnalyticsCollectionEnabled,
kFIRIsAnalyticsCollectionDeactivated
];
for (NSString *key in measurementKeys) {
id value = infoDictionary[key] ?: self.optionsDictionary[key] ?: nil;
if (!value) {
continue;
}
tempAnalyticsOptions[key] = value;
}
self->_analyticsOptionsDictionary = tempAnalyticsOptions;
});
return _analyticsOptionsDictionary;
}
- (NSDictionary *)analyticsOptionsDictionary {
return [self analyticsOptionsDictionaryWithInfoDictionary:[NSBundle mainBundle].infoDictionary];
}
/**
* Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
* GoogleService-Info.plist. This uses the old plist flag IS_MEASUREMENT_ENABLED, which should still
* be supported.
*/
- (BOOL)isMeasurementEnabled {
if (self.isAnalyticsCollectionDeactivated) {
return NO;
}
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
if (value == nil) {
// TODO: This could probably be cleaned up since FIROptions shouldn't know about FIRApp or have
// to check if it's the default app. The FIROptions instance can't be modified after
// `+configure` is called, so it's not a good place to copy it either in case the flag is
// changed at runtime.
// If no values are set for Analytics, fall back to the global collection switch in FIRApp.
// Analytics only supports the default FIRApp, so check that first.
if (![FIRApp isDefaultAppConfigured]) {
return NO;
}
// Fall back to the default app's collection switch when the key is not in the dictionary.
return [FIRApp defaultApp].automaticDataCollectionEnabled;
}
return [value boolValue];
}
- (BOOL)isAnalyticsCollectionExpicitlySet {
// If it's de-activated, it classifies as explicity set. If not, it's not a good enough indication
// that the developer wants FirebaseAnalytics enabled so continue checking.
if (self.isAnalyticsCollectionDeactivated) {
return YES;
}
// Check if the current Analytics flag is set.
id collectionEnabledObject = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
if (collectionEnabledObject && [collectionEnabledObject isKindOfClass:[NSNumber class]]) {
// It doesn't matter what the value is, it's explicitly set.
return YES;
}
// Check if the old measurement flag is set.
id measurementEnabledObject = self.analyticsOptionsDictionary[kFIRIsMeasurementEnabled];
if (measurementEnabledObject && [measurementEnabledObject isKindOfClass:[NSNumber class]]) {
// It doesn't matter what the value is, it's explicitly set.
return YES;
}
// No flags are set to explicitly enable or disable FirebaseAnalytics.
return NO;
}
- (BOOL)isAnalyticsCollectionEnabled {
if (self.isAnalyticsCollectionDeactivated) {
return NO;
}
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionEnabled];
if (value == nil) {
return self.isMeasurementEnabled; // Fall back to older plist flag.
}
return [value boolValue];
}
- (BOOL)isAnalyticsCollectionDeactivated {
NSNumber *value = self.analyticsOptionsDictionary[kFIRIsAnalyticsCollectionDeactivated];
if (value == nil) {
return NO; // Analytics Collection is not deactivated when the key is not in the dictionary.
}
return [value boolValue];
}
- (BOOL)isAnalyticsEnabled {
return [self.optionsDictionary[kFIRIsAnalyticsEnabled] boolValue];
}
- (BOOL)isSignInEnabled {
return [self.optionsDictionary[kFIRIsSignInEnabled] boolValue];
}
@end

View File

@ -0,0 +1,256 @@
// Copyright 2017 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Private/FIRReachabilityChecker+Internal.h"
#import "Private/FIRReachabilityChecker.h"
#import "Private/FIRLogger.h"
#import "Private/FIRNetwork.h"
#import "Private/FIRNetworkMessageCode.h"
static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
SCNetworkReachabilityFlags flags,
void *info);
static const struct FIRReachabilityApi kFIRDefaultReachabilityApi = {
SCNetworkReachabilityCreateWithName,
SCNetworkReachabilitySetCallback,
SCNetworkReachabilityScheduleWithRunLoop,
SCNetworkReachabilityUnscheduleFromRunLoop,
CFRelease,
};
static NSString *const kFIRReachabilityUnknownStatus = @"Unknown";
static NSString *const kFIRReachabilityConnectedStatus = @"Connected";
static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected";
@interface FIRReachabilityChecker ()
@property(nonatomic, assign) const struct FIRReachabilityApi *reachabilityApi;
@property(nonatomic, assign) FIRReachabilityStatus reachabilityStatus;
@property(nonatomic, copy) NSString *host;
@property(nonatomic, assign) SCNetworkReachabilityRef reachability;
@end
@implementation FIRReachabilityChecker
@synthesize reachabilityApi = reachabilityApi_;
@synthesize reachability = reachability_;
- (const struct FIRReachabilityApi *)reachabilityApi {
return reachabilityApi_;
}
- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi {
if (reachability_) {
NSString *message =
@"Cannot change reachability API while reachability is running. "
@"Call stop first.";
[loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeReachabilityChecker000
message:message];
return;
}
reachabilityApi_ = reachabilityApi;
}
@synthesize reachabilityStatus = reachabilityStatus_;
@synthesize host = host_;
@synthesize reachabilityDelegate = reachabilityDelegate_;
@synthesize loggerDelegate = loggerDelegate_;
- (BOOL)isActive {
return reachability_ != nil;
}
- (void)setReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate {
if (reachabilityDelegate &&
(![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(FIRReachabilityDelegate)])) {
FIRLogError(kFIRLoggerCore,
[NSString stringWithFormat:@"I-NET%06ld",
(long)kFIRNetworkMessageCodeReachabilityChecker005],
@"Reachability delegate doesn't conform to Reachability protocol.");
return;
}
reachabilityDelegate_ = reachabilityDelegate;
}
- (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
if (loggerDelegate &&
(![(NSObject *)loggerDelegate conformsToProtocol:@protocol(FIRNetworkLoggerDelegate)])) {
FIRLogError(kFIRLoggerCore,
[NSString stringWithFormat:@"I-NET%06ld",
(long)kFIRNetworkMessageCodeReachabilityChecker006],
@"Reachability delegate doesn't conform to Logger protocol.");
return;
}
loggerDelegate_ = loggerDelegate;
}
- (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate
loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate
withHost:(NSString *)host {
self = [super init];
[self setLoggerDelegate:loggerDelegate];
if (!host || !host.length) {
[loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeReachabilityChecker001
message:@"Invalid host specified"];
return nil;
}
if (self) {
[self setReachabilityDelegate:reachabilityDelegate];
reachabilityApi_ = &kFIRDefaultReachabilityApi;
reachabilityStatus_ = kFIRReachabilityUnknown;
host_ = [host copy];
reachability_ = nil;
}
return self;
}
- (void)dealloc {
reachabilityDelegate_ = nil;
loggerDelegate_ = nil;
[self stop];
}
- (BOOL)start {
if (!reachability_) {
reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]);
if (!reachability_) {
return NO;
}
SCNetworkReachabilityContext context = {
0, /* version */
(__bridge void *)(self), /* info (passed as last parameter to reachability callback) */
NULL, /* retain */
NULL, /* release */
NULL /* copyDescription */
};
if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) ||
!reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(),
kCFRunLoopCommonModes)) {
reachabilityApi_->releaseFn(reachability_);
reachability_ = nil;
[loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
messageCode:kFIRNetworkMessageCodeReachabilityChecker002
message:@"Failed to start reachability handle"];
return NO;
}
}
[loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeReachabilityChecker003
message:@"Monitoring the network status"];
return YES;
}
- (void)stop {
if (reachability_) {
reachabilityStatus_ = kFIRReachabilityUnknown;
reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
kCFRunLoopCommonModes);
reachabilityApi_->releaseFn(reachability_);
reachability_ = nil;
}
}
- (FIRReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
FIRReachabilityStatus status = kFIRReachabilityNotReachable;
// If the Reachable flag is not set, we definitely don't have connectivity.
if (flags & kSCNetworkReachabilityFlagsReachable) {
// Reachable flag is set. Check further flags.
if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
// Connection required flag is not set, so we have connectivity.
#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
: kFIRReachabilityViaWifi;
#elif TARGET_OS_OSX
status = kFIRReachabilityViaWifi;
#endif
} else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand |
kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
!(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
// If the connection on demand or connection on traffic flag is set, and user intervention
// is not required, we have connectivity.
#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
: kFIRReachabilityViaWifi;
#elif TARGET_OS_OSX
status = kFIRReachabilityViaWifi;
#endif
}
}
return status;
}
- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
FIRReachabilityStatus status = [self statusForFlags:flags];
if (reachabilityStatus_ != status) {
NSString *reachabilityStatusString;
if (status == kFIRReachabilityUnknown) {
reachabilityStatusString = kFIRReachabilityUnknownStatus;
} else {
reachabilityStatusString = (status == kFIRReachabilityNotReachable)
? kFIRReachabilityDisconnectedStatus
: kFIRReachabilityConnectedStatus;
}
[loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
messageCode:kFIRNetworkMessageCodeReachabilityChecker004
message:@"Network status has changed. Code, status"
contexts:@[ @(status), reachabilityStatusString ]];
reachabilityStatus_ = status;
[reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
}
}
@end
static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
SCNetworkReachabilityFlags flags,
void *info) {
FIRReachabilityChecker *checker = (__bridge FIRReachabilityChecker *)info;
[checker reachabilityFlagsChanged:flags];
}
// This function used to be at the top of the file, but it was moved here
// as a workaround for a suspected compiler bug. When compiled in Release mode
// and run on an iOS device with WiFi disabled, the reachability code crashed
// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter.
// After unsuccessfully trying to diagnose the cause of the crash, it was
// discovered that moving this function to the end of the file magically fixed
// the crash. If you are going to edit this file, exercise caution and make sure
// to test thoroughly with an iOS device under various network conditions.
const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status) {
switch (status) {
case kFIRReachabilityUnknown:
return @"Reachability Unknown";
case kFIRReachabilityNotReachable:
return @"Not reachable";
case kFIRReachabilityViaWifi:
return @"Reachable via Wifi";
case kFIRReachabilityViaCellular:
return @"Reachable via Cellular Data";
default:
return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef Firebase_VERSION
#error "Firebase_VERSION is not defined: add -DFirebase_VERSION=... to the build invocation"
#endif
#ifndef FIRCore_VERSION
#error "FIRCore_VERSION is not defined: add -DFIRCore_VERSION=... to the build invocation"
#endif
// The following two macros supply the incantation so that the C
// preprocessor does not try to parse the version as a floating
// point number. See
// https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
const unsigned char *const FIRVersionString = (const unsigned char *const)STR(Firebase_VERSION);
const unsigned char *const FIRCoreVersionString = (const unsigned char *const)STR(FIRCore_VERSION);

View File

@ -0,0 +1,49 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRAnalyticsConfiguration.h"
/// Values stored in analyticsEnabledState. Never alter these constants since they must match with
/// values persisted to disk.
typedef NS_ENUM(int64_t, FIRAnalyticsEnabledState) {
// 0 is the default value for keys not found stored in persisted config, so it cannot represent
// kFIRAnalyticsEnabledStateSetNo. It must represent kFIRAnalyticsEnabledStateNotSet.
kFIRAnalyticsEnabledStateNotSet = 0,
kFIRAnalyticsEnabledStateSetYes = 1,
kFIRAnalyticsEnabledStateSetNo = 2,
};
/// The user defaults key for the persisted measurementEnabledState value. FIRAPersistedConfig reads
/// measurementEnabledState using this same key.
static NSString *const kFIRAPersistedConfigMeasurementEnabledStateKey =
@"/google/measurement/measurement_enabled_state";
static NSString *const kFIRAnalyticsConfigurationSetEnabledNotification =
@"FIRAnalyticsConfigurationSetEnabledNotification";
static NSString *const kFIRAnalyticsConfigurationSetMinimumSessionIntervalNotification =
@"FIRAnalyticsConfigurationSetMinimumSessionIntervalNotification";
static NSString *const kFIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification =
@"FIRAnalyticsConfigurationSetSessionTimeoutIntervalNotification";
@interface FIRAnalyticsConfiguration (Internal)
/// Sets whether analytics collection is enabled for this app on this device, and a flag to persist
/// the value or not. The setting should not be persisted if being set by the global data collection
/// flag.
- (void)setAnalyticsCollectionEnabled:(BOOL)analyticsCollectionEnabled
persistSetting:(BOOL)shouldPersist;
@end

View File

@ -0,0 +1,48 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** @class FIRAppAssociationRegistration
@brief Manages object associations as a singleton-dependent: At most one object is
registered for any given host/key pair, and the object shall be created on-the-fly when
asked for.
*/
@interface FIRAppAssociationRegistration <ObjectType> : NSObject
/** @fn registeredObjectWithHost:key:creationBlock:
@brief Retrieves the registered object with a particular host and key.
@param host The host object.
@param key The key to specify the registered object on the host.
@param creationBlock The block to return the object to be registered if not already.
The block is executed immediately before this method returns if it is executed at all.
It can also be executed multiple times across different method invocations if previous
execution of the block returns @c nil.
@return The registered object for the host/key pair, or @c nil if no object is registered
and @c creationBlock returns @c nil.
@remarks The method is thread-safe but non-reentrant in the sense that attempting to call this
method again within the @c creationBlock with the same host/key pair raises an exception.
The registered object is retained by the host.
*/
+ (nullable ObjectType)registeredObjectWithHost:(id)host
key:(NSString *)key
creationBlock:(ObjectType _Nullable (^)(void))creationBlock;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,214 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRApp.h"
#import "FIRErrors.h"
/**
* The internal interface to FIRApp. This is meant for first-party integrators, who need to receive
* FIRApp notifications, log info about the success or failure of their configuration, and access
* other internal functionality of FIRApp.
*
* TODO(b/28296561): Restructure this header.
*/
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, FIRConfigType) {
FIRConfigTypeCore = 1,
FIRConfigTypeSDK = 2,
};
/**
* Names of services provided by Firebase.
*/
extern NSString *const kFIRServiceAdMob;
extern NSString *const kFIRServiceAuth;
extern NSString *const kFIRServiceAuthUI;
extern NSString *const kFIRServiceCrash;
extern NSString *const kFIRServiceDatabase;
extern NSString *const kFIRServiceDynamicLinks;
extern NSString *const kFIRServiceInstanceID;
extern NSString *const kFIRServiceInvites;
extern NSString *const kFIRServiceMessaging;
extern NSString *const kFIRServiceMeasurement;
extern NSString *const kFIRServiceRemoteConfig;
extern NSString *const kFIRServiceStorage;
/**
* Names of services provided by the Google pod, but logged by the Firebase pod.
*/
extern NSString *const kGGLServiceAnalytics;
extern NSString *const kGGLServiceSignIn;
extern NSString *const kFIRDefaultAppName;
extern NSString *const kFIRAppReadyToConfigureSDKNotification;
extern NSString *const kFIRAppDeleteNotification;
extern NSString *const kFIRAppIsDefaultAppKey;
extern NSString *const kFIRAppNameKey;
extern NSString *const kFIRGoogleAppIDKey;
/**
* The format string for the User Defaults key used for storing the data collection enabled flag.
* This includes formatting to append the Firebase App's name.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
/**
* The plist key used for storing the data collection enabled flag.
*/
extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
/**
* A notification fired containing diagnostic information when SDK errors occur.
*/
extern NSString *const kFIRAppDiagnosticsNotification;
/** @var FIRAuthStateDidChangeInternalNotification
@brief The name of the @c NSNotificationCenter notification which is posted when the auth state
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
the notification is a dictionary possibly containing the key:
@c FIRAuthStateDidChangeInternalNotificationTokenKey (the new access token.) If it does not
contain this key it indicates a sign-out event took place.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotification;
/** @var FIRAuthStateDidChangeInternalNotificationTokenKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new access token.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey;
/** @var FIRAuthStateDidChangeInternalNotificationAppKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the FIRApp associated with the auth instance.
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationAppKey;
/** @var FIRAuthStateDidChangeInternalNotificationUIDKey
@brief A key present in the dictionary object parameter of the
@c FIRAuthStateDidChangeInternalNotification notification. The value associated with this
key will contain the new user's UID (or nil if there is no longer a user signed in).
*/
extern NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey;
/** @typedef FIRTokenCallback
@brief The type of block which gets called when a token is ready.
*/
typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error);
/** @typedef FIRAppGetTokenImplementation
@brief The type of block which can provide an implementation for the @c getTokenWithCallback:
method.
@param forceRefresh Forces the token to be refreshed.
@param callback The block which should be invoked when the async call completes.
*/
typedef void (^FIRAppGetTokenImplementation)(BOOL forceRefresh, FIRTokenCallback callback);
/** @typedef FIRAppGetUID
@brief The type of block which can provide an implementation for the @c getUID method.
*/
typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
@interface FIRApp ()
/** @property getTokenImplementation
@brief Gets or sets the block to use for the implementation of
@c getTokenForcingRefresh:withCallback:
*/
@property(nonatomic, copy) FIRAppGetTokenImplementation getTokenImplementation;
/** @property getUIDImplementation
@brief Gets or sets the block to use for the implementation of @c getUID.
*/
@property(nonatomic, copy) FIRAppGetUIDImplementation getUIDImplementation;
/**
* Creates an error for failing to configure a subspec service. This method is called by each
* FIRApp notification listener.
*/
+ (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
errorCode:(FIRErrorCode)code
service:(NSString *)service
reason:(NSString *)reason;
/**
* Checks if the default app is configured without trying to configure it.
*/
+ (BOOL)isDefaultAppConfigured;
/**
* Registers a given third-party library with the given version number to be reported for
* analyitcs.
*
* @param library Name of the library
* @param version Version of the library
*/
// clang-format off
+ (void)registerLibrary:(NSString *)library
withVersion:(NSString *)version NS_SWIFT_NAME(registerLibrary(_:version:));
// clang-format on
/**
* A concatenated string representing all the third-party libraries and version numbers.
*/
+ (NSString *)firebaseUserAgent;
/**
* Used by each SDK to send logs about SDK configuration status to Clearcut.
*/
- (void)sendLogsWithServiceName:(NSString *)serviceName
version:(NSString *)version
error:(NSError *)error;
/**
* Can be used by the unit tests in eack SDK to reset FIRApp. This method is thread unsafe.
*/
+ (void)resetApps;
/**
* Can be used by the unit tests in each SDK to set customized options.
*/
- (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options;
/** @fn getTokenForcingRefresh:withCallback:
@brief Retrieves the Firebase authentication token, possibly refreshing it.
@param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason
other than an expiration.
@param callback The block to invoke when the token is available.
*/
- (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback;
/**
* Expose the UID of the current user for Firestore.
*/
- (nullable NSString *)getUID;
/**
* WARNING: THIS SETTING DOES NOT WORK YET. IT WILL BE MOVED TO THE PUBLIC HEADER ONCE ALL SDKS
* CONFORM TO THIS PREFERENCE. DO NOT RELY ON IT.
*
* Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES`
* unless `FirebaseAutomaticDataCollectionEnabled` is set to `NO` in your app's Info.plist. This
* value is persisted across runs of the app so that it can be set once when users have consented to
* collection.
*/
@property(nonatomic, readwrite, getter=isAutomaticDataCollectionEnabled)
BOOL automaticDataCollectionEnabled;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,52 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* This class provides utilities for accessing resources in bundles.
*/
@interface FIRBundleUtil : NSObject
/**
* Finds all relevant bundles, starting with [NSBundle mainBundle].
*/
+ (NSArray *)relevantBundles;
/**
* Reads the options dictionary from one of the provided bundles.
*
* @param resourceName The resource name, e.g. @"GoogleService-Info".
* @param fileType The file type (extension), e.g. @"plist".
* @param bundles The bundles to expect, in priority order. See also
* +[FIRBundleUtil relevantBundles].
*/
+ (NSString *)optionsDictionaryPathWithResourceName:(NSString *)resourceName
andFileType:(NSString *)fileType
inBundles:(NSArray *)bundles;
/**
* Finds URL schemes defined in all relevant bundles, starting with those from
* [NSBundle mainBundle].
*/
+ (NSArray *)relevantURLSchemes;
/**
* Checks if the bundle identifier exists in the given bundles.
*/
+ (BOOL)hasBundleIdentifier:(NSString *)bundleIdentifier inBundles:(NSArray *)bundles;
@end

View File

@ -0,0 +1,55 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** Error codes in Firebase error domain. */
typedef NS_ENUM(NSInteger, FIRErrorCode) {
/**
* Unknown error.
*/
FIRErrorCodeUnknown = 0,
/**
* Loading data from the GoogleService-Info.plist file failed. This is a fatal error and should
* not be ignored. Further calls to the API will fail and/or possibly cause crashes.
*/
FIRErrorCodeInvalidPlistFile = -100,
/**
* Validating the Google App ID format failed.
*/
FIRErrorCodeInvalidAppID = -101,
/**
* Error code for failing to configure a specific service.
*/
FIRErrorCodeAdMobFailed = -110,
FIRErrorCodeAppInviteFailed = -112,
FIRErrorCodeCloudMessagingFailed = -113,
FIRErrorCodeConfigFailed = -114,
FIRErrorCodeDatabaseFailed = -115,
FIRErrorCodeCrashReportingFailed = -118,
FIRErrorCodeDurableDeepLinkFailed = -119,
FIRErrorCodeAuthFailed = -120,
FIRErrorCodeInstanceIDFailed = -121,
FIRErrorCodeStorageFailed = -123,
/**
* Error codes returned by Dynamic Links
*/
FIRErrorCodeDynamicLinksStrongMatchNotAvailable = -124,
FIRErrorCodeDynamicLinksManualRetrievalNotEnabled = -125,
FIRErrorCodeDynamicLinksPendingLinkOnlyAvailableAtFirstLaunch = -126,
FIRErrorCodeDynamicLinksPendingLinkRetrievalAlreadyRunning = -127,
};

View File

@ -0,0 +1,33 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#include "FIRErrorCode.h"
extern NSString *const kFirebaseErrorDomain;
extern NSString *const kFirebaseAdMobErrorDomain;
extern NSString *const kFirebaseAppInviteErrorDomain;
extern NSString *const kFirebaseAuthErrorDomain;
extern NSString *const kFirebaseCloudMessagingErrorDomain;
extern NSString *const kFirebaseConfigErrorDomain;
extern NSString *const kFirebaseCoreErrorDomain;
extern NSString *const kFirebaseCrashReportingErrorDomain;
extern NSString *const kFirebaseDatabaseErrorDomain;
extern NSString *const kFirebaseDurableDeepLinkErrorDomain;
extern NSString *const kFirebaseInstanceIDErrorDomain;
extern NSString *const kFirebasePerfErrorDomain;
extern NSString *const kFirebaseStorageErrorDomain;

View File

@ -0,0 +1,159 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRLoggerLevel.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The Firebase services used in Firebase logger.
*/
typedef NSString *const FIRLoggerService;
extern FIRLoggerService kFIRLoggerABTesting;
extern FIRLoggerService kFIRLoggerAdMob;
extern FIRLoggerService kFIRLoggerAnalytics;
extern FIRLoggerService kFIRLoggerAuth;
extern FIRLoggerService kFIRLoggerCore;
extern FIRLoggerService kFIRLoggerCrash;
extern FIRLoggerService kFIRLoggerDatabase;
extern FIRLoggerService kFIRLoggerDynamicLinks;
extern FIRLoggerService kFIRLoggerFirestore;
extern FIRLoggerService kFIRLoggerInstanceID;
extern FIRLoggerService kFIRLoggerInvites;
extern FIRLoggerService kFIRLoggerMLKit;
extern FIRLoggerService kFIRLoggerMessaging;
extern FIRLoggerService kFIRLoggerPerf;
extern FIRLoggerService kFIRLoggerRemoteConfig;
extern FIRLoggerService kFIRLoggerStorage;
extern FIRLoggerService kFIRLoggerSwizzler;
/**
* The key used to store the logger's error count.
*/
extern NSString *const kFIRLoggerErrorCountKey;
/**
* The key used to store the logger's warning count.
*/
extern NSString *const kFIRLoggerWarningCountKey;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Enables or disables Analytics debug mode.
* If set to YES, the logging level for Analytics will be set to FIRLoggerLevelDebug.
* Enabling the debug mode has no effect if the app is running from App Store.
* (required) analytics debug mode flag.
*/
void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode);
/**
* Changes the default logging level of FIRLoggerLevelNotice to a user-specified level.
* The default level cannot be set above FIRLoggerLevelNotice if the app is running from App Store.
* (required) log level (one of the FIRLoggerLevel enum values).
*/
void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel);
/**
* Checks if the specified logger level is loggable given the current settings.
* (required) log level (one of the FIRLoggerLevel enum values).
* (required) whether or not this function is called from the Analytics component.
*/
BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent);
/**
* Logs a message to the Xcode console and the device log. If running from AppStore, will
* not log any messages with a level higher than FIRLoggerLevelNotice to avoid log spamming.
* (required) log level (one of the FIRLoggerLevel enum values).
* (required) service name of type FIRLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
extern void FIRLogBasic(FIRLoggerLevel level,
FIRLoggerService service,
NSString *messageCode,
NSString *message,
// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
// See: http://stackoverflow.com/q/29095469
#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX
va_list args_ptr
#else
va_list _Nullable args_ptr
#endif
);
/**
* The following functions accept the following parameters in order:
* (required) service name of type FIRLoggerService.
* (required) message code starting from "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* See go/firebase-log-proposal for details.
* (required) message string which can be a format string.
* (optional) the list of arguments to substitute into the format string.
* Example usage:
* FIRLogError(kFIRLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
*/
extern void FIRLogError(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogWarning(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogNotice(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
NS_FORMAT_FUNCTION(3, 4);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@interface FIRLoggerWrapper : NSObject
/**
* Objective-C wrapper for FIRLogBasic to allow weak linking to FIRLogger
* (required) log level (one of the FIRLoggerLevel enum values).
* (required) service name of type FIRLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
+ (void)logWithLevel:(FIRLoggerLevel)level
withService:(FIRLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,46 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/// A mutable dictionary that provides atomic accessor and mutators.
@interface FIRMutableDictionary : NSObject
/// Returns an object given a key in the dictionary or nil if not found.
- (id)objectForKey:(id)key;
/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary.
- (void)setObject:(id)object forKey:(id<NSCopying>)key;
/// Removes the object given its session ID from the dictionary.
- (void)removeObjectForKey:(id)key;
/// Removes all objects.
- (void)removeAllObjects;
/// Returns the number of current objects in the dictionary.
- (NSUInteger)count;
/// Returns an object given a key in the dictionary or nil if not found.
- (id)objectForKeyedSubscript:(id<NSCopying>)key;
/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary.
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key;
/// Returns the immutable dictionary.
- (NSDictionary *)dictionary;
@end

View File

@ -0,0 +1,87 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRNetworkConstants.h"
#import "FIRNetworkLoggerProtocol.h"
#import "FIRNetworkURLSession.h"
/// Delegate protocol for FIRNetwork events.
@protocol FIRNetworkReachabilityDelegate
/// Tells the delegate to handle events when the network reachability changes to connected or not
/// connected.
- (void)reachabilityDidChange;
@end
/// The Network component that provides network status and handles network requests and responses.
/// This is not thread safe.
///
/// NOTE:
/// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the
/// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler:
@interface FIRNetwork : NSObject
/// Indicates if network connectivity is available.
@property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected;
/// Indicates if there are any uploads in progress.
@property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress;
/// An optional delegate that can be used in the event when network reachability changes.
@property(nonatomic, weak) id<FIRNetworkReachabilityDelegate> reachabilityDelegate;
/// An optional delegate that can be used to log messages, warnings or errors that occur in the
/// network operations.
@property(nonatomic, weak) id<FIRNetworkLoggerDelegate> loggerDelegate;
/// Indicates whether the logger should display debug messages.
@property(nonatomic, assign) BOOL isDebugModeEnabled;
/// The time interval in seconds for the network request to timeout.
@property(nonatomic, assign) NSTimeInterval timeoutInterval;
/// Initializes with the default reachability host.
- (instancetype)init;
/// Initializes with a custom reachability host.
- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost;
/// Handles events when background session with the given ID has finished.
+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler;
/// Compresses and sends a POST request with the provided data to the URL. The session will be
/// background session if usingBackgroundSession is YES. Otherwise, the POST session is default
/// session. Returns a session ID or nil if an error occurs.
- (NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(FIRNetworkCompletionHandler)handler;
/// Sends a GET request with the provided data to the URL. The session will be background session
/// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a
/// session ID or nil if an error occurs.
- (NSString *)getURL:(NSURL *)url
headers:(NSDictionary *)headers
queue:(dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(FIRNetworkCompletionHandler)handler;
@end

View File

@ -0,0 +1,75 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/// Error codes in Firebase Network error domain.
/// Note: these error codes should never change. It would make it harder to decode the errors if
/// we inadvertently altered any of these codes in a future SDK version.
typedef NS_ENUM(NSInteger, FIRNetworkErrorCode) {
/// Unknown error.
FIRNetworkErrorCodeUnknown = 0,
/// Error occurs when the request URL is invalid.
FIRErrorCodeNetworkInvalidURL = 1,
/// Error occurs when request cannot be constructed.
FIRErrorCodeNetworkRequestCreation = 2,
/// Error occurs when payload cannot be compressed.
FIRErrorCodeNetworkPayloadCompression = 3,
/// Error occurs when session task cannot be created.
FIRErrorCodeNetworkSessionTaskCreation = 4,
/// Error occurs when there is no response.
FIRErrorCodeNetworkInvalidResponse = 5
};
#pragma mark - Network constants
/// The prefix of the ID of the background session.
extern NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix;
/// The sub directory to store the files of data that is being uploaded in the background.
extern NSString *const kFIRNetworkApplicationSupportSubdirectory;
/// Name of the temporary directory that stores files for background uploading.
extern NSString *const kFIRNetworkTempDirectoryName;
/// The period when the temporary uploading file can stay.
extern const NSTimeInterval kFIRNetworkTempFolderExpireTime;
/// The default network request timeout interval.
extern const NSTimeInterval kFIRNetworkTimeOutInterval;
/// The host to check the reachability of the network.
extern NSString *const kFIRNetworkReachabilityHost;
/// The key to get the error context of the UserInfo.
extern NSString *const kFIRNetworkErrorContext;
#pragma mark - Network Status Code
extern const int kFIRNetworkHTTPStatusOK;
extern const int kFIRNetworkHTTPStatusNoContent;
extern const int kFIRNetworkHTTPStatusCodeMultipleChoices;
extern const int kFIRNetworkHTTPStatusCodeMovedPermanently;
extern const int kFIRNetworkHTTPStatusCodeFound;
extern const int kFIRNetworkHTTPStatusCodeNotModified;
extern const int kFIRNetworkHTTPStatusCodeMovedTemporarily;
extern const int kFIRNetworkHTTPStatusCodeNotFound;
extern const int kFIRNetworkHTTPStatusCodeCannotAcceptTraffic;
extern const int kFIRNetworkHTTPStatusCodeUnavailable;
#pragma mark - Error Domain
extern NSString *const kFIRNetworkErrorDomain;

View File

@ -0,0 +1,50 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRLoggerLevel.h"
#import "FIRNetworkMessageCode.h"
/// The log levels used by FIRNetworkLogger.
typedef NS_ENUM(NSInteger, FIRNetworkLogLevel) {
kFIRNetworkLogLevelError = FIRLoggerLevelError,
kFIRNetworkLogLevelWarning = FIRLoggerLevelWarning,
kFIRNetworkLogLevelInfo = FIRLoggerLevelInfo,
kFIRNetworkLogLevelDebug = FIRLoggerLevelDebug,
};
@protocol FIRNetworkLoggerDelegate <NSObject>
@required
/// Tells the delegate to log a message with an array of contexts and the log level.
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
messageCode:(FIRNetworkMessageCode)messageCode
message:(NSString *)message
contexts:(NSArray *)contexts;
/// Tells the delegate to log a message with a context and the log level.
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
messageCode:(FIRNetworkMessageCode)messageCode
message:(NSString *)message
context:(id)context;
/// Tells the delegate to log a message with the log level.
- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel
messageCode:(FIRNetworkMessageCode)messageCode
message:(NSString *)message;
@end

View File

@ -0,0 +1,52 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum.
typedef NS_ENUM(NSInteger, FIRNetworkMessageCode) {
// FIRNetwork.m
kFIRNetworkMessageCodeNetwork000 = 900000, // I-NET900000
kFIRNetworkMessageCodeNetwork001 = 900001, // I-NET900001
kFIRNetworkMessageCodeNetwork002 = 900002, // I-NET900002
kFIRNetworkMessageCodeNetwork003 = 900003, // I-NET900003
// FIRNetworkURLSession.m
kFIRNetworkMessageCodeURLSession000 = 901000, // I-NET901000
kFIRNetworkMessageCodeURLSession001 = 901001, // I-NET901001
kFIRNetworkMessageCodeURLSession002 = 901002, // I-NET901002
kFIRNetworkMessageCodeURLSession003 = 901003, // I-NET901003
kFIRNetworkMessageCodeURLSession004 = 901004, // I-NET901004
kFIRNetworkMessageCodeURLSession005 = 901005, // I-NET901005
kFIRNetworkMessageCodeURLSession006 = 901006, // I-NET901006
kFIRNetworkMessageCodeURLSession007 = 901007, // I-NET901007
kFIRNetworkMessageCodeURLSession008 = 901008, // I-NET901008
kFIRNetworkMessageCodeURLSession009 = 901009, // I-NET901009
kFIRNetworkMessageCodeURLSession010 = 901010, // I-NET901010
kFIRNetworkMessageCodeURLSession011 = 901011, // I-NET901011
kFIRNetworkMessageCodeURLSession012 = 901012, // I-NET901012
kFIRNetworkMessageCodeURLSession013 = 901013, // I-NET901013
kFIRNetworkMessageCodeURLSession014 = 901014, // I-NET901014
kFIRNetworkMessageCodeURLSession015 = 901015, // I-NET901015
kFIRNetworkMessageCodeURLSession016 = 901016, // I-NET901016
kFIRNetworkMessageCodeURLSession017 = 901017, // I-NET901017
kFIRNetworkMessageCodeURLSession018 = 901018, // I-NET901018
// FIRReachabilityChecker.m
kFIRNetworkMessageCodeReachabilityChecker000 = 902000, // I-NET902000
kFIRNetworkMessageCodeReachabilityChecker001 = 902001, // I-NET902001
kFIRNetworkMessageCodeReachabilityChecker002 = 902002, // I-NET902002
kFIRNetworkMessageCodeReachabilityChecker003 = 902003, // I-NET902003
kFIRNetworkMessageCodeReachabilityChecker004 = 902004, // I-NET902004
kFIRNetworkMessageCodeReachabilityChecker005 = 902005, // I-NET902005
kFIRNetworkMessageCodeReachabilityChecker006 = 902006, // I-NET902006
};

View File

@ -0,0 +1,60 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "FIRNetworkLoggerProtocol.h"
typedef void (^FIRNetworkCompletionHandler)(NSHTTPURLResponse *response,
NSData *data,
NSError *error);
typedef void (^FIRNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *response,
NSData *data,
NSString *sessionID,
NSError *error);
typedef void (^FIRNetworkSystemCompletionHandler)(void);
/// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses.
@interface FIRNetworkURLSession
: NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>
/// Indicates whether the background network is enabled. Default value is NO.
@property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled;
/// The logger delegate to log message, errors or warnings that occur during the network operations.
@property(nonatomic, weak) id<FIRNetworkLoggerDelegate> loggerDelegate;
/// Calls the system provided completion handler after the background session is finished.
+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler;
/// Initializes with logger delegate.
- (instancetype)initWithNetworkLoggerDelegate:(id<FIRNetworkLoggerDelegate>)networkLoggerDelegate
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/// Sends an asynchronous POST request and calls the provided completion handler when the request
/// completes or when errors occur, and returns an ID of the session/connection.
- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
completionHandler:(FIRNetworkURLSessionCompletionHandler)handler;
/// Sends an asynchronous GET request and calls the provided completion handler when the request
/// completes or when errors occur, and returns an ID of the session.
- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
completionHandler:(FIRNetworkURLSessionCompletionHandler)handler;
@end

View File

@ -0,0 +1,114 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIROptions.h"
/**
* Keys for the strings in the plist file.
*/
extern NSString *const kFIRAPIKey;
extern NSString *const kFIRTrackingID;
extern NSString *const kFIRGoogleAppID;
extern NSString *const kFIRClientID;
extern NSString *const kFIRGCMSenderID;
extern NSString *const kFIRAndroidClientID;
extern NSString *const kFIRDatabaseURL;
extern NSString *const kFIRStorageBucket;
extern NSString *const kFIRBundleID;
extern NSString *const kFIRProjectID;
/**
* Keys for the plist file name
*/
extern NSString *const kServiceInfoFileName;
extern NSString *const kServiceInfoFileType;
/**
* This header file exposes the initialization of FIROptions to internal use.
*/
@interface FIROptions ()
/**
* resetDefaultOptions and initInternalWithOptionsDictionary: are exposed only for unit tests.
*/
+ (void)resetDefaultOptions;
/**
* Initializes the options with dictionary. The above strings are the keys of the dictionary.
* This is the designated initializer.
*/
- (instancetype)initInternalWithOptionsDictionary:(NSDictionary *)serviceInfoDictionary;
/**
* defaultOptions and defaultOptionsDictionary are exposed in order to be used in FIRApp and
* other first party services.
*/
+ (FIROptions *)defaultOptions;
+ (NSDictionary *)defaultOptionsDictionary;
/**
* Indicates whether or not Analytics collection was explicitly enabled via a plist flag or at
* runtime.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionExpicitlySet;
/**
* Whether or not Analytics Collection was enabled. Analytics Collection is enabled unless
* explicitly disabled in GoogleService-Info.plist.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionEnabled;
/**
* Whether or not Analytics Collection was completely disabled. If YES, then
* isAnalyticsCollectionEnabled will be NO.
*/
@property(nonatomic, readonly) BOOL isAnalyticsCollectionDeactivated;
/**
* The version ID of the client library, e.g. @"1100000".
*/
@property(nonatomic, readonly, copy) NSString *libraryVersionID;
/**
* The flag indicating whether this object was constructed with the values in the default plist
* file.
*/
@property(nonatomic) BOOL usingOptionsFromDefaultPlist;
/**
* Whether or not Measurement was enabled. Measurement is enabled unless explicitly disabled in
* GoogleService-Info.plist.
*/
@property(nonatomic, readonly) BOOL isMeasurementEnabled;
/**
* Whether or not Analytics was enabled in the developer console.
*/
@property(nonatomic, readonly) BOOL isAnalyticsEnabled;
/**
* Whether or not SignIn was enabled in the developer console.
*/
@property(nonatomic, readonly) BOOL isSignInEnabled;
/**
* Whether or not editing is locked. This should occur after FIROptions has been set on a FIRApp.
*/
@property(nonatomic, getter=isEditingLocked) BOOL editingLocked;
@end

View File

@ -0,0 +1,47 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRReachabilityChecker.h"
typedef SCNetworkReachabilityRef (*FIRReachabilityCreateWithNameFn)(CFAllocatorRef allocator,
const char *host);
typedef Boolean (*FIRReachabilitySetCallbackFn)(SCNetworkReachabilityRef target,
SCNetworkReachabilityCallBack callback,
SCNetworkReachabilityContext *context);
typedef Boolean (*FIRReachabilityScheduleWithRunLoopFn)(SCNetworkReachabilityRef target,
CFRunLoopRef runLoop,
CFStringRef runLoopMode);
typedef Boolean (*FIRReachabilityUnscheduleFromRunLoopFn)(SCNetworkReachabilityRef target,
CFRunLoopRef runLoop,
CFStringRef runLoopMode);
typedef void (*FIRReachabilityReleaseFn)(CFTypeRef cf);
struct FIRReachabilityApi {
FIRReachabilityCreateWithNameFn createWithNameFn;
FIRReachabilitySetCallbackFn setCallbackFn;
FIRReachabilityScheduleWithRunLoopFn scheduleWithRunLoopFn;
FIRReachabilityUnscheduleFromRunLoopFn unscheduleFromRunLoopFn;
FIRReachabilityReleaseFn releaseFn;
};
@interface FIRReachabilityChecker (Internal)
- (const struct FIRReachabilityApi *)reachabilityApi;
- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi;
@end

View File

@ -0,0 +1,83 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
/// Reachability Status
typedef enum {
kFIRReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable.
kFIRReachabilityNotReachable, ///< Host is not reachable.
kFIRReachabilityViaWifi, ///< Host is reachable via Wifi.
kFIRReachabilityViaCellular, ///< Host is reachable via cellular.
} FIRReachabilityStatus;
const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status);
@class FIRReachabilityChecker;
@protocol FIRNetworkLoggerDelegate;
/// Google Analytics iOS Reachability Checker.
@protocol FIRReachabilityDelegate
@required
/// Called when network status has changed.
- (void)reachability:(FIRReachabilityChecker *)reachability
statusChanged:(FIRReachabilityStatus)status;
@end
/// Google Analytics iOS Network Status Checker.
@interface FIRReachabilityChecker : NSObject
/// The last known reachability status, or FIRReachabilityStatusUnknown if the
/// checker is not active.
@property(nonatomic, readonly) FIRReachabilityStatus reachabilityStatus;
/// The host to which reachability status is to be checked.
@property(nonatomic, copy, readonly) NSString *host;
/// The delegate to be notified of reachability status changes.
@property(nonatomic, weak) id<FIRReachabilityDelegate> reachabilityDelegate;
/// The delegate to be notified to log messages.
@property(nonatomic, weak) id<FIRNetworkLoggerDelegate> loggerDelegate;
/// `YES` if the reachability checker is active, `NO` otherwise.
@property(nonatomic, readonly) BOOL isActive;
/// Initialize the reachability checker. Note that you must call start to begin checking for and
/// receiving notifications about network status changes.
///
/// @param reachabilityDelegate The delegate to be notified when reachability status to host
/// changes.
///
/// @param loggerDelegate The delegate to send log messages to.
///
/// @param host The name of the host.
///
- (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate
loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate
withHost:(NSString *)host;
- (instancetype)init NS_UNAVAILABLE;
/// Start checking for reachability to the specified host. This has no effect if the status
/// checker is already checking for connectivity.
///
/// @return `YES` if initiating status checking was successful or the status checking has already
/// been initiated, `NO` otherwise.
- (BOOL)start;
/// Stop checking for reachability to the specified host. This has no effect if the status
/// checker is not checking for connectivity.
- (void)stop;
@end

View File

@ -0,0 +1,23 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/** The version of the Firebase SDK. */
FOUNDATION_EXPORT const unsigned char *const FIRVersionString;
/** The version of the FirebaseCore Component. */
FOUNDATION_EXPORT const unsigned char *const FIRCoreVersionString;

Some files were not shown because too many files have changed in this diff Show More