2
This commit is contained in:
@@ -79,6 +79,8 @@
|
||||
049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */; };
|
||||
049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20D2EC1CD2800FAB05D /* KBAlert.m */; };
|
||||
049FB2112EC1F72F00FAB05D /* KBMyListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2102EC1F72F00FAB05D /* KBMyListCell.m */; };
|
||||
049FB2172EC20A6600FAB05D /* BMLongPressDragCellCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2132EC20A6600FAB05D /* BMLongPressDragCellCollectionView.m */; };
|
||||
049FB21A2EC20A9E00FAB05D /* KBMyKeyBoardVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB2192EC20A9E00FAB05D /* KBMyKeyBoardVC.m */; };
|
||||
04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; };
|
||||
04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; };
|
||||
04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; };
|
||||
@@ -133,6 +135,7 @@
|
||||
A1B2E1012EBC7AAA00000001 /* KBTopThreeView.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0022EBC7AAA00000001 /* KBTopThreeView.m */; };
|
||||
A1B2E1022EBC7AAA00000001 /* HomeHotCell.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2E0042EBC7AAA00000001 /* HomeHotCell.m */; };
|
||||
ECC9EE02174D86E8D792472F /* Pods_keyBoard.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 967065BB5230E43F293B3AF9 /* Pods_keyBoard.framework */; };
|
||||
049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -275,6 +278,14 @@
|
||||
049FB20D2EC1CD2800FAB05D /* KBAlert.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAlert.m; sourceTree = "<group>"; };
|
||||
049FB20F2EC1F72F00FAB05D /* KBMyListCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyListCell.h; sourceTree = "<group>"; };
|
||||
049FB2102EC1F72F00FAB05D /* KBMyListCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyListCell.m; sourceTree = "<group>"; };
|
||||
049FB2122EC20A6600FAB05D /* BMLongPressDragCellCollectionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BMLongPressDragCellCollectionView.h; sourceTree = "<group>"; };
|
||||
049FB2132EC20A6600FAB05D /* BMLongPressDragCellCollectionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BMLongPressDragCellCollectionView.m; sourceTree = "<group>"; };
|
||||
049FB2142EC20A6600FAB05D /* BMLongPressDragCellCollectionViewDataSource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BMLongPressDragCellCollectionViewDataSource.h; sourceTree = "<group>"; };
|
||||
049FB2152EC20A6600FAB05D /* BMLongPressDragCellCollectionViewDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BMLongPressDragCellCollectionViewDelegate.h; sourceTree = "<group>"; };
|
||||
049FB2182EC20A9E00FAB05D /* KBMyKeyBoardVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyKeyBoardVC.h; sourceTree = "<group>"; };
|
||||
049FB2192EC20A9E00FAB05D /* KBMyKeyBoardVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyKeyBoardVC.m; sourceTree = "<group>"; };
|
||||
049FB31B2EC21BCD00FAB05D /* KBMyKeyboardCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBMyKeyboardCell.h; sourceTree = "<group>"; };
|
||||
049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBMyKeyboardCell.m; sourceTree = "<group>"; };
|
||||
04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = "<group>"; };
|
||||
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = "<group>"; };
|
||||
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = "<group>"; };
|
||||
@@ -619,6 +630,7 @@
|
||||
048908D32EBF618E00FABA60 /* Vender */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
049FB2162EC20A6600FAB05D /* BMLongPressDragCellCollectionView */,
|
||||
048908D92EBF61AF00FABA60 /* UICollectionViewLeftAlignedLayout */,
|
||||
);
|
||||
path = Vender;
|
||||
@@ -633,6 +645,17 @@
|
||||
path = UICollectionViewLeftAlignedLayout;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
049FB2162EC20A6600FAB05D /* BMLongPressDragCellCollectionView */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
049FB2122EC20A6600FAB05D /* BMLongPressDragCellCollectionView.h */,
|
||||
049FB2132EC20A6600FAB05D /* BMLongPressDragCellCollectionView.m */,
|
||||
049FB2142EC20A6600FAB05D /* BMLongPressDragCellCollectionViewDataSource.h */,
|
||||
049FB2152EC20A6600FAB05D /* BMLongPressDragCellCollectionViewDelegate.h */,
|
||||
);
|
||||
path = BMLongPressDragCellCollectionView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
04A9FE122EB4D0D20020DB6D /* Manager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -831,6 +854,8 @@
|
||||
04FC95BC2EB1E3B1007BD342 /* V */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
049FB31B2EC21BCD00FAB05D /* KBMyKeyboardCell.h */,
|
||||
049FB31C2EC21BCD00FAB05D /* KBMyKeyboardCell.m */,
|
||||
048908E12EBF760000FABA60 /* MySkinCell.h */,
|
||||
048908E22EBF760000FABA60 /* MySkinCell.m */,
|
||||
048908E42EBF841B00FABA60 /* KBSkinDetailTagCell.h */,
|
||||
@@ -856,6 +881,8 @@
|
||||
children = (
|
||||
04FC95D02EB1E7AE007BD342 /* MyVC.h */,
|
||||
04FC95D12EB1E7AE007BD342 /* MyVC.m */,
|
||||
049FB2182EC20A9E00FAB05D /* KBMyKeyBoardVC.h */,
|
||||
049FB2192EC20A9E00FAB05D /* KBMyKeyBoardVC.m */,
|
||||
048908DE2EBF73DC00FABA60 /* MySkinVC.h */,
|
||||
048908DF2EBF73DC00FABA60 /* MySkinVC.m */,
|
||||
048908E12EBF821700FABA60 /* KBSkinDetailVC.h */,
|
||||
@@ -1355,6 +1382,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
049FB31D2EC21BCD00FAB05D /* KBMyKeyboardCell.m in Sources */,
|
||||
048909F62EC0AAAA00FABA60 /* KBCategoryTitleCell.m in Sources */,
|
||||
048909F72EC0AAAA00FABA60 /* KBCategoryTitleView.m in Sources */,
|
||||
04890A042EC0BBBB00FABA60 /* KBCategoryTitleImageCell.m in Sources */,
|
||||
@@ -1377,6 +1405,7 @@
|
||||
048908EF2EBF861800FABA60 /* KBSkinSectionTitleCell.m in Sources */,
|
||||
048908E32EBF821700FABA60 /* KBSkinDetailVC.m in Sources */,
|
||||
0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */,
|
||||
049FB21A2EC20A9E00FAB05D /* KBMyKeyBoardVC.m in Sources */,
|
||||
048908D22EBF611D00FABA60 /* KBHistoryMoreCell.m in Sources */,
|
||||
04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */,
|
||||
0477BDF72EBC63A80055D639 /* KBTestVC.m in Sources */,
|
||||
@@ -1392,6 +1421,7 @@
|
||||
048908E02EBF73DC00FABA60 /* MySkinVC.m in Sources */,
|
||||
048908F22EC047FD00FABA60 /* KBShopHeadView.m in Sources */,
|
||||
048908EC2EBF849300FABA60 /* KBSkinTagsContainerCell.m in Sources */,
|
||||
049FB2172EC20A6600FAB05D /* BMLongPressDragCellCollectionView.m in Sources */,
|
||||
0477BDF02EBB76E30055D639 /* HomeSheetVC.m in Sources */,
|
||||
048908E62EBF841B00FABA60 /* KBSkinDetailTagCell.m in Sources */,
|
||||
04FC97002EB30A00007BD342 /* KBGuideTopCell.m in Sources */,
|
||||
|
||||
24
keyBoard/Class/Me/V/KBMyKeyboardCell.h
Normal file
24
keyBoard/Class/Me/V/KBMyKeyboardCell.h
Normal file
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// KBMyKeyboardCell.h
|
||||
// keyBoard
|
||||
//
|
||||
// 自定义键盘截图页的标签 Cell(带表情、文案、右上角删除圆点)。
|
||||
// 使用 Masonry 约束,支持根据文案动态计算宽度。
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBMyKeyboardCell : UICollectionViewCell
|
||||
|
||||
/// 配置展示内容
|
||||
- (void)configEmoji:(NSString *)emoji title:(NSString *)title;
|
||||
|
||||
/// 根据文案计算自适应尺寸(高度固定,宽度随文本变化)
|
||||
+ (CGSize)sizeForEmoji:(NSString *)emoji title:(NSString *)title;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
161
keyBoard/Class/Me/V/KBMyKeyboardCell.m
Normal file
161
keyBoard/Class/Me/V/KBMyKeyboardCell.m
Normal file
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// KBMyKeyboardCell.m
|
||||
// keyBoard
|
||||
//
|
||||
// 截图页标签样式 cell,实现:
|
||||
// - 左侧 Emoji,中间标题;
|
||||
// - 右上角小圆点(减号),用于删除编辑态展示;
|
||||
// - 白底圆角,阴影/描边轻微以贴近设计;
|
||||
// - 暴露 +sizeForEmoji:title: 以便外部根据文本动态决定 item 宽度。
|
||||
//
|
||||
|
||||
#import "KBMyKeyboardCell.h"
|
||||
|
||||
@interface KBMyKeyboardCell ()
|
||||
@property (nonatomic, strong) UIView *coverView; // 左侧表情
|
||||
|
||||
@property (nonatomic, strong) UILabel *emojiLabel; // 左侧表情
|
||||
@property (nonatomic, strong) UILabel *titleLabel; // 标题
|
||||
@property (nonatomic, strong) UIView *minusBadge; // 右上角减号圆点
|
||||
@property (nonatomic, strong) UILabel *minusLabel; // 减号
|
||||
@end
|
||||
|
||||
@implementation KBMyKeyboardCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.contentView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// 轻微阴影,让卡片更立体
|
||||
self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.08].CGColor;
|
||||
self.layer.shadowOpacity = 1.0;
|
||||
self.layer.shadowOffset = CGSizeMake(0, 2);
|
||||
self.layer.shadowRadius = 6;
|
||||
|
||||
[self.contentView addSubview:self.coverView];
|
||||
|
||||
[self.coverView addSubview:self.emojiLabel];
|
||||
[self.coverView addSubview:self.titleLabel];
|
||||
[self.contentView addSubview:self.minusBadge];
|
||||
[self.minusBadge addSubview:self.minusLabel];
|
||||
|
||||
[self.coverView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.bottom.left.equalTo(self.contentView).offset(0);
|
||||
make.height.mas_equalTo(36);
|
||||
}];
|
||||
|
||||
// Masonry 布局:左右 12 内边距,高度固定由外部控制
|
||||
[self.emojiLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.coverView).offset(12);
|
||||
make.centerY.equalTo(self.coverView);
|
||||
}];
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.emojiLabel.mas_right).offset(8);
|
||||
make.centerY.equalTo(self.coverView);
|
||||
make.right.lessThanOrEqualTo(self.coverView).offset(-12);
|
||||
}];
|
||||
|
||||
// 右上角删除圆点(展示用),默认显示;如需编辑态控制,可对其 hidden 做处理
|
||||
[self.minusBadge mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.height.mas_equalTo(11);
|
||||
make.top.equalTo(self.contentView).offset(0);
|
||||
make.right.equalTo(self.contentView).offset(0);
|
||||
}];
|
||||
[self.minusLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.center.equalTo(self.minusBadge);
|
||||
}];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)prepareForReuse {
|
||||
[super prepareForReuse];
|
||||
self.emojiLabel.text = @"";
|
||||
self.titleLabel.text = @"";
|
||||
}
|
||||
|
||||
- (void)configEmoji:(NSString *)emoji title:(NSString *)title {
|
||||
// 配置 UI 文案(为空补空串,避免计算崩溃)
|
||||
self.emojiLabel.text = emoji ?: @"";
|
||||
self.titleLabel.text = title ?: @"";
|
||||
}
|
||||
|
||||
+ (CGSize)sizeForEmoji:(NSString *)emoji title:(NSString *)title {
|
||||
// 固定高度 44,宽度 = 左右内边距(12+12) + Emoji 宽(按 20 号字估算) + 间距(8) + 文案宽
|
||||
UIFont *emojiFont = [UIFont systemFontOfSize:20];
|
||||
UIFont *titleFont = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
|
||||
|
||||
CGFloat emojiW = 0;
|
||||
if (emoji.length > 0) {
|
||||
CGSize s = [emoji sizeWithAttributes:@{NSFontAttributeName:emojiFont}];
|
||||
emojiW = ceil(s.width);
|
||||
} else {
|
||||
emojiW = 0; // 没有表情则不占宽
|
||||
}
|
||||
|
||||
CGFloat textW = 0;
|
||||
if (title.length > 0) {
|
||||
CGSize s = [title sizeWithAttributes:@{NSFontAttributeName:titleFont}];
|
||||
textW = ceil(s.width);
|
||||
}
|
||||
|
||||
CGFloat width = 12 + emojiW + (emojiW > 0 ? 8 : 0) + textW + 12;
|
||||
CGFloat minW = 84; // 最小宽保证视觉
|
||||
CGFloat maxW = UIScreen.mainScreen.bounds.size.width - 16 * 2; // 不超过屏幕可视宽
|
||||
width = MAX(minW, MIN(width, maxW));
|
||||
return CGSizeMake(width, 41.0);
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
|
||||
- (UILabel *)emojiLabel {
|
||||
if (!_emojiLabel) {
|
||||
_emojiLabel = [UILabel new];
|
||||
_emojiLabel.font = [UIFont systemFontOfSize:20];
|
||||
_emojiLabel.text = @"😀"; // 默认
|
||||
}
|
||||
return _emojiLabel;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [UILabel new];
|
||||
_titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
|
||||
_titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A];
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIView *)minusBadge {
|
||||
if (!_minusBadge) {
|
||||
_minusBadge = [UIView new];
|
||||
_minusBadge.backgroundColor = [UIColor colorWithHex:KBColorValue];
|
||||
_minusBadge.layer.cornerRadius = 5.5;
|
||||
_minusBadge.layer.masksToBounds = YES;
|
||||
}
|
||||
return _minusBadge;
|
||||
}
|
||||
|
||||
- (UILabel *)minusLabel {
|
||||
if (!_minusLabel) {
|
||||
_minusLabel = [UILabel new];
|
||||
_minusLabel.text = @"-";
|
||||
_minusLabel.textColor = [UIColor whiteColor];
|
||||
_minusLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightBold];
|
||||
_minusLabel.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _minusLabel;
|
||||
}
|
||||
|
||||
- (UIView *)coverView{
|
||||
if (!_coverView) {
|
||||
_coverView = [[UIView alloc] init];
|
||||
_coverView.backgroundColor = [UIColor whiteColor];
|
||||
_coverView.layer.cornerRadius = 4;
|
||||
_coverView.layer.masksToBounds = true;
|
||||
}
|
||||
return _coverView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
16
keyBoard/Class/Me/VC/KBMyKeyBoardVC.h
Normal file
16
keyBoard/Class/Me/VC/KBMyKeyBoardVC.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// KBMyKeyBoardVC.h
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/10.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBMyKeyBoardVC : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
240
keyBoard/Class/Me/VC/KBMyKeyBoardVC.m
Normal file
240
keyBoard/Class/Me/VC/KBMyKeyBoardVC.m
Normal file
@@ -0,0 +1,240 @@
|
||||
//
|
||||
// KBMyKeyBoardVC.m
|
||||
// keyBoard
|
||||
//
|
||||
// Created by Mac on 2025/11/10.
|
||||
//
|
||||
|
||||
#import "KBMyKeyBoardVC.h"
|
||||
#import "BMLongPressDragCellCollectionView.h"
|
||||
#import "UICollectionViewLeftAlignedLayout.h"
|
||||
#import "KBMyKeyboardCell.h"
|
||||
|
||||
/// 复用标识
|
||||
static NSString * const kKBMyKeyboardCellId = @"kKBMyKeyboardCellId";
|
||||
|
||||
/// 截图页 - 自定义键盘管理
|
||||
/// 要点:
|
||||
/// 1)使用 BMLongPressDragCellCollectionView 支持长按拖拽排序;
|
||||
/// 2)cell 宽度根据文案自适应;
|
||||
/// 3)全部使用 Masonry 进行布局,并采用懒加载创建控件;
|
||||
@interface KBMyKeyBoardVC () <BMLongPressDragCellCollectionViewDelegate, BMLongPressDragCellCollectionViewDataSource>
|
||||
|
||||
// UI
|
||||
@property (nonatomic, strong) UIView *sheetView; // 底部白色容器(圆角)
|
||||
@property (nonatomic, strong) BMLongPressDragCellCollectionView *collectionView; // 可拖拽的列表
|
||||
@property (nonatomic, strong) UIButton *saveButton; // 保存按钮
|
||||
|
||||
// 数据源(必须是二维数组,库内部会在拖动时直接调整顺序)
|
||||
@property (nonatomic, strong) NSMutableArray<NSMutableArray<NSDictionary *> *> *dataSourceArray; // {emoji,title}
|
||||
|
||||
@end
|
||||
|
||||
@interface KBMyKeyBoardVC ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation KBMyKeyBoardVC
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor colorWithHex:0xF6F8F9];
|
||||
self.navigationController.title = @"My KeyBoard";
|
||||
// 布局视图
|
||||
[self.view addSubview:self.sheetView];
|
||||
[self.sheetView addSubview:self.collectionView];
|
||||
[self.view addSubview:self.saveButton];
|
||||
|
||||
|
||||
[self.sheetView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view);
|
||||
make.top.equalTo(self.view).offset(KB_NAV_TOTAL_HEIGHT + 60);
|
||||
make.bottom.equalTo(self.view).offset(16);
|
||||
}];
|
||||
|
||||
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.top.equalTo(self.sheetView);
|
||||
make.bottom.equalTo(self.saveButton.mas_top).offset(-15);
|
||||
}];
|
||||
|
||||
[self.saveButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self.view).insets(UIEdgeInsetsMake(0, 24, 0, 24));
|
||||
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-12);
|
||||
make.height.mas_equalTo(50);
|
||||
}];
|
||||
|
||||
// 初始数据
|
||||
[self buildDefaultData];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
// 隐藏系统导航栏
|
||||
// [self.navigationController setNavigationBarHidden:YES animated:animated];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
// if (self.isMovingFromParentViewController || self.isBeingDismissed) {
|
||||
// [self.navigationController setNavigationBarHidden:NO animated:animated];
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
/// 构造示例数据(二维数组,仅 1 个 section)
|
||||
- (void)buildDefaultData {
|
||||
NSArray *arr = @[
|
||||
@{@"emoji":@"😊", @"title":@"Humor"},
|
||||
@{@"emoji":@"😄", @"title":@"Jokes"},
|
||||
@{@"emoji":@"🥰", @"title":@"Love"},
|
||||
@{@"emoji":@"🤔", @"title":@"Thinking"},
|
||||
@{@"emoji":@"🔥", @"title":@"Hot"},
|
||||
@{@"emoji":@"🎉", @"title":@"Celebrate"},
|
||||
@{@"emoji":@"🧠", @"title":@"Brainstorm"},
|
||||
@{@"emoji":@"🐱", @"title":@"Cats"},
|
||||
@{@"emoji":@"😂", @"title":@"LOL"},
|
||||
@{@"emoji":@"📸", @"title":@"Photography"},
|
||||
@{@"emoji":@"🌟", @"title":@"Star"},
|
||||
@{@"emoji":@"🍀", @"title":@"Lucky"},
|
||||
@{@"emoji":@"📚", @"title":@"Knowledge"},
|
||||
@{@"emoji":@"🎵", @"title":@"Music"},
|
||||
@{@"emoji":@"🚀", @"title":@"Launch"},
|
||||
@{@"emoji":@"😊", @"title":@"Humor"},
|
||||
@{@"emoji":@"😄", @"title":@"Jokes"},
|
||||
@{@"emoji":@"🥰", @"title":@"Love"},
|
||||
@{@"emoji":@"🤔", @"title":@"Thinking"},
|
||||
@{@"emoji":@"🔥", @"title":@"Hot"},
|
||||
@{@"emoji":@"🎉", @"title":@"Celebrate"},
|
||||
@{@"emoji":@"🧠", @"title":@"Brainstorm"},
|
||||
@{@"emoji":@"🐱", @"title":@"Cats"},
|
||||
@{@"emoji":@"😂", @"title":@"LOL"},
|
||||
@{@"emoji":@"📸", @"title":@"Photography"},
|
||||
@{@"emoji":@"🌟", @"title":@"Star"},
|
||||
@{@"emoji":@"🍀", @"title":@"Lucky"},
|
||||
@{@"emoji":@"📚", @"title":@"Knowledge"},
|
||||
@{@"emoji":@"🎵", @"title":@"Music"},
|
||||
@{@"emoji":@"🚀", @"title":@"Launch"},
|
||||
@{@"emoji":@"😊", @"title":@"Humor"},
|
||||
@{@"emoji":@"😄", @"title":@"Jokes"},
|
||||
@{@"emoji":@"🥰", @"title":@"Love"},
|
||||
@{@"emoji":@"🤔", @"title":@"Thinking"},
|
||||
@{@"emoji":@"🔥", @"title":@"Hot"},
|
||||
@{@"emoji":@"🎉", @"title":@"Celebrate"},
|
||||
@{@"emoji":@"🧠", @"title":@"Brainstorm"},
|
||||
@{@"emoji":@"🐱", @"title":@"Cats"},
|
||||
@{@"emoji":@"😂", @"title":@"LOL"},
|
||||
@{@"emoji":@"📸", @"title":@"Photography"},
|
||||
@{@"emoji":@"🌟", @"title":@"Star"},
|
||||
@{@"emoji":@"🍀", @"title":@"Lucky"},
|
||||
@{@"emoji":@"📚", @"title":@"Knowledge"},
|
||||
@{@"emoji":@"🎵", @"title":@"Music"},
|
||||
@{@"emoji":@"🚀", @"title":@"Launch"},
|
||||
];
|
||||
self.dataSourceArray = [@[[arr mutableCopy]] mutableCopy];
|
||||
[self.collectionView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark - BMLongPressDragCellCollectionViewDataSource
|
||||
|
||||
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return self.dataSourceArray.count; }
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return self.dataSourceArray[section].count;
|
||||
}
|
||||
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
KBMyKeyboardCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBMyKeyboardCellId forIndexPath:indexPath];
|
||||
NSDictionary *d = self.dataSourceArray[indexPath.section][indexPath.item];
|
||||
[cell configEmoji:d[@"emoji"] title:d[@"title"]];
|
||||
return cell;
|
||||
}
|
||||
|
||||
// 拖拽库要求实现:返回当前“二维数组”数据源
|
||||
- (NSArray<NSArray<id> *> *)dataSourceWithDragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView {
|
||||
return self.dataSourceArray;
|
||||
}
|
||||
|
||||
// 拖拽后回调:保存最新数据
|
||||
- (void)dragCellCollectionView:(BMLongPressDragCellCollectionView *)dragCellCollectionView newDataArrayAfterMove:(nullable NSArray<NSArray<id> *> *)newDataArray {
|
||||
self.dataSourceArray = [newDataArray mutableCopy];
|
||||
}
|
||||
|
||||
#pragma mark - BMLongPressDragCellCollectionViewDelegate (布局)
|
||||
|
||||
// 根据文案长度动态返回 item 尺寸
|
||||
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
NSDictionary *d = self.dataSourceArray[indexPath.section][indexPath.item];
|
||||
return [KBMyKeyboardCell sizeForEmoji:d[@"emoji"] title:d[@"title"]];
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
|
||||
return UIEdgeInsetsMake(12, 12, 12, 12);
|
||||
}
|
||||
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 10; }
|
||||
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 10; }
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onSave {
|
||||
// 这里只做示意:保存当前顺序
|
||||
NSLog(@"保存顺序: %@", self.dataSourceArray);
|
||||
[KBHUD showInfo:@"已保存"];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy UI
|
||||
|
||||
//- (UILabel *)titleLabel {
|
||||
// if (!_titleLabel) {
|
||||
// _titleLabel = [UILabel new];
|
||||
// _titleLabel.text = @"My Keyboard"; // 顶部标题
|
||||
// _titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
|
||||
// _titleLabel.textColor = [UIColor colorWithHex:0x1B1F1A];
|
||||
// }
|
||||
// return _titleLabel;
|
||||
//}
|
||||
|
||||
- (UIView *)sheetView {
|
||||
if (!_sheetView) {
|
||||
_sheetView = [UIView new];
|
||||
_sheetView.backgroundColor = [UIColor whiteColor];
|
||||
_sheetView.layer.cornerRadius = 32.0;
|
||||
_sheetView.layer.masksToBounds = YES;
|
||||
}
|
||||
return _sheetView;
|
||||
}
|
||||
|
||||
- (BMLongPressDragCellCollectionView *)collectionView {
|
||||
if (!_collectionView) {
|
||||
UICollectionViewLeftAlignedLayout *layout = [UICollectionViewLeftAlignedLayout new];
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
|
||||
// layout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16);
|
||||
_collectionView = [[BMLongPressDragCellCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.backgroundColor = [UIColor clearColor];
|
||||
_collectionView.delegate = self; // 注意:代理为 BMLongPressDragCellCollectionViewDelegate
|
||||
_collectionView.dataSource = self; // 注意:数据源为 BMLongPressDragCellCollectionViewDataSource
|
||||
_collectionView.alwaysBounceVertical = YES;
|
||||
_collectionView.showsVerticalScrollIndicator = NO;
|
||||
[_collectionView registerClass:KBMyKeyboardCell.class forCellWithReuseIdentifier:kKBMyKeyboardCellId];
|
||||
}
|
||||
return _collectionView;
|
||||
}
|
||||
|
||||
- (UIButton *)saveButton {
|
||||
if (!_saveButton) {
|
||||
_saveButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[_saveButton setTitle:@"Save" forState:UIControlStateNormal];
|
||||
_saveButton.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold];
|
||||
[_saveButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
_saveButton.backgroundColor = [UIColor colorWithHex:KBColorValue];
|
||||
_saveButton.layer.cornerRadius = 25;
|
||||
_saveButton.layer.masksToBounds = YES;
|
||||
[_saveButton addTarget:self action:@selector(onSave) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _saveButton;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -10,6 +10,7 @@
|
||||
#import "KBSkinDetailVC.h"
|
||||
#import "KBMyHeaderView.h" // 顶部视图独立封装
|
||||
#import "KBMyListCell.h"
|
||||
#import "KBMyKeyBoardVC.h"
|
||||
|
||||
@interface MyVC () <UITableViewDelegate, UITableViewDataSource>
|
||||
@property (nonatomic, strong) BaseTableView *tableView; // 列表
|
||||
@@ -100,6 +101,8 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
KBMyKeyBoardVC *vc = [[KBMyKeyBoardVC alloc] init];
|
||||
[self.navigationController pushViewController:vc animated:true];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 https://liangdahong.com
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
////////////////////
|
||||
/// v3.1.4
|
||||
////////////////////
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "BMLongPressDragCellCollectionViewDelegate.h"
|
||||
#import "BMLongPressDragCellCollectionViewDataSource.h"
|
||||
|
||||
/*
|
||||
- (NSArray<NSArray<id> *> *)dataSourceWithDragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView {
|
||||
return self.dataSourceArray;
|
||||
// 此协议方法里返回数据源
|
||||
}
|
||||
|
||||
- (void)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView newDataArrayAfterMove:(nullable NSArray <NSArray <id> *> *)newDataArray {
|
||||
self.dataSourceArray = [newDataArray mutableCopy];
|
||||
// 此协议方法里保存数据源
|
||||
}
|
||||
*/
|
||||
@interface BMLongPressDragCellCollectionView : UICollectionView
|
||||
|
||||
@property (nonatomic, weak) id<BMLongPressDragCellCollectionViewDelegate> delegate;
|
||||
@property (nonatomic, weak) id<BMLongPressDragCellCollectionViewDataSource> dataSource;
|
||||
|
||||
/// 长按触发拖拽所需时间,默认是 0.5 秒。
|
||||
@property (nonatomic, assign) NSTimeInterval minimumPressDuration;
|
||||
|
||||
/// 是否可以拖拽 默认为 YES。
|
||||
@property (nonatomic, assign, getter=isCanDrag) IBInspectable BOOL canDrag;
|
||||
|
||||
/// 长按拖拽时拖拽中的的 Cell 缩放比例,默认是 1.2 倍。
|
||||
@property (nonatomic, assign) IBInspectable CGFloat dragZoomScale;
|
||||
|
||||
/// 拖拽的 Cell 在拖拽移动时的透明度 默认是: 1.0 不透明。
|
||||
@property (nonatomic, assign) IBInspectable CGFloat dragCellAlpha;
|
||||
|
||||
/// 拖拽到 UICollectionView 边缘时 UICollectionView 的滚动速度
|
||||
/// 默认为: 8.0f ,建议设置 5.0f 到 15.0f 之间。
|
||||
@property (nonatomic, assign) IBInspectable CGFloat dragSpeed;
|
||||
|
||||
/// 拖拽 View 的背景颜色,默认和被拖拽的 Cell 一样。
|
||||
@property (nonatomic, strong) IBInspectable UIColor *dragSnapedViewBackgroundColor;
|
||||
|
||||
/// 移动到指定位置
|
||||
/// @param indexPath 移动到的位置
|
||||
/// 内部只会处理当前正在拖拽的情况,会把拖拽的 Cell 移动到指定位置,建议在停止手势时或者认为适当的时候使用。
|
||||
- (void)dragMoveItemToIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,729 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 https://liangdahong.com
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import "BMLongPressDragCellCollectionView.h"
|
||||
|
||||
typedef NS_ENUM(NSUInteger, BMLongPressDragCellCollectionViewScrollDirection) {
|
||||
BMLongPressDragCellCollectionViewScrollDirectionNone = 0,
|
||||
BMLongPressDragCellCollectionViewScrollDirectionLeft = 1,
|
||||
BMLongPressDragCellCollectionViewScrollDirectionRight = 2,
|
||||
BMLongPressDragCellCollectionViewScrollDirectionUp = 3,
|
||||
BMLongPressDragCellCollectionViewScrollDirectionDown = 4,
|
||||
};
|
||||
|
||||
@interface BMLongPressDragCellCollectionView ()
|
||||
|
||||
/// 长按手势
|
||||
@property (nonatomic, strong, nullable) UILongPressGestureRecognizer *longGesture;
|
||||
/// 截图快照
|
||||
@property (nonatomic, strong, nullable) UIView *snapedView;
|
||||
/// 定时器
|
||||
@property (nonatomic, strong, nullable) CADisplayLink *edgeTimer;
|
||||
/// 旧的IndexPath
|
||||
@property (nonatomic, strong, nullable) NSIndexPath *oldIndexPath;
|
||||
/// 当前路径
|
||||
@property (nonatomic, strong, nullable) NSIndexPath *currentIndexPath;
|
||||
|
||||
@property (nonatomic, assign) BOOL isEndDrag; ///< 是否已经停止拖动
|
||||
@property (nonatomic, assign) BOOL banReload; ///< 禁止刷新
|
||||
@property (nonatomic, assign) CGPoint oldPoint; ///< 旧的位置
|
||||
@property (nonatomic, assign) CGPoint lastPoint; ///< 最后的触摸点
|
||||
|
||||
@end
|
||||
|
||||
@implementation BMLongPressDragCellCollectionView
|
||||
|
||||
@dynamic delegate, dataSource;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout {
|
||||
if (self = [super initWithFrame:frame collectionViewLayout:layout]) {
|
||||
[self initConfiguration];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
if (self = [super initWithCoder:aDecoder]) {
|
||||
[self initConfiguration];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self _stopEdgeTimer];
|
||||
}
|
||||
|
||||
#pragma mark - getters setters
|
||||
|
||||
- (void)setCanDrag:(BOOL)canDrag {
|
||||
_canDrag = canDrag;
|
||||
self.longGesture.enabled = _canDrag;
|
||||
}
|
||||
|
||||
- (void)setMinimumPressDuration:(NSTimeInterval)minimumPressDuration {
|
||||
_minimumPressDuration = minimumPressDuration;
|
||||
self.longGesture.minimumPressDuration = minimumPressDuration;
|
||||
}
|
||||
|
||||
- (void)setDragZoomScale:(CGFloat)dragZoomScale {
|
||||
if (dragZoomScale < 0.1) {
|
||||
dragZoomScale = 1.2;
|
||||
}
|
||||
_dragZoomScale = dragZoomScale;
|
||||
}
|
||||
|
||||
- (void)setDragSpeed:(CGFloat)dragSpeed {
|
||||
if (dragSpeed < 0.5) {
|
||||
dragSpeed = 8.0;
|
||||
}
|
||||
_dragSpeed = dragSpeed;
|
||||
}
|
||||
|
||||
- (void)setDragSnapedViewBackgroundColor:(UIColor *)dragSnapedViewBackgroundColor {
|
||||
_dragSnapedViewBackgroundColor = dragSnapedViewBackgroundColor;
|
||||
_snapedView.backgroundColor = dragSnapedViewBackgroundColor;
|
||||
}
|
||||
|
||||
- (UILongPressGestureRecognizer *)longGesture {
|
||||
if (!_longGesture) {
|
||||
_longGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlelongGesture:)];
|
||||
_longGesture.minimumPressDuration = _minimumPressDuration;
|
||||
}
|
||||
return _longGesture;
|
||||
}
|
||||
|
||||
// iOS 10 新特性 对UICollectionView做了优化,但是这里如果使用了会导致bug,重写此方法是为了让其无法修改
|
||||
// https://developer.apple.com/documentation/uikit/uicollectionview/1771771-prefetchingenabled
|
||||
// https://sxgfxm.github.io/blog/2016/10/18/uicollectionview-ios10-new-features
|
||||
- (void)setPrefetchingEnabled:(BOOL)prefetchingEnabled {
|
||||
[super setPrefetchingEnabled:NO];
|
||||
}
|
||||
|
||||
#pragma mark - 私有方法
|
||||
|
||||
- (void)initConfiguration {
|
||||
_canDrag = YES;
|
||||
_minimumPressDuration = 0.5;
|
||||
_dragZoomScale = 1.2;
|
||||
_dragCellAlpha = 1.0;
|
||||
_dragSpeed = 8.0;
|
||||
[self addGestureRecognizer:self.longGesture];
|
||||
// iOS 10 新特性 对 UICollectionView 做了优化,但是这里如果使用了会导致bug
|
||||
// https://developer.apple.com/documentation/uikit/uicollectionview/1771771-prefetchingenabled
|
||||
// https://sxgfxm.github.io/blog/2016/10/18/uicollectionview-ios10-new-features
|
||||
if (@available(iOS 10.0, *)) {
|
||||
self.prefetchingEnabled = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super reloadData];
|
||||
}
|
||||
|
||||
- (void)insertSections:(NSIndexSet *)sections {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super insertSections:sections];
|
||||
}
|
||||
|
||||
- (void)deleteSections:(NSIndexSet *)sections {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super deleteSections:sections];
|
||||
}
|
||||
|
||||
- (void)reloadSections:(NSIndexSet *)sections {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super reloadSections:sections];
|
||||
}
|
||||
|
||||
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super moveSection:section toSection:newSection];
|
||||
}
|
||||
|
||||
- (void)insertItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super insertItemsAtIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
- (void)deleteItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super deleteItemsAtIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
- (void)reloadItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super reloadItemsAtIndexPaths:indexPaths];
|
||||
}
|
||||
|
||||
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath {
|
||||
if (_banReload) {
|
||||
return;
|
||||
}
|
||||
[super moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
|
||||
}
|
||||
|
||||
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
|
||||
UICollectionViewCell *cell = [super dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
|
||||
// 处理复用时的问题,为了保证显示的正确性
|
||||
if (_isEndDrag) {
|
||||
cell.hidden = NO;
|
||||
} else {
|
||||
cell.hidden = (self.oldIndexPath
|
||||
&& self.oldIndexPath.item == indexPath.item
|
||||
&& self.oldIndexPath.section == indexPath.section);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (nullable NSIndexPath *)_getChangedNullIndexPath {
|
||||
__block NSIndexPath *index = nil;
|
||||
CGPoint point = [self.longGesture locationInView:self];
|
||||
NSInteger number = self.numberOfSections;
|
||||
while (number--) {
|
||||
NSInteger row = [self.dataSource collectionView:self numberOfItemsInSection:number];
|
||||
if (row == 0) {
|
||||
// 如果这组是空的
|
||||
CGRect headerFrame = [self layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:number]].frame;
|
||||
CGRect footerFrame = [self layoutAttributesForSupplementaryElementOfKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:number]].frame;
|
||||
CGRect frame = CGRectZero;
|
||||
if (((UICollectionViewFlowLayout *)self.collectionViewLayout).scrollDirection == UICollectionViewScrollDirectionHorizontal) {
|
||||
// 水平方向
|
||||
frame = CGRectMake(CGRectGetMinX(headerFrame),
|
||||
CGRectGetMinY(headerFrame),
|
||||
CGRectGetMaxX(footerFrame) - CGRectGetMidX(headerFrame),
|
||||
CGRectGetWidth(footerFrame));
|
||||
|
||||
if (frame.size.width < 10.0) {
|
||||
// 如果这组的宽度小于 10.0,就设置一个默认值 10.0,主要是为了判断触摸点缩放在这组内。
|
||||
frame.size.width = 10.0;
|
||||
frame.size.height = CGRectGetHeight(self.frame);
|
||||
frame.origin.x -= 5.0;
|
||||
}
|
||||
} else {
|
||||
// 垂直方向
|
||||
frame = CGRectMake(CGRectGetMinX(headerFrame),
|
||||
CGRectGetMinY(headerFrame),
|
||||
CGRectGetWidth(footerFrame),
|
||||
CGRectGetMaxY(footerFrame) - CGRectGetMidY(headerFrame));
|
||||
|
||||
if (frame.size.height < 10.0) {
|
||||
// 如果这组的高度小于 10.0,就设置一个默认值 10.0,主要是为了判断触摸点缩放在这组内。
|
||||
frame.size.height = 10.0;
|
||||
frame.size.width = CGRectGetWidth(self.frame);
|
||||
frame.origin.y -= 5.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (CGRectContainsPoint(frame, point)) {
|
||||
// 触摸点在空的组内
|
||||
return [NSIndexPath indexPathForItem:0 inSection:number];
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
- (nullable NSIndexPath *)_getChangedIndexPath {
|
||||
__block NSIndexPath *index = nil;
|
||||
CGPoint point = [self.longGesture locationInView:self];
|
||||
// 遍历拖拽的 Cell 的中心点在哪一个 Cell 里
|
||||
[[self visibleCells] enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (CGRectContainsPoint(obj.frame, point)) {
|
||||
index = [self indexPathForCell:obj];
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
// 找到而且不是当前的 Cell 就返回此 index
|
||||
if (index) {
|
||||
if ((index.section == self.oldIndexPath.section) && (index.item == self.oldIndexPath.item)) {
|
||||
return nil;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// 获取最应该交换的 Cell
|
||||
__block CGFloat width = MAXFLOAT;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[[self visibleCells] enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (CGRectContainsPoint(obj.frame, point)) {
|
||||
index = [self indexPathForCell:obj];
|
||||
*stop = YES;
|
||||
}
|
||||
__strong typeof(weakSelf) self = weakSelf;
|
||||
CGPoint p1 = self.snapedView.center;
|
||||
CGPoint p2 = obj.center;
|
||||
// 计算距离
|
||||
CGFloat distance = sqrt(pow((p1.x - p2.x), 2) + pow((p1.y - p2.y), 2));
|
||||
if (distance < width) {
|
||||
width = distance;
|
||||
index = [self indexPathForCell:obj];
|
||||
}
|
||||
}];
|
||||
if (!index) {
|
||||
return nil;
|
||||
}
|
||||
if ((index.section == self.oldIndexPath.section) && (index.item == self.oldIndexPath.item)) {
|
||||
// 最近的就是隐藏的 Cell 时, return nil
|
||||
return nil;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
// 处理临界点问题
|
||||
- (BMLongPressDragCellCollectionViewScrollDirection)_setScrollDirection {
|
||||
if (((self.bounds.size.height + self.contentOffset.y - _snapedView.center.y) < (_snapedView.bounds.size.height / 2))
|
||||
&& ((self.bounds.size.height + self.contentOffset.y) < self.contentSize.height)) {
|
||||
return BMLongPressDragCellCollectionViewScrollDirectionDown;
|
||||
|
||||
} else if (((_snapedView.center.y - self.contentOffset.y) < (_snapedView.bounds.size.height / 2))
|
||||
&& self.contentOffset.y > 0) {
|
||||
return BMLongPressDragCellCollectionViewScrollDirectionUp;
|
||||
|
||||
} else if (((self.bounds.size.width + self.contentOffset.x) - (_snapedView.center.x)) < (_snapedView.bounds.size.width / 2)
|
||||
&& (self.bounds.size.width + self.contentOffset.x) < self.contentSize.width) {
|
||||
return BMLongPressDragCellCollectionViewScrollDirectionRight;
|
||||
|
||||
} else if (((_snapedView.center.x - self.contentOffset.x) < (_snapedView.bounds.size.width) / 2)
|
||||
&& self.contentOffset.x > 0) {
|
||||
return BMLongPressDragCellCollectionViewScrollDirectionLeft;
|
||||
}
|
||||
|
||||
return BMLongPressDragCellCollectionViewScrollDirectionNone;
|
||||
}
|
||||
|
||||
// 处理 UICollectionView 数据源
|
||||
- (void)_updateSourceData {
|
||||
// 获取数据源
|
||||
NSMutableArray *array = [self.dataSource dataSourceWithDragCellCollectionView:self].mutableCopy;
|
||||
// 处理数据
|
||||
BOOL dataTypeCheck = ([self numberOfSections] != 1 || ([self numberOfSections] == 1 && [array[0] isKindOfClass:NSArray.class]));
|
||||
if (dataTypeCheck) {
|
||||
for (int i = 0; i < array.count; i ++) {
|
||||
// 如果是嵌套数组 就把数组里面的数组换为 NSMutableArray。
|
||||
[array replaceObjectAtIndex:i withObject:[array[i] mutableCopy]];
|
||||
}
|
||||
}
|
||||
if (_currentIndexPath.section == _oldIndexPath.section) {
|
||||
|
||||
NSMutableArray *orignalSection = dataTypeCheck ? array[_oldIndexPath.section] : array;
|
||||
|
||||
if (_currentIndexPath.item > _oldIndexPath.item) {
|
||||
for (NSUInteger i = _oldIndexPath.item; i < _currentIndexPath.item ; i ++) {
|
||||
[orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i + 1];
|
||||
}
|
||||
|
||||
} else {
|
||||
for (NSUInteger i = _oldIndexPath.item; i > _currentIndexPath.item ; i --) {
|
||||
[orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
NSMutableArray *orignalSection = array[_oldIndexPath.section];
|
||||
NSMutableArray *currentSection = array[_currentIndexPath.section];
|
||||
[currentSection insertObject:orignalSection[_oldIndexPath.item] atIndex:_currentIndexPath.item];
|
||||
// https://github.com/liangdahong/BMLongPressDragCellCollectionView/issues/16
|
||||
// 这里之前删除指定对象会有问题,如果数据源中的对象为 [字符串 而且是一样的时候, 因为 OC 中对字符串的内存做了特殊处理 ],会把相同的字符串全部删除导致崩溃
|
||||
// [orignalSection removeObject:orignalSection[_oldIndexPath.item]];
|
||||
[orignalSection removeObjectAtIndex:_oldIndexPath.item];
|
||||
}
|
||||
// ==========处理数据
|
||||
// 更新外面的数据源
|
||||
// 这里会触发外面的使用者修改数据源,但外面的使用者可以会在此调用 reloadData 相关方法,所以做了拦截操作
|
||||
// https://github.com/liangdahong/BMLongPressDragCellCollectionView/issues/14
|
||||
self.banReload = YES;
|
||||
[self.delegate dragCellCollectionView:self newDataArrayAfterMove:array];
|
||||
self.banReload = NO;
|
||||
}
|
||||
|
||||
- (void)_setEdgeTimer{
|
||||
if (!_edgeTimer) {
|
||||
_edgeTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(_edgeScroll)];
|
||||
[_edgeTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_stopEdgeTimer{
|
||||
if (_edgeTimer) {
|
||||
[_edgeTimer invalidate];
|
||||
_edgeTimer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_edgeScroll {
|
||||
BMLongPressDragCellCollectionViewScrollDirection scrollDirection = [self _setScrollDirection];
|
||||
|
||||
if (BMLongPressDragCellCollectionViewScrollDirectionNone == scrollDirection) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (scrollDirection) {
|
||||
case BMLongPressDragCellCollectionViewScrollDirectionLeft:{
|
||||
// 这里的动画必须设为 NO
|
||||
[self setContentOffset:CGPointMake(self.contentOffset.x - _dragSpeed, self.contentOffset.y) animated:NO];
|
||||
_snapedView.center = CGPointMake(_snapedView.center.x - _dragSpeed, _snapedView.center.y);
|
||||
_lastPoint.x -= _dragSpeed;
|
||||
}
|
||||
break;
|
||||
case BMLongPressDragCellCollectionViewScrollDirectionRight:{
|
||||
[self setContentOffset:CGPointMake(self.contentOffset.x + _dragSpeed, self.contentOffset.y) animated:NO];
|
||||
_snapedView.center = CGPointMake(_snapedView.center.x + _dragSpeed, _snapedView.center.y);
|
||||
_lastPoint.x += _dragSpeed;
|
||||
}
|
||||
break;
|
||||
case BMLongPressDragCellCollectionViewScrollDirectionUp:{
|
||||
[self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y - _dragSpeed) animated:NO];
|
||||
_snapedView.center = CGPointMake(_snapedView.center.x, _snapedView.center.y - _dragSpeed);
|
||||
_lastPoint.y -= _dragSpeed;
|
||||
}
|
||||
break;
|
||||
case BMLongPressDragCellCollectionViewScrollDirectionDown:{
|
||||
[self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + _dragSpeed) animated:NO];
|
||||
_snapedView.center = CGPointMake(_snapedView.center.x, _snapedView.center.y + _dragSpeed);
|
||||
_lastPoint.y += _dragSpeed;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果Cell 拖拽到了边沿时
|
||||
// 截图视图位置移动
|
||||
[UIView animateWithDuration:0.1 animations:^{
|
||||
self.snapedView.center = self.lastPoint;
|
||||
}];
|
||||
|
||||
// 获取应该交换的 Cell 的位置
|
||||
NSIndexPath *index1 = [self _getChangedNullIndexPath];
|
||||
NSIndexPath *index = nil;
|
||||
|
||||
index = index1 ? : [self _getChangedIndexPath];
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionView:changedDragAtPoint:)]) {
|
||||
[self.delegate dragCellCollectionView:self changedDragAtPoint:_lastPoint];
|
||||
}
|
||||
|
||||
if (!index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 是否可以交换
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionViewShouldBeginExchange:sourceIndexPath:toIndexPath:)]) {
|
||||
if (![self.delegate dragCellCollectionViewShouldBeginExchange:self sourceIndexPath:_oldIndexPath toIndexPath:index]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_currentIndexPath = index;
|
||||
|
||||
UICollectionViewCell *cell1 = [self cellForItemAtIndexPath:_currentIndexPath];
|
||||
|
||||
NSIndexPath *sourceIndexPath = _oldIndexPath;
|
||||
NSIndexPath *toIndexPath = _currentIndexPath;
|
||||
if (!index1) {
|
||||
self.oldPoint = cell1.center;
|
||||
// 更新数据源
|
||||
[self _updateSourceData];
|
||||
// 移动 cell
|
||||
[self moveItemAtIndexPath:_oldIndexPath toIndexPath:_currentIndexPath];
|
||||
// 设置移动后的起始 indexPath
|
||||
_oldIndexPath = _currentIndexPath;
|
||||
// 为了防止在缓存池取出的 Cell 已隐藏,
|
||||
// 以后可以优化
|
||||
[self reloadItemsAtIndexPaths:@[_oldIndexPath]];
|
||||
|
||||
} else {
|
||||
// 更新数据源
|
||||
[self _updateSourceData];
|
||||
// 移动 cell
|
||||
[self moveItemAtIndexPath:_oldIndexPath toIndexPath:_currentIndexPath];
|
||||
self.oldPoint = [self cellForItemAtIndexPath:_currentIndexPath].center;
|
||||
// 设置移动后的起始 indexPath
|
||||
_oldIndexPath = _currentIndexPath;
|
||||
[self reloadItemsAtIndexPaths:@[_oldIndexPath]];
|
||||
}
|
||||
|
||||
// 交换结束时
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionViewShouldEndExchange:sourceIndexPath:toIndexPath:)]) {
|
||||
[self.delegate dragCellCollectionViewShouldEndExchange:self sourceIndexPath:sourceIndexPath toIndexPath:toIndexPath];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - 事件响应
|
||||
|
||||
- (void)handlelongGesture:(UILongPressGestureRecognizer *)longGesture {
|
||||
CGPoint point = [longGesture locationInView:self];
|
||||
NSIndexPath *indexPath = [self indexPathForItemAtPoint:point];
|
||||
switch (longGesture.state) {
|
||||
case UIGestureRecognizerStateBegan: {
|
||||
// 手势开始
|
||||
self.userInteractionEnabled = NO;
|
||||
_oldIndexPath = indexPath;
|
||||
// 判断手势落点位置是否在 Cell 上, 没有按在 cell 上就 break
|
||||
if (_oldIndexPath == nil) {
|
||||
self.longGesture.enabled = NO;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (self.canDrag) {
|
||||
self.longGesture.enabled = YES;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// 将要开始拖拽时,询问此位置的Cell是否可以拖拽
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionViewShouldBeginMove:indexPath:)]) {
|
||||
if (![self.delegate dragCellCollectionViewShouldBeginMove:self indexPath:_oldIndexPath]) {
|
||||
_oldIndexPath = nil;
|
||||
self.longGesture.enabled = NO;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (self.canDrag) {
|
||||
self.longGesture.enabled = YES;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.isEndDrag = NO;
|
||||
// 取出正在长按的cell
|
||||
UICollectionViewCell *cell = [self cellForItemAtIndexPath:_oldIndexPath];
|
||||
self.oldPoint = cell.center;
|
||||
// 先置 nil
|
||||
if (_snapedView) {
|
||||
_snapedView = nil;
|
||||
}
|
||||
// 是否外部提供拖拽View
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionView: startDragAtIndexPath:)]) {
|
||||
_snapedView = [self.delegate dragCellCollectionView:self startDragAtIndexPath:indexPath];
|
||||
}
|
||||
if (!_snapedView) {
|
||||
// 使用系统截图功能,得到 cell 的快照 view
|
||||
_snapedView = [cell snapshotViewAfterScreenUpdates:NO];
|
||||
}
|
||||
|
||||
// 修改颜色
|
||||
if (_dragSnapedViewBackgroundColor) {
|
||||
_snapedView.backgroundColor = _dragSnapedViewBackgroundColor;
|
||||
}
|
||||
|
||||
// 让外面做一些特殊处理
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionView: dragView:indexPath:)]) {
|
||||
[self.delegate dragCellCollectionView:self dragView:_snapedView indexPath:indexPath];
|
||||
}
|
||||
|
||||
// 设置frame
|
||||
_snapedView.frame = cell.frame;
|
||||
// 添加到 collectionView 不然无法显示
|
||||
[self addSubview:_snapedView];
|
||||
// 截图后隐藏当前 cell
|
||||
cell.hidden = YES;
|
||||
|
||||
// 获取当前触摸的中心点
|
||||
CGPoint currentPoint = point;
|
||||
|
||||
// 动画放大和移动到触摸点下面
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
self.snapedView.transform = CGAffineTransformMakeScale(self.dragZoomScale, self.dragZoomScale);
|
||||
self.snapedView.center = CGPointMake(currentPoint.x, currentPoint.y);
|
||||
self.snapedView.alpha = self.dragCellAlpha;
|
||||
}];
|
||||
}
|
||||
break;
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
// 开启 collectionView 的边缘自动滚动检测
|
||||
// https://github.com/liangdahong/BMLongPressDragCellCollectionView/issues/15
|
||||
if (!_edgeTimer) {
|
||||
[self _setEdgeTimer];
|
||||
}
|
||||
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionView:changedDragAtPoint:)]) {
|
||||
[self.delegate dragCellCollectionView:self changedDragAtPoint:point];
|
||||
}
|
||||
// 当前手指位置
|
||||
_lastPoint = point;
|
||||
// 截图视图位置移动
|
||||
[UIView animateWithDuration:0.1 animations:^{
|
||||
self.snapedView.center = self.lastPoint;
|
||||
}];
|
||||
|
||||
NSIndexPath *index1 = [self _getChangedNullIndexPath];
|
||||
NSIndexPath *index = nil;
|
||||
|
||||
if (index1) {
|
||||
index = index1;
|
||||
} else {
|
||||
index = [self _getChangedIndexPath];
|
||||
}
|
||||
|
||||
// 没有取到 || 距离隐藏的 Cell 最近时就返回
|
||||
if (!index) {
|
||||
break;
|
||||
}
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionViewShouldBeginExchange:sourceIndexPath:toIndexPath:)]) {
|
||||
if (![self.delegate dragCellCollectionViewShouldBeginExchange:self sourceIndexPath:_oldIndexPath toIndexPath:index]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_currentIndexPath = index;
|
||||
UICollectionViewCell *cell1 = [self cellForItemAtIndexPath:_currentIndexPath];
|
||||
|
||||
NSIndexPath *sourceIndexPath = _oldIndexPath;
|
||||
NSIndexPath *toIndexPath = _currentIndexPath;
|
||||
if (!index1) {
|
||||
self.oldPoint = cell1.center;
|
||||
// 更新数据源
|
||||
[self _updateSourceData];
|
||||
// 移动 cell
|
||||
[self moveItemAtIndexPath:_oldIndexPath toIndexPath:_currentIndexPath];
|
||||
// 设置移动后的起始 indexPath
|
||||
_oldIndexPath = _currentIndexPath;
|
||||
[self reloadItemsAtIndexPaths:@[_oldIndexPath]];
|
||||
|
||||
} else {
|
||||
// 更新数据源
|
||||
[self _updateSourceData];
|
||||
// 移动 cell
|
||||
[self moveItemAtIndexPath:_oldIndexPath toIndexPath:_currentIndexPath];
|
||||
self.oldPoint = [self cellForItemAtIndexPath:_currentIndexPath].center;
|
||||
// 设置移动后的起始 indexPath
|
||||
_oldIndexPath = _currentIndexPath;
|
||||
[self reloadItemsAtIndexPaths:@[_oldIndexPath]];
|
||||
}
|
||||
|
||||
// 交换结束时
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionViewShouldEndExchange:sourceIndexPath:toIndexPath:)]) {
|
||||
[self.delegate dragCellCollectionViewShouldEndExchange:self sourceIndexPath:sourceIndexPath toIndexPath:toIndexPath];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
self.userInteractionEnabled = YES;
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(dragCellCollectionView:endedDragAtPoint:)]) {
|
||||
[self.delegate dragCellCollectionView:self endedDragAtPoint:point];
|
||||
}
|
||||
|
||||
if (longGesture.isEnabled) {
|
||||
|
||||
if (!self.oldIndexPath) {
|
||||
return;
|
||||
}
|
||||
UICollectionViewCell *cell = [self cellForItemAtIndexPath:_oldIndexPath];
|
||||
// 结束动画过程中停止交互,防止出问题
|
||||
self.userInteractionEnabled = NO;
|
||||
// 结束拖拽了
|
||||
self.isEndDrag = YES;
|
||||
// 给截图视图一个动画移动到隐藏 cell 的新位置
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
if (!cell) {
|
||||
self.snapedView.center = self.oldPoint;
|
||||
} else {
|
||||
self.snapedView.center = cell.center;
|
||||
}
|
||||
self.snapedView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
|
||||
self.snapedView.alpha = 1.0;
|
||||
} completion:^(BOOL finished) {
|
||||
// 移除截图视图、显示隐藏的 cell 并开启交互
|
||||
[self.snapedView removeFromSuperview];
|
||||
self.snapedView = nil;
|
||||
cell.hidden = NO;
|
||||
self.userInteractionEnabled = YES;
|
||||
}];
|
||||
}
|
||||
// 关闭定时器
|
||||
self.oldIndexPath = nil;
|
||||
[self _stopEdgeTimer];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dragMoveItemToIndexPath:(NSIndexPath *)newIndexPath {
|
||||
if (self.isEndDrag) {
|
||||
return;
|
||||
}
|
||||
self.isEndDrag = YES;
|
||||
self.longGesture.enabled = NO;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (self.canDrag) {
|
||||
self.longGesture.enabled = YES;
|
||||
}
|
||||
});
|
||||
|
||||
if (_oldIndexPath) {
|
||||
_currentIndexPath = newIndexPath;
|
||||
// 更新数据源
|
||||
[self _updateSourceData];
|
||||
// 移动 cell
|
||||
[self moveItemAtIndexPath:_oldIndexPath toIndexPath:_currentIndexPath];
|
||||
// 设置移动后的起始 indexPath
|
||||
_oldIndexPath = newIndexPath;
|
||||
[self reloadItemsAtIndexPaths:@[newIndexPath]];
|
||||
|
||||
UICollectionViewCell *cell = [self cellForItemAtIndexPath:_oldIndexPath];
|
||||
cell.hidden = YES;
|
||||
|
||||
// 结束动画过程中停止交互,防止出问题
|
||||
self.userInteractionEnabled = NO;
|
||||
// 结束拖拽了
|
||||
self.isEndDrag = YES;
|
||||
// 给截图视图一个动画移动到隐藏 cell 的新位置
|
||||
[UIView animateWithDuration:0.25 animations:^{
|
||||
if (!cell) {
|
||||
self.snapedView.center = self.oldPoint;
|
||||
} else {
|
||||
self.snapedView.center = cell.center;
|
||||
}
|
||||
self.snapedView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
|
||||
self.snapedView.alpha = 1.0;
|
||||
} completion:^(BOOL finished) {
|
||||
// 移除截图视图、显示隐藏的 cell 并开启交互
|
||||
[self.snapedView removeFromSuperview];
|
||||
cell.hidden = NO;
|
||||
self.userInteractionEnabled = YES;
|
||||
}];
|
||||
}
|
||||
|
||||
// 关闭定时器
|
||||
_oldIndexPath = nil;
|
||||
[self _stopEdgeTimer];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 https://liangdahong.com
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class BMLongPressDragCellCollectionView;
|
||||
|
||||
@protocol BMLongPressDragCellCollectionViewDataSource <UICollectionViewDataSource>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
获取数据源,内部会在合适的时候获取最新的数据源,必须实现
|
||||
|
||||
@param dragCellCollectionView dragCellCollectionView
|
||||
@return 最新的数据源
|
||||
|
||||
- (nullable NSArray *)dataSourceWithDragCellCollectionView:(BMLongPressDragCellCollectionView *)dragCellCollectionView {
|
||||
return self.dataArray;
|
||||
}
|
||||
*/
|
||||
- (nullable NSArray <NSArray <id> *> *)dataSourceWithDragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,98 @@
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2019 https://liangdahong.com
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class BMLongPressDragCellCollectionView;
|
||||
|
||||
@protocol BMLongPressDragCellCollectionViewDelegate <UICollectionViewDelegateFlowLayout>
|
||||
|
||||
@required
|
||||
|
||||
/**
|
||||
Cell 有交换时调用
|
||||
|
||||
@param dragCellCollectionView dragCellCollectionView
|
||||
@param newDataArray 最新的数据源,需要使用者保存,在拖拽的时候 Cell 已经移动,需保存最新的数据源
|
||||
|
||||
- (void)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView newDataArrayAfterMove:(nullable NSArray <NSArray <id> *> *)newDataArray {
|
||||
self.dataArray = [newDataArray copy];
|
||||
}
|
||||
*/
|
||||
- (void)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView newDataArrayAfterMove:(nullable NSArray <NSArray <id> *> *)newDataArray;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
将要开始拖拽时,询问此位置的 Cell 是否能拖拽
|
||||
|
||||
@param dragCellCollectionView dragCellCollectionView
|
||||
@param indexPath indexPath
|
||||
@return YES: 正常拖拽和移动 NO:此 Cell 不可拖拽,如:增加按钮等。
|
||||
*/
|
||||
- (BOOL)dragCellCollectionViewShouldBeginMove:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView indexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/**
|
||||
将要开始拖拽时,向外面获取需要拖拽的 View,如果不实现就内部自动处理。
|
||||
|
||||
@param dragCellCollectionView dragCellCollectionView
|
||||
@param indexPath indexPath
|
||||
@return dragView
|
||||
*/
|
||||
- (UIView *)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView startDragAtIndexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/// 开始拖拽时,让外面的使用者可以对拖拽的 View 做额外操作
|
||||
/// @param dragCellCollectionView dragCellCollectionView
|
||||
/// @param dragView dragView
|
||||
/// @param indexPath indexPath
|
||||
- (void)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView dragView:(UIView *)dragView indexPath:(NSIndexPath *)indexPath;
|
||||
|
||||
/// 正在拖拽时
|
||||
/// @param dragCellCollectionView dragCellCollectionView
|
||||
/// @param point 手指触摸点对于 collectionView 的位置
|
||||
/// 可以参考 https://github.com/liangdahong/ToutiaoDemo/blob/master/ToutiaoDemo/Classes/Edit/Controller/BMChannelEditVC.m 的使用
|
||||
- (void)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView changedDragAtPoint:(CGPoint)point;
|
||||
|
||||
/// cell 将要交换时,询问是否能交换
|
||||
/// @param dragCellCollectionView dragCellCollectionView
|
||||
/// @param sourceIndexPath 原来的 IndexPath
|
||||
/// @param destinationIndexPath 将要交换的 IndexPath
|
||||
/// YES: 正常拖拽和移动 NO:此Cell不可拖拽,如:增加按钮等。
|
||||
- (BOOL)dragCellCollectionViewShouldBeginExchange:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView sourceIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
|
||||
|
||||
/// 结束交换时
|
||||
/// @param dragCellCollectionView dragCellCollectionView
|
||||
/// @param sourceIndexPath 原来的 IndexPath
|
||||
/// @param destinationIndexPath 被交换的 IndexPath
|
||||
- (void)dragCellCollectionViewShouldEndExchange:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView sourceIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
|
||||
|
||||
/// 结束拖拽时
|
||||
/// @param dragCellCollectionView dragCellCollectionView
|
||||
/// @param point 手指触摸点对于 collectionView 的位置
|
||||
/// 可以参考 https://github.com/liangdahong/ToutiaoDemo/blob/master/ToutiaoDemo/Classes/Edit/Controller/BMChannelEditVC.m 的使用
|
||||
- (void)dragCellCollectionView:(__kindof BMLongPressDragCellCollectionView *)dragCellCollectionView endedDragAtPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
Reference in New Issue
Block a user