iOS日历界面

最近开发的产品中,有很多日历相关的需求,所以整理一下开发日历相关的东西。
1、时间戳转换
2、UI实现
3、第三方库推荐
4、值得注意的坑

1、时间戳转换

在开发日历相关功能的时候,后台与前端肯定是通过时间戳来通信,所以当我们遇到了有关日历的需求的时候,首先要准备一个工具类,把时间戳和时间信息来回转化。同时要注意:java的时间戳一般是毫秒级的,OC处理的时间戳是秒级的,所以后台传过来的时间戳要注意除1000。

调接口的时候,可以使用时间戳转换网站来确认两端参数或数值的准确。
下面是我使用的NSDate分类,基本可以满足所有的需求了。

NSDate+Extension.h:

#import <Foundation/Foundation.h>


@interface NSDate (Extensions)

/// 获取年
+ (NSInteger)year:(NSString *)dateStr;
/// 获取月
+ (NSInteger)month:(NSString *)dateStr;
/// 获取星期
+ (NSInteger)week:(NSString *)dateStr;
/// 获取星期 中文 日
+ (NSString *)getWeekFromDate:(NSDate *)date;
/// 获取星期 中文 周日
+ (NSString *)getChineseWeekFrom:(NSString *)dateStr;
/// 获取日
+ (NSInteger)day:(NSString *)dateStr;
/// 获取月共有多少天
+ (NSInteger)daysInMonth:(NSString *)dateStr;

/// 获取当前日期 2018-01-01
+ (NSString *)currentDay;
/// 获取当前小时 00:00
+ (NSString *)currentHour;
/// 获取下月最后一天
+ (NSString *)nextMonthLastDay;

/// 判断是否是今天
+ (BOOL)isToday:(NSString *)dateStr;
/// 判断是否是明天
+ (BOOL)isTomorrow:(NSString *)dateStr;
/// 判断是否是后天
+ (BOOL)isAfterTomorrow:(NSString *)dateStr;
/// 判断是否是过去的时间
+ (BOOL)isHistoryTime:(NSString *)dateStr;

/// 从时间戳获取具体时间 格式:6:00
+ (NSString *)hourStringWithInterval:(NSTimeInterval)timeInterval;
/// 从时间戳获取具体小时 格式:6
+ (NSString *)hourTagWithInterval:(NSTimeInterval)timeInterval;
/// 从毫秒级时间戳获取具体小时 格式:600
+ (NSString *)hourNumberWithInterval:(NSTimeInterval)timeInterval;
/// 从时间戳获取具体日期 格式:2018-03-05
+ (NSString *)timeStringWithInterval:(NSTimeInterval)timeInterval;
/// 从具体日期获取时间戳 毫秒
+ (NSTimeInterval)timeIntervalFromDateString:(NSString *)dateStr;

/// 获取当前天的后几天的星期
+ (NSString *)getWeekAfterDay:(NSInteger)day;
/// 获取当前天的后几天的日
+ (NSString *)getDayAfterDay:(NSInteger)day;
/// 获取当前月的后几月
+ (NSString *)getMonthAfterMonth:(NSInteger)Month;

@end


/*
 G: 公元时代,例如AD公元
 yy: 年的后2位
 yyyy: 完整年
 MM: 月,显示为1-12
 MMM: 月,显示为英文月份简写,如 Jan
 MMMM: 月,显示为英文月份全称,如 Janualy
 dd: 日,2位数表示,如02
 d: 日,1-2位显示,如 2
 EEE: 简写星期几,如Sun
 EEEE: 全写星期几,如Sunday
 aa: 上下午,AM/PM
 H: 时,24小时制,0-23
 K:时,12小时制,0-11
 m: 分,1-2位
 mm: 分,2位
 s: 秒,1-2位
 ss: 秒,2位
 S: 毫秒
 Z:GMT  
 */

NSDate+Extension.m:

#import "NSDate+Extensions.h"

@implementation NSDate (Extensions)

/// 获取年
+ (NSInteger)year:(NSString *)dateStr {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *startDate = [dateFormatter dateFromString:dateStr];
    NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:startDate];
    return components.year;
}

/// 获取月
+ (NSInteger)month:(NSString *)dateStr {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *startDate = [dateFormatter dateFromString:dateStr];
    NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:startDate];
    return components.month;
}


