您好,登錄后才能下訂單哦!
1、程序結構:
千言萬語,不如一張圖來的清晰
1) 由于CalendarController包含了一個UITableView指針,因此CalendarController需要實現UITableDataSource以及UITableViewDelegate與UITableView進行交互。 2) UITableView包含多個CalendarView,這樣就能利用UITableView的手勢滑動功能以及Cell重用功能。 3) CalendarView繼承自UIControl,因為UIControl將相對底層的觸摸事件轉換為容易操作的控件事件。主要為了使用UIControlEventTouchUpInside這個事件。 4) CalendarDelegate仿照ios mvc模式,用于類之間的解耦(面向接口編程)以及類之間的通信。
2、 CalendarDelegate 協議:
@protocol CalendarDelegate <NSObject> //年月和UITableView以及其中CalendarView之間關系映射 //具體見下面代碼分析 -(int) calcCalendarCount; -(SDate) mapIndexToYearMonth : (int) index; -(int) mapYearMonthToIndex : (SDate) date; //用于顯示到指定的年月范圍 -(void) showCalendarAtYearMonth : (SDate) date; //用于時間期限管理以及選中判斷 -(BOOL) isInSelectedDateRange : (SDate) date; -(void) setSelectedDateRangeStart : (SDate) start end : (SDate) end; -(void) setEndSelectedDate : (SDate) end; //迫使整個UITableView重繪 -(void) repaintCalendarViews; //計數器,用于判斷touch次數 -(void) updateHitCounter; -(int) getHitCounter; @end
3、 CalendarController:
//.h文件接口聲明 #import <UIKit/UIKit.h> @interface ViewController :UIViewController @end
//.m文件 #import "ViewController.h" #import "CalendarDelegate.h" #import "CalendarView.h" //實現了如下三個delegate @interface ViewController () <UITableViewDataSource,UITableViewDelegate,CalendarDelegate> { //用于計算出多少個月歷,具體見下面代碼 int _startYear; int _endYear; //每個月歷控件的高度,上面的個數和此地的高度,就可以計算整個UITableView的高度以及進行定位操作 float _calendarHeight; //用于選中操作時候,時間范圍的比較(time_t實際是個64位的整型值,適合做比較操作,具體看實現代碼) time_t _startTime; time_t _endTime; } //選中值的年月表示方式,方便顯示而已,實際操作都轉換成time_t類型 @property (nonatomic,assign) SDate begDate; @property (nonatomic,assign) SDate endDate; //點擊計數器,用于確定當前點擊的奇偶性,因此改月歷控件涉及兩次操作,用于區域選者 @property (nonatomic) int hitCounter; //作為Calendar的父容器,用于處理滑動以及cell重用 @property (weak, nonatomic) IBOutlet UITableView *tableView; @end
-(int) calcCalendarCount { SDate date; date_get_now(&date); //計算出當前的年月到n年前的1月份的月數 //加設當前為2016年8月,n為5,則月份范圍為[2011年1月---2016年8月 總計月數為68],具體算法如下: int diff = _endYear - _startYear + 1; diff = diff * 12; diff -= 12 - date.month; return diff; } //UITableView的DatatSource有個必須實現的協議函數,用于返回當前UITableView可以容納的總數: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { int ret = [self calcCalendarCount]; return ret; }
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { //返回的是當前的calendarView的高度 //UITableView需要知道月歷(月份)的個數以及月歷控件的高度,就可以計算出整個UITableView的Content的height了 return _calendarHeight; }
千言萬語,不如再來一張圖來的清晰
-(SDate) mapIndexToYearMonth : (int) index { SDate ret; //調用c函數,將索引號映射成年月,用于UITableView創建calendarView時現實月歷標題 date_map_index_to_year_month(&ret, _startYear, index); return ret; } //調用mapIndexToYearMonth: - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString* calendarID = @"calendarID"; float width = self.tableView.frame.size.width; //從行索引號映射到年月 SDate date = [self mapIndexToYearMonth:(int)indexPath.row]; //獲取重用的cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:calendarID ]; //如果為null,說明不存在,創建該cell if(cell == nil) { //可以在此斷點,查看一下具體生成了多少個calendarView(我這里生成了3個) //說明UITableView可見rect有三個calendarView相交 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:calendarID]; [cell setTag:10]; //手動創建CalendarView CalendarView* calendarView = [[CalendarView alloc] initWithFrame:CGRectMake(0, 0, width, _calendarHeight)]; //設置CalednarDelegate calendarView.calendarDelegate = self; //給定一個tag號,用于重用時獲得該view [calendarView setTag:1000]; [cell.contentView addSubview:calendarView]; } //通過tag號,獲取view CalendarView* view =(CalendarView*) [cell.contentView viewWithTag:1000]; //設置CalendarView的年月 [view setYearMonth:date.year month:date.month]; //[view setNeedsDisplay]; return cell; } -(int) mapYearMonthToIndex:(SDate)date { int yearDiff = date.year - _startYear; int index = yearDiff * 12; index += date.month; index -= 1; return index; } //調用mapYearMonthToIndex -(void) showCalendarAtYearMonth:(SDate)date { if(date.year < _startYear || date.year > _endYear) return; //將年月表示映射成UITableView中的索引號,根據索引計算出要滾動到的目的地 int idx = [self mapYearMonthToIndex:date]; //如上圖所示:當idx = calendarViews.length-1時,可能存在超過整個UITableView ContentSize.height情況,此時,UITableView會自動調整contentOffset的值,使其符合定位到最底端,android listview也是如此。 self.tableView.contentOffset = CGPointMake(0.0F, idx * _calendarHeight ); }
1) 從上圖以及代碼,應該很清楚的了解了映射和定位問題的過程 2) 從上圖中,我們也可以了解到UITableView的滾動原理,UITableView的Frame是Clip區域,滾動的內容存放于Content中。 3) UITableView可以說是移動開發中最常用,最重要的一個控件(還有一個是UICollectionView)。有兩個主要功能點:滾動(UIScrollView父類)和cell復用。以后有機會我們來從頭到尾實現一個帶有上述功能的控件。
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //獲取當前的年月 SDate date; date_get_now(&date); //default有3年 _startYear = date.year-3; _endYear = date.year; /* //當年也支持 _startYear = date.year; _endYear = date.year; */ //touch 計數器 _hitCounter = 0; float scale = 0.6F;//硬編碼,最好由外部設置 //float scale = 0.5F;//硬編碼,最好由外部設置 _calendarHeight = self.tableView.frame.size.height * scale; self.tableView.dataSource = self; self.tableView.delegate = self; //default定位顯示當前月份 if (self.begDate.year == 0) { self.begDate = date; [self showCalendarAtYearMonth:date]; }else{ //當然你也可以設置具體月份重點顯示 [self showCalendarAtYearMonth:self.begDate]; } }
到目前為止,支持CalendarController運行的所有方法都分析完畢,接下來我們要看一下CalendarView相關的實現。(CalendarDelegate還有一些方法沒分析,因為這些方法是由CalendarView調用的,由此可見,IOS中的Delegate除了面向接口編程外,還有一個功能就是類之間的通信)
4、 CalendarView:
//.h文件 @interface CalendarView : UIControl -(void) setYearMonth : (int) year month : (int) month; @property (weak, nonatomic) id calendarDelegate; @end
//.m文件 @interface CalendarView() { /* blf: 引用c結構,所有月歷相關操作委托給SCalendar的相關函數 SCalendar 使用棧內存分配 */ SCalendar _calendar; //這是一個很重要的變量,具體源碼中說明 int _lastMonthDayCount; //存放月歷的日期和星期字符串 NSMutableArray* _dayAndWeekStringArray; //string繪制時的大小 CGSize _dayStringDrawingSize; CGSize _weekStringDrawingSize; }
- (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code //年月區塊和星期區塊的大小按當前view高度的比例來設定 float yearMonthHeight = frame.size.height * 0.095F; float weekHeight = frame.size.height * 0.089F; //初始化月歷控件,計算出各個區塊部分的大小 calendar_init(&_calendar, frame.size, yearMonthHeight, weekHeight); SDate date = _calendar.date; //此時date是上個月 date_get_prev_month(&date, 1); self.backgroundColor = [UIColor clearColor]; //設置日期區塊的大小 CGRect rc; calendar_get_day_cell_rect(&_calendar,&rc,0,0); CGSize size; size.height = rc.size.height- 15 ; size.width = rc.size.width - 15; //預先分配38個字符串容量的數組 _dayAndWeekStringArray = [NSMutableArray arrayWithCapacity:38]; //0--30表示最多31天日期字符串 for(int i = 0; i < 31; i++) [_dayAndWeekStringArray addObject: [NSString stringWithFormat:@"%02d",i+1]]; //31--37存儲星期字符串 [_dayAndWeekStringArray addObject:@"周日"]; [_dayAndWeekStringArray addObject:@"周一"]; [_dayAndWeekStringArray addObject:@"周二"]; [_dayAndWeekStringArray addObject:@"周三"]; [_dayAndWeekStringArray addObject:@"周四"]; [_dayAndWeekStringArray addObject:@"周五"]; [_dayAndWeekStringArray addObject:@"周六"]; //計算出日期字符串的繪制用尺寸 _dayStringDrawingSize = [self getStringDrawingSize: [_dayAndWeekStringArray objectAtIndex:0]]; //計算出星期字符串的繪制用尺寸 _weekStringDrawingSize = [self getStringDrawingSize: [_dayAndWeekStringArray objectAtIndex:31]]; //UIControl基于控件的事件處理系統,掛接UIControlEventTouchUpInside處理程序 [self addTarget:self action:@selector(handleTouchEvent:forEvent:) forControlEvents:UIControlEventTouchUpInside]; } return self; } //計算要繪制字符串的尺寸的函數如下: -(CGSize) getStringDrawingSize:(NSString*)str { NSAttributedString* attStr = [[NSAttributedString alloc] initWithString:str]; NSRange range = NSMakeRange(0, attStr.length); NSDictionary* dic = [attStr attributesAtIndex:0 effectiveRange:&range]; CGRect rect = [str boundingRectWithSize:CGSizeMake(0, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:dic context:nil]; return rect.size; }
從上面類聲明和初始化代碼,引出幾個問題:
1)為什么繼承自UIControl? 2)為什么delegate使用weak? 3)為什么delegate 聲明為id? 4)為什么棧分配? 5)為什么同一個CalendarView的類聲明需要分別在.h和.m文件中,或者換種說法:這樣做有什么好處? 6)為什么初始化只實現了initWithFrame,沒有實現initWithCoder,在哪種情況下,還需要override initWithCoder函數?
-(void) drawStringInRectWithSize : (NSString*) string rect:(CGRect)rect size:(CGSize) size color : (UIColor*) color { CGPoint pos; //下面算法是讓文字位于要繪制的Rect的水平和垂直中心 //也就是劇中對齊 pos.x = (rect.size.width - size.width) * 0.5F; pos.y = (rect.size.height - size.height) * 0.5F; pos.x += rect.origin.x; pos.y += rect.origin.y; //由于周日和周六與平常文字顏色有差別,因此需要color NSDictionary * attsDict = [NSDictionary dictionaryWithObjectsAndKeys: color, NSForegroundColorAttributeName, nil ]; [string drawAtPoint:pos withAttributes:attsDict]; }
1) opengles API 利用gpu加速,速度最快,難度相對最大,自由度也最高,需要創建專用的GL上下文環境。基于狀態機模式,需要設置各種繪制狀態以及恢復狀態。最重要的是跨平臺,android以及windows,Linux都可以用(cocos2d-x基于opengles)。 2) quartz API 使用cpu光柵化,不需要GL上下文環境,直接可在控件表面進行繪制,相對底層,基于狀態機模式,需要設置各種繪制狀態以及恢復狀態 3) UIKit中對quartz API的二次封裝,例如UIBezierPath類,封裝了大部分的shape,方便易用,我們就用這個類來進行繪制。上面兩種API,以后有機會我們可以專門來分析一下。
圓的貝塞爾路徑對象(由圓心和半徑定義):
-(void) drawCircleInRect : (CGRect) rect color : (UIColor*) color isFill : (BOOL) isFill { //取width和height最小的值作為要繪制的圓的直徑,這樣就不會將圓繪制范圍超出rect float radiu = rect.size.width < rect.size.height ? rect.size.width : rect.size.height; //將圓的中心點從rect的左上角平移到rect的中心點 CGPoint center; center.x = rect.origin.x + rect.size.width * 0.5F; center.y = rect.origin. y + rect.size.height * 0.5F; //圓是由圓心和半徑定義的 radiu *= 0.5F; //創建一個圓的bezier路徑對象 UIBezierPath* circle = [UIBezierPath bezierPathWithArcCenter:center radius:radiu startAngle:0.0F endAngle:2.0F*3.1415926F clockwise:true]; //填充繪制(日期選中狀態) if(isFill == YES) { [color setFill]; [circle fill]; } else { //沒選中狀態,用stroke方式繪制 [color setStroke]; [circle stroke]; } }
圓角矩形的貝塞爾對象(由Rect和半徑定義):
-(void) drawRoundRect : (CGRect) rect radius : (CGFloat)radius { UIBezierPath* roundRect = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; [[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] setFill]; [roundRect fill]; }
代碼很長,我們拆分成幾個區塊來分析
//1、見上圖,繪制年月信息 //blf:獲取原生繪圖context指針,所有原生繪圖api都是c語言api方式 //CGContextRef context = UIGraphicsGetCurrentContext(); CGRect rc; calendar_get_year_month_section_rect(&_calendar, &rc); //NSString* drawStr = @" " + _calendar.date.year + @"年" + _calendar.date.month + @"月"; NSString* drawStr = [NSString stringWithFormat:@"%d年%d月",_calendar.date.year,_calendar.date.month]; //繪制年月信息 [self drawYearMonthStr:drawStr rect:rc];
//2、見上圖,繪制星期信息 //_dayAndWeekStringArray中31-37索引保存的是星期字符串 for(int i= 0; i < 7; i++) { //獲取星期區塊中某個cell的rect calendar_get_week_cell_rect(&_calendar, &rc, i); if(i == 0 || i == 6) { //雙休日黑色 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex:31 + i] rect:rc size:_weekStringDrawingSize color: [UIColor blackColor]]; } else { //其他時間藍色 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex:31 + i] rect:rc size:_weekStringDrawingSize color: [UIColor blueColor]]; } }
//3、見上圖紅色邊框部分,繪制上個月日期信息 CGPoint dayRectOffset; //獲取日期區塊的rect calendar_get_day_section_rect(&_calendar, &rc); //紀錄日期區塊的起始位置 dayRectOffset = rc.origin; //當前月份1號在日期cells中的起始索引號 int begin = _calendar.dayBeginIdx; //當前月份結束索引號 int end = begin + _calendar.dayCount; //繪制上個月的日期,假設begin = 5 i=[4,3,2,1,0] for(int i = begin - 1; i >= 0; i--) { calendar_get_day_cell_rect_by_index(&_calendar, &rc, i); //計算出位置偏移量 rc.origin.x += dayRectOffset.x; rc.origin.y += dayRectOffset.y; //縮小一下繪制rect的尺寸而已 rc.origin.x += 5; rc.origin.y += 5; rc.size.width -= 10; rc.size.height -= 10; //繪制圓圈 [self drawCircleInRect:rc color:[UIColor colorWithRed:245/255.0 green:245/255.0 blue:245/255.0 alpha:1.0] isFill:YES]; //計算方式涉及到了_lastMonthDayCount //假設上個月有30天,本月的begin為5,則 //則30-(5-4)= 29 ---->0base--->30號 // 30- (5-3)= 28 ---->0base--->29號 // 30- (5-2)= 27 ---->0base--->28號 // 30- (5-1)= 26 ---->0base--->27號 // 30- (5-0)= 25 ---->0base--->26號 int dayIdx = _lastMonthDayCount - (begin - i); //繪制圓圈中的日期 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: dayIdx] rect:rc size:_dayStringDrawingSize color:[UIColor colorWithRed:223/255.0 green:223/255.0 blue:223/255.0 alpha:1.0]]; }
//4、見上圖紅色邊框部分,繪制下個月日期信息 for(int i = end; i < 42; i++) { calendar_get_day_cell_rect_by_index(&_calendar, &rc, i); rc.origin.x += dayRectOffset.x; rc.origin.y += dayRectOffset.y; rc.origin.x += 5; rc.origin.y += 5; rc.size.width -= 10; rc.size.height -= 10; [self drawCircleInRect:rc color:[UIColor colorWithRed:245/255.0 green:245/255.0 blue:245/255.0 alpha:1.0] isFill:YES]; //索引是i-end,很容易理解的 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: i - end] rect:rc size:_dayStringDrawingSize color:[UIColor colorWithRed:223/255.0 green:223/255.0 blue:223/255.0 alpha:1.0]]; }
typedef struct _selectRange { int rowIdx; //為了方便處理是否同一行 int columIdx;//行列轉換一緯數組索引 CGRect rect; //紀錄要繪制的rect } selectRange;
//5、繪制當前月份的日期,包括選中,未選中以及日期文字 //使用c結構,并初始化相關變量 selectRange ranges[31]; memset(ranges,0,sizeof(ranges)); int rangeCount = 0; //繪制當前月的日期 for(int i = begin ; i < end; i++) { calendar_get_day_cell_rect_by_index(&_calendar, &rc, i); rc.origin.x += dayRectOffset.x; rc.origin.y += dayRectOffset.y; rc.origin.x += 5; rc.origin.y += 5; rc.size.width -= 10; rc.size.height -= 10; SDate date; date_set(&date, _calendar.date.year, _calendar.date.month, i - begin + 1 ); //如果當前日期在選中時間范圍內,則batch起來,由drawSelectRange進行繪制 //因為需要處理換行這種效果(drawSelectRange中處理,因此緩存起來二次處理比較方便) //與delegate通信 if([self.calendarDelegate isInSelectedDateRange:date]) { ranges[rangeCount].rowIdx = i / 7; //映射成行索引 ranges[rangeCount].columIdx = i % 7; //映射成列索引 ranges[rangeCount].rect = rc; //當前行列的rect紀錄下來 rangeCount++; //計數器增加1 } else { //沒有選中的,就直接繪制圓圈和當中的日期號 [self drawCircleInRect:rc color:[UIColor colorWithRed:234/255.0 green:234/255.0 blue:234/255.0 alpha:1.0] isFill:NO]; [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: i - _calendar.dayBeginIdx] rect:rc size:_dayStringDrawingSize color:[UIColor colorWithRed:107/255.0 green:107/255.0 blue:107/255.0 alpha:1.0]]; } } //NSLog(@"select day count = %d",rangeCount); //rangeCount紀錄了選中的數量,ranges則紀錄了要繪制的所有信息 [self drawSelectRange:ranges count:rangeCount]; //選中的圈圈的文字由下面代碼繪制 for(int i = 0; i < rangeCount; i++) { //重新將行列(二維)索引號映射一緯數組索引號 int idx = ranges[i].rowIdx * 7 + ranges[i].columIdx; //idx - begin就是當前的要繪制的日期文字的索引號 [self drawStringInRectWithSize:[_dayAndWeekStringArray objectAtIndex: idx - begin] rect:ranges[i].rect size:_dayStringDrawingSize color:[UIColor whiteColor]]; }
//blf:注意 參數ranges是數組名,數組名表示數組的首地址 // 還有就是selectRange是c結構,當做指針操作時要用->而不是.尋址操作符 -(void) drawSelectRange : (selectRange* ) ranges count : (int) count { //兩種情況下count = 1 //第一選則,或者第二次選中的和第一次選中的是同一個日期cell //此時是繪制圓形而不是roundedRect if(count == 1) { [self drawCircleInRect : ranges[0].rect color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES]; //退出函數 return; } //并不是第一次選者且第二次選者不是和第一次選者一致時 //獲取cell rect的width CGRect rect; calendar_get_day_cell_rect_by_index(&_calendar, &rect, 0); float width = rect.size.width; //用于紀錄上一次的行號,初始化,紀錄的是第一行的索引號 int lastRowIdx = ranges[0].rowIdx; //計數器,用來紀錄當前行的cell的數量 int sameRowCellCount = 0; for(int i = 0; i < count; i++) { //從ranges數組中獲取一個結構時候,使用了&取地址操作符 //因為防止發生拷貝,如果不是取地址的話,賦值會發生memcopy行為 selectRange* range = &ranges[i]; //行號相同,則同一行啦 if(range->rowIdx == lastRowIdx) { sameRowCellCount++; } else { //行號不同,說明換行了,因此要繪制當前行 CGRect rc; //i - sameRowCellCount找到起始索引 rc.origin = ranges[i - sameRowCellCount].rect.origin; rc.size.height = range->rect.size.height; rc.size.width = width* (sameRowCellCount) - 10.0F; //很可能存在這種情況,既選中的是周六開始的,因此繪制的是圓形而不是roundedRect if(sameRowCellCount == 1) { [self drawCircleInRect:rc color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES]; } else { //一般情況,繪制roundedRect [self drawRoundRect:rc radius:rc.size.height]; } sameRowCellCount = 1;//標記值,為了下面繪制最后一行的代碼使用,=1和>1要分別處理 //紀錄上一次的行號 lastRowIdx = range->rowIdx; } } //將最后一行拆分出來單獨處理,這樣就方便處理一些特殊情況 //繪制最后一行 if(sameRowCellCount > 0) { CGRect rc; rc.origin = ranges[count - sameRowCellCount].rect.origin; rc.size.height = ranges[count - sameRowCellCount].rect.size.height; rc.size.width = width* (sameRowCellCount) - 10.0F; //最后一行有多個cell被選中 if(sameRowCellCount != 1) { [self drawRoundRect:rc radius:rc.size.height]; } else//最后一行僅周日被選中,只有一個,圓圈 [self drawCircleInRect:rc color:[UIColor colorWithRed:52/255.0 green:175/255.0 blue:248/255.0 alpha:1.0] isFill:YES]; } }
//由于UITableView采用了cell重用機制,因此僅有很屏幕rect相交的cell存在 //所以是cells一直輪替交換,所以我們必須在每次自繪時候判斷當前的cell中的月歷的每個日期是否處于選中狀態 //而本函數就是起到這樣的作用,判斷月歷中某個日期是否處于選中的區間范圍 -(BOOL)isInSelectedDateRange : (SDate) date { time_t curr = date_get_time_t(&date); if(curr < _startTime || curr > _endTime) return NO; return YES; }
控件的狀態初始化 控件的繪制 控件的事件觸發和處理 控件的布局
-(void) handleTouchEvent:(id) sender forEvent:(UIEvent *)event { NSSet *touches = [event allTouches]; UITouch *touch = [touches anyObject]; //獲取UITouch,將其轉換到當前CalendarView的局部坐標系表示 CGPoint upLoc = [touch locationInView:self]; //通過局部坐標系的點獲取點擊處的cell的索引號,優化部分請看c的相關實現 //這個碰撞檢測原理實際在游戲中經常使用,分區縮小范圍,然后檢測該范圍內所有物體的與點(2D) //或光線(3D)是否發生碰撞,用于此處也非常適合 int hitIdx = calendar_get_hitted_day_cell_index(&_calendar, upLoc); //選中了,則 if(hitIdx != -1) { SDate date; date_set(&date, _calendar.date.year, _calendar.date.month, hitIdx - _calendar.dayBeginIdx + 1); //=0為第一次點擊,僅選中一個cell //mod為了周而復始,并在[0,1]之間 if([self.calendarDelegate getHitCounter] % 2 == 0) { //第一次點擊,讓開始和結束Date相同 [self.calendarDelegate setSelectedDateRangeStart:date end:date]; } else//=1為第二次點擊,形成選區 { [self.calendarDelegate setEndSelectedDate:date]; } //每次點擊,delegate中的點擊計數器都要遞增的 [self.calendarDelegate updateHitCounter]; //需要觸發重繪,讓ios進行重新繪制,這個很關鍵,有一些細節,在下面會說明的 [self.calendarDelegate repaintCalendarViews]; } }
//屬于CalendarDelegate的接口函數,實現代碼如下: -(void) repaintCalendarViews { //[self.tableView setNeedsDisplay]; for(UIView * subview in self.tableView.subviews) { for(UIView* view2 in subview.subviews) { UITableViewCell* cell = (UITableViewCell*)view2; CalendarView* cview =(CalendarView*) [cell.contentView.subviews objectAtIndex:0]; [cview setNeedsDisplay]; } } }
1) 由于calendarView的選擇可能跨越多個CalendarView,因此不能僅僅在CalendarView級別setNeedsDisplay,而是需要讓整個UITableView以及他的所有子孫控件都要重繪。 2) 按照正常思路,你在UITableView上調用setNeedsDisplay,你會發現無效。 3) 由此可見,IOS中的臟區局部刷新機制采用的是以控件為基礎的后備緩沖圖,而不是以整個屏幕為基礎的后背緩沖圖。 4) 以控件為基礎的后備緩沖圖內存消耗高,但是能夠解決重復繪制,提高效率,典型的以空間換時間策略。 介紹一個微軟開源項目WinObjc,非常強大,可以在gitHub中去查找。 為win10和Winphone實現了整個ios sdk,目的是讓ios的app直接在winphone上跑。 我研究過他整個局部刷新的機制,還是蠻帥的。 foundation,uikit, glkit,spritekit,gamekit,homekit....各種kit都實現了。而且最重要的是有源碼。 5) 以整個屏幕(或者說整個APP顯示根節點的size)為大小的后備緩沖區,其只需要增加一張內存位圖。 獲取臟區后,僅僅遞歸該臟區以及所有和父節點臟區相交部分的區域 進行更新,因此更新區域會逐漸減小,但是不能完全去除重復繪制。 我曾經實現了opengl和dx版本的2D局部刷新機制,并入到一個2d UI引擎中,利用后背緩沖區以及 基于修改投影矩陣方式,在光柵化之前裁剪掉所有不可見的頂點后, 其渲染速度飛速提高,并且CPU使用率控制在5%以下,大部分時間都是 在1%)。源碼不能公布,因為是商業代碼,但是demo以后可以在github上下載,很帥的IPhone4仿真模擬。
#define MYSWAP(x,y,type) \ { \ type t = x; \ x = y; \ y = t; \ } -(void)setSelectedDateRangeStart:(SDate)start end:(SDate)end { //將date轉換為time_t _startTime = date_get_time_t(&start); _endTime = date_get_time_t(&end); //如果起始時間大于結束時間,說明先點擊后一天,再點擊前一天,繪制時的邏輯不正確,需要交換一下時間 if(_startTime > _endTime) { MYSWAP(_startTime,_endTime,time_t); //紀錄下年月表示起始結束date _begDate = end; _endDate = start; }else{ _begDate = start;//記錄日期 _endDate = end; } } -(void)setEndSelectedDate:(SDate)end { //同上,只是針對第二次點擊而已 _endTime = date_get_time_t(&end); if(_startTime > _endTime) { MYSWAP(_startTime,_endTime,time_t); _endDate = _begDate; _begDate = end; }else{ _endDate = end; } } -(void) updateHitCounter { _hitCounter++; } -(int) getHitCounter { return _hitCounter; }
至此,IOS版本的源碼全部分析完畢,希望對大家有幫助。
關于控件的布局,本DEMO中沒什么用到,控件的布局可以說是比較復雜的部分,有各種算法,各種方法,是個比較大的主題,以后有機會探討。
總體來說,apple公司的objc編譯器前端程序Clang支持Objc,c,c++的詞法分析,AST的產生,然后進入llvm,生成對應CPU的指令。再在IOS上運行,由于都是二進制,所以效率非常高(蘋果公司不允許使用虛擬機代碼方式,只能以靜態鏈接庫【二進制】方式 運行app,高效, 難以反編譯,因此相對非常安全,唯一的例外是運行于瀏覽器中的js代碼)。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。