前语

最近看之前写的代码突然感觉非常陌生,自我感觉完成的逻辑还不错,所以想整理一下,一是加深印象,二是想共享一下。

日程事历我们都用过,苹果也有自己的。但细心想想里面核算视图frame的逻辑还是非常有意思的。

需求

  1. 显示用户一天内的所有行程。
  2. 行程卡片的高度由行程时长决议
  3. 行程卡片的宽度由同一时刻段内堆叠的行程个数决议。
  4. 行程卡片的开端时刻完毕时刻依照半小时取整,取整规则如下
iOS 日程事历行程功能实现(Object-C)

设计图如下:

iOS 日程事历行程功能实现(Object-C)

分析

这个需求其实最难的一点在于如何核算出同一时刻段内堆叠行程的数量。因为有依照半小时取整的逻辑,能够经过0-48遍历,看每个时刻线上,有多少个行程是包括该时刻线的。

某两个时刻段内的行程堆叠的数量不相同的,也就是说卡片的宽度并不是都相同的,所以第一步,肯定是将行程分红堆,然后在每一堆上钩算出卡片合适的宽度。

最终一点就是,同一时刻线上的卡片,要核算出合适的index,然后再经过宽度才干知道起x轴的坐标。

代码

卡片对应的ViewModel


@interface ZDCalendarEventViewModel : NSObject
@property (nonatomic, strong) NSString *eventName;
@property (nonatomic, strong) NSString *eventStatusName;
@property (nonatomic, assign) NSInteger generateType;
@property (nonatomic, strong) NSString *eventTime;
/// 开端时刻的序列,经过startTimeIndex核算y坐标
@property (nonatomic, assign) NSInteger startTimeIndex;
/// 完毕时刻的序列,经过startTimeIndex核算y坐标
@property (nonatomic, assign) NSInteger endTimeIndex;
///时刻的继续数,半小时为单位,经过continueTimeCount核算高速
@property (nonatomic, assign) NSInteger continueTimeCount;
///水平方向的序列,经过horizontalIndex 来核算x坐标
@property (nonatomic, assign) NSInteger horizontalIndex;
- (void)bindModel:(ZDScheduleEventModel *)model;
@end

每个堆的ViewModel

@interface ZDScheduleEventHeapViewModel : NSObject
/// 事情viewModelList
@property (nonatomic, copy) NSArray<ZDCalendarEventViewModel *> *eventViewModels;
/// 事情一行最多的数量
@property (nonatomic, assign) NSInteger maxLineCount;
- (void)bindEventViewModels:(NSArray *)eventList;
@end

将ZDCalendarEventViewModel分红若干堆。 eventViewModels是经过开端时刻排序的。

- (void)bindViewModels:(NSArray *)eventViewModels {
    NSMutableArray *heapList = [[NSMutableArray alloc] init];
  NSInteger startIndex = 0;
  NSInteger endIndex = 0;
  NSMutableArray *temp;
    // 先分红二维数组
  for (ZDCalendarEventViewModel *model in eventViewModels) {
        // 在时刻范围内
    if (model.startTimeIndex >= startIndex &&
      model.startTimeIndex < endIndex) {
        // 假如不在时刻范围内,从头创立List来寄存
    } else {
      temp = [NSMutableArray array];
      [heapList addObject:temp];
      startIndex = model.startTimeIndex;
    }
        // 扩大完毕时刻
    endIndex = MAX(model.endTimeIndex, endIndex);
    [temp addObject:model];
  }
  NSMutableArray *heapVMList = [NSMutableArray array];
  for (NSArray *heap in heapList) {
    ZDScheduleEventHeapViewModel *heapViewModel = [[ZDScheduleEventHeapViewModel alloc] init];
    [heapViewModel bindEventViewModels:heap];
    [heapVMList addObject:heapViewModel];
  }
  self.eventHeapViewModels = [heapVMList copy];
}

其间ZDScheduleEventHeapViewModel的bindEventViewModels方法如下。

- (void)bindEventViewModels:(NSArray *)eventList {
  self.eventViewModels = eventList;
  // 
  NSMutableArray *eventViewModels = [NSMutableArray arrayWithArray:eventList];
  NSInteger lineMaxCount = 0;
    // TODO:优化
  for (int i = 0; i < 48; i ++) {
    //需求在此次循环核算出x的
    NSMutableArray *layoutArr = [NSMutableArray array];
    //现已在之前循环核算过x的
    NSMutableDictionary *layoutedMap =[NSMutableDictionary dictionary];
    for (ZDCalendarEventViewModel *event in eventViewModels) {
      // 行程跨度,包括当时时刻线
      if (event.startTimeIndex <= i && event.endTimeIndex > i) {
                // 没有核算过index的,放入数组内
        if (event.horizontalIndex == ZD_Schedule_Deafult_Index) {
          [layoutArr addObject:event];
                // 在之前时刻线循环中现已核算出inde的,放字典里,查index是否占用情况。
        } else {
          [layoutedMap setObject:event forKey:@(event.horizontalIndex)];
        }
      }
    }
    //现已被占用个数,需求跳过
    NSInteger occupationCount = 0;
    for (int j = 0; j < layoutArr.count; j ++) {
      ZDCalendarEventViewModel *event = layoutArr[j];
            // 找到第1个没有被占用的index
      while ([layoutedMap objectForKey:@(j + occupationCount)] != nil) {
        occupationCount ++;
      }
      event.horizontalIndex = j + occupationCount;
    }
        // 数量 = 本次核算index的 + 之前核算过index的
    NSInteger allCount = layoutArr.count + layoutedMap.count;
    if (allCount > lineMaxCount) {lineMaxCount = allCount;}
  }
    // 找出该堆中数量最大的
  self.maxLineCount = lineMaxCount;
}

依照时刻线遍历,在该时刻线上的行程分为2种,一种是从之前的时刻线连续下来的,这种的行程在x轴上的index现已固定了,另外一种是开端在该时刻线上的,这种行程在x轴上的index需求看之前的index有没有被占用,假如占用需求向后移动。

优化

能够看到上面的方法内是经过0-48来遍历的,其实不必,能够在上一步中的遍历中(分堆过程),记载出堆的ViewModel的开端时刻和完毕时刻,经过开端时刻和完毕时刻来替代0-48来遍历,能够优化一部分功能。

制品展现

数据:

tripList = (
			{
				oneLevelCode = "DBC2022019";
				bizExecutionTime = "2023-05-30 10:30:00";
				endTime = "11:30";
				bizExecutionEndTime = "2023-05-30 11:30:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				begTime = "10:30";
				oneLevelName = "自定义";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			},
			{
				bizExecutionTime = "2023-05-30 11:30:00";
				endTime = "12:30";
				bizExecutionEndTime = "2023-05-30 12:30:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				oneLevelName = "自定义";
				begTime = "11:30";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			},
			{
				bizExecutionTime = "2023-05-30 12:00:00";
				endTime = "12:30";
				bizExecutionEndTime = "2023-05-30 12:30:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				oneLevelName = "自定义";
				begTime = "12:00";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			},
			{
				bizExecutionTime = "2023-05-30 12:30:00";
				endTime = "14:00";
				bizExecutionEndTime = "2023-05-30 14:00:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				oneLevelName = "自定义";
				begTime = "12:30";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			},
			{
				bizExecutionTime = "2023-05-30 12:30:00";
				endTime = "17:00";
				bizExecutionEndTime = "2023-05-30 17:00:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				begTime = "12:30";
				oneLevelName = "自定义";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			},
			{
				bizExecutionTime = "2023-05-30 13:30:00";
				endTime = "14:00";
				bizExecutionEndTime = "2023-05-30 14:00:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				begTime = "13:30";
				oneLevelName = "自定义";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			},
			{
				bizExecutionTime = "2023-05-30 14:00:00";
				endTime = "14:30";
				bizExecutionEndTime = "2023-05-30 14:30:00";
				isTimeOut = 0;
				state = "dcl";
				twoLevelCode = "DBC2022019002";
				oneLevelName = "自定义";
				begTime = "14:00";
				who = "";
				generateType = 2;
				twoLevelName = "歇息";
			}
		); 

UI展现如下:

iOS 日程事历行程功能实现(Object-C)