/// 获取星期
+ (NSInteger)week:(NSString *)dateStr {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *startDate = [dateFormatter dateFromString:dateStr];
    NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday) fromDate:startDate];
    return components.weekday - 1;
}

/// 获取星期 中文
+ (NSString *)getWeekFromDate:(NSDate *)date {
    NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday) fromDate:date];
    NSInteger week = components.weekday - 1;
    NSDictionary *weekDic = @{@"0":@"日",@"1":@"一",@"2":@"二",@"3":@"三",@"4":@"四",@"5":@"五",@"6":@"六"};
    NSString *key = [NSString stringWithFormat:@"%ld",(long)week];
    return weekDic[key];
}

/// 获取星期中文
+ (NSString *)getChineseWeekFrom:(NSString *)dateStr {
    NSDictionary *weekDic = @{@"0":@"周日",@"1":@"周一",@"2":@"周二",@"3":@"周三",@"4":@"周四",@"5":@"周五",@"6":@"周六"};
    NSInteger week = [NSDate week:dateStr];
    NSString *weekKey = [NSString stringWithFormat:@"%ld",(long)week];
    return weekDic[weekKey];
}

/// 获取日
+ (NSInteger)day:(NSString *)dateStr {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *startDate = [dateFormatter dateFromString:dateStr];
    NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:startDate];
    return components.day;
}

/// 获取月共有多少天
+ (NSInteger)daysInMonth:(NSString *)dateStr {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:[NSDate timeIntervalFromDateString:dateStr] / 1000];
    NSRange daysInLastMonth = [[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:date];
    return daysInLastMonth.length;
}

//获取当前日期
+ (NSString *)currentDay {
    NSDateFormatter *formater = [[ NSDateFormatter alloc] init];
    NSDate *date = [NSDate date];
    [formater setDateFormat:@"yyyy-MM-dd"];
    NSString * time = [formater stringFromDate:date];
    return time;
}

//获取当前小时
+ (NSString *)currentHour {
    NSDateFormatter *formater = [[ NSDateFormatter alloc] init];
    NSDate *curDate = [NSDate date];
    [formater setDateFormat:@"H:mm"];
    NSString * curTime = [formater stringFromDate:curDate];
    return curTime;
}

//找到两个月后的第一天~ 然后通过减一天来找到下个月的最后一天,所以,下月最后一天
+ (NSString *)nextMonthLastDay {
    NSCalendar* calendar = [NSCalendar currentCalendar];
    NSDateComponents *dateComponents = [calendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];
    //设置日为1号
    dateComponents.day =1;
    //设置月份为后延2个月
    dateComponents.month +=2;
    NSDate * endDayOfNextMonth = [calendar dateFromComponents:dateComponents];
    //两个月后的1号往前推1天,即为下个月最后一天
    endDayOfNextMonth = [endDayOfNextMonth dateByAddingTimeInterval:-1];
    //格式化输出
    NSDateFormatter *formater = [[ NSDateFormatter alloc] init];
    [formater setDateFormat:@"yyyy-MM-dd"];
    NSString * curTime = [formater stringFromDate:endDayOfNextMonth];
    return curTime;
}

///判断是否是今天
+ (BOOL)isToday:(NSString *)dateStr {
    BOOL isDay = NO;
    NSString *day = [NSDate timeStringWithInterval:[NSDate date].timeIntervalSince1970];
    if ([dateStr isEqualToString:day]) {
        isDay = YES;
    }
    return isDay;
}

///判断是否是明天
+ (BOOL)isTomorrow:(NSString *)dateStr {
    BOOL isDay = NO;
    NSTimeInterval time = [NSDate date].timeIntervalSince1970 + 24 * 3600;
    NSString *day = [NSDate timeStringWithInterval:time];
    if ([dateStr isEqualToString:day]) {
        isDay = YES;
    }
    return isDay;
}

///判断是否是后天
+ (BOOL)isAfterTomorrow:(NSString *)dateStr {
    BOOL isDay = NO;
    NSTimeInterval time = [NSDate date].timeIntervalSince1970 + 48 * 3600;
    NSString *day = [NSDate timeStringWithInterval:time];
    if ([dateStr isEqualToString:day]) {
        isDay = YES;
    }
    return isDay;
}

/// 判断是否是过去的时间
+ (BOOL)isHistoryTime:(NSString *)dateStr {
    BOOL activity = NO;
    NSTimeInterval timeInterval = [NSDate timeIntervalFromDateString: dateStr];
    NSTimeInterval currentInterval = [NSDate timeIntervalFromDateString:[NSDate currentDay]];
    if (timeInterval < currentInterval) {
        activity = YES;
    }
    return activity;
}

/// 从时间戳获取具体时间 格式:6:00
+ (NSString *)hourStringWithInterval:(NSTimeInterval)timeInterval {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"H:mm"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
    NSString *dateString = [dateFormatter stringFromDate:date];
    
    return dateString;
}

/// 从时间戳获取具体小时 格式:6
+ (NSString *)hourTagWithInterval:(NSTimeInterval)timeInterval {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"H"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
    NSString *dateString = [dateFormatter stringFromDate:date];
    return dateString;
}

/// 从毫秒级时间戳获取具体小时 格式:600
+ (NSString *)hourNumberWithInterval:(NSTimeInterval)timeInterval {
    NSString *hourStr = [self hourStringWithInterval:timeInterval / 1000];
    NSString *hourNumber = [hourStr stringByReplacingOccurrencesOfString:@":" withString:@""];
    return hourNumber;
}

/// 从时间戳获取具体日期 格式:2018-03-05
+ (NSString *)timeStringWithInterval:(NSTimeInterval)timeInterval {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
    NSString *dateString = [dateFormatter stringFromDate:date];
    return dateString;
}

/// 根据具体日期获取时间戳(毫秒)
+ (NSTimeInterval)timeIntervalFromDateString:(NSString *)dateStr {
    //要精确到毫秒2018-01-01 与 2018-01-01 00:00 都要转换成2018-01-01 00:00:00
    if (dateStr.length == 10) {
        dateStr = [dateStr stringByAppendingString:@" 00:00:00"];
    } else if (dateStr.length == 16) {
        dateStr = [dateStr stringByAppendingString:@":00"];
    }
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    [dateFormatter setLocale:[NSLocale currentLocale]];
    NSDate *date = [dateFormatter dateFromString:dateStr];
    NSTimeInterval interval = [date timeIntervalSince1970] * 1000;
    return interval;
}

/// 获取当前天的后几天的星期
+ (NSString *)getWeekAfterDay:(NSInteger)day {
    NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday) fromDate:[NSDate date]];
    NSInteger currentWeek = components.weekday - 1;
    NSDictionary *weekDic = @{@"0":@"日",@"1":@"一",@"2":@"二",@"3":@"三",@"4":@"四",@"5":@"五",@"6":@"六"};
    NSInteger week = currentWeek + day;
    if (week >= 7) {
        week -= 7;
    }
    NSString *key = [NSString stringWithFormat:@"%ld",(long)week];
    return weekDic[key];
}


/// 获取当前天的后几天的日
+ (NSString *)getDayAfterDay:(NSInteger)day {
    NSTimeInterval time = [NSDate date].timeIntervalSince1970 + 24 * 3600 * day;
    NSString *date = [NSDate timeStringWithInterval:time];
    NSInteger dayNum = [self day:date];
    NSString *dayStr = [NSString stringWithFormat:@"%ld",(long)dayNum];
    return dayStr;
}

/// 获取当前月的后几月
+ (NSString *)getMonthAfterMonth:(NSInteger)Month {
    NSDate *currentDate = [NSDate date];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy-MM"];
    
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents *lastMonthComps = [[NSDateComponents alloc] init];
    //    [lastMonthComps setYear:1]; // year = 1表示1年后的时间 year = -1为1年前的日期,month day 类推
    [lastMonthComps setMonth:Month];
    NSDate *newdate = [calendar dateByAddingComponents:lastMonthComps toDate:currentDate options:0];
    NSString *dateStr = [formatter stringFromDate:newdate];
    return dateStr;
}

@end
2、UI实现
示例页面

接下来讲下页面构成,其他的实现起来都差不多,主要是利用CollectionView

示例页面构成

蓝色星期视图是个独立的View,红色是CollectionView,有自定义的组头UICollectionReusableView和cell

首先要设计个model,满足我们展示视图的需要,比如CalendarModel,这个model属性看情况自定义:

CalendarModel.h:

#import <Foundation/Foundation.h>

//哪一天
typedef NS_ENUM(NSInteger, DayType) {
    Other = 0,
    Today,                    /**< 今天 */
    Tomorrow,                 /**< 明天 */
    AfterTomorrow             /**< 后天 */
};


@interface ReplaceCalendarModel : NSObject

@property (nonatomic, strong) NSString *time;                     /**< 时间戳 */
@property (nonatomic, strong) NSString *year;
@property (nonatomic, strong) NSString *month;
@property (nonatomic, strong) NSString *day;
@property (nonatomic, strong) NSString *week;
@property (nonatomic, assign) DayType dayType;                    /**< 是否是今天,明天,后天 */
@property (nonatomic, assign) BOOL isSelected;                    /**< 是否被选择 */

@end

组头UICollectionReusableView就一个Label就可以了,展示年月,cell就是两个Label,展示日和其他信息,然后根据model来展示组头和cell就可以了,这两个实现起来比较简单,比较复杂的是collectionView的数据源处理,接下来我主要记下这个

首先看页面要展示多少个月的日期,像这个页面展示的是当前月当前日到下一个月月底的时间,在initWithFrame里面初始化CollectionView的数据源:

    //今天的日期为开始和下个月最后一日为结束
    self.dataSource = [NSMutableArray array];
    //当前月
    NSMutableArray *currentMonth = [self createMonthDataInday:[NSDate currentDay]];
    //下个月
    NSMutableArray *nextMonth = [self createMonthDataInday:[NSDate nextMonthLastDay]];
    //初始化数据源的组
    [self.dataSource addObject:currentMonth];
    [self.dataSource addObject:nextMonth];
//根据某一日数据初始化该月数据源
- (NSMutableArray *)createMonthDataInday:(NSString *)dateStr {
    NSMutableArray *monthData = [NSMutableArray array];
    NSString *yearStr = [NSString stringWithFormat:@"%ld",[NSDate year:dateStr]];
    NSString *monthStr = [NSString stringWithFormat:@"%ld",[NSDate month:dateStr]];
    NSString *monthStr02 = [NSString stringWithFormat:@"%02ld",[NSDate month:dateStr]];
    //第一天的星期
    NSInteger firstDayWeek = [NSDate week:[NSString stringWithFormat:@"%@-%@-01",yearStr,monthStr]];
    //如果是当前月,"第一天星期几"就以今天的星期为开始
    if ([NSDate isToday:dateStr]) {
        firstDayWeek = [NSDate week:dateStr];
    }
    //这个月的天数
    NSInteger days = [NSDate daysInMonth:dateStr];
    //每天的数字
    for (int i = 0; i < 42; i++) {
        CalendarModel *model = [[CalendarModel alloc] init];
        model.year = yearStr;
        model.month = monthStr;
        model.dayType = Other;
        //在第一天之前的格子为空
        if (i < firstDayWeek) {
            model.day = @"";
        } else if ( i > days + firstDayWeek - 1) {
            //最后一天之后的,不添加进数据源
            continue;
        } else {
            //其他的都是有效数据
            model.day = [NSString stringWithFormat:@"%ld", i - firstDayWeek + 1];
            model.date = [NSString stringWithFormat:@"%@-%@-%@",yearStr,monthStr02,[NSString stringWithFormat:@"%02ld", i - firstDayWeek + 1]];
            //如果是过去的时间,就跳出循环,不添加进数据源
            if ([NSDate isHistoryTime:model.date]) {
                continue;
            }
            //今天明天后天
            if ([NSDate isToday:model.date]) {
                model.dayType = Today;
                model.isSelected = YES;
            } else if ([NSDate isTomorrow:model.date]) {
                model.dayType = Tomorrow;
            } else if ([NSDate isAfterTomorrow:model.date]) {
                model.dayType = AfterTomorrow;
            }
        }
        [monthData addObject:model];
    }
    
    return monthData;
}
3、第三方库推荐

FSCalendar

4、值得注意的坑

推荐阅读更多精彩内容