iOS - Разрешаване на движение на редове по подразбиране в `UITableView` без режим на редактиране

Искам да разреша движението на реда по подразбиране в UITableView, без да е в режим на редактиране и без да компрометирам поведението по подразбиране на UITableView.

подвижна клетка

Изображението по-горе показва клетка в режим на редактиране, с разрешено движение.

Опитах просто да стартирам for (UIView *subview in cell.subviews) (докато моят UITableView беше в режим на редактиране), но бутонът не се появи:

<UITableViewCellScrollView: 0x8cabd80; frame = (0 0; 320 44); autoresize = W+H; gestureRecognizers = <NSArray: 0x8c9ba20>; layer = <CALayer: 0x8ca14b0>; contentOffset: {0, 0}>

Как мога да разреша/добавя „бутона“ за движение, без да активирам режима за редактиране в моя UITableView?

Създаването и добавянето на UIButton с function по подразбиране за движение също е опция.


person Aleksander Azizi    schedule 03.04.2014    source източник
comment
Може би опитайте метода в тази скорошна статия:raywenderlich.com/63089/   -  person Jack    schedule 04.04.2014
comment
Това може да работи и знам, че този метод е възможен. Но въпросът ми е как да го активирам с кода по подразбиране за движение на клетките.   -  person Aleksander Azizi    schedule 04.04.2014
comment
@AleksanderAzizi Мога ли да попитам защо се страхуваш от режима на редактиране?   -  person Leo Natan    schedule 26.04.2014
comment
Но защо не представите изгледа на таблицата в режим на редактиране на първо място? Ще ви кажа защо предлагам това. Без да докоснете частния API, не е възможно да използвате методите за пренареждане на Apple. Ще трябва да внедрите сами или да използвате отворен код (има налични).   -  person Leo Natan    schedule 26.04.2014
comment
Отговорено чрез 100% делегатни методи за изглед на таблица. Използвам този код в приложение на живо.   -  person William Falcon    schedule 27.04.2014
comment
@LeoNatan Мислех, че това също може да работи, но плъзгането по подразбиране (наляво) за изтриване след това е деактивирано.   -  person Aleksander Azizi    schedule 27.04.2014


Отговори (5)


Всъщност правя нещо подобно за едно от моите приложения. Той използва делегатните методи за редактиране на таблици и малко „измамване“ на потребителя. 100% вградена функционалност на Apple.

1 - Настройте таблицата на редактиране (аз го правя във viewWillAppear)

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [self.tableView setEditing:YES];
}

2 - Скриване на иконата на аксесоар по подразбиране:

-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView 
        editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
        //remove any editing accessories (AKA) delete button 
        return UITableViewCellAccessoryNone;
       }

3 - Режимът за редактиране да не се премества всичко надясно (в клетката)

 -(BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath{
return NO;
}

4 - В този момент трябва да можете да плъзгате клетките наоколо, без да изглежда, че е в режим на редактиране. Тук заблуждаваме потребителя. Създайте своя собствена икона за „преместване“ (три реда в регистъра по подразбиране, каквато икона искате във вашия случай) и добавете imageView точно там, където обикновено би се появил в клетката.

5 - Накрая, внедрете метода на делегиране, за да пренаредите реално своя източник на данни.

-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{

    //Get original id
    NSMutableArray *originalArray = [self.model.items objectAtIndex:sourceIndexPath.section];
    Item * original = [originalArray objectAtIndex:sourceIndexPath.row];

    //Get destination id
    NSMutableArray *destinationArray = [self.model.items objectAtIndex:destinationIndexPath.section];
    Item * destination = [destinationArray objectAtIndex:destinationIndexPath.row];

    CGPoint temp = CGPointMake([original.section intValue], [original.row intValue]);

    original.row = destination.row;
    original.section = destination.section;

    destination.section = @(temp.x);
    destination.row = @(temp.y);

    //Put destination value in original array
    [originalArray replaceObjectAtIndex:sourceIndexPath.row withObject:destination];

    //put original value in destination array
    [destinationArray replaceObjectAtIndex:destinationIndexPath.row withObject:original];

    //reload tableview smoothly to reflect changes
    dispatch_async(dispatch_get_main_queue(), ^{
        [UIView transitionWithView:tableView duration:duration options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
            [tableView reloadData];
        } completion:NULL];
    });
 }
person William Falcon    schedule 26.04.2014
comment
Мислех, че това също може да работи, имам абсолютно същия код, но плъзгането по подразбиране (наляво) за изтриване е деактивирано, когато UITableView е в режим на редактиране. Следователно въпросът ми как да активирам движението на реда, без да използвам режим на редактиране. - person Aleksander Azizi; 27.04.2014
comment
Ако трябва да плъзнете наляво, активирайте го, като използвате различна реализация. Вижте моето приложение, което използва това, под списъка с хранителни стоки. Това звучи точно като това, което търсите. appstore.com/mealidea - person William Falcon; 27.04.2014
comment
Моята реализация на плъзгане тук е основна, но ако вградите scrollview в клетката, можете да получите същия ефект като ябълката - person William Falcon; 27.04.2014
comment
Не можете да редактирате таблица, без да я поставите в режим на редактиране! С изключение на разпознаватели на жестове, екранни снимки и ръчно преместване на всяка клетка, това е възможно най-близо - person William Falcon; 27.04.2014
comment
Освен това въпросът ви пита само за движението на реда по подразбиране. В него НЕ се споменава плъзгане за изтриване... - person William Falcon; 27.04.2014
comment
Много добре, добавих раздел към въпроса си, който трябва да е очевиден. Отговорът, който сте дали, използва точно същия код, който вече имам, и причинява проблема с компрометирането на друга функционалност в UITableView, което го прави безполезен в този случай. - person Aleksander Azizi; 27.04.2014
comment
Наградата ще изтече след по-малко от 24 часа. Ако искате да получите пълната сума на наградата (300), моля, променете отговора си, така че да попълва without compromising the default behaviour of the UITableView. Ако критериите не са изпълнени, ще ви бъде дадена само половината (150) и отговорът ви няма да бъде приет. - person Aleksander Azizi; 04.05.2014
comment
Не знам какво имате предвид, без да компрометирате поведението на dedault на UITableView, мисля, че този код вече прави това... - person William Falcon; 04.05.2014
comment
compromising the default behaviour в този случай се отнася до това как UITableView работи и се държи, когато се използва нормално. Чрез добавяне на вашия код към съществуващ напълно работещ UITableView, функции като (включително, но не само) плъзгане за изтриване/премахване са компрометирани (не работят). - person Aleksander Azizi; 04.05.2014
comment
@AleksanderAzizi - любопитно защо не харесахте моето решение, което не компрометира поведението по подразбиране на tableview. - person TomSwift; 05.05.2014

Отговорът на Уилям Фалкън в swift 3

1 - Настройте таблицата на редактиране (аз го правя във viewWillAppear)

override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(animated: animated)
   tableView.setEditing(true, animated: false)
}

2 - Скриване на иконата на аксесоар по подразбиране:

override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
    return .none
}

3 - Режимът за редактиране да не се премества всичко надясно (в клетката)

override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
    return false
}

4 - Не се изисква в swift 3

5 - Пренаредете своя масив

Допълнителна забележка

Ако искате клетките на вашата таблица да могат да се избират, добавете следния код във функцията viewWillAppear().

tableView.allowsSelectionDuringEditing = true
person James Parker    schedule 25.02.2017

За Swift 5... Знам, че въпросът пита БЕЗ редактиране, но ако единствените ви функционални нужди са да

  1. да можете да пренареждате клетки и
  2. да можете да избирате клетки

След това можете да следвате това (източник за пренареждане: https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/reorderable-cells/):

  1. комплект tableView.isEditing = true
  2. комплект tableView.allowsSelectionDuringEditing = true
  3. С UITableViewDataSource добавете следните методи:
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
    return .none
}

func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
    return false
}

Може или не може да се наложи да замените тези функции. Не ми трябваше.

  1. (Мисля) в UITableViewDelegate добавете това:
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    let movedObject = items[sourceIndexPath.row]
    items.remove(at: sourceIndexPath.row)
    items.insert(movedObject, at: destinationIndexPath.row)
}

където items е масивът от източници на данни, който обработва броя на елементите във вашия табличен изглед.

Ключът е стъпка 2, в която най-накрая можете да добавите метода func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath).

person acw    schedule 29.03.2020

Ако не искате да настроите UITableView в режим на редактиране, тогава ще трябва да преоткриете възможността за плъзгане на клетки.

Представям сравнително пълно решение, което позволява на потребителя да мести редове във видимата област на UITableView. Работи независимо дали UITableView е в режим на редактиране или не. Ще трябва да го разширите, ако искате таблицата да се превърта, когато ред се плъзга близо до горната или долната част на видимата област. Вероятно има някои неизправности и крайни случаи, които също ще трябва да откриете.

@implementation TSTableViewController
{
    NSMutableArray* _dataSource;
}

- (void) viewDidLoad
{
    [super viewDidLoad];

    _dataSource = [NSMutableArray new];
    for ( int i = 0 ; i < 10 ; i++ )
    {
        [_dataSource addObject: [NSString stringWithFormat: @"cell %d", i]];
    }
}

- (void) longPress: (UILongPressGestureRecognizer*) lpgr
{
    static NSString* dragCellData = nil;
    static UIView*   dragCellView = nil;
    static NSInteger dragCellOffset = 0;

    // determine the cell we're hovering over, etc:
    CGPoint pt = [lpgr locationInView: self.tableView];
    NSIndexPath* ip = [self.tableView indexPathForRowAtPoint: pt];
    UITableViewCell* cell = [self.tableView cellForRowAtIndexPath: ip];
    CGPoint ptInCell = [lpgr locationInView: cell];

    // where the current placeholder cell is, if any:
    NSInteger placeholderIndex = [_dataSource indexOfObject: @"placeholder"];

    switch ( lpgr.state )
    {
        case UIGestureRecognizerStateBegan:
        {
            // get a snapshot-view of the cell we're going to drag:
            cell.selected = cell.highlighted = NO;
            dragCellView = [cell snapshotViewAfterScreenUpdates: YES];
            dragCellView.clipsToBounds       = NO;
            dragCellView.layer.shadowRadius  = 10;
            dragCellView.layer.shadowColor   = [UIColor blackColor].CGColor;
            dragCellView.layer.masksToBounds = NO;
            dragCellView.frame = [cell convertRect: cell.bounds
                                        toView: self.tableView.window];

            // used to position the dragCellView nicely:
            dragCellOffset = ptInCell.y;

            // the cell will be removed from the view hierarchy by the tableview, so transfer the gesture recognizer to our drag view, and add it into the view hierarchy:
            [dragCellView addGestureRecognizer: lpgr];
            [self.tableView.window addSubview: dragCellView];


            // swap out the cell for a placeholder:
            dragCellData = _dataSource[ip.row];
            _dataSource[ip.row] = @"placeholder";

            [self.tableView reloadRowsAtIndexPaths: @[ip]
                                  withRowAnimation: UITableViewRowAnimationNone];

            break;
        }

        case UIGestureRecognizerStateChanged:
        {
            // where should we move the placeholder to?
            NSInteger insertIndex = ptInCell.y < cell.bounds.size.height / 2.0 ? ip.row : ip.row + 1;
            if ( insertIndex != placeholderIndex )
            {
                // remove from the datasource and the tableview:
                [_dataSource removeObjectAtIndex: placeholderIndex];
                [self.tableView deleteRowsAtIndexPaths: @[ [NSIndexPath indexPathForRow: placeholderIndex inSection: 0] ]
                                      withRowAnimation: UITableViewRowAnimationFade];
                // adjust:
                if ( placeholderIndex < insertIndex )
                {
                    insertIndex--;
                }

                // insert to the datasource and tableview:
                [_dataSource insertObject: @"placeholder"
                                  atIndex: insertIndex];
                [self.tableView insertRowsAtIndexPaths: @[ [NSIndexPath indexPathForRow: insertIndex inSection: 0] ]
                                      withRowAnimation: UITableViewRowAnimationFade];
            }

            // move our dragCellView
            CGRect f = dragCellView.frame;
            f.origin.y = pt.y - dragCellOffset;
            dragCellView.frame = f;

            break;
        }

        case UIGestureRecognizerStateEnded:
        {
            // replace the placeholdercell with the cell we were dragging
            [_dataSource replaceObjectAtIndex: placeholderIndex
                                   withObject: dragCellData];
            [self.tableView reloadRowsAtIndexPaths: @[ [NSIndexPath indexPathForRow: placeholderIndex inSection: 0] ]
                                  withRowAnimation: UITableViewRowAnimationFade];

            // reset state
            [dragCellView removeFromSuperview];
            dragCellView = nil;
            dragCellData = nil;

            break;
        }

        default:
        {
            break;
        }
    }
}

#pragma mark - Table view data source

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

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString* cellData = _dataSource[indexPath.row];
    if ( [cellData isEqualToString: @"placeholder" ] )
    {
        // an empty cell to denote where the "drop" would go
        return [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
                                      reuseIdentifier: nil];
    }

    // a cell...
    UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
                                                   reuseIdentifier: nil];

    // our "fake" move handle & gesture recognizer

    UILongPressGestureRecognizer* lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: @selector( longPress:) ];
    lpgr.minimumPressDuration = 0.3;

    UILabel* dragLabelView = [UILabel new];
    dragLabelView.text = @"☰";
    dragLabelView.userInteractionEnabled = YES;
    [dragLabelView addGestureRecognizer: lpgr];
    [dragLabelView sizeToFit];

    cell.textLabel.text = cellData;

    if ( tableView.isEditing )
    {
        cell.editingAccessoryView = dragLabelView;
    }
    else
    {
        cell.accessoryView = dragLabelView;
    }

    return cell;
}

@end
person TomSwift    schedule 02.05.2014

За да накарате клетките да се движат, трябва да приложите съответните <UITableViewDelegate> методи за преместване и изрично разрешаване на движението на клетката (индексния път). Иконата на индикатора за движение няма да се покаже, защото зависи от режима на редактиране. Докато табличният изглед е в състояние на редактиране, иконата за преместване ще се припокрива с иконата accessoryType. Режимът на редактиране идва по подразбиране с кръглия бутон за изтриване от лявата страна, който можете да скриете така, докато редактирате.

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (self.editing) return UITableViewCellEditingStyleNone;
    return UITableViewCellEditingStyleDelete;
}

По този начин поддържате класа си UITableView с методите UITableViewDelegate чисти, за да разберете, не е необходимо да прилагате някакъв вид самостоятелно направен режим на движение. И можете да използвате свойството за редактиране на tableview, за да разграничите как трябва да изглежда клетката във вашия tableView:cellForRowAtIndexPath: метод. Така че дори изключването на функцията за преместване е по-лесно, като настроите изгледа на таблицата си обратно на editing = NO, иконата за преместване ще изчезне и символът accessoryType ще се покаже отново.

person Ol Sen    schedule 16.01.2020