使用 UICollectionView 實現日曆簽到功能

来源:http://www.cnblogs.com/theDesertIslandOutOfTheWorld/archive/2016/03/02/5235016.html
-Advertisement-
Play Games

使用實例詳細闡述 日曆簽到功能 的實現方案,及 項目文件結構的設置方法


概述


在 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;
    }

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 先看下onBackPressed和onKeyDown的區別 在Android上有兩種方法來獲取該按鈕的事件 1.直接獲取按鈕按下事件,此方法相容Android 1.0到Android 2.1 也是常規方法,直接重寫Activity的onKeyDown方法即可,代碼如下: @Override publ
  • 帶你走進游戲開發的世界之游戲幀動畫的處理<ignore_js_op> 1.幀動畫的原理 幀動畫幀動畫顧名思義,一幀一幀播放的動畫就是幀動畫。 幀動畫和我們小時候看的動畫片的原理是一樣的,在相同區域快速切換圖片給人們呈現一種視覺的假象感覺像是在播放動畫,其實不過是N張圖片在一幀一幀的切換罷了。 如圖所
  • 很多時候,AFNetworking都是目前iOS開發者網路庫中的不二選擇。Github上2W+的star數足見其流行程度。而從iOS7.0開始,蘋果推出了新的網路庫繼承者NSURLSession後,AFNetworking也毫不猶豫地加入了對其的支持。3.0+更加只是提供了NSURLSession的
  • 上一篇亂說了一陣socket,這篇要說說怎麼幹活了。畢竟用過的起來才行。 我的項目裡面使用的是CocoaAsyncSocket,這個是對CFSocket的封裝。如果你覺得自己可以實現封裝或者直接用原生的,我可以告訴你,很累;關鍵是等你弄出來,項目可能都要交了。這個庫,支持TCP和UDP;有GCD和R
  • - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ static BOOL showFlag = NO; if (!showFlag) { XZHomeViewController *home =
  • 由於項目需要root安裝軟體,並且希望在合適的時候引導用戶去開啟root安裝,故需要檢測手機是否root。 最基本的判斷如下,直接運行一個底層命令。(參考https://github.com/Trinea/android-common/blob/master/src/cn/trinea/androi
  • 我以前寫過不少建議文章,學生時代寫過怎麼學習填鴨,畢業後寫過怎麼學習投資交易,最近寫過怎麼學習iOS開發。 寫的這些建議文章都有一個共同的毛病,建議多而全,使得看得人覺得難而累。 這次的建議,我儘量寫得簡化一點。 1、iOS開發學習iOS開發把我的工資提升了6倍多。而且,即使提升到16倍,我也不覺得
  • 前言:你用過Eclipse快捷鍵 Alt + Shift + J 麽?你看過源碼麽?如果看過,你註意過源碼上面的註釋麽?你知道為什麼看源碼註釋有些標識的參數可以直接點擊跳轉麽? 先出個題目,定義一個最簡單的Person類,三個屬性,一個name,一個age,一個性別,一個帶所有屬性參數的構造函數,你
一周排行
    -Advertisement-
    Play Games
  • 一:背景 準備開個系列來聊一下 PerfView 這款工具,熟悉我的朋友都知道我喜歡用 WinDbg,這東西雖然很牛,但也不是萬能的,也有一些場景他解決不了或者很難解決,這時候藉助一些其他的工具來輔助,是一個很不錯的主意。 很多朋友喜歡在項目中以記錄日誌的方式來監控項目的流轉情況,其實 CoreCL ...
  • 本來閑來無事,準備看看Dapper擴展的源碼學習學習其中的編程思想,同時整理一下自己代碼的單元測試,為以後的進一步改進打下基礎。 突然就發現問題了,源碼也不看了,開始改代碼,改了好久。 測試Dapper.LiteSql數據批量插入的時候,耗時20秒,感覺不正常,於是我測試了非Dapper版的Lite ...
  • 需求如下,在DEV框架項目中,需要在表格中增加一列顯示圖片,並且能編輯該列圖片,然後進行保存等操作,最終效果如下 這裡使用的是PictureEdit控制項來實現,打開DEV GridControl設計器,在ColumnEdit選擇PictureEdit: 綁定圖片代碼如下: DataTable dtO ...
  • 前兩天微軟偷偷更新了Visual Studio 2022 正式版版本 17.3 發佈,發佈摘要: MAUI 工作負荷 GA 生成 MAUI/Blazor CSS 熱重載支持 現在,你將能夠使用我們的新增功能在 Visual Studio 中使用每個更新試用一系列新功能。 選擇每個功能以瞭解有關特定功 ...
  • 航天和軍工領域的數字化轉型和建設正在積極推進,在與航天二院、航天三院、航天六院、航天九院、無線電廠、兵工廠等單位交流的過程中,用戶更聚焦試驗和生產過程中的痛點,迫切需要解決軟體平臺統一監測和控制設備及軟體與設備協同的問題。 ...
  • .NET 項目預設情況下 日誌是使用的 ILogger 介面,預設提供一下四種日誌記錄程式: 控制台 調試 EventSource EventLog 這四種記錄程式都是預設包含在 .NET 運行時庫中。關於這四種記錄程式的詳細介紹可以直接查看微軟的官方文檔 https://docs.microsof ...
  • 一:背景 上一篇我們聊到瞭如何去找 熱點函數,這一篇我們來看下當你的程式出現了 非托管記憶體泄漏 時如何去尋找可疑的代碼源頭,其實思路很簡單,就是在 HeapAlloc 或者 VirtualAlloc 時做 Hook 攔截,記錄它的調用棧以及分配的記憶體量, PerfView 會將這個 分配量 做成一個 ...
  • 背景 在 CI/CD 流程當中,測試是 CI 中很重要的部分。跟開發人員關係最大的就是單元測試,單元測試編寫完成之後,我們可以使用 IDE 或者 dot cover 等工具獲得單元測試對於業務代碼的覆蓋率。不過我們需要一個獨立的 CLI 工具,這樣我們才能夠在 Jenkins 的 CI 流程集成。 ...
  • 一、應用場景 大家在使用Mybatis進行開發的時候,經常會遇到一種情況:按照月份month將數據放在不同的表裡面,查詢數據的時候需要跟不同的月份month去查詢不同的表。 但是我們都知道,Mybatis是ORM持久層框架,即:實體關係映射,實體Object與資料庫表之間是存在一一對應的映射關係。比 ...
  • 我國目前並未出台專門針對網路爬蟲技術的法律規範,但在司法實踐中,相關判決已屢見不鮮,K 哥特設了“K哥爬蟲普法”專欄,本欄目通過對真實案例的分析,旨在提高廣大爬蟲工程師的法律意識,知曉如何合法合規利用爬蟲技術,警鐘長鳴,做一個守法、護法、有原則的技術人員。 案情介紹 深圳市快鴿互聯網科技有限公司 2 ...