简体   繁体   中英

Selecting multiple cells in a UICollectionView

What I'm trying to achieve is something like the video length editor of, say, WhatsApp: you can modify the length by dragging the beginning or the ending of the reel.

I need to be able to select the cells by dragging a finger trough the collection view, then if you touch the last or the first selected cell and drag again, the selection should adjust.

EDIT: I've been able to do almost all I needed, but there's still something that's not right.

I ended up using a long press gesture recognizer that's triggering the "editing mode" after 0.3 seconds. The problems are: sometimes only the first time entering the "editing mode" you can't really edit anything, so you'll have to release the long press and start again.

I had to lock the editing direction because I was not able to make it work both ways. That means that if you enter the editing mode and start moving left, for example, you can't move back to the right.

This is my code so far:

ReservationViewController.m

#import "ReservationViewController.h"
#import "RoomTest.h"

@interface ReservationCollectionViewCell : UICollectionViewCell

@property (weak, nonatomic) IBOutlet UILabel *timeLbl;
@property (weak, nonatomic) IBOutlet UIView *upLine;
@property (weak, nonatomic) IBOutlet UIView *leftLine;
@property (weak, nonatomic) IBOutlet UIView *bottomLine;
@property (weak, nonatomic) IBOutlet UIView *rightLine;

@end

@implementation ReservationCollectionViewCell

@end


@interface ReservationViewController ()

@property (strong, nonatomic) NSMutableArray *cellsArray;
@property (strong, nonatomic) NSMutableArray *selectedIndexes;
@property (strong, nonatomic) NSArray *orderedIndexes;
@property (strong, nonatomic) NSIndexPath *startPath;
@property (nonatomic) BOOL dirLockAddSelection;
@property (nonatomic) BOOL dirLockDelSelection;
@property (weak, nonatomic) IBOutlet UILabel *resultLbl;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (strong, nonatomic) NSString *selectedRoom;
@property (weak, nonatomic) IBOutlet UILabel *roomLbl;
@property (nonatomic) BOOL gestureActivated;

@end

@implementation ReservationViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _cellsArray = [[NSMutableArray alloc] initWithCapacity:96];
    _selectedIndexes = [[NSMutableArray alloc] init];
    _startPath = [[NSIndexPath alloc] init];

    _selectedRoom = [NSString new];

    // Test data
    NSMutableArray *tempArray = [[NSMutableArray alloc] init];
    for (int i=0; i<5; i++) {
        RoomTest *temp = [[RoomTest alloc] init];
        temp.start = @"05:00";
        temp.end = @"08:30";
        temp.room = [NSString stringWithFormat:@"%d00",i+1];
        [tempArray addObject:temp];
    }
    // End of test data


    _rooms = [[NSArray alloc] initWithArray:tempArray];

    NSDateComponents *components= [[NSDateComponents alloc] init];
    [components setSecond:00];
    [components setMinute:00];
    [components setHour:00];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDate *zeroDate = [calendar dateFromComponents:components];
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    [df setDateFormat:@"HH:mm"];
    NSString *test = [df stringFromDate:zeroDate];

    for (int i=0; i<1440; i+=15) {
        [_cellsArray addObject:test];
        [components setMinute:15];
        zeroDate = [calendar dateByAddingComponents:components toDate:zeroDate options:0];
        test = [df stringFromDate:zeroDate];
    }

    UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] init];
    [longPressRecognizer addTarget:self action:@selector(didLongPress:)];
    longPressRecognizer.minimumPressDuration = 0.3;
    [_collectionView addGestureRecognizer: longPressRecognizer];
}

- (void)didLongPress: (UILongPressGestureRecognizer *)gesture {
    if (UIGestureRecognizerStateBegan == gesture.state) {
        _gestureActivated = YES;
        CGPoint location = [gesture locationInView:_collectionView];
        _startPath = [_collectionView indexPathForItemAtPoint:location];
        NSLog(@"Start: %ld", (long)_startPath.row);
    } else if (UIGestureRecognizerStateChanged == gesture.state) {
        CGPoint location = [gesture locationInView:_collectionView];
        NSIndexPath *indexPath = [_collectionView indexPathForItemAtPoint:location];
        NSLog(@"Changed: %ld", (long)indexPath.row);

        if ([_orderedIndexes count] == 0 && ![_selectedIndexes containsObject:@(indexPath.row)]) {
            [_selectedIndexes addObject:@(indexPath.row)];
            [_collectionView reloadData];
        } else {
            NSNumber *first = (NSNumber*)[_orderedIndexes firstObject];
            NSNumber *last = (NSNumber*)[_orderedIndexes lastObject];
            if ((_startPath.row == [first intValue] && indexPath.row < _startPath.row) || (_startPath.row == [last intValue] && indexPath.row > _startPath.row)) {
                if (![_selectedIndexes containsObject:@(indexPath.row)] && indexPath.row != _startPath.row && !_dirLockDelSelection) {
                    [_selectedIndexes addObject:@(indexPath.row)];
                    _dirLockAddSelection = YES;
                }
            } else if ((_startPath.row == [first intValue] && indexPath.row > _startPath.row)) {
                if ([_selectedIndexes containsObject:@(indexPath.row-1)] && [_selectedIndexes count] > 0 && !_dirLockAddSelection) {
                    [_selectedIndexes removeObject:@(indexPath.row-1)];
                    _dirLockDelSelection = YES;
                }
            } else if ((_startPath.row == [last intValue] && indexPath.row < _startPath.row)) {
                if ([_selectedIndexes containsObject:@(indexPath.row+1)] && [_selectedIndexes count] > 0 && !_dirLockAddSelection) {
                    [_selectedIndexes removeObject:@(indexPath.row+1)];
                    _dirLockDelSelection = YES;
                }
            }

            NSArray *visibleCells = [_collectionView indexPathsForVisibleItems];
            NSSortDescriptor *rowDescriptor = [[NSSortDescriptor alloc] initWithKey:@"row" ascending:YES];
            NSArray *sortedRows = [visibleCells sortedArrayUsingDescriptors:@[rowDescriptor]];
            if (indexPath == [sortedRows lastObject]) {
                NSIndexPath *newPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0];
                [_collectionView scrollToItemAtIndexPath:newPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
            } else if (indexPath == [sortedRows firstObject]) {
                NSIndexPath *newPath = [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
                [_collectionView scrollToItemAtIndexPath:newPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
            }
            NSIndexPath *temp = [sortedRows lastObject];
            NSLog(@"Current Row: %ld - Visible Cell Row: %ld", (long)indexPath.row, (long)temp.row);
            [_collectionView reloadData];
        }
    } else if(UIGestureRecognizerStateEnded == gesture.state) {
        CGPoint location = [gesture locationInView:_collectionView];
        NSIndexPath *indexPath = [_collectionView indexPathForItemAtPoint:location];
        NSLog(@"End: %ld", (long)indexPath.row);
        _orderedIndexes = [_selectedIndexes sortedArrayUsingSelector:@selector(compare:)];
        _dirLockAddSelection = NO;
        _dirLockDelSelection = NO;
        _gestureActivated = NO;
        [_collectionView reloadData];
    }
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 100.0;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [_rooms count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:@"tableCellReserve" forIndexPath:indexPath];

    RoomTest *room = _rooms[indexPath.row];

    cell.textLabel.text = room.room;

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    RoomTest *selRoom = _rooms[indexPath.row];
    _selectedRoom = selRoom.room;

    for (RoomTest *test in _rooms) {
        if ([_selectedRoom isEqualToString:test.room]) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[_cellsArray indexOfObject:test.start] inSection:0];

            int start = [_cellsArray indexOfObject:test.start];
            int end = [_cellsArray indexOfObject:test.end];
            _selectedIndexes = [[NSMutableArray alloc] init];
            for (NSString *time in _cellsArray) {
                if ([_cellsArray indexOfObject:time] >= start && [_cellsArray indexOfObject:time] <= end) {
                    [_selectedIndexes addObject:@([_cellsArray indexOfObject:time])];
                }
            }

            [_collectionView reloadData];
            [_collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
        }
    }
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 96;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    ReservationCollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:@"reserveCell" forIndexPath:indexPath];

    cell.upLine.hidden = YES;
    cell.leftLine.hidden = YES;
    cell.bottomLine.hidden = YES;
    cell.rightLine.hidden = YES;

    if ((indexPath.row % 2) == 0) {
        cell.timeLbl.text = _cellsArray[indexPath.row];
    } else {
        cell.timeLbl.text = @"";
    }

    int first = [[_selectedIndexes valueForKeyPath:@"@min.intValue"] intValue];
    int last = [[_selectedIndexes valueForKeyPath:@"@max.intValue"] intValue];

    if ([_selectedIndexes count] > 0) {
        if (indexPath.row == first && indexPath.row == last) {
            cell.leftLine.layer.borderColor = [UIColor redColor].CGColor;
            cell.leftLine.layer.borderWidth = 2.0;
            cell.leftLine.hidden = NO;
            cell.upLine.hidden = NO;
            cell.bottomLine.hidden = NO;
            cell.rightLine.hidden = NO;
            cell.rightLine.layer.borderColor = [UIColor redColor].CGColor;
            cell.rightLine.layer.borderWidth = 2.0;
        } else  if (first == indexPath.row) {
            cell.leftLine.layer.borderColor = [UIColor redColor].CGColor;
            cell.leftLine.layer.borderWidth = 2.0;
            cell.leftLine.hidden = NO;
            cell.upLine.hidden = NO;
            cell.bottomLine.hidden = NO;
            cell.rightLine.hidden = YES;
        } else if (last == indexPath.row){
            cell.leftLine.hidden = YES;
            cell.upLine.hidden = NO;
            cell.bottomLine.hidden = NO;
            cell.rightLine.hidden = NO;
            cell.rightLine.layer.borderColor = [UIColor redColor].CGColor;
            cell.rightLine.layer.borderWidth = 2.0;
        } else if ([_selectedIndexes containsObject:@(indexPath.row)]) {
            cell.leftLine.hidden = YES;
            cell.rightLine.hidden = YES;
            cell.upLine.hidden = NO;
            cell.bottomLine.hidden = NO;
        }
        if (_gestureActivated && (first == indexPath.row || last == indexPath.row)) {
            cell.leftLine.layer.borderWidth = 4.0;
            cell.rightLine.layer.borderWidth = 4.0;
        } else {
            cell.leftLine.layer.borderWidth = 2.0;
            cell.rightLine.layer.borderWidth = 2.0;
        }
    }

    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(50.0, _collectionView.bounds.size.height);
}

