Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a UITableView with custom picker-like edit mode

In a regular UITableView in edit mode you drag the cell into the position you want the cell to be in and the other cells can pop into place. I want to create a UITableView edit mode where you select a cell and it is held in the center as you scroll the tableview to move the selected item, holding the selected item in the center with the table cells moving around the center selected cell.

A valid 'bounty worthy' answer will require a minimally working example that holds a selected cell in the center of the table and can be moved by swiping the table up and down. Including the edge cases of first and last position in the table. Alternatively, you can outline the key points of what you think would work and if they lead me in the right direction, then you'll get the bounty.

Update 1

I have established a project called PickerTableView on GitHub. Working on the develop branch. Selection is working and I'm working on subclassing TableView to handle the movement of the cell on scroll. Finding a working solution before me will still earn the bounty.

Further Clarification

Based upon a comment, I'll provide some ASCII art.

The TableView

|==========|
|      Next|
|==========|
|          |
|----------|
|          |
|----------|
|          |
|----------|
|          |
|----------|
|          |
|==========|

Select a cell, then tap Next

|==========|
|      Next|
|==========|
|          |
|----------|
|          |
|----------|
|         X|
|----------|
|          |
|----------|
|          |
|==========|

Tableview Editing Mode

|=============|
|         Done|
|=============|
|             |
|-------------|
|             |
|-------------|
| This cell is|
| Highlighted |
| and locked  |
| in place    |
|-------------|
|             |
|-------------|   
|             |
|=============|

As you scroll the tableview the cells that were not selected flow around the selected cell while the selected cell stays in the middle.

like image 528
Cameron Lowell Palmer Avatar asked Dec 23 '13 20:12

Cameron Lowell Palmer


People also ask

What is prototype cell?

A prototype cell acts a template for your cell's appearance. It includes the views you want to display and their arrangement within the content area of the cell. At runtime, the table's data source object creates actual cells from the prototypes and configures them with your app's data.

What is Tableview?

A table view displays a single column of vertically scrolling content, divided into rows and sections. Each row of a table displays a single piece of information related to your app.


2 Answers

Would it be acceptable if the selected cell was not really a cell but a separate UIView (that could be made to look like a cell)? If so, you could do something like this:

  1. When a cell is tapped remove it from the table and show the cell-like UIView over the table.
  2. Reposition cells as they scroll by responding to -scrollViewDidScroll:.
  3. When the Done button is tapped, reinsert the item into the table and hide the cell-like UIView

To see it in action, I've created a UIViewController subclass for you to test:

PickerTableViewController.h

#import <UIKit/UIKit.h>

@interface PickerTableViewController : UIViewController

@end

@interface SelectedItemView : UIView

@property (nonatomic, readonly) UILabel *label;

@end

PickerTableViewController.m

#import "PickerTableViewController.h"

@interface PickerTableViewController () <UITableViewDataSource, UITableViewDelegate>

@property (strong, nonatomic) UIButton *button;
@property (strong, nonatomic) UITableView *tableView;
@property (strong, nonatomic) UIView *tableViewContainer;
@property (strong, nonatomic) SelectedItemView *selectedItemView;

@property (strong, nonatomic) NSMutableArray *items;
@property (nonatomic, getter = isPicking) BOOL picking;
@property (strong, nonatomic) NSNumber *selectedItem;

@end

@implementation PickerTableViewController

- (id)init
{
    self = [super init];
    if (self) {
        // generate random cell contents
        NSInteger countItems = 20;
        self.items = [NSMutableArray arrayWithCapacity:countItems];
        for (int i = 0; i < countItems; i++) {
            [self.items addObject:@(arc4random() % 100)];
        }
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.button.backgroundColor = [UIColor yellowColor];
    [self.button setTitle:@"Done" forState:UIControlStateNormal];
    [self.button addTarget:self action:@selector(stopPicking) forControlEvents:UIControlEventTouchUpInside];
    self.button.enabled = self.isPicking;
    [self.view addSubview:self.button];

    // use a container for easy alignment of selected item view to center of table
    _tableViewContainer = [[UIView alloc] init];
    [self.view addSubview:_tableViewContainer];

    _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableViewContainer addSubview:self.tableView];
}

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    CGFloat const buttonHeight = 100.0f;
    CGRect const buttonFrame = CGRectMake(0.0f, 0.0f, self.view.bounds.size.width, buttonHeight);
    self.button.frame = buttonFrame;

    CGRect tableFrame = self.view.bounds;
    tableFrame.origin.y += buttonHeight;
    tableFrame.size.height -= buttonHeight;
    self.tableViewContainer.frame = tableFrame;
    self.tableView.frame = self.tableViewContainer.bounds;

    // allow table to scroll to first and last row
    CGFloat selectedItemViewY = self.selectedItemView.center.y;
    self.tableView.contentInset = UIEdgeInsetsMake(selectedItemViewY, 0.0f, selectedItemViewY, 0.0f);
}

#pragma mark - Custom

- (SelectedItemView *)selectedItemView
{
    if (!_selectedItemView) {
        CGRect frame = CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width, self.tableView.rowHeight);
        _selectedItemView = [[SelectedItemView alloc] initWithFrame:frame];
        _selectedItemView.center = CGPointMake(self.tableView.bounds.size.width * 0.5f, self.tableView.bounds.size.height * 0.5f);
        _selectedItemView.hidden = YES;
        [self.tableViewContainer addSubview:_selectedItemView];
    }
    return _selectedItemView;
}

- (void)startPickingForItemAtIndex:(NSInteger)index
{
    if (self.isPicking) {
        return;
    }
    self.picking = YES;

    // update tableview
    self.selectedItem = [self.items objectAtIndex:index];
    [self.items removeObjectAtIndex:index];
    [self.tableView reloadData];
    [self repositionCells];

    // update views
    self.selectedItemView.label.text = [NSString stringWithFormat:@"%@", self.selectedItem];
    self.selectedItemView.hidden = NO;
    self.button.enabled = YES;
}

- (void)stopPicking
{
    if (!self.isPicking) {
        return;
    }
    self.picking = NO;

    // calculate new index for item
    NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:@"row" ascending:YES];
    NSArray *sds = [NSArray arrayWithObject:sd];
    NSArray *indexPaths = [[self.tableView indexPathsForVisibleRows] sortedArrayUsingDescriptors:sds];
    NSInteger selectedItemIndex = NSNotFound;
    for (NSIndexPath *indexPath in indexPaths) {
        if ([self isCellAtIndexPathBelowSelectedItemView:indexPath]) {
            selectedItemIndex = indexPath.row;
            break;
        }
    }
    if (selectedItemIndex == NSNotFound) {
        selectedItemIndex = self.items.count;
    }

    // update tableview
    [self.items insertObject:self.selectedItem atIndex:selectedItemIndex];
    self.selectedItem = nil;
    [self.tableView reloadData];

    // update views
    self.selectedItemView.hidden = YES;
    self.button.enabled = NO;
}

- (BOOL)isCellAtIndexPathBelowSelectedItemView:(NSIndexPath *)indexPath
{
    CGFloat yInTable = indexPath.row * self.tableView.rowHeight;
    CGFloat yInContainer = yInTable - self.tableView.contentOffset.y;
    return yInContainer > self.selectedItemView.frame.origin.y;
}


#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    cell.textLabel.text = [NSString stringWithFormat:@"%@", [self.items objectAtIndex:indexPath.row]];

    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.isPicking) {
        [self stopPicking];
    }

    // scroll position seems to be confused... UITableViewScrollPositionMiddle doesn't work?
    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];

    [self startPickingForItemAtIndex:indexPath.row];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (self.isPicking) {
        [self repositionCells];
    }
}

- (void)repositionCells
{
    CGFloat tableOffset = self.tableView.contentOffset.y;
    NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
    CGFloat selectedItemViewY = self.selectedItemView.frame.origin.y;
    CGFloat const bufferHeight = self.tableView.rowHeight; // adjust to liking
    for (NSIndexPath *indexPath in indexPaths) {
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        CGRect cellFrame = cell.frame;

        CGFloat cellYInTable = indexPath.row * self.tableView.rowHeight;
        cellFrame.origin.y = cellYInTable;

        CGFloat cellYInContainer = cellYInTable - tableOffset;
        if (cellYInContainer <= selectedItemViewY) {
            cellFrame.origin.y -= bufferHeight;
        } else {
            cellFrame.origin.y += bufferHeight;
        }
        cell.frame = cellFrame;
    }
}

@end

@interface SelectedItemView ()

@property (strong, nonatomic) UILabel *label;

@end

@implementation SelectedItemView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.userInteractionEnabled = NO;
        self.backgroundColor = [UIColor blueColor];
        _label = [[UILabel alloc] init];
        _label.backgroundColor = [UIColor clearColor];
        [self addSubview:_label];
    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.label.frame = self.bounds;
}

@end
like image 172
Joseph Chen Avatar answered Nov 04 '22 11:11

Joseph Chen


when u select a cell in edit mode. Do these:

  1. Scroll the cell to center position.
  2. use renderInContext to cut out an image of the moved cell.
  3. put the image in an imageView, and add it to the tableView's superview. In accordance to center position, relative to tableView's superView.
  4. after that u are free to scroll around the table. Just delete the selected cell beforehand, to avoid duplicacy in UI.
  5. On pressing next. Insert a row in the desired position, with the desired data and remove the added ImageView.
  6. Animations involved are entirely left to ur discretion :D

Cheers have fun.

like image 28
devluv Avatar answered Nov 04 '22 11:11

devluv