• 使用 UICollectionView 实现日历签到功能


    概述


    在 App 中,日历通常与签到功能结合使用。是提高用户活跃度的一种方式,同时,签到数据中蕴含了丰富的极其有价值的信息。下面我们就来看看如何在 App 中实现日历签到功能。
    

    效果图


    。。。。。

    思路分析


    实现日历签到功能的思路有很多种,这里我们就使用 UICollectionView 来实现具备签到功能的日历
    
    • 基础知识
      • 关于瀑布流(若你对 UICollectionView 及 UICollectionViewLayout 的知识不太了解,请参照以下文章)

      • 关于布局(使用 CocoaPods 集成第三方框架 Masonry 进行界面布局,若你对 CocoaPods 的使用不太了解,请参照以下文章)

      • 关于 pch 文件的配置方法,请参照以下文章

      • 关于项目的文件结构

          在应用开发过程中,我们通常会指定一定的文件结构,便于项目的开发和维护。该博客中的文件结构如图:
        

        • 图中一级文件结构的的划分是按照应用的业务模块划分,如:
          • Sections:应用的功能模块
          • Network:应用的网络请求模块
          • Common:应用中通用的文件,通常是自定义控件
          • Macro:应用中的宏
          • Category:应用中的分类
          • Other:其他,如:Appdelegate,main,pch 等文件
        • 图中二级文件结构的的划分是按照应用的功能模块划分,如:
          • Mine:是一个项目中,通常具备的功能模块
        • 图中三级级文件结构的的划分是按照 MVC 架构模式划分,如:
          • Model
          • View
          • Controller
    • 思路
      • 显示“日历”所需要的数据(使用 NSDate 的分类提供)
        • 当前月总共有多少天,即:“日历” CollectionView 中 Item 的数量
        • 当前月中共有多少周,即:“日历” CollectionView 的行数
        • 当前月中第一天在一周内的索引,即:实现“日历”中的每一天与周几信息对应
        • 当天在当月中的索引(第几天),即:点击“签到”按钮时,通过索引找到“日历” CollectionView 中的 Item 更新 “签到”标签的状态
      • “日历”布局(使用 IDCalendarCollectionViewFlowLayout 定义)
        • 在这里使用自定义流水布局来设置 “日历” CollectionView 的布局(使用 frame 布局 每一个 Item)
      • “日历” 控件
        • 整个日历控件(IDCalendarSignInView)中封装了以下控件
          • calendarDateView:显示日期,如:2016年03月
          • calendarWeekdayView:显示周几信息,如:日 一 二 …… 六
          • calendarCollectionView:显示日历
          • seperatorView:分割线
        • 子控件的布局
          • 在这里,统一在 layoutSubviews 方法中布局子控件(此时获取到的 IDCalendarSignInView 控件的 frame 才是最终确定的)。这一个规范,尽量遵守,可以避免很多难以调试的 bug

    具体实现


    • 声明

      • 此部分主要阐述代码实现,代码中有详细的注释,若对以上思路不太理解,可以结合代码整理自己的思路
      • 由于篇幅限制,在这里,不再贴出应用实现的全部代码。若有需要的朋友,请联系我,我将提供完整的工程文件。感谢您的理解和支持,您的支持是我坚持下去最大的动力,真心的谢谢你们。以下是我的 Blog 地址:
    • 提供显示“日历”所需要的数据( NSDate+Calculate 文件 )

      • 获取当前月总共有多少天

        + (NSInteger)numberOfDaysInCurrentMonth {
            // 初始化日历
            NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
            // 获取系统当前日期
            NSDate *currentDate = [NSDate date];
            // 获取当前日期中当前月中天的范围
            NSRange range = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:currentDate];
            // 得到当前月中总共有多少天(即范围的长度)
            NSInteger numberOfDaysInCurrentMonth = range.length;
            return numberOfDaysInCurrentMonth;
        }
        
      • 获取当前月中共有多少周

        + (NSInteger)numberOfWeeksInCurrentMonth {
            // 初始化日历
            NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
            // 获取系统当前日期
            NSDate *currentDate = [NSDate date];
            // 获取当前日期中当前月中周的范围
            NSRange range = [calendar rangeOfUnit:NSCalendarUnitWeekOfMonth inUnit:NSCalendarUnitMonth forDate:currentDate];
            // 得到当前月中总共有多少周(即范围的长度)
            NSInteger numberOfWeeksInCurrentMonth = range.length;
            return numberOfWeeksInCurrentMonth;
        }
        
    • 自定义流水布局( IDCalendarCollectionViewFlowLayout 文件 )

      • 设置每一个 Item 的布局

        /** 设置 indexPath 位置的 Item 的布局属性 */
        - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
            // 获取 indexPath 位置的布局属性
            UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
            // 计算每一个 Item 的 frame
            CGFloat collectionViewLeftPadding = self.collectionView.contentInset.left;
            CGFloat collectionViewRightPadding = self.collectionView.contentInset.right;
            // size
            CGFloat itemWidth = (self.collectionView.bounds.size.width - collectionViewLeftPadding - collectionViewRightPadding - 5*IDCalendarCollectionViewCellHerizontalMargin) / 7.0;
            CGFloat itemHeigh = self.collectionView.bounds.size.height / [NSDate numberOfWeeksInCurrentMonth];
            // origin
            CGFloat itemX = ((self.indexOfWeekForFirstDayInCurrentMonth + indexPath.item) % 7) * (itemWidth + IDCalendarCollectionViewCellHerizontalMargin);
            CGFloat itemY = ((self.indexOfWeekForFirstDayInCurrentMonth + indexPath.item) / 7) * itemHeigh;
            layoutAttributes.frame = CGRectMake(itemX, itemY, itemWidth, itemHeigh);
            // 返回 indexPath 位置的 Item 的布局属性
            return layoutAttributes;
        }
        
    • 自定义“日历” CollectionView 的 Cell( IDCalendarCollectionViewCell 文件 )

      • 提供接口,用于控制签到标签的状态

        /** 是否已经签到 */
        @property (nonatomic, assign) BOOL haveSignedIn;
        /** 重写 set 方法,用于更新 “签到” 标签的状态 */
        - (void)setHaveSignedIn:(BOOL)haveSignedIn {
            _haveSignedIn = haveSignedIn;
            if (_haveSignedIn) {
                self.signInLabel.hidden = NO;
            } else {
                self.signInLabel.hidden = YES;
            }
        }
        
      • 添加子控件

        - (instancetype)initWithFrame:(CGRect)frame {
            if (self = [super initWithFrame:frame]) {
                // 初始化 “日期数字” label,并添加到 cell 中
                self.dateLabel = [[UILabel alloc] init];
                self.dateLabel.textColor = [UIColor colorWithRed:122/255.0 green:122/255.0 blue:122/255.0 alpha:1.0];
                [self.contentView addSubview:self.dateLabel];
                // 初始化 “签到” label,并添加到 cell 中
                self.signInLabel = [[UILabel alloc] init];
                self.signInLabel.hidden = YES; // 默认隐藏“签到”标签
                self.signInLabel.textColor = [UIColor colorWithRed:228/255.0 green:49/255.0 blue:42/255.0 alpha:1.0];
                self.signInLabel.font = [UIFont systemFontOfSize:10];
                self.signInLabel.text = @"签到";
                [self.contentView addSubview:self.signInLabel];
            }
            return self;
        }
        
    • 自定义“日历签到”控件( IDCalendarSignInView 文件)

      • 添加子控件

        /** 设置 “日期” 部分 */
        - (void)setupCalendarDateView {
            // 初始化 “日期” View,并添加到 IDCalendarSignInView
            self.calendarDateView = [[UIView alloc] init];
            [self addSubview:self.calendarDateView];
            // 初始化分割线 并添加到 “日期” View
            self.dateSeperatorView = [[UIView alloc] init];
            self.dateSeperatorView.backgroundColor = [UIColor colorWithRed:226/255.0 green:226/255.0 blue:226/255.0 alpha:1.0];
            [self.calendarDateView addSubview:self.dateSeperatorView];
            // 初始化日期 label 并添加到 “日期” View
            self.calendarDateLabel = [[UILabel alloc] init];
            self.calendarDateLabel.font = [UIFont systemFontOfSize:15];
            NSDate *currentDate = [NSDate date];
            NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
            dateFormatter.dateFormat = @"yyyy年MM月";
            NSString *dateString = [dateFormatter stringFromDate:currentDate];
            self.calendarDateLabel.text = dateString;
            [self.calendarDateView addSubview:self.calendarDateLabel];
        }
        /** 设置 “周几” 部分 */
        - (void)setupCalendarWeekdayView {
            // 初始化 “日期” View,并添加到 IDCalendarSignInView
            self.calendarWeekdayView = [[UIView alloc] init];
            [self addSubview:self.calendarWeekdayView];
            // 初始化分割线 并添加到 “周几” View
            self.weekdaySeperatorView = [[UIView alloc] init];
            self.weekdaySeperatorView.backgroundColor = [UIColor colorWithRed:226/255.0 green:226/255.0 blue:226/255.0 alpha:1.0];
            [self.calendarWeekdayView addSubview:self.weekdaySeperatorView];
            // 初始化 “周几” label 并添加到 “周几” View
            NSArray *weekday = @[@"日", @"一", @"二", @"三", @"四", @"五", @"六"];
            for (NSInteger i = 0; i < 7; i++) {
                UILabel *weekDayLabel = [[UILabel alloc] initWithFrame:CGRectZero];
                weekDayLabel.textAlignment = NSTextAlignmentCenter;
                weekDayLabel.font = [UIFont systemFontOfSize:13];
                weekDayLabel.textColor = [UIColor colorWithRed:97/255.0 green:97/255.0 blue:97/255.0 alpha:1.0];
                weekDayLabel.text = weekday[i];
                // 将 “周几” 信息保存在成员变量中
                [self.weekdayLabelArray addObject:weekDayLabel];
                [self.calendarWeekdayView addSubview:weekDayLabel];
            }
        }
        /** 设置 “日历” 部分 */
        - (void)setupCalendarCollectionView {
            // 设置 "日历" 的布局
            IDCalendarCollectionViewFlowLayout *flowLayout = [[IDCalendarCollectionViewFlowLayout alloc] init];
            flowLayout.headerReferenceSize = CGSizeMake(self.bounds.size.width, 20);
            // 初始化 “日历” CollectionView,设置相关属性,并添加到 IDCalendarSignInView
            self.calendarCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:flowLayout];
            self.calendarCollectionView.backgroundColor = [UIColor whiteColor];
            self.calendarCollectionView.scrollEnabled = NO;
            self.calendarCollectionView.dataSource = self;
            self.calendarCollectionView.delegate = self;
            [self.calendarCollectionView registerClass:[IDCalendarCollectionViewCell class] forCellWithReuseIdentifier:IDCalendarCollectionViewCellIdentifier];
            [self addSubview:self.calendarCollectionView];
            // 初始化分割线 并添加到 “日历” View
            self.collectionViewSeperatorArray = [NSMutableArray array];
            for (NSInteger i = 0; i < [NSDate numberOfWeeksInCurrentMonth]; i++) {
                UIView *collectionViewSeperator = [[UIView alloc] initWithFrame:CGRectZero];
                collectionViewSeperator.backgroundColor = [UIColor colorWithRed:226/255.0 green:226/255.0 blue:226/255.0 alpha:1.0];
                [self.collectionViewSeperatorArray addObject:collectionViewSeperator];
                [self.calendarCollectionView addSubview:collectionViewSeperator];
            }
            // 设置“日历” View 的内边距
            self.calendarCollectionView.contentInset = UIEdgeInsetsMake(0, 15, 0, 15);
        }
        
      • 布局子控件

        - (void)layoutSubviews {
            [super layoutSubviews];
            // “日期”
            [self.calendarDateView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.leading.trailing.equalTo(self);
                make.top.equalTo(self.mas_top);
                make.height.offset(35);
            }];
            // “周几”
            [self.calendarWeekdayView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.leading.trailing.equalTo(self);
                make.top.equalTo(self.calendarDateView.mas_bottom);
                make.height.offset(38);
            }];
            // “日历”
            [self.calendarCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(self.calendarWeekdayView.mas_bottom);
                make.leading.trailing.bottom.equalTo(self);
            }];
            // “日期” 部分的分割线
            [self.dateSeperatorView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.leading.trailing.top.equalTo(self.calendarDateView);
                make.height.offset(1);
            }];
            // 周几信息
            for (NSInteger i = 0; i < 7; i++) {
                self.weekdayLabelArray[i].frame = CGRectMake(i * (self.calendarCollectionViewItemSize.width + 10) + 15, 0, self.calendarCollectionViewItemSize.width, 35);
            }
            // “周几” 部分的分割线
            [self.weekdaySeperatorView mas_makeConstraints:^(MASConstraintMaker *make) {
                make.leading.top.trailing.equalTo(self.calendarWeekdayView);
                make.height.offset(1);
            }];
            // “日历” 显示日期的 label
            [self.calendarDateLabel mas_makeConstraints:^(MASConstraintMaker *make) {
                make.centerX.centerY.equalTo(self.calendarDateView);
            }];
            // “日历” 部分的分割线
            for (NSInteger i = 0; i < [NSDate numberOfWeeksInCurrentMonth]; i++) {
                self.collectionViewSeperatorArray[i].frame = CGRectMake(0, i * self.calendarCollectionViewItemSize.height, [UIScreen mainScreen].bounds.size.width - 30, 1);
            }
        }
        
      • 提供“日历” CollectionView 的数据源

        - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
            return [NSDate numberOfDaysInCurrentMonth];
        }
        - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
            IDCalendarCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:IDCalendarCollectionViewCellIdentifier forIndexPath:indexPath];
            cell.numberTextInDateLabel = indexPath.item + 1;
            return cell;
        }
        
    • “签到”按钮( IDConfirmButton 文件)

      • 类似于“签到”按钮这样外观的按钮,是应用中比较常见的按钮,如:“确定”、“取消”按钮通常也是这种外观,通常我们将这样的按钮写成一个通用的空间,放到 Common 文件中

      • 未指定拉伸区域时的效果

      • 指定拉伸区域后的效果

      • 自定义 IDConfirmButton

        - (instancetype)initWithFrame:(CGRect)frame {
            if (self = [super initWithFrame:frame]) {
                [self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
                [self setBackgroundImage:[UIImage resizedImageWithName:@"btn_normal"] forState:UIControlStateNormal];
                [self setBackgroundImage:[UIImage resizedImageWithName:@"btn_highlight"] forState:UIControlStateHighlighted];
                [self setBackgroundImage:[UIImage resizedImageWithName:@"btn_disable"] forState:UIControlStateDisabled];
                self.layer.cornerRadius = 5.0;
                self.layer.masksToBounds = YES;
            }
            return self;
        }
        
      • IDConfirmButton 的图片,若提供的图片的尺寸,不等于按钮的尺寸,图片就会被拉伸。当图片具有圆角是,拉伸后的图片通常不复合需求,所以我们需要指定图片的拉伸区域(UIImage+Resizing)

        /** 返回一张图片,按指定方式拉伸的图片:width * 0.5 : height * 0.5 */
        + (UIImage *)resizedImageWithName:(NSString *)name {
            UIImage *image = [UIImage imageNamed:name];
            return [image stretchableImageWithLeftCapWidth:image.size.width * 0.5 topCapHeight:image.size.height * 0.5];
        }
        

    添加“日历签到”控件


    • 将“日历签到”控件添加到控制器的 view 上

      - (void)viewDidLoad {
          [super viewDidLoad];
          // 添加“日历签到”视图
          self.calendarSignInView = [[IDCalendarSignInView alloc] init];
          [self.view addSubview:self.calendarSignInView];
          // 添加“签到”按钮
          self.signInButton = [[IDConfirmButton alloc] init];
          [self.signInButton setTitle:@"签到" forState:UIControlStateNormal];
          [self.signInButton addTarget:self action:@selector(signInButtonClick:) forControlEvents:UIControlEventTouchUpInside];
          [self.view addSubview:self.signInButton];
      }
      
    • 点击“签到”按钮,更新签到标签的状态

      - (void)signInButtonClick:(UIButton *)button {
          self.calendarSignInView.isSignInForToday = YES;
      }
      
  • 相关阅读:
    用存储过程实现二进制字段的更新
    在Google 上搜书的方法 (转)
    Oracle开发中,关于查询的一个问题
    北京公司招聘 .net开发人员
    UIAlertView的使用方法
    FMEFB开始上传
    应用中的安全管理方案
    使用NHibernate时需要考虑的另一个问题
    .net使用Com组件的问题
    使用NHibernate时,如何提高访问Oracle数据库(ODP.net)的性能
  • 原文地址:https://www.cnblogs.com/theDesertIslandOutOfTheWorld/p/5235016.html
Copyright © 2020-2023  润新知