2025-11-08 21:44:41 +08:00
|
|
|
|
//
|
|
|
|
|
|
// MySkinVC.m
|
|
|
|
|
|
// keyBoard
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
#import "MySkinVC.h"
|
2025-11-17 14:53:23 +08:00
|
|
|
|
#import "KBSkinDetailVC.h"
|
2025-11-08 21:44:41 +08:00
|
|
|
|
#import <Masonry/Masonry.h>
|
|
|
|
|
|
#import "UIColor+Extension.h"
|
2025-11-09 21:41:35 +08:00
|
|
|
|
#import <MJRefresh/MJRefresh.h>
|
|
|
|
|
|
#import "UIScrollView+KBEmptyView.h" // 统一空态封装(LYEmptyView)
|
2025-11-08 21:44:41 +08:00
|
|
|
|
|
|
|
|
|
|
#import "MySkinCell.h"
|
|
|
|
|
|
|
|
|
|
|
|
static NSString * const kMySkinCellId = @"kMySkinCellId";
|
|
|
|
|
|
|
|
|
|
|
|
@interface MySkinVC () <UICollectionViewDataSource, UICollectionViewDelegate>
|
|
|
|
|
|
@property (nonatomic, strong) UICollectionView *collectionView; // 列表
|
|
|
|
|
|
@property (nonatomic, strong) UIView *bottomView; // 底部操作条
|
|
|
|
|
|
@property (nonatomic, strong) UILabel *selectedLabel; // 已选择数量
|
|
|
|
|
|
@property (nonatomic, strong) UIButton *deleteButton; // 删除
|
|
|
|
|
|
|
|
|
|
|
|
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *data; // 简单数据源
|
2025-11-09 21:41:35 +08:00
|
|
|
|
@property (nonatomic, assign) NSInteger loadCount; // 刷新计数(用于演示空/有数据切换)
|
2025-11-08 21:44:41 +08:00
|
|
|
|
@property (nonatomic, assign, getter=isEditingMode) BOOL editingMode; // 是否编辑态
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation MySkinVC
|
|
|
|
|
|
|
|
|
|
|
|
- (void)viewDidLoad {
|
|
|
|
|
|
[super viewDidLoad];
|
|
|
|
|
|
self.view.backgroundColor = [UIColor whiteColor];
|
|
|
|
|
|
|
2025-11-17 13:30:01 +08:00
|
|
|
|
// self.title = @"My Skin"; // 标题
|
2025-11-08 21:44:41 +08:00
|
|
|
|
|
2025-11-17 14:53:23 +08:00
|
|
|
|
// 右上角 Editor/Cancel 使用 BaseViewController 自定义导航栏的 kb_rightButton
|
|
|
|
|
|
self.kb_rightButton.hidden = NO;
|
|
|
|
|
|
[self.kb_rightButton setTitle:@"Editor" forState:UIControlStateNormal];
|
|
|
|
|
|
[self.kb_rightButton removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents];
|
|
|
|
|
|
[self.kb_rightButton addTarget:self action:@selector(onToggleEdit) forControlEvents:UIControlEventTouchUpInside];
|
2025-11-08 21:44:41 +08:00
|
|
|
|
|
2025-11-09 21:41:35 +08:00
|
|
|
|
// 数据源初始化为空(演示空态 + 下拉刷新)
|
|
|
|
|
|
self.data = [NSMutableArray array];
|
2025-11-08 21:44:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 视图
|
|
|
|
|
|
[self.view addSubview:self.collectionView];
|
|
|
|
|
|
[self.view addSubview:self.bottomView];
|
|
|
|
|
|
|
|
|
|
|
|
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
2025-11-17 13:30:01 +08:00
|
|
|
|
make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT);
|
2025-11-08 21:44:41 +08:00
|
|
|
|
make.left.right.equalTo(self.view);
|
2025-11-17 13:30:01 +08:00
|
|
|
|
make.bottom.equalTo(self.view.mas_bottom);
|
2025-11-08 21:44:41 +08:00
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.right.equalTo(self.view);
|
|
|
|
|
|
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
|
|
|
|
|
|
make.height.mas_equalTo(64);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
2025-11-09 21:41:35 +08:00
|
|
|
|
// 空态视图(LYEmptyView)统一样式 + 重试按钮
|
2025-11-10 15:38:30 +08:00
|
|
|
|
KBWeakSelf
|
2025-11-09 21:41:35 +08:00
|
|
|
|
[self.collectionView kb_makeDefaultEmptyViewWithImage:nil
|
|
|
|
|
|
title:@"暂无皮肤"
|
|
|
|
|
|
detail:@"下拉刷新试试"
|
|
|
|
|
|
buttonTitle:@"重试"
|
|
|
|
|
|
tapHandler:nil
|
|
|
|
|
|
buttonHandler:^{ [weakSelf.collectionView.mj_header beginRefreshing]; }];
|
|
|
|
|
|
[self.collectionView kb_setLYAutoShowEnabled:NO]; // 采用手动控制显隐
|
|
|
|
|
|
|
|
|
|
|
|
// 立即按当前数据源状态显示一次空态(首屏就应展示空视图)
|
|
|
|
|
|
[self.collectionView kb_endLoadingForEmpty];
|
|
|
|
|
|
|
|
|
|
|
|
// 下拉刷新(演示网络加载 + 空态切换)
|
|
|
|
|
|
self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(fetchData)];
|
|
|
|
|
|
|
|
|
|
|
|
// 首次进入自动刷新
|
|
|
|
|
|
[self.collectionView.mj_header beginRefreshing];
|
|
|
|
|
|
|
2025-11-08 21:44:41 +08:00
|
|
|
|
// 初始:非编辑态,隐藏底部
|
|
|
|
|
|
self.bottomView.hidden = YES;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-09 21:41:35 +08:00
|
|
|
|
#pragma mark - Data
|
|
|
|
|
|
|
|
|
|
|
|
- (void)fetchData {
|
|
|
|
|
|
// 模拟网络延迟 1.0s
|
|
|
|
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
|
|
|
self.loadCount += 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 交替返回:奇数次空数据,偶数次有数据(演示空态/非空切换)
|
|
|
|
|
|
[self.data removeAllObjects];
|
|
|
|
|
|
if (self.loadCount % 2 == 0) {
|
|
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
|
|
|
|
[self.data addObject:@{ @"title": @"Dopamine" }];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[self.collectionView reloadData];
|
|
|
|
|
|
[self.collectionView kb_endLoadingForEmpty]; // 根据数据源显示/隐藏 emptyView
|
|
|
|
|
|
[self.collectionView.mj_header endRefreshing];
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-08 21:44:41 +08:00
|
|
|
|
#pragma mark - Actions
|
|
|
|
|
|
|
|
|
|
|
|
- (void)onToggleEdit {
|
|
|
|
|
|
self.editingMode = !self.editingMode;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新顶部按钮
|
2025-11-17 14:53:23 +08:00
|
|
|
|
[self.kb_rightButton setTitle:(self.isEditingMode ? @"Cancel" : @"Editor")
|
|
|
|
|
|
forState:UIControlStateNormal];
|
2025-11-08 21:44:41 +08:00
|
|
|
|
|
|
|
|
|
|
// 控制底部栏显隐
|
|
|
|
|
|
self.bottomView.hidden = !self.isEditingMode;
|
|
|
|
|
|
|
|
|
|
|
|
// 列表进入/退出多选
|
|
|
|
|
|
self.collectionView.allowsMultipleSelection = self.isEditingMode;
|
|
|
|
|
|
// 直接刷新可见 cell,确保切换后立刻出现/隐藏 markView
|
|
|
|
|
|
for (UICollectionViewCell *c in self.collectionView.visibleCells) {
|
|
|
|
|
|
if (![c isKindOfClass:MySkinCell.class]) continue;
|
|
|
|
|
|
MySkinCell *cell = (MySkinCell *)c;
|
|
|
|
|
|
cell.editing = self.isEditingMode;
|
|
|
|
|
|
NSIndexPath *ip = [self.collectionView indexPathForCell:cell];
|
|
|
|
|
|
BOOL selected = [[self.collectionView indexPathsForSelectedItems] containsObject:ip];
|
|
|
|
|
|
[cell updateSelected:selected];
|
|
|
|
|
|
}
|
|
|
|
|
|
// 再做一次 reload 保守刷新(防止 offscreen cell)
|
|
|
|
|
|
[self.collectionView reloadData];
|
|
|
|
|
|
|
|
|
|
|
|
// 清空选择并刷新底部文案
|
|
|
|
|
|
for (NSIndexPath *ip in [self.collectionView indexPathsForSelectedItems]) {
|
|
|
|
|
|
[self.collectionView deselectItemAtIndexPath:ip animated:NO];
|
|
|
|
|
|
}
|
|
|
|
|
|
[self updateBottomUI];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)onDelete {
|
|
|
|
|
|
// 根据选中项删除
|
|
|
|
|
|
NSArray<NSIndexPath *> *selected = [[self.collectionView indexPathsForSelectedItems] sortedArrayUsingSelector:@selector(compare:)];
|
|
|
|
|
|
if (selected.count == 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 批量更新,先改数据,再删 UI
|
|
|
|
|
|
[self.collectionView performBatchUpdates:^{
|
|
|
|
|
|
NSMutableIndexSet *set = [NSMutableIndexSet indexSet];
|
|
|
|
|
|
for (NSIndexPath *ip in selected) { [set addIndex:ip.item]; }
|
|
|
|
|
|
[self.data removeObjectsAtIndexes:set];
|
|
|
|
|
|
[self.collectionView deleteItemsAtIndexPaths:selected];
|
|
|
|
|
|
} completion:^(BOOL finished) {
|
|
|
|
|
|
[self updateBottomUI];
|
2025-11-09 21:41:35 +08:00
|
|
|
|
// 根据当前数据源显隐空视图(删除到 0 条时应显示空态)
|
|
|
|
|
|
[self.collectionView kb_endLoadingForEmpty];
|
2025-11-08 21:44:41 +08:00
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)updateBottomUI {
|
|
|
|
|
|
NSInteger count = self.collectionView.indexPathsForSelectedItems.count;
|
|
|
|
|
|
self.selectedLabel.text = [NSString stringWithFormat:@"Selected: %ld Skins", (long)count];
|
|
|
|
|
|
|
|
|
|
|
|
BOOL enable = count > 0;
|
|
|
|
|
|
self.deleteButton.enabled = enable;
|
|
|
|
|
|
if (enable) {
|
2025-11-10 19:51:23 +08:00
|
|
|
|
self.deleteButton.backgroundColor = [UIColor colorWithHex:KBColorValue];
|
2025-11-08 21:44:41 +08:00
|
|
|
|
[self.deleteButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
2025-11-10 19:51:23 +08:00
|
|
|
|
self.deleteButton.layer.borderColor = [UIColor colorWithHex:KBColorValue].CGColor;
|
2025-11-08 21:44:41 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
self.deleteButton.backgroundColor = [UIColor colorWithHex:0xF2F2F2];
|
|
|
|
|
|
[self.deleteButton setTitleColor:[UIColor colorWithHex:0xC8C8C8] forState:UIControlStateNormal];
|
|
|
|
|
|
self.deleteButton.layer.borderColor = [UIColor colorWithHex:0xE6E6E6].CGColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - UICollectionView
|
|
|
|
|
|
|
|
|
|
|
|
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
|
|
|
|
|
return self.data.count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
|
|
|
MySkinCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kMySkinCellId forIndexPath:indexPath];
|
|
|
|
|
|
NSDictionary *d = self.data[indexPath.item];
|
|
|
|
|
|
[cell configWithTitle:d[@"title"] image:nil];
|
|
|
|
|
|
cell.editing = self.isEditingMode; // 控制是否显示选择圆点
|
|
|
|
|
|
// 同步选中状态(复用时)
|
|
|
|
|
|
BOOL selected = [[collectionView indexPathsForSelectedItems] containsObject:indexPath];
|
|
|
|
|
|
[cell updateSelected:selected];
|
|
|
|
|
|
return cell;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
|
|
|
if (!self.isEditingMode) {
|
|
|
|
|
|
// 非编辑态:可在此进入详情,当前示例不处理
|
|
|
|
|
|
[collectionView deselectItemAtIndexPath:indexPath animated:YES];
|
2025-11-17 14:53:23 +08:00
|
|
|
|
KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init];
|
|
|
|
|
|
[self.navigationController pushViewController:vc animated:true];
|
2025-11-08 21:44:41 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
MySkinCell *cell = (MySkinCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
|
|
|
|
|
[cell updateSelected:YES];
|
|
|
|
|
|
[self updateBottomUI];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
|
|
|
if (!self.isEditingMode) return;
|
|
|
|
|
|
MySkinCell *cell = (MySkinCell *)[collectionView cellForItemAtIndexPath:indexPath];
|
|
|
|
|
|
[cell updateSelected:NO];
|
|
|
|
|
|
[self updateBottomUI];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
|
|
|
if (![cell isKindOfClass:MySkinCell.class]) return;
|
|
|
|
|
|
MySkinCell *c = (MySkinCell *)cell;
|
|
|
|
|
|
c.editing = self.isEditingMode; // 保证滚动出现的新 cell 同步编辑态
|
|
|
|
|
|
BOOL selected = [[collectionView indexPathsForSelectedItems] containsObject:indexPath];
|
|
|
|
|
|
[c updateSelected:selected];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Lazy
|
|
|
|
|
|
|
|
|
|
|
|
- (UICollectionView *)collectionView {
|
|
|
|
|
|
if (!_collectionView) {
|
|
|
|
|
|
UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
|
|
|
|
|
|
CGFloat inset = 16;
|
|
|
|
|
|
CGFloat spacing = 12;
|
|
|
|
|
|
CGFloat W = UIScreen.mainScreen.bounds.size.width;
|
|
|
|
|
|
CGFloat itemW = floor((W - inset * 2 - spacing) / 2.0);
|
|
|
|
|
|
CGFloat itemH = itemW * 0.82f; // 接近截图比例
|
|
|
|
|
|
layout.itemSize = CGSizeMake(itemW, itemH);
|
|
|
|
|
|
layout.minimumInteritemSpacing = spacing;
|
|
|
|
|
|
layout.minimumLineSpacing = spacing;
|
|
|
|
|
|
layout.sectionInset = UIEdgeInsetsMake(12, inset, 12, inset);
|
|
|
|
|
|
|
|
|
|
|
|
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
|
|
|
|
|
_collectionView.backgroundColor = [UIColor whiteColor];
|
|
|
|
|
|
_collectionView.dataSource = self;
|
|
|
|
|
|
_collectionView.delegate = self;
|
|
|
|
|
|
_collectionView.allowsMultipleSelection = NO; // 初始
|
|
|
|
|
|
[_collectionView registerClass:MySkinCell.class forCellWithReuseIdentifier:kMySkinCellId];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _collectionView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIView *)bottomView {
|
|
|
|
|
|
if (!_bottomView) {
|
|
|
|
|
|
_bottomView = [UIView new];
|
|
|
|
|
|
_bottomView.backgroundColor = [UIColor whiteColor];
|
|
|
|
|
|
_bottomView.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.06].CGColor;
|
|
|
|
|
|
_bottomView.layer.shadowOpacity = 1;
|
|
|
|
|
|
_bottomView.layer.shadowOffset = CGSizeMake(0, -2);
|
|
|
|
|
|
|
|
|
|
|
|
[_bottomView addSubview:self.selectedLabel];
|
|
|
|
|
|
[_bottomView addSubview:self.deleteButton];
|
|
|
|
|
|
|
|
|
|
|
|
[self.selectedLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.left.equalTo(_bottomView).offset(16);
|
|
|
|
|
|
make.centerY.equalTo(_bottomView);
|
|
|
|
|
|
}];
|
|
|
|
|
|
[self.deleteButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.right.equalTo(_bottomView).offset(-16);
|
|
|
|
|
|
make.centerY.equalTo(_bottomView);
|
|
|
|
|
|
make.width.mas_equalTo(92);
|
|
|
|
|
|
make.height.mas_equalTo(36);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
return _bottomView;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UILabel *)selectedLabel {
|
|
|
|
|
|
if (!_selectedLabel) {
|
|
|
|
|
|
_selectedLabel = [UILabel new];
|
|
|
|
|
|
_selectedLabel.textColor = [UIColor colorWithHex:0x666666];
|
|
|
|
|
|
_selectedLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
|
|
|
|
|
|
_selectedLabel.text = @"Selected: 0 Skins";
|
|
|
|
|
|
}
|
|
|
|
|
|
return _selectedLabel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (UIButton *)deleteButton {
|
|
|
|
|
|
if (!_deleteButton) {
|
|
|
|
|
|
_deleteButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
|
|
|
|
[_deleteButton setTitle:@"Delete" forState:UIControlStateNormal];
|
|
|
|
|
|
_deleteButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
|
|
|
|
|
|
_deleteButton.layer.cornerRadius = 18;
|
|
|
|
|
|
_deleteButton.layer.borderWidth = 1;
|
|
|
|
|
|
_deleteButton.clipsToBounds = YES;
|
|
|
|
|
|
[_deleteButton addTarget:self action:@selector(onDelete) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
|
// 初始禁用态样式
|
|
|
|
|
|
_deleteButton.enabled = NO;
|
|
|
|
|
|
_deleteButton.backgroundColor = [UIColor colorWithHex:0xF2F2F2];
|
|
|
|
|
|
[_deleteButton setTitleColor:[UIColor colorWithHex:0xC8C8C8] forState:UIControlStateNormal];
|
|
|
|
|
|
_deleteButton.layer.borderColor = [UIColor colorWithHex:0xE6E6E6].CGColor;
|
|
|
|
|
|
}
|
|
|
|
|
|
return _deleteButton;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@end
|