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

@end

NS_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 loading
    for (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 event
    NSLog(@"Clock at index %ld clicked", (long)index);
}

- (void)clockListView:(ClockListView *)clockListView didSwitchClockAtIndex:(NSInteger)index on:(BOOL)on {
    // Handle switch state change
    NSLog(@"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;

@end

NS_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 {
    // 清空之前的 cell
    for (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.3
                              delay:0
                            options:UIViewAnimationOptionCurveEaseOut
                         animations:^{
            CGRect frame = cell.frame;
            frame.origin.y -= (self.cellHeight + self.cellSpacing);
            cell.frame = frame;
        } completion:nil];
    }

    // 淡出动画
    [UIView animateWithDuration:0.3
                          delay:0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
        //当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变化时会导致画面突然往下坠,所以先滚到视图最下面再修改contentSize
        if (isLastCellVisible) {
            //删除的是最后一个cell
            CGFloat 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秒是为了让动画完成后再更新 contentSize
            dispatch_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 是打开的,执行点击事件,否则关闭其他 cell
    if (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);

@end

NS_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

相关推荐
HarderCoder7 小时前
iOS 知识积累第一弹:从 struct 到 APP 生命周期的全景复盘
ios
goodSleep11 小时前
更新Mac OS Tahoe26用命令恢复 Mac 启动台时不小心禁用了聚焦搜索
macos
叽哥18 小时前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
小溪彼岸3 天前
macOS自带截图命令ScreenCapture
macos
法的空间3 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918413 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview