ios最好的自定义日历控件实现方式

ios最好的自定义日历控件实现方式

首页休闲益智colornumber官方版更新时间:2024-06-17

对于手机客户端的产品来说,界面元素变得越来越丰富,控件图片越来越多,与之带来的问题就是要在元素很多的情况下,在技术方面如何保证用户的使用质量。简单的说,比如一个日历控件,可以按周,月,年进行显示。周还好说,那么在月和年下显示就有意思了~一堆格子。用UICollectionView吗?等写完以后滚动一下试试,保证体验是非常差劲的。而且因为项目的需要,肯定不会仅仅显示出日期就完了,可能还要在每个日期格子上面添加标记,那么恭喜你了,体验还要更差。那么怎么解决这个问题呢?既然要解决问题,就先要发现问题在哪。发现最大的问题就在于reloadData这个方法。因为在月和年显示的时候,同样的控件需要显示的很多,在滚出屏幕时回收,进入屏幕时需要显示不同的内容,所以需要调用其reloadData的方法,之后系统会调用cellForItemAtIndexPath这个回调。但因为这个cellForItemAtIndexPath方法是在主线程中被调用的,且在月和年的时候格子太多了(尤其是年,一年12个月,1屏就是1年)。所以问题找到了,主线程的工作太多了。主线程的工作既然多,那么分到子线程不就完了吗?但大家都知道,对于UI类的工作必须要放到主线程里进行工作,那怎么办呢?于是这里有2个方法就能用了,一个是图形的上下文,这个方法也是大多数网上的文章讨论过的方法。我觉得方法过于复杂且不利于维护,所以极力推荐第二种方法,CALayer。 首先说为什么要用这些东西?因为我们要将月这个数据用图片的形式进行展示!而不是某一种控件,生成图片的工作在子线程中,显示图片的工作在主线程中!这样就不会出现很差的用户体验了。滚动起来是非常流畅的。以下会写出部分代码。笔者认为这大概是我看过的日历控件中实现的最好的一个了~哈哈,自夸一下。无论从技术实现方式还是扩展性上来看。

首先是声明文件BaseCalendarCellLayer为每个日期的基类

@interface BaseCalendarCellLayer : CALayer

{

@protected

CATextLayer *showDateLayer;

}

@property(nonatomic,strong)UIFont *font;

@property(nonatomic,strong)NSString *showDate;

@property(nonatomic,strong)UIColor *textColor;

@property(nonatomic,strong)UIImage *backgroundImage;

//@property(nonatomic,readonly)NSDate *currentDate;

-(instancetype)initWithFrame:(CGRect)frame forDate:(NSDate*)date;

-(void)sizeToFit;

@end

BaseCalendarCellLayer定义实现

#import "BaseCalendarCellLayer.h"

@interface BaseCalendarCellLayer()

{

}

@end

@implementation BaseCalendarCellLayer

-(instancetype)initWithFrame:(CGRect)frame forDate:(NSDate*)date

{

self = [super init];

self.frame = frame;

//_currentDate = date;

showDateLayer = [CATextLayer layer];

showDateLayer.drawsAsynchronously = YES;

showDateLayer.contentsScale = [[UIScreen mainScreen] scale];

UIFont *font = [UIFont systemFontOfSize:10];

self.font = font;

showDateLayer.alignmentMode = kCAAlignmentCenter;

showDateLayer.frame = self.bounds;

showDateLayer.backgroundColor = [UIColor clearColor].CGColor;

self.textColor = [UIColor blackColor];

[self addSublayer:showDateLayer];

return self;

}

-(void)setFont:(UIFont *)font

{

_font = font;

CGFontRef fontRef = nil;

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {

fontRef = CGFontCreateCopyWithVariations((__bridge CGFontRef)(font), (__bridgeCFDictionaryRef)(@{}));

}

if (fontRef) {

[showDateLayer setFontSize:font.pointSize];

[showDateLayer setFont:fontRef];

CFRelease(fontRef);

}

}

-(void)setShowDate:(NSString *)showDate

{

_showDate = showDate;

showDateLayer.string = _showDate;

}

-(void)setTextColor:(UIColor *)textColor

{

_textColor = textColor;

showDateLayer.foregroundColor = _textColor.CGColor;

}

-(void)sizeToFit

{

if(!_showDate)

return;

CGSize size = [_showDate boundingRectWithSize:self.bounds.size options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:_font} context:nil].size;

CGRect showDateLayerFrame = showDateLayer.frame;

showDateLayerFrame.origin.x = self.frame.size.width/2-size.width/2;

showDateLayerFrame.origin.y = self.frame.size.height/2-size.height/2;

showDateLayerFrame.size = size;

showDateLayer.frame = showDateLayerFrame;

}

@end

接下来就是日历的控件了,还是先上定义文件

#import <UIKit/UIKit.h>

#import "BaseCalendarCellLayer.h"

@class CalendarLayerControl;

@protocol CalendarLayerControlDelegate <NSObject>

@optional

- (void)calendarLayerControl:(CalendarLayerControl *)calendarLayerControl forIndex:(int)index didSelectDate:(NSDate *)date;//@"yyyy-MM-dd"

- (void)calendarLayerControl:(CalendarLayerControl *)calendarLayerControl forCell:(BaseCalendarCellLayer*)cell forIndex:(int)index forDate:(NSDate *)date;//@"yyyy-MM-dd"

@end

@interface CalendarLayerControl : UIView

{

@protected

CALayer *calendarLayer;

NSMutableArray *dateLayers;

NSMutableArray *dates;

}

@property(nonatomic,weak)id<CalendarLayerControlDelegate> delegate;

@property(nonatomic)NSDate *selectDate;

@property (nonatomic, assign, readonly) NSUInteger year;

@property (nonatomic, assign, readonly) NSUInteger month;

@property (nonatomic, assign, readonly) long rowCount;//日历行数

-(instancetype)initWithFrame:(CGRect)frame forCellClass:(Class)cellClass;

-(void)reloadData;

-(BaseCalendarCellLayer*)cellForRowAtIndex:(int)index;

@end

实现文件,其中的OperationQueueManage为笔者自定义的线程队列,可以用GCD取代,效率可能没有笔者的方式好,但是使用没有问题。CalendarManage为关于日历的一个数据模型,篇幅原因就不公开了。很简单,大家可以看看开源代码或者自己完成一个。最重要的工作基本都在下面了。

#import "CalendarLayerControl.h"

#import "CalendarManage.h"

#import "OperationQueueManage.h"

#define WeekDayCount 7

@interface CalendarLayerControl()

{

NSDate *firstOfMonth;

Class _cellClass;

CALayer *showCalendarImageLayer;

NSDateFormatter *formateDay;

NSDateFormatter *formateYearMonth;

BaseCalendarCellLayer *cellLayer;

CGRect viewFrame;

CGFloat mainScreenScale;

NSCalendar *nscalendar;

}

-(void)removeDateLayer;

-(void)createDateLayer;

-(NSDate *)dateForCellAtIndexPath:(int)cellIndex;

@end

@implementation CalendarLayerControl

-(instancetype)initWithFrame:(CGRect)frame forCellClass:(Class)cellClass

{

if(![cellClass isSubclassOfClass:[BaseCalendarCellLayer class]])

return nil;

self = [super initWithFrame:frame];

dateLayers = [[NSMutableArray alloc] init];

_cellClass = cellClass;

showCalendarImageLayer = [CALayer layer];

showCalendarImageLayer.drawsAsynchronously = YES;

showCalendarImageLayer.frame = self.bounds;

showCalendarImageLayer.contentsScale = [[UIScreen mainScreen] scale];

showCalendarImageLayer.backgroundColor = [UIColor clearColor].CGColor;

[self.layer addSublayer:showCalendarImageLayer];

formateDay = [[NSDateFormatter alloc] init];

[formateDay setDateFormat:@"d"];

formateYearMonth = [[NSDateFormatter alloc] init];

dates = [[NSMutableArray alloc] init];

calendarLayer = [CALayer layer];

calendarLayer.drawsAsynchronously = YES;

nscalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];

return self;

}

-(void)setSelectDate:(NSDate *)selectDate

{

if(!selectDate)

return;

_selectDate = selectDate;

firstOfMonth = [[CalendarManage manage] GetFirstDayOfMonth:_selectDate];

_rowCount = [[CalendarManage manage] numberOfRowsInMonth:_selectDate];

[formateYearMonth setDateFormat:@"yyyy"];

_year = [[formateYearMonth stringFromDate:_selectDate] integerValue];

[formateYearMonth setDateFormat:@"MM"];

_month =[[formateYearMonth stringFromDate:_selectDate] integerValue];

}

-(void)createDateLayer

{

long dateCounts =_rowCount*WeekDayCount;

double cellX = 0;

double cellY = 0;

double cellHorizontalSpacing = 0;

double cellVerticalSpacing = 0;

double cellWidth = (viewFrame.size.width-WeekDayCount*cellHorizontalSpacing)/WeekDayCount;

double cellHeight = (viewFrame.size.height-_rowCount*cellVerticalSpacing)/_rowCount;

@autoreleasepool

{

for (int n=0; n<dateCounts; n )

{

NSDate *dayDate = [self dateForCellAtIndexPath:n];

cellLayer = [[_cellClass alloc] initWithFrame:CGRectMake(cellX, cellY, cellWidth, cellHeight) forDate:dayDate];

cellLayer.drawsAsynchronously = YES;

cellLayer.backgroundColor = [UIColor clearColor].CGColor;

CGRect originalFrame = cellLayer.frame;

cellX = cellX cellWidth cellHorizontalSpacing;

if((n 1)%(WeekDayCount) == 0)

{

cellX = 0;

cellY = cellY cellHeight cellVerticalSpacing;

}

BOOL isSameMonth = [[CalendarManage manage] checkSameMonth:firstOfMonth AnotherMonth:dayDate];

if(!isSameMonth)

continue;

cellLayer.showDate = [formateDay stringFromDate:dayDate];

if(self.delegate && [self.delegate respondsToSelector:@selector(calendarLayerControl:forCell:forIndex:forDate:)])

{

[self.delegate calendarLayerControl:self forCell:cellLayer forIndex:n forDate:dayDate];

}

//禁止使用delegate修改其大小和位置

cellLayer.frame = originalFrame;

[cellLayer sizeToFit];

[calendarLayer addSublayer:cellLayer];

[dates addObject:dayDate];

[dateLayers addObject:cellLayer];

}

}

}

-(void)removeDateLayer

{

calendarLayer = [CALayer layer];

calendarLayer.drawsAsynchronously = YES;

//[calendarLayer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];

[dateLayers removeAllObjects];

[dates removeAllObjects];

}

//获取cell的日期 (日 -> 六 格式,如需修改星期排序只需修改该函数即可)

-(NSDate *)dateForCellAtIndexPath:(int)cellIndex

{

NSInteger ordinalityOfFirstDay = [nscalendar ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitWeekOfMonth forDate:firstOfMonth];

NSDateComponents *dateComponents = [NSDateComponents new];

dateComponents.day = (1 - ordinalityOfFirstDay) cellIndex;

return [nscalendar dateByAddingComponents:dateComponents toDate:firstOfMonth options:0];

}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event

{

UITouch *touch = [touches anyObject];

CGPoint point = [touch locationInView:self];

for (int n=0; n<dateLayers.count; n )

{

BaseCalendarCellLayer *layer = [dateLayers objectAtIndex:n];

if(CGRectContainsPoint(layer.frame, point))

{

if(self.delegate && [self.delegate respondsToSelector:@selector(calendarLayerControl:forIndex:didSelectDate:)])

{

NSDate *dayDate = [dates objectAtIndex:n];

[self.delegate calendarLayerControl:self forIndex:n didSelectDate:dayDate];

}

break;

}

}

}

-(void)reloadData

{

if(!self.selectDate)

return;

viewFrame = self.bounds;

mainScreenScale = [[UIScreen mainScreen] scale];

[[OperationQueueManage share] addBlockToGlobalOperationQueue:^{

@synchronized(self)

{

[self removeDateLayer];

[self createDateLayer];

UIGraphicsBeginImageContextWithOptions(self->viewFrame.size, NO, self->mainScreenScale);

CGContextRef context = UIGraphicsGetCurrentContext();

[self->calendarLayer renderInContext:context];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

self->showCalendarImageLayer.contents = (__bridge id)(image.CGImage);

self->showCalendarImageLayer.frame = self.bounds;

//在主线程runloop会自动调用,子线程由于runloop未开启,所以需要显式调用,操他妈的

[CATransaction flush];

}

}];

}

-(BaseCalendarCellLayer*)cellForRowAtIndex:(int)index

{

return [dateLayers objectAtIndex:index];

}

/*

// Only override drawRect: if you perform custom drawing.

// An empty implementation adversely affects performance during animation.

- (void)drawRect:(CGRect)rect {

// Drawing code

}

*/

@end

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved