在我们的实际开发中,CollectionView是一种非常实用而又稍难的控件,如果想要在复杂的场景下使用,则需要考虑的比较全面。如果又在CollectionView添加其他的控件,比如在cell里面再添加一个按钮,那么点击触发的事件前后顺序就非常重要了。再者,如果一个界面中包含了一个输入控件,需要弹出键盘时,键盘遮挡对于界面上的其他控件的使用就会造成较大的影响。今天我的案例具体需求描述下:界面中有一个TextField,点击输入的时候弹出键盘,并且整个界面向上移动,让键盘不遮挡其他控件。并且在点击界面背景、CollectionView空白部分,cell,和cell上面的按钮的时候可以收回键盘。并且在点击cell的时候可以触发didSelected方法,点击cell上面的按钮触发另一个点击方法。案例代码已经上传至 https://github.com/chenyufeng1991/ShowHiddenKeyboard 。欢迎大家下载使用。如果想对CollectionView有更为复杂的用法,可以移步这里 https://github.com/chenyufeng1991/CollectionView 。还有就是项目中使用Masonry来进行自动布局,Masonry的使用可以参考这篇博客《Autolayout第三方库Masonry的入门与实践》 。
(1)首先在AppDelegate.h中定义一些全局变量
//定义宏,用于block
#define WeakSelf(weakSelf) __weak __typeof(&*self)weakSelf = self;
typedef NS_ENUM(NSInteger,KeyBoardState){
KeyboardHidden = 0,
KeyboardShowing
};
定义WeakSelf宏定义是因为在使用Masonry的时候会大量用到block,为了防止引起循环引用,需要使用__weak修饰self.
下面的枚举是键盘的两种状态,显示或者隐藏。
(2)我要实现的UI效果图如下:
其中黄色部分是一个ImageView,背景设置了黄色。绿色部分是一个TextField。下面带图片的黑色部分是一个CollectionView,那张图片就是一个cell。我使用Masonry来
实现下面的UI。
先声明下属性:
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) UIImageView *topImageView;
@property (nonatomic, strong) UITextField *inputField;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray *collArr;
@property (nonatomic, assign) KeyBoardState status;
// 包容整个界面的容器View
CGRect tureFame = self.view.frame;
tureFame.origin.y = 64;// 获取剔除导航栏后的真正y位置
self.contentView = [[UIView alloc] init];
self.contentView.backgroundColor = [UIColor whiteColor];
self.contentView.frame = tureFame;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.contentView];
// 顶部图片
WeakSelf(weakSelf);
self.topImageView = [[UIImageView alloc] init];
self.topImageView.backgroundColor = [UIColor yellowColor];
[self.contentView addSubview:self.topImageView];
[self.topImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(weakSelf.contentView);
make.left.equalTo(weakSelf.contentView);
make.right.equalTo(weakSelf.contentView);
make.height.equalTo(@50);
}];
// 文本输入框
self.inputField = [[UITextField alloc] init];
self.inputField.text = @"请输入";
self.inputField.backgroundColor = [UIColor colorWithRed:0.507 green:1.000 blue:0.520 alpha:1.000];
[self.contentView addSubview:self.inputField];
[self.inputField mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(weakSelf.topImageView.mas_bottom);
make.left.equalTo(weakSelf.contentView);
make.right.equalTo(weakSelf.contentView);
make.height.equalTo(@150);
}];
// CollectionView
self.collArr = [[NSMutableArray alloc] initWithObjects:[UIImage imageNamed:@"beauty"], nil];
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 320, 70) collectionViewLayout:flowLayout];
[self.collectionView registerClass:[CustomCollectionViewCell class] forCellWithReuseIdentifier:@"CollectionCell"];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.contentView addSubview:self.collectionView];
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.inputField.mas_bottom).offset(20);
make.left.equalTo(self.contentView);
make.right.equalTo(self.contentView);
make.height.equalTo(@70);
}];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(clickCollectionView:)];
// tap.cancelsTouchesInView = NO;
[self.collectionView addGestureRecognizer:tap];
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
#pragma mark - 键盘处理事件
- (void)keyboardWillShow
{
self.status = KeyboardShowing;
CGRect frame = self.contentView.frame;
frame.origin.y = self.contentView.frame.origin.y - 50;
self.contentView.frame = frame;
}
- (void)keyboardWillHide
{
self.status = KeyboardHidden;
CGRect frame = self.contentView.frame;
frame.origin.y = self.contentView.frame.origin.y + 50;
self.contentView.frame = frame;
}
因为点击背景可以使键盘隐藏,所以我需要重写touchesEnded方法:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self hideKeyboard];
}
- (void)hideKeyboard
{
[self.inputField endEditing:YES];// 这里会阻断响应链
}
完成上述代码后实现动图效果如下:
有了以上这样的设置方法之后,如果键盘弹起会遮挡一些UI,这样的bug是不是也会修复了呢?哈哈。
(5)好了,下面要开始来研究CollectionView了。可能这里有些奇怪,为什么要把键盘和CollectionView(其实TableView也一样)来一起讲呢?这里是有原因的。因为CollectionView里面的点击事件更为复杂,比如你可以点击在CollectionView的空白区域,或者是在某个Cell上,或者是Cell上面的某个按钮。其实处理方法都是有所不同的,再结合键盘的回收,那么就更有意思了。
下面先来自定义一个cell,取名为CustomCollectionViewCell,继承自UICollectionViewCell:
CustomCollectionViewCell.h文件如下:
@interface CustomCollectionViewCell : UICollectionViewCell
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) UIButton *button;
@end
@implementation CustomCollectionViewCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
[self.imageView setUserInteractionEnabled:YES];
[self addSubview:self.imageView];
self.button = [[UIButton alloc] initWithFrame:CGRectMake(40, 0, 20, 20)];
[self.button setImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal];
[self addSubview:self.button];
}
return self;
}
@end
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"CollectionView中的Cell被点击了:%ld",(long)indexPath.row);
}
#pragma mark - UiCollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.collArr.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
cell.imageView.image = self.collArr[indexPath.row];
[cell.button addTarget:self
action:@selector(clickCloseButton:)
forControlEvents:UIControlEventTouchUpInside];
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(60, 60);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(0, 5, 0, 5);
}
为了和背景白色区分,这里CollectionView的背景设置为了黑色,这个黑色部分也就是没有cell的区域。
(6)在我上面初始化CollectionView的时候,可以看到我为这个CollectionView添加了一个手势:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(clickCollectionView:)];
// tap.cancelsTouchesInView = NO;
[self.collectionView addGestureRecognizer:tap];
- (void)clickCollectionView:(id)sender
{
CGPoint pointTouch = [sender locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:pointTouch];
if (indexPath != nil)
{
[self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
}
[self hideKeyboard];
}
CGPoint是为了获取在CollectionView中的点击位置。NSIndexPath是为了获取该位置是在CollectionView中的哪一个cell,如果NSIndePath为nil,表示点击位置在空白区域,否则就是某个具体cell,然后就去手动调用didSelectItem方法。最后再去隐藏键盘。
那么对于cell中的按钮点击如何处理呢?可以看到在cellForItem方法中我为cell中的button添加了一个target,并去触发一个clickCloseButton:方法,该方法的实现如下:
- (void)clickCloseButton:(id)sender
{
CustomCollectionViewCell *cell = (CustomCollectionViewCell *)[sender superview];
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
NSLog(@"关闭按钮被点击:%ld",(long)indexPath.row);
[self hideKeyboard];
// 如果想要在点击关闭按钮的时候也调用didSelected方法,可以在这里手动调用;我先默认不调用;
#if 0
if (indexPath != nil)
{
[self collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
}
#endif
}
这里通过[sender superview]来获取按钮的父view,这个父View就是容纳它的cell,然后就可以获取这个cell的NSIndexPath了。这里我首先要说明一下sender到底是什么东西,英文意思是发送者。其实很容易理解,当我点击按钮,那么这个sender就是那个按钮。而在tap手势中,sender就是UIGestureRecognizer.。上面我用#if 0...#end禁掉了几行代码,如果你有特殊需求,在点击button的时候要触发didSelectItem,同样可以手动调用。
走了这么远,我们不要忘了为什么而出发。记住我们的需求是点击CollectionView的空白区域能隐藏键盘;点击cell的时候调用didSelectItem方法,同时隐藏键盘;点击cell中按钮的时候调用按钮点击方法clickCloseButton:方法,同时隐藏键盘。最重要的是,在点击cell和按钮的时候我要获取NSIndexPath,以保证我后面的其他业务逻辑可以进行。经过测试,可以成功实现。测试如下:
(7)还记不记得上面在添加手势时我们注释了一行代码:
// tap.cancelsTouchesInView = NO;
手势操作,Target-Action等事件响应机制要讲解的内容还是比较多的,我们只能在平时的实际开发中不断去实践,试错,积累,才会不断的进步。后续相关文章,会继续和大家分享。