使用 Objective-C 使用平移手势突出显示集合视图单元格
Highlight UICollectionViewCells with PanGesture using Objective-C
当用户将手指拖过单元格时,我使用下面的代码来跟踪单元格(效果很好)。也就是说,我想在用户将手指拖到它们上面时突出显示每个单元格(或更改我的自定义单元格中背景视图的颜色)。我怎样才能做到这一点?见下文。
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
[self.ringCollectionView addGestureRecognizer:panGesture];
}
- (void) handlePanGesture:(UIPanGestureRecognizer*) panGesture
{
CGPoint location = [panGesture locationInView:self.ringCollectionView];
NSIndexPath *indexPath = [self.ringCollectionView indexPathForItemAtPoint:location];
NSMutableArray *selectedIndexes = [NSMutableArray arrayWithArray:[self.ringCollectionView indexPathsForSelectedItems]];
if (![selectedIndexes containsObject:@(indexPath.row)]) {
NSLog(@"THIS CELL IS %ld", (long)indexPath.row);
}
else
if (panGesture.state == UIGestureRecognizerStateEnded) {
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"RingCollectionViewCell";
RingCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Tapping");
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(118, 118);
}
首先,我认为通过“本机”UICollectionView
机制跟踪选定的单元格是合理且一致的。单元格内不需要自定义手势处理程序。但是,为了表示选定状态,您需要设置 selectedBackgroundView
属性,如下所示:
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"RingCollectionViewCell" forIndexPath:indexPath];
if (!cell.selectedBackgroundView) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor grayColor];
}
return cell;
}
我不知道你的RingCollectionViewCell
class是怎么设计的,但是请注意,你需要确保这个单元格的contentView
背景色是透明的,否则会模糊selectedBackgroundView
.
现在是棘手的部分。 UIPanGestureRecognizer
获取事件的速度非常快,当用户将 his/her 手指保持在屏幕上时,将经常调用 handle 方法。因此,您需要以某种方式抑制不应切换单元格选定状态的事件。
我的建议是忽略所有发生在同一个牢房中的后果事件。为了实现这种行为,我们需要存储最后一个单元格的索引路径。让我们为此使用一个简单的 属性:
@interface ViewController ()
@property (strong, nonatomic, nullable) NSIndexPath *trackingCellIndexPath;
@end
现在您需要在手势处理方法中将最后触摸的单元格的索引路径分配给此 属性。当手势完成或取消时,您还需要重置此 属性。如果给定的索引路径不等于跟踪的索引路径,则切换选择,否则忽略触摸事件:
- (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer {
// Reset the tracking state when the gesture is finished
switch (recognizer.state) {
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
self.trackingCellIndexPath = nil;
return;
default:
break;
}
// Obtain the cell the user is currently dragging over
CGPoint location = [recognizer locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];
// If the user currently doesn't touch any cell, reset the tracking state and prepare to listen to another cell
if (!indexPath) {
if (self.trackingCellIndexPath) {
self.trackingCellIndexPath = nil;
}
return;
}
// If current event is subsequent gesture event which happens within the same cell, ignore it
if (self.trackingCellIndexPath == indexPath) {
return;
}
// If the cell hasn't been previously tracked, switch the selected state and start tracking it
self.trackingCellIndexPath = indexPath;
if ([self.collectionView.indexPathsForSelectedItems containsObject:indexPath]) {
[self.collectionView deselectItemAtIndexPath:indexPath animated:YES];
} else {
[self.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
}
}
最后要改变的是集合视图的选择模式。正如您希望多个单元格保持选中状态,只需在 viewDidLoad
:
中的某处切换集合视图的 allowsMultipleSelection
- (void)viewDidLoad {
...
self.collectionView.allowsMultipleSelection = YES;
}
因此,为了改变颜色,您需要决定如何处理手势,以及如何改变颜色。考虑以下因素:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic) CGFloat redColor;
@property (nonatomic) CGFloat greenColor;
@property (nonatomic) CGFloat blueColor;
@property (nonatomic) CGPoint startPoint;
@end
@implementation ViewController
// insuring reasonable values
-(void)setRedColor:(CGFloat)colorX {
CGFloat x = colorX;
x = x > 0 ? x : 0;
x = x < 1.0 ? x : 1.0;
_redColor = x;
}
-(void)setGreenColor:(CGFloat)colorY {
CGFloat y = colorY;
y = y > 0 ? y : 0;
y = y < 1.0 ? y : 1.0;
_greenColor = y;
}
- (void)setBlueColor:(CGFloat)colorZ {
CGFloat z = colorZ;
z = z > 0 ? z : 0;
z = z < 1.0 ? z : 1.0;
_blueColor = z;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *panView = [UIView new];
panView.backgroundColor = UIColor.blueColor;
panView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:panView];
[self.view addConstraints:@[
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]
]];
[panView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action: @selector(handlePanGesture:)] ];
}
//重要部分
-(void)handlePanGesture:(UIPanGestureRecognizer *) panGesture {
//getting our position from super for total location area
UIView *aView = [[panGesture view] superview];
if (panGesture.state == UIGestureRecognizerStateBegan) {
//saving the start of our pans point
_startPoint = [panGesture translationInView: aView];
}else if (panGesture.state == UIGestureRecognizerStateChanged) {
//where our pan moved
//For this example I'm just using changed value to set color using CGFLoat, but you could also determine a direction and go through a UIcolor array or however you want to actually set your color.
CGPoint trin = [panGesture translationInView:aView];
CGFloat xInc = (trin.x - _startPoint.x);// find how much left or right the finger moved.
CGFloat yInc = (trin.y - _startPoint.y); // find how much up or down the finger moved.
CGFloat base = 1.0; //used to keep values in range
//altering colors based on movement
//just as an example for the color change. Ideally you would implement a better color scheme.
[self setRedColor: self.redColor + (xInc == 0 ? 0 : base / xInc)];
[self setGreenColor: self.greenColor + (yInc == 0 ? 0 : base / yInc)];
self.blueColor = self.greenColor + self.redColor;
//setting the new color of our panView
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1 animations:^{
panGesture.view.backgroundColor = [UIColor colorWithRed: self.redColor
green: self.greenColor
blue: self.blueColor
alpha:1.0];
}];
});
} else if (panGesture.state == UIGestureRecognizerStateEnded) {
_startPoint = CGPointZero;
}
}
@end
当用户将手指拖过单元格时,我使用下面的代码来跟踪单元格(效果很好)。也就是说,我想在用户将手指拖到它们上面时突出显示每个单元格(或更改我的自定义单元格中背景视图的颜色)。我怎样才能做到这一点?见下文。
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
[self.ringCollectionView addGestureRecognizer:panGesture];
}
- (void) handlePanGesture:(UIPanGestureRecognizer*) panGesture
{
CGPoint location = [panGesture locationInView:self.ringCollectionView];
NSIndexPath *indexPath = [self.ringCollectionView indexPathForItemAtPoint:location];
NSMutableArray *selectedIndexes = [NSMutableArray arrayWithArray:[self.ringCollectionView indexPathsForSelectedItems]];
if (![selectedIndexes containsObject:@(indexPath.row)]) {
NSLog(@"THIS CELL IS %ld", (long)indexPath.row);
}
else
if (panGesture.state == UIGestureRecognizerStateEnded) {
}
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 10;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"RingCollectionViewCell";
RingCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Tapping");
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
return CGSizeMake(118, 118);
}
首先,我认为通过“本机”UICollectionView
机制跟踪选定的单元格是合理且一致的。单元格内不需要自定义手势处理程序。但是,为了表示选定状态,您需要设置 selectedBackgroundView
属性,如下所示:
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"RingCollectionViewCell" forIndexPath:indexPath];
if (!cell.selectedBackgroundView) {
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
cell.selectedBackgroundView.backgroundColor = [UIColor grayColor];
}
return cell;
}
我不知道你的RingCollectionViewCell
class是怎么设计的,但是请注意,你需要确保这个单元格的contentView
背景色是透明的,否则会模糊selectedBackgroundView
.
现在是棘手的部分。 UIPanGestureRecognizer
获取事件的速度非常快,当用户将 his/her 手指保持在屏幕上时,将经常调用 handle 方法。因此,您需要以某种方式抑制不应切换单元格选定状态的事件。
我的建议是忽略所有发生在同一个牢房中的后果事件。为了实现这种行为,我们需要存储最后一个单元格的索引路径。让我们为此使用一个简单的 属性:
@interface ViewController ()
@property (strong, nonatomic, nullable) NSIndexPath *trackingCellIndexPath;
@end
现在您需要在手势处理方法中将最后触摸的单元格的索引路径分配给此 属性。当手势完成或取消时,您还需要重置此 属性。如果给定的索引路径不等于跟踪的索引路径,则切换选择,否则忽略触摸事件:
- (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer {
// Reset the tracking state when the gesture is finished
switch (recognizer.state) {
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
self.trackingCellIndexPath = nil;
return;
default:
break;
}
// Obtain the cell the user is currently dragging over
CGPoint location = [recognizer locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];
// If the user currently doesn't touch any cell, reset the tracking state and prepare to listen to another cell
if (!indexPath) {
if (self.trackingCellIndexPath) {
self.trackingCellIndexPath = nil;
}
return;
}
// If current event is subsequent gesture event which happens within the same cell, ignore it
if (self.trackingCellIndexPath == indexPath) {
return;
}
// If the cell hasn't been previously tracked, switch the selected state and start tracking it
self.trackingCellIndexPath = indexPath;
if ([self.collectionView.indexPathsForSelectedItems containsObject:indexPath]) {
[self.collectionView deselectItemAtIndexPath:indexPath animated:YES];
} else {
[self.collectionView selectItemAtIndexPath:indexPath animated:YES scrollPosition:UICollectionViewScrollPositionNone];
}
}
最后要改变的是集合视图的选择模式。正如您希望多个单元格保持选中状态,只需在 viewDidLoad
:
allowsMultipleSelection
- (void)viewDidLoad {
...
self.collectionView.allowsMultipleSelection = YES;
}
因此,为了改变颜色,您需要决定如何处理手势,以及如何改变颜色。考虑以下因素:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic) CGFloat redColor;
@property (nonatomic) CGFloat greenColor;
@property (nonatomic) CGFloat blueColor;
@property (nonatomic) CGPoint startPoint;
@end
@implementation ViewController
// insuring reasonable values
-(void)setRedColor:(CGFloat)colorX {
CGFloat x = colorX;
x = x > 0 ? x : 0;
x = x < 1.0 ? x : 1.0;
_redColor = x;
}
-(void)setGreenColor:(CGFloat)colorY {
CGFloat y = colorY;
y = y > 0 ? y : 0;
y = y < 1.0 ? y : 1.0;
_greenColor = y;
}
- (void)setBlueColor:(CGFloat)colorZ {
CGFloat z = colorZ;
z = z > 0 ? z : 0;
z = z < 1.0 ? z : 1.0;
_blueColor = z;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *panView = [UIView new];
panView.backgroundColor = UIColor.blueColor;
panView.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:panView];
[self.view addConstraints:@[
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeading multiplier:1 constant:0],
[NSLayoutConstraint constraintWithItem:panView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:0]
]];
[panView addGestureRecognizer:[[UIPanGestureRecognizer alloc] initWithTarget:self action: @selector(handlePanGesture:)] ];
}
//重要部分
-(void)handlePanGesture:(UIPanGestureRecognizer *) panGesture {
//getting our position from super for total location area
UIView *aView = [[panGesture view] superview];
if (panGesture.state == UIGestureRecognizerStateBegan) {
//saving the start of our pans point
_startPoint = [panGesture translationInView: aView];
}else if (panGesture.state == UIGestureRecognizerStateChanged) {
//where our pan moved
//For this example I'm just using changed value to set color using CGFLoat, but you could also determine a direction and go through a UIcolor array or however you want to actually set your color.
CGPoint trin = [panGesture translationInView:aView];
CGFloat xInc = (trin.x - _startPoint.x);// find how much left or right the finger moved.
CGFloat yInc = (trin.y - _startPoint.y); // find how much up or down the finger moved.
CGFloat base = 1.0; //used to keep values in range
//altering colors based on movement
//just as an example for the color change. Ideally you would implement a better color scheme.
[self setRedColor: self.redColor + (xInc == 0 ? 0 : base / xInc)];
[self setGreenColor: self.greenColor + (yInc == 0 ? 0 : base / yInc)];
self.blueColor = self.greenColor + self.redColor;
//setting the new color of our panView
dispatch_async(dispatch_get_main_queue(), ^{
[UIView animateWithDuration:1 animations:^{
panGesture.view.backgroundColor = [UIColor colorWithRed: self.redColor
green: self.greenColor
blue: self.blueColor
alpha:1.0];
}];
});
} else if (panGesture.state == UIGestureRecognizerStateEnded) {
_startPoint = CGPointZero;
}
}
@end