-(IBAction)onOKBtnPressed:(id)sender {
    if ([_selectedIndexes count] > 0) {
        //        if (_delegate != nil) {
        int first = [[_selectedIndexes valueForKeyPath:@"@min.intValue"] intValue];
        int last = [[_selectedIndexes valueForKeyPath:@"@max.intValue"] intValue];

        //            NSArray *temp = [[NSArray alloc] initWithObjects:_cellsArray[first], _cellsArray[last], nil];
        //            [_delegate onSelectionEnded:temp];
        _resultLbl.text = [NSString stringWithFormat:@"Selected %@ from %@ to %@", _selectedRoom, _cellsArray[first], _cellsArray[last]];
        //        }
    }
}

@end

The view controller contains a vertical table view on the left that's showing the rooms' numbers, and on its right there's the collection view showing the timeline.

When a cell is selected, I'm just drawing some lines in the cell to show that is selected.

Any help to solve my problems is very welcome, and so will be any suggestions about the code I wrote so far.

I've solved almost all the problems I had. The only thing I don't like now is the "auto scroll" of the collection view when the user is selecting cells and drags the finger on the edge of the screen.

The collection view is supposed to scroll, while the user keeps he's finger on the screen, and the selection has to continue.

As I got it now it works, but the scrolling is not smooth and I don't like it at all.

This is the modified -didLongPress method:

- (void)didLongPress: (UILongPressGestureRecognizer *)gesture {
    if (UIGestureRecognizerStateBegan == gesture.state) {
        _gestureActivated = YES;
        CGPoint location = [gesture locationInView:_collectionView];
        _startPath = [_collectionView indexPathForItemAtPoint:location];
        _lastIndex = _startPath;
        _orderedIndexes = [_selectedIndexes sortedArrayUsingSelector:@selector(compare:)];
        NSLog(@"Start: %ld", (long)_startPath.row);
    } else if (UIGestureRecognizerStateChanged == gesture.state) {
        CGPoint location = [gesture locationInView:_collectionView];
        NSIndexPath *indexPath = [_collectionView indexPathForItemAtPoint:location];
        NSLog(@"Changed: %ld", (long)indexPath.row);
        if ([_orderedIndexes count] == 0 && ![_selectedIndexes containsObject:@(indexPath.row)]) {
            [_selectedIndexes addObject:@(indexPath.row)];
        } else {
            NSNumber *first = (NSNumber*)[_orderedIndexes firstObject];
            NSNumber *last = (NSNumber*)[_orderedIndexes lastObject];

            if (_startPath.row == [last intValue] || _startPath.row == [first intValue]) {
                if ([_selectedIndexes count] == 0) {
                    [_selectedIndexes addObject:@(indexPath.row)];
                }
                if ((_lastIndex.row - indexPath.row) == 1 || (_lastIndex.row - indexPath.row) == -1) {
                    if (![_selectedIndexes containsObject:@(indexPath.row)]) {
                        [_selectedIndexes addObject:@(indexPath.row)];
                    } else if ([_selectedIndexes containsObject:@(indexPath.row)] && [_selectedIndexes containsObject:@(_lastIndex.row)]) {
                        [_selectedIndexes removeObject:@(_lastIndex.row)];
                    }
                }

                _lastIndex = indexPath;

                //Auto-scroll collectionview if touch is on the first or last visible cells
                NSArray *visibleCells = [_collectionView indexPathsForVisibleItems];
                NSSortDescriptor *rowDescriptor = [[NSSortDescriptor alloc] initWithKey:@"row" ascending:YES];
                NSArray *sortedRows = [visibleCells sortedArrayUsingDescriptors:@[rowDescriptor]];
                if (indexPath == [sortedRows lastObject]) {
                    NSIndexPath *newPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0];
                    if (newPath.row <= [_cellsArray count]-1) {
                        [_collectionView scrollToItemAtIndexPath:newPath atScrollPosition:UICollectionViewScrollPositionRight animated:YES];
                    }
                } else if (indexPath == [sortedRows firstObject]) {
                    NSIndexPath *newPath = [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
                    if (newPath.row >= 0) {
                        [_collectionView scrollToItemAtIndexPath:newPath atScrollPosition:UICollectionViewScrollPositionLeft animated:YES];
                    }
                }
            }
        }
    } else if(UIGestureRecognizerStateEnded == gesture.state) {
        CGPoint location = [gesture locationInView:_collectionView];
        NSIndexPath *indexPath = [_collectionView indexPathForItemAtPoint:location];
        NSLog(@"End: %ld", (long)indexPath.row);
        _orderedIndexes = [_selectedIndexes sortedArrayUsingSelector:@selector(compare:)];
        _gestureActivated = NO;
    }
    [_collectionView reloadData];
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM