当前位置: 首页 > news >正文

iOS—仿tableView自定义闹钟列表

自定义View实现闹钟列表,左滑删除,滑动列表时收起删除按钮。用代理的方法实现ListView的创建,删除以及开关回调,并实现动画效果。

ClockViewCell使用block通知ListView,ListView通过代理通知上层ClockView

1、文件组成

ClockView一共由3部分组成

(1)ClockView为底层View,仅用于添加ListView,可以自定义ListView中Cell的高度,每个Cell的间距,利用代理实现Cell的个数、创建Cell以及删除等回调。

(2)ClockListView为列表本体,用于承载每一个Cell,管理Cell的各种操作,上下滑动手势收回删除按钮等,对Cell中的block回调进行处理,再通过代理通知ClockView。block包括手势、删除、开关等操作。

(3)Cell作为每一个闹钟内容的载体,实现时间、周期以及闹钟开关展示。对于cell中的手势、以及操作用block的方式通知ClockListView,ClockListView再通知ClockView。Cell中的左滑删除手势为了避免ClockListView的UIScrollView上下滑动冲突,只有横向滑动才处理cell,否则优先响应UIScrollView。

具体看下述代码,大部分均做了注释

2、代码构成

 (1)ClockView调用ListView

ClockView.h
#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN@interface ClockView : UIView@endNS_ASSUME_NONNULL_END
ClockView.m

dataArray由真实情况而定,本文只进行随机展示

#import "ClockView.h"
#import "ClockViewCell.h"
#import "ClockListView.h"@interface ClockView () <ClockListViewDelegate>@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) ClockListView *listView;@end@implementation ClockView- (instancetype)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {[self initView];}return self;
}- (void)initView {UILabel *topLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 0, self.bounds.size.width - 40, 52)];topLabel.text = @"     ⁨⁩ 当到达所设置的时间,智能床将缓缓升起,帮你从梦中唤醒。";topLabel.textColor = [UIColor colorWithRed:111/255.0 green:111/255.0 blue:111/255.0 alpha:1.0];;topLabel.font = [UIFont systemFontOfSize:18];topLabel.textAlignment = NSTextAlignmentLeft;topLabel.numberOfLines = 0;[self addSubview:topLabel];[self addSubview:self.listView];[self reloadData];
}- (void)reloadData {self.dataArray = [NSMutableArray array];// Example data loadingfor (int i = 0; i < 10; i++) {[self.dataArray addObject:@(i)];}[self.listView reloadData];
}#pragma mark - delegate- (NSInteger)numberOfCellsInClockListView:(ClockListView *)clockListView {return self.dataArray.count;
}- (ClockViewCell *)clockListView:(ClockListView *)clockListView cellForRowAtIndex:(NSInteger)index {
//    ClockViewCell *cell = [clockListView dequeueReusableCellWithIdentifier:@"ClockCell"];ClockViewCell *cell = [clockListView dequeueCell];NSString *time = [NSString stringWithFormat:@"10:%02ld", index];[cell configureWithTime:time repeat:@"周一 周二" on:YES];return cell;
}- (void)clockListView:(ClockListView *)clockListView didDeleteClockAtIndex:(NSInteger)index {[self.dataArray removeObjectAtIndex:index];
//    [clockListView reloadData];
}- (void)clockListView:(ClockListView *)clockListView didClickClockAtIndex:(NSInteger)index {// Handle clock click eventNSLog(@"Clock at index %ld clicked", (long)index);
}- (void)clockListView:(ClockListView *)clockListView didSwitchClockAtIndex:(NSInteger)index on:(BOOL)on {// Handle switch state changeNSLog(@"Clock at index %ld switched %@", (long)index, on ? @"ON" : @"OFF");
}#pragma mark - lazy load- (ClockListView *)listView {if (!_listView) {_listView = [[ClockListView alloc] initWithFrame:CGRectMake(0, 72, self.bounds.size.width, self.bounds.size.height - 72)];_listView.backgroundColor = [UIColor colorWithRed:242/255.0 green:243/255.0 blue:245/255.0 alpha:1.0];;_listView.delegate = self;_listView.cellHeight = 96;//96_listView.cellSpacing = 20;//20}return _listView;
}@end

(2)ListView

ClockListView.h

创建代理以供使用,暂未实现cell回收机制

#import <UIKit/UIKit.h>
#import "ClockViewCell.h"NS_ASSUME_NONNULL_BEGIN@class ClockListView;@protocol ClockListViewDelegate <NSObject>@optional
/// 删除cell的回调
/// - Parameters:
///   - clockListView: clockListView
///   - index: index
- (void)clockListView:(ClockListView *)clockListView didDeleteClockAtIndex:(NSInteger)index;@optional
/// 点击cell的回调
/// - Parameters:
///   - clockListView: clockListView
///   - index: index
- (void)clockListView:(ClockListView *)clockListView didClickClockAtIndex:(NSInteger)index;@optional
- (void)clockListView:(ClockListView *)clockListView didSwitchClockAtIndex:(NSInteger)index on:(BOOL)on;/// 设置cell的样式
/// - Parameters:
///   - clockListView: clockListView
///   - index: index
- (ClockViewCell *)clockListView:(ClockListView *)clockListView cellForRowAtIndex:(NSInteger)index;/// 设置cell的个数
/// - Parameter clockListView: clockListView
- (NSInteger)numberOfCellsInClockListView:(ClockListView *)clockListView;@end@interface ClockListView : UIView/// 代理
@property (nonatomic, weak) id<ClockListViewDelegate> delegate;
/// cell的高度
@property (nonatomic, assign) CGFloat cellHeight;
/// 每个cell之间的间距
@property (nonatomic, assign) CGFloat cellSpacing;- (ClockViewCell *)dequeueCell;
//- (ClockViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;- (void)reloadData;@endNS_ASSUME_NONNULL_END
ClockListView.m
#import "ClockListView.h"@interface ClockListView () <UIScrollViewDelegate>@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableArray<ClockViewCell *> *cellArray;
//@property (nonatomic, strong) NSMutableSet<ClockViewCell *> *reusableCells;
@property (nonatomic, assign) NSInteger rowCount;
@property (nonatomic, assign) NSInteger cellIndex;@end@implementation ClockListView- (instancetype)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {self.rowCount = 0;self.cellHeight = 96;self.cellSpacing = 20;
//        self.reusableCells = [NSMutableSet set];[self config];}return self;
}- (ClockViewCell *)dequeueCell {ClockViewCell *cell = self.cellArray[self.cellIndex];return cell;
}// 提供重用 cell,暂未实现
//- (ClockViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier {
//    ClockViewCell *cell = [self.reusableCells anyObject];
//    if (cell) {
//        [self.reusableCells removeObject:cell];
//    } else {
//        cell = [[ClockViewCell alloc] initWithReuseIdentifier:identifier];
//    }
//    return cell;
//}- (void)config {if ([self.delegate respondsToSelector:@selector(numberOfCellsInClockListView:)]) {self.rowCount = [self.delegate numberOfCellsInClockListView:self];[self initView];}
}- (void)initView {self.cellArray = [NSMutableArray array];for (int i = 0; i < self.rowCount; i++) {ClockViewCell *cell = [[ClockViewCell alloc] initWithFrame:CGRectMake(0, (self.cellSpacing + self.cellHeight) * i, self.bounds.size.width, self.cellHeight)];[cell configureWithTime:@"10:21" repeat:@"周一 周二" on:YES];[self.scrollView addSubview:cell];[self.cellArray addObject:cell];__weak typeof(self) weakSelf = self;cell.deleteBlock = ^(ClockViewCell *cell) {NSInteger index = [weakSelf.cellArray indexOfObject:cell];[weakSelf deleteCellAtIndex:index];};cell.switchBlock = ^(ClockViewCell *cell, BOOL on) {// 处理开关状态变化NSInteger index = [weakSelf.cellArray indexOfObject:cell];if ([weakSelf.delegate respondsToSelector:@selector(clockListView:didSwitchClockAtIndex:on:)]) {[weakSelf.delegate clockListView:weakSelf didSwitchClockAtIndex:index on:on];}};cell.tapBlock = ^(ClockViewCell *cell) {// 处理点击事件[weakSelf cellTapActoin:cell];};cell.panBlock = ^(ClockViewCell *cell) {// 处理滑动手势[weakSelf closeAllExcept:cell];};if ([self.delegate respondsToSelector:@selector(clockListView:cellForRowAtIndex:)]) {self.cellIndex = i;[self.delegate clockListView:self cellForRowAtIndex:i];}}self.scrollView.contentSize = CGSizeMake(self.bounds.size.width, (self.cellSpacing + self.cellHeight) * self.cellArray.count);
}- (void)reloadData {// 清空之前的 cellfor (ClockViewCell *cell in self.cellArray) {
//        [self.reusableCells addObject:cell];[cell removeFromSuperview];}[self.cellArray removeAllObjects];// 重新加载数据[self config];
}#pragma mark - UIScollerView delegate- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {[self closeAllSwipeCells];
}#pragma mark - methods/// 删除 cell
/// - Parameter index: cell 的索引
- (void)deleteCellAtIndex:(NSInteger)index {if (index >= self.cellArray.count) return;// 判断最后一个 cell 是否可见BOOL isLastCellVisible = [self isLastCellVisible];NSInteger count = self.cellArray.count;ClockViewCell *cellToDelete = self.cellArray[index];// 移除数据源[self.cellArray removeObjectAtIndex:index];// 更新后续 cell 的位置,如果cell不是最后一个,后面的cell要实现上移for (NSInteger i = index; i < count - 1; i++) {ClockViewCell *cell = self.cellArray[i];[UIView animateWithDuration:0.3delay:0options:UIViewAnimationOptionCurveEaseOutanimations:^{CGRect frame = cell.frame;frame.origin.y -= (self.cellHeight + self.cellSpacing);cell.frame = frame;} completion:nil];}// 淡出动画[UIView animateWithDuration:0.3delay:0options:UIViewAnimationOptionCurveEaseOutanimations:^{//当cell为最后一项时if (index == count - 1) {CGRect frame = cellToDelete.frame;frame.origin.y += self.cellHeight;cellToDelete.frame = frame;}cellToDelete.alpha = 0.0;} completion:^(BOOL finished) {[cellToDelete removeFromSuperview];// 判断最后一个cell是否可见,如果最后一个cell可见,当删除任意一个cell时,底部会出现空虚,contentsize变化时会导致画面突然往下坠,所以先滚到视图最下面再修改contentSizeif (isLastCellVisible) {//删除的是最后一个cellCGFloat offsetY = MAX(self.scrollView.contentSize.height - self.scrollView.bounds.size.height - (self.cellSpacing + self.cellHeight), 0);[self.scrollView setContentOffset:CGPointMake(0, offsetY) animated:YES];//延迟0.3秒,这里延迟0.3秒是为了让动画完成后再更新 contentSizedispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{self.scrollView.contentSize = CGSizeMake(self.bounds.size.width, (self.cellSpacing + self.cellHeight) * (count - 1));});} else {self.scrollView.contentSize = CGSizeMake(self.bounds.size.width, (self.cellSpacing + self.cellHeight) * (count - 1));}}];// 通知代理if ([self.delegate respondsToSelector:@selector(clockListView:didDeleteClockAtIndex:)]) {[self.delegate clockListView:self didDeleteClockAtIndex:index];}
}/// 是否最后一个 cell 可见
- (BOOL)isLastCellVisible {if (self.cellArray.count == 0) return NO;ClockViewCell *lastCell = self.cellArray.lastObject;CGRect cellFrameInScrollView = lastCell.frame;CGRect visibleRect = CGRectMake(self.scrollView.contentOffset.x,self.scrollView.contentOffset.y,self.scrollView.bounds.size.width,self.scrollView.bounds.size.height);return CGRectIntersectsRect(cellFrameInScrollView, visibleRect);
}- (void)closeAllExcept:(ClockViewCell *)exceptCell {for (ClockViewCell *cell in self.cellArray) {if (cell != exceptCell) {[cell closeSwipe];}}
}- (void)closeAllSwipeCells {for (ClockViewCell *cell in self.cellArray) {if (cell.isOpen) {[cell closeSwipe];}}
}- (void)cellTapActoin:(ClockViewCell *)currentCell {if (currentCell.isOpen) {[currentCell closeSwipe];return;}// 检查是否有其他 cell 是打开的BOOL isOpen = NO;for (ClockViewCell *cell in self.cellArray) {if (cell != currentCell && cell.isOpen) {isOpen = YES;break;}}// 如果没有其他 cell 是打开的,执行点击事件,否则关闭其他 cellif (isOpen) {[self closeAllExcept:currentCell];} else {// 通知代理if ([self.delegate respondsToSelector:@selector(clockListView:didClickClockAtIndex:)]) {NSInteger index = [self.cellArray indexOfObject:currentCell];if (index != NSNotFound) {[self.delegate clockListView:self didClickClockAtIndex:index];}}}
}#pragma mark - lazy load- (UIScrollView *)scrollView {if (!_scrollView) {_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];_scrollView.backgroundColor = [UIColor colorWithRed:242/255.0 green:243/255.0 blue:245/255.0 alpha:1.0];;_scrollView.showsVerticalScrollIndicator = NO;_scrollView.showsHorizontalScrollIndicator = NO;_scrollView.contentSize = CGSizeMake(self.bounds.size.width, self.bounds.size.height);_scrollView.delegate = self;[self addSubview:_scrollView];}return _scrollView;
}@end

(3)Cell

ClockViewCell.h

使用blcck通知Listview

#import <UIKit/UIKit.h>NS_ASSUME_NONNULL_BEGIN@interface ClockViewCell : UIView/// 更新cell数据
/// - Parameters:
///   - time: 时间
///   - repeat: 重复日期
///   - on: 开关状态
- (void)configureWithTime:(NSString *)time repeat:(NSString *)repeat on:(BOOL)on;/// 删除按钮是否跟随视图滑动,默认YES
@property (nonatomic, assign) BOOL isFollowing;
/// 是否为打开状态,默认NO
@property (nonatomic, assign) BOOL isOpen;/// cell的高度
@property (nonatomic, assign) CGFloat cellHeight;
/// 每个cell之间的间距
@property (nonatomic, assign) CGFloat cellSpacing;//- (void)openSwipe;
/// 恢复视图位置
- (void)closeSwipe;//@property (nonatomic, copy, readonly) NSString *reuseIdentifier;//- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier;@property (nonatomic, copy) void(^deleteBlock)(ClockViewCell *cell);
@property (nonatomic, copy) void(^switchBlock)(ClockViewCell *cell, BOOL on);
@property (nonatomic, copy) void(^tapBlock)(ClockViewCell *cell);
@property (nonatomic, copy) void(^panBlock)(ClockViewCell *cell);@endNS_ASSUME_NONNULL_END
 ClockViewCell.h
#import "ClockViewCell.h"@interface ClockViewCell () <UIGestureRecognizerDelegate>@property (nonatomic, assign) CGFloat contentViewX;
@property (nonatomic, strong) UIView *bgView;
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UILabel *timeLabel;
@property (nonatomic, strong) UILabel *repeatLabel;
@property (nonatomic, strong) UISwitch *switchView;
@property (nonatomic, strong) UIButton *deleteBtn;@end@implementation ClockViewCell- (instancetype)initWithFrame:(CGRect)frame {self = [super initWithFrame:frame];if (self) {self.isOpen = NO;[self initView];[self addGesture];}return self;
}//- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
//    if (self = [super initWithFrame:CGRectZero]) {
//        _reuseIdentifier = [reuseIdentifier copy];
//        // 在这里加 subviews
//        
//        self.isOpen = NO;
//        [self initView];
//        [self addGesture];
//    }
//    return self;
//}- (void)addGesture {UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];pan.cancelsTouchesInView = NO;pan.delegate = self;[self.contentView addGestureRecognizer:pan];//点击手势UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];[self.contentView addGestureRecognizer:tap];
}#pragma mark - UIGestureRecognizerDelegate- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {return NO;
}- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;CGPoint translation = [pan translationInView:self];return fabs(translation.x) > fabs(translation.y); // 横向滑动才触发 cell 滑动}return YES;
}- (void)handleTap:(UITapGestureRecognizer *)gesture {if (self.isOpen) {[self closeSwipe];return;}if (self.tapBlock) {self.tapBlock(self);}
}#pragma mark - 手势处理- (void)handlePan:(UIPanGestureRecognizer *)gesture {if (self.panBlock) {self.panBlock(self);}CGPoint translation = [gesture translationInView:self];static CGFloat originalX = 0;static CGFloat originalXOfBtn = 0;if (gesture.state == UIGestureRecognizerStateBegan) {originalX = self.contentView.frame.origin.x;// 记录初始位置originalXOfBtn = self.deleteBtn.frame.origin.x;} else if (gesture.state == UIGestureRecognizerStateChanged) {CGFloat newX = 0;CGFloat effectWidth = 0;//为了让按钮多一个滑动过多时的回弹效果,显得不那么生硬,根据具体需要增加if (originalX == self.contentViewX) {// 如果初始位置为self.contentViewX,则允许向左滑动newX = MIN(0 + effectWidth, MAX(-76 - effectWidth, translation.x));// 限制左滑范围默认 0 到 -76} else {newX = MIN(76 + effectWidth, MAX(0 - effectWidth, translation.x));// 限制左滑范围默认 0 到 76}self.contentView.frame = CGRectMake(newX + originalX, 0, self.bounds.size.width - 40, self.bounds.size.height);if (self.isFollowing) {self.deleteBtn.frame = CGRectMake(originalXOfBtn + newX, 0, 70, self.bounds.size.height);}} else if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateCancelled) {CGFloat threshold = -38 + self.contentViewX; // 触发删除按钮的阈值// 滑动超过一半,固定显示全部按钮if (self.contentView.frame.origin.x < threshold) {[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{self.contentView.frame = CGRectMake(-56, 0, self.bounds.size.width - 40, self.bounds.size.height);if (self.isFollowing) {self.deleteBtn.frame = CGRectMake(self.bgView.bounds.size.width - 70, 0, 70, self.bounds.size.height);}} completion:^(BOOL finished) {self.isOpen = YES;}];} else {// 否则弹回[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{self.contentView.frame = CGRectMake(20, 0, self.bounds.size.width - 40, self.bounds.size.height);if (self.isFollowing) {self.deleteBtn.frame = CGRectMake(self.bgView.bounds.size.width + 6, 0, 70, self.bounds.size.height);}} completion:^(BOOL finished) {self.isOpen = NO;}];}}
}- (void)initView {self.contentViewX = 20;self.bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width - 20, self.bounds.size.height)];self.bgView.layer.masksToBounds = YES;[self addSubview:self.bgView];self.deleteBtn = [[UIButton alloc] initWithFrame:CGRectMake(self.bgView.bounds.size.width - 70, 0, 70, self.bounds.size.height)];[self.deleteBtn addTarget:self action:@selector(deleteAction:) forControlEvents:UIControlEventTouchUpInside];self.deleteBtn.layer.cornerRadius = 15;self.deleteBtn.layer.masksToBounds = YES;[self.deleteBtn setTitle:@"删除" forState:UIControlStateNormal];[self.deleteBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];self.deleteBtn.titleLabel.font = [UIFont systemFontOfSize:20];self.deleteBtn.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];;[self.bgView addSubview:self.deleteBtn];self.contentView = [[UIView alloc] initWithFrame:CGRectMake(self.contentViewX, 0, self.bgView.bounds.size.width - self.contentViewX, self.bounds.size.height)];self.contentView.backgroundColor = [UIColor whiteColor];self.contentView.layer.cornerRadius = 15;self.contentView.layer.masksToBounds = YES;[self.bgView addSubview:self.contentView];self.timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(15, 16, 100, 30)];self.timeLabel.font = [UIFont systemFontOfSize:20];self.timeLabel.textColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0];;[self.contentView addSubview:self.timeLabel];self.repeatLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 59, 300, 25)];self.repeatLabel.font = [UIFont systemFontOfSize:16];self.repeatLabel.textColor = [UIColor colorWithRed:111/255.0 green:111/255.0 blue:111/255.0 alpha:1.0];;[self.contentView addSubview:self.repeatLabel];self.switchView = [[UISwitch alloc] initWithFrame:CGRectZero];self.switchView.center = CGPointMake(self.contentView.bounds.size.width - 45, 30);self.switchView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;self.switchView.onTintColor = [UIColor colorWithRed:56/255.0 green:255/255.0 blue:209/255.0 alpha:1.0];;[self.contentView addSubview:self.switchView];[self.switchView addTarget:self action:@selector(switchAction:) forControlEvents:UIControlEventValueChanged];self.isFollowing = YES;
}- (void)deleteAction:(UIButton *)sender {if (self.deleteBlock) {self.deleteBlock(self);}
}- (void)switchAction:(UISwitch *)sender {if (self.switchBlock) {self.switchBlock(self, sender.isOn);}
}#pragma mark - reload- (void)configureWithTime:(NSString *)time repeat:(NSString *)repeat on:(BOOL)on {self.timeLabel.text = time;self.repeatLabel.text = repeat;[self.switchView setOn:on animated:NO];
}- (void)closeSwipe {if (!self.isOpen) return;self.isOpen = NO;[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{self.contentView.frame = CGRectMake(20, 0, self.bounds.size.width - 40, self.bounds.size.height);if (self.isFollowing) {self.deleteBtn.frame = CGRectMake(self.bgView.bounds.size.width + 6, 0, 70, self.bounds.size.height);}} completion:^(BOOL finished) {}];
}#pragma mark - lazy load and setters- (void)setIsFollowing:(BOOL)isFollowing {_isFollowing = isFollowing;if (isFollowing) {self.deleteBtn.frame = CGRectMake(self.bgView.bounds.size.width + 6, 0, 70, self.bounds.size.height);} else {//宽度高度缩小,上右下缩小1,因为Core Animation 的“抗锯齿边缘融合”问题(subpixel blending),圆角边缘的半透明像素会“透”出红色self.deleteBtn.frame = CGRectMake(self.bgView.bounds.size.width - 70, 1, 69, self.bounds.size.height - 2);}
}@end

资源链接:https://github.com/MrZWCui/ClockView.git

相关文章:

  • KUKA机器人关机时冷启动介绍
  • iOS - 音频: Core Audio - 播放
  • Java云原生+quarkus
  • python:sklearn 主成分分析(PCA)
  • Android 手动删除 AAR jar 包 中的文件
  • Weka通过10天的内存指标数据计算内存指标动态阈值
  • Mac 创建QT按钮以及一些操作
  • Kafka的Rebalance机制可能引发什么问题?如何优化?怎么减少不必要的Rebalance
  • 四.割草机技术总结--4.基站发送给流动站的差分数据传输标准RTCM
  • Elasticsearch 内存使用指南
  • milvus编译与使用
  • 日本IT行业|salesforce开发语言占据的地位
  • 【C++11】类的新功能
  • Android——Serializable和Parcelable
  • C++ 如何计算两个gps 的距离
  • Vue3调度器错误解析,完美解决Unhandled error during execution of scheduler flush.
  • ElasticSearch入门
  • 若依后台管理系统-v3.8.8-登录模块--个人笔记
  • 043-代码味道-循环依赖
  • 健康养生:拥抱活力生活
  • 北京银行一季度净赚超76亿降逾2%,不良贷款率微降
  • 巴西外长维埃拉:国际形势日益复杂,金砖国家必须发挥核心作用
  • 杭州6宗涉宅用地收金125.76亿元,萧山区地块楼面价冲破5万元/平米
  • 江西省国资委原副主任李键主动向组织交代问题,接受审查调查
  • 人社部:将会同更多部门分行业、分领域制定专项培训计划
  • 央媒关注给保洁人员设休息室:让每一份踏实奋斗得到尊重呵护