Przesuń palcem do opcji Usuń i przycisku „Więcej” (jak w aplikacji Mail na iOS 7)

246

Jak utworzyć przycisk „więcej”, gdy użytkownik przesuwa komórkę w widoku tabeli (np. Aplikacja poczty w systemie iOS 7)

Szukałem tych informacji zarówno tutaj, jak i na forum Cocoa Touch, ale nie mogę znaleźć odpowiedzi i mam nadzieję, że ktoś mądrzejszy ode mnie da mi rozwiązanie.

Chciałbym, aby gdy użytkownik przesunął komórkę widoku tabeli, aby wyświetlić więcej niż jeden przycisk edycji (domyślnie jest to przycisk usuwania). W aplikacji Mail na iOS 7 możesz przesunąć palcem, aby usunąć, ale pojawia się przycisk „WIĘCEJ”.

wprowadź opis zdjęcia tutaj

Guy Kahlon
źródło
Aby dodać przycisk „Usuń”, wdrażam następujące dwie funkcje. - (BOOL) tableView: (UITableView *) tableView canEditRowAtIndexPath: (NSIndexPath *) indexPath; - (void) tableView: (UITableView *) tableView commitEditingStyle: (UITableViewCellEditingStyle) editStyle forRowAtIndexPath: (NSIndexPath *) indexPath; I chcę dodać przycisk „Więcej” obok niego.
Guy Kahlon
3
@MonishBansal Bansal Wygląda na to, że ktoś w tym wątku ( devforums.apple.com/message/860459#860459 na forum programistów Apple) opracował własną implementację. Możesz znaleźć projekt, który robi to, co chcesz na GitHub: github.com/daria-kopaliani/DAContextMenuTableViewController
Guy Kahlon
8
@GuyKahlonMatrix dzięki za rozwiązanie działa jak urok. To pytanie jest wynikiem nr 1 w wielu wyszukiwaniach w Google, a ludzie są zmuszeni do wymiany wiedzy za pomocą komentarzy, ponieważ pewien facet uznał, że bardziej pomocne jest zamknięcie pytania i głoszenie demokracji. To miejsce wyraźnie potrzebuje lepszych modów.
Şafak Gezer
2
Jeśli możesz kierować reklamy na iOS 8, moja odpowiedź poniżej będzie taka, jakiej chcesz.
Johnny

Odpowiedzi:

126

Jak wdrożyć

Wygląda na to, że iOS 8 otwiera ten interfejs API. Wskazówki dotyczące takiej funkcjonalności są dostępne w wersji Beta 2.

Aby coś zadziałało, zaimplementuj następujące dwie metody na delegacie swojego UITableView, aby uzyskać pożądany efekt (patrz przykład w gist).

- tableView:editActionsForRowAtIndexPath:
- tableView:commitEditingStyle:forRowAtIndexPath:


Znane problemy

Dokumentacja mówi, że tableView: commitEditingStyle: forRowAtIndexPath to:

„Nie jest wywoływany dla akcji edycji za pomocą UITableViewRowAction - zamiast tego zostanie wywołana procedura obsługi akcji.”

Jednak przeciąganie nie działa bez niego. Nawet jeśli skrót metody jest pusty, to na razie go potrzebuje. Jest to oczywiście błąd w wersji beta 2.


Źródła

https://twitter.com/marksands/status/481642991745265664 https://gist.github.com/marksands/76558707f583dbb8f870

Oryginalna odpowiedź: https://stackoverflow.com/a/24540538/870028


Aktualizacja:

Przykładowy kod z działającym (w Swift): http://dropbox.com/s/0fvxosft2mq2v5m/DeleteRowExampleSwift.zip

Przykładowy kod zawiera tę łatwą do naśladowania metodę w MasterViewController.swift, a za pomocą tej metody można uzyskać zachowanie pokazane na zrzucie ekranu OP:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {

    var moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "More", handler:{action, indexpath in
        println("MORE•ACTION");
    });
    moreRowAction.backgroundColor = UIColor(red: 0.298, green: 0.851, blue: 0.3922, alpha: 1.0);

    var deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler:{action, indexpath in
        println("DELETE•ACTION");
    });

    return [deleteRowAction, moreRowAction];
}
Jasio
źródło
1
Wydaje się to być poprawne, ale w Xcode 6 GM gest machnięcia wydaje się nie działać. Dostęp do funkcji editAction można uzyskać, ustawiając widok tabeli w trybie edycji. Czy ktoś jeszcze uważa, że ​​machnięcie nie działa?
Siegfoult,
@Siegfoult Czy próbowałeś wdrożyć (nawet jeśli pozostawiono puste) tableView: commitEditingStyle: forRowAtIndexPath :?
Johnny
Nie pracuję w celu c .. Ten sam kod, który napisałem. proszę zasugerować kilka wskazówek.
Solid Soft,
@SolidSoft Czy masz przykładowy projekt, na który mógłbym spojrzeć? Mogę w ten sposób lepiej pomóc.
Johnny,
3
Aby odpowiedzieć na mój komentarz. Nazywasz tableView.editing = false( NOw objc) i komórka będzie „blisko”.
Ben Lachman
121

Utworzyłem nową bibliotekę do implementacji przesuwanych przycisków, która obsługuje różne przejścia i przyciski rozwijane, takie jak aplikacja poczty na iOS 8.

https://github.com/MortimerGoro/MGSwipeTableCell

Ta biblioteka jest kompatybilna ze wszystkimi różnymi sposobami tworzenia UITableViewCell i jest testowana na iOS 5, iOS 6, iOS 7 i iOS 8.

Oto próbka niektórych przejść:

Przejście graniczne:

Przejście graniczne

Przejście klipu

Przejście klipu

Przejście 3D:

wprowadź opis zdjęcia tutaj

MortimerGoro
źródło
1
Świetna robota! Byłoby wspaniale mieć wywołania zwrotne w celu dostosowania animacji.
Pacu,
1
@MortimerGoro Miły człowiek pracy. Wygląda dobrze. Próbuję zaimplementować podobny efekt w jednym z moich projektów Android. Powiedz mi, jak mogę to osiągnąć w Androidzie?
Nitesh Kumar,
na iOS 8 + iPad, po prostu nie dostaję machnięcia.
ScorpionKing2k5
To niesamowita biblioteka i bardzo dobre jest to, że wciąż ją obsługuje.
confile
@MortimerGoro, próbowałem z frameworkiem „MGSwipeTableCel”, ale problem polega na tym, że kiedy ponownie ładuję tabelę, przycisk machnięcia jest ukryty. Wszelkie obejścia tego problemu.
Ganesh Guturi
71

Odpowiedź Johnny'ego jest słuszna do wyrażenia opinii. Dodałem to poniżej w celu-c, aby było bardziej zrozumiałe dla początkujących (i tych z nas, którzy odmawiają nauki składni Swift :)

Upewnij się, że zadeklarowałeś opcję uitableviewdelegate i masz następujące metody:

 -(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
 UITableViewRowAction *button = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 1" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
    {
        NSLog(@"Action to perform with Button 1");
    }];
    button.backgroundColor = [UIColor greenColor]; //arbitrary color
    UITableViewRowAction *button2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 2" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
                                    {
                                        NSLog(@"Action to perform with Button2!");
                                    }];
    button2.backgroundColor = [UIColor blueColor]; //arbitrary color

    return @[button, button2]; //array with all the buttons you want. 1,2,3, etc...
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// you need to implement this method too or nothing will work:

}
 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES; //tableview must be editable or nothing will work...
    }
timothykc
źródło
1
ważne, aby wspomnieć canEditRowAtIndexPath
Heckscheibe
Jeśli ponownie załaduję tabelę po przesunięciu komórki, to czy te przyciski przesunięcia są widoczne lub ukryte?
Ganesh Guturi
25

To (raczej śmiesznie) prywatny interfejs API.

Następujące dwie metody są prywatne i wysyłane do delegata UITableView:

-(NSString *)tableView:(UITableView *)tableView titleForSwipeAccessoryButtonForRowAtIndexPath:(NSIndexPath *)indexPath;
-(void)tableView:(UITableView *)tableView swipeAccessoryButtonPushedForRowAtIndexPath:(NSIndexPath *)indexPath;

Są dość oczywiste.

Jonathan.
źródło
4
Apple otworzyło tę funkcję w systemie iOS 8. Zobacz odpowiedź Johnny'ego poniżej.
Siegfoult,
24

Aby poprawić odpowiedź Johnny'ego, można to teraz zrobić za pomocą publicznego interfejsu API w następujący sposób:

func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {

    let moreRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: "More", handler:{action, indexpath in
        print("MORE•ACTION");
    });
    moreRowAction.backgroundColor = UIColor(red: 0.298, green: 0.851, blue: 0.3922, alpha: 1.0);

    let deleteRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: "Delete", handler:{action, indexpath in
        print("DELETE•ACTION");
    });

    return [deleteRowAction, moreRowAction];
}
Arunabh Das
źródło
17

Mam nadzieję, że nie możesz poczekać, aż jabłko da ci to, czego potrzebujesz, prawda? Oto moja opcja.

Utwórz niestandardową komórkę. Mają w sobie dwa uiviewy

1. upper
2. lower

W dolnym widoku dodaj dowolne potrzebne przyciski. Rozgrywaj swoje działania tak, jak inne IBAction. możesz wybrać czas animacji, styl i cokolwiek innego.

teraz dodaj uiswipegesture do górnego widoku i odsłaniaj swój dolny widok gestem machnięcia. Zrobiłem to już wcześniej i jest to dla mnie najprostsza opcja.

Mam nadzieję, że to pomoże.

Deepukjayan
źródło
7

Nie jest to możliwe przy użyciu standardowego zestawu SDK. Istnieją jednak różne rozwiązania innych firm, które mniej więcej naśladują zachowanie w Mail.app. Niektóre z nich (np. MCSwipeTableViewCell , DAContextMenuTableViewController , RMSwipeTableViewCell ) wykrywają przeciągnięcia za pomocą rozpoznawania gestów, niektóre (np. SWTableViewCell ) umieszczają drugi UISScrollView poniżej standardu UITableViewCellScrollView(prywatny podgląd UITableViewCell), a niektóre modyfikują zachowanie UITableViewCellScrollView.

Najbardziej podoba mi się ostatnie podejście, ponieważ obsługa dotykowa jest najbardziej naturalna. W szczególności MSCMoreOptionTableViewCell jest dobry. Twój wybór może się różnić w zależności od konkretnych potrzeb (czy potrzebujesz również panoramy od lewej do prawej, czy potrzebujesz zgodności z iOS 6 itp.). Należy również pamiętać, że większość z tych podejść wiąże się z dużym obciążeniem: mogą łatwo zepsuć się w przyszłej wersji iOS, jeśli Apple wprowadzi zmiany w UITableViewCellhierarchii widoków podrzędnych.

Ortwin Gentz
źródło
7

Kod wersji Swift 3 bez użycia biblioteki:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        tableView.tableFooterView = UIView(frame: CGRect.zero) //Hiding blank cells.
        tableView.separatorInset = UIEdgeInsets.zero
        tableView.dataSource = self
        tableView.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 4
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)

        return cell
    }

    //Enable cell editing methods.
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {

        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

    }

    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {

        let more = UITableViewRowAction(style: .normal, title: "More") { action, index in
            //self.isEditing = false
            print("more button tapped")
        }
        more.backgroundColor = UIColor.lightGray

        let favorite = UITableViewRowAction(style: .normal, title: "Favorite") { action, index in
            //self.isEditing = false
            print("favorite button tapped")
        }
        favorite.backgroundColor = UIColor.orange

        let share = UITableViewRowAction(style: .normal, title: "Share") { action, index in
            //self.isEditing = false
            print("share button tapped")
        }
        share.backgroundColor = UIColor.blue

        return [share, favorite, more]
    }

}
mriaz0011
źródło
6

Musisz podklasę UITableViewCelli metodę podklasy, willTransitionToState:(UITableViewCellStateMask)statektóra jest wywoływana za każdym razem, gdy użytkownik przesuwa komórkę. Te stateflagi pozwoli Ci wiedzieć, jeśli przycisk Usuń pokazuje, i pokazać / ukryć tam swój przycisk Więcej.

Niestety ta metoda nie daje ani szerokości przycisku Usuń, ani czasu animacji. Musisz więc obserwować i zakodować na stałe ramkę i czas animacji przycisku More w kodzie (osobiście uważam, że Apple musi coś z tym zrobić).

Khanh Nguyen
źródło
7
„Osobiście uważam, że Apple musi coś z tym zrobić”. Zgadzam się. Czy już napisałeś im zgłoszenie błędu / prośbę o funkcję?
Tafkadasoh
4

Do szybkiego programowania

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
  if editingStyle == UITableViewCellEditingStyle.Delete {
    deleteModelAt(indexPath.row)
    self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
  }
  else if editingStyle == UITableViewCellEditingStyle.Insert {
    println("insert editing action")
  }
}

func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
  var archiveAction = UITableViewRowAction(style: .Default, title: "Archive",handler: { (action: UITableViewRowAction!, indexPath: NSIndexPath!) in
        // maybe show an action sheet with more options
        self.tableView.setEditing(false, animated: false)
      }
  )
  archiveAction.backgroundColor = UIColor.lightGrayColor()

  var deleteAction = UITableViewRowAction(style: .Normal, title: "Delete",
      handler: { (action: UITableViewRowAction!, indexPath: NSIndexPath!) in
        self.deleteModelAt(indexPath.row)
        self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic);
      }
  );
  deleteAction.backgroundColor = UIColor.redColor()

  return [deleteAction, archiveAction]
}

func deleteModelAt(index: Int) {
  //... delete logic for model
}
Michael Yagudaev
źródło
@bibscy zapraszamy do zaproponowania edycji. Dawno nie używałem szybkiego, więc nie jestem pewien, jaka jest poprawna składnia
Michael Yagudaev
3

TO MOŻE CI POMÓC.

-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
 UITableViewRowAction *button = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 1" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
    {
        NSLog(@"Action to perform with Button 1");
    }];
    button.backgroundColor = [UIColor greenColor]; //arbitrary color
    UITableViewRowAction *button2 = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:@"Button 2" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
                                    {
                                        NSLog(@"Action to perform with Button2!");
                                    }];
    button2.backgroundColor = [UIColor blueColor]; //arbitrary color

    return @[button, button2]; //array with all the buttons you want. 1,2,3, etc...
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
// you need to implement this method too or nothing will work:

}
 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return YES; //tableview must be editable or nothing will work...
    }
santoshIOS
źródło
3

Chciałem dodać tę samą funkcjonalność do mojej aplikacji i po przejściu tak wielu różnych samouczków ( raywenderlich jest najlepszym rozwiązaniem dla majsterkowiczów), dowiedziałem się, że Apple ma swoją UITableViewRowActionklasę, co jest bardzo przydatne.

Musisz zmienić metodę punktu kotłowego Tableview na:

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?  {
    // 1   
    var shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Share" , handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
    // 2
    let shareMenu = UIAlertController(title: nil, message: "Share using", preferredStyle: .ActionSheet)

    let twitterAction = UIAlertAction(title: "Twitter", style: UIAlertActionStyle.Default, handler: nil)
    let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)

    shareMenu.addAction(twitterAction)
    shareMenu.addAction(cancelAction)


    self.presentViewController(shareMenu, animated: true, completion: nil)
    })
    // 3
    var rateAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Rate" , handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
    // 4
    let rateMenu = UIAlertController(title: nil, message: "Rate this App", preferredStyle: .ActionSheet)

    let appRateAction = UIAlertAction(title: "Rate", style: UIAlertActionStyle.Default, handler: nil)
    let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)

    rateMenu.addAction(appRateAction)
    rateMenu.addAction(cancelAction)


    self.presentViewController(rateMenu, animated: true, completion: nil)
    })
    // 5
    return [shareAction,rateAction]
  }

Możesz dowiedzieć się więcej na ten temat na tej stronie . Własna dokumentacja Apple jest naprawdę przydatna do zmiany koloru tła:

Kolor tła przycisku akcji.

Deklaracja CEL-C @ właściwość (nieatomowa, kopia) UIColor * backgroundColor Dyskusja Ta właściwość służy do określania koloru tła dla przycisku. Jeśli nie określisz wartości dla tej właściwości, UIKit przypisuje domyślny kolor na podstawie wartości we właściwości stylu.

Dostępność Dostępne w iOS 8.0 i nowszych.

Jeśli chcesz zmienić czcionkę przycisku, jest to nieco trudniejsze. Widziałem inny post na SO. Aby podać kod, a także link, oto kod, którego tam użyli. Będziesz musiał zmienić wygląd przycisku. Musisz podać konkretne odniesienie do komórki widoku tabeli, w przeciwnym razie zmieniłbyś wygląd przycisku w całej aplikacji (nie chciałem tego, ale możesz, nie wiem :))

Cel C:

+ (void)setupDeleteRowActionStyleForUserCell {

    UIFont *font = [UIFont fontWithName:@"AvenirNext-Regular" size:19];

    NSDictionary *attributes = @{NSFontAttributeName: font,
                      NSForegroundColorAttributeName: [UIColor whiteColor]};

    NSAttributedString *attributedTitle = [[NSAttributedString alloc] initWithString: @"DELETE"
                                                                          attributes: attributes];

    /*
     * We include UIView in the containment hierarchy because there is another button in UserCell that is a direct descendant of UserCell that we don't want this to affect.
     */
    [[UIButton appearanceWhenContainedIn:[UIView class], [UserCell class], nil] setAttributedTitle: attributedTitle
                                                                                          forState: UIControlStateNormal];
}

Szybki:

    //create your attributes however you want to
    let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(UIFont.systemFontSize())] as Dictionary!            

   //Add more view controller types in the []
    UIButton.appearanceWhenContainedInInstancesOfClasses([ViewController.self])

To najłatwiejsza i najbardziej podszywana strumieniem wersja IMHO. Mam nadzieję, że to pomoże.

Aktualizacja: Oto wersja Swift 3.0:

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    var shareAction:UITableViewRowAction = UITableViewRowAction(style: .default, title: "Share", handler: {(action, cellIndexpath) -> Void in
        let shareMenu = UIAlertController(title: nil, message: "Share using", preferredStyle: .actionSheet)

        let twitterAction = UIAlertAction(title: "Twitter", style: .default, handler: nil)
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

        shareMenu.addAction(twitterAction)
        shareMenu.addAction(cancelAction)


        self.present(shareMenu,animated: true, completion: nil)
    })

    var rateAction:UITableViewRowAction = UITableViewRowAction(style: .default, title: "Rate" , handler: {(action, cellIndexpath) -> Void in
        // 4
        let rateMenu = UIAlertController(title: nil, message: "Rate this App", preferredStyle: .actionSheet)

        let appRateAction = UIAlertAction(title: "Rate", style: .default, handler: nil)
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

        rateMenu.addAction(appRateAction)
        rateMenu.addAction(cancelAction)


        self.present(rateMenu, animated: true, completion: nil)
    })
    // 5
    return [shareAction,rateAction]
}
Septronic
źródło
1
Dzięki za odpowiedź, jestem pewien, że pomoże wielu programistom. Tak, masz rację, właściwie Apple zapewnia to rozwiązanie z iOS 8. Ale niestety to natywne rozwiązanie nie zapewnia pełnej funkcjonalności. Na przykład w aplikacji Apple Mail masz przyciski z dwóch stron (jeden przycisk z lewej strony i trzy z prawej strony) z bieżącym interfejsem API firmy Apple nie można dodawać przycisków z obu stron, a także obecny interfejs API nie obsługuje domyślna akcja, gdy użytkownik przeciąga palcem po obu stronach. Najlepszym rozwiązaniem na razie IMHO jest open source MGSwipeTableCell.
Guy Kahlon
@GuyKahlon tak, masz absolutną rację, jeśli chodzi o problem z przesunięciem lewej i prawej strony, i zgadzam się, że dla większej personalizacji MGSwipeTableCell jest najlepszy. Własny Apple nie jest najbardziej wyrafinowaną opcją, ale uważam, że jest najbardziej prosty w przypadku prostych zadań.
Septronic
@Septronic Czy możesz zaktualizować kod do Swift 3? shareMenu.nie stosuje addActionmetody. Dzięki
bibscy
@bibscy Dodałem szybką wersję. Czy potrzebujesz również tego atrybutu? sharemenu to tylko UIAlertController, więc powinien podjąć działanie. Spróbuj i daj mi znać, jeśli będzie szczęście :)
Septronic
3

Rzeczywista odpowiedź Swift 3

To jest TYLKO funkcja, której potrzebujesz. Nie potrzebujesz funkcji CanEdit ani CommitEditingStyle do akcji niestandardowych.

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    let action1 = UITableViewRowAction(style: .default, title: "Action1", handler: {
        (action, indexPath) in
        print("Action1")
    })
    action1.backgroundColor = UIColor.lightGray
    let action2 = UITableViewRowAction(style: .default, title: "Action2", handler: {
        (action, indexPath) in
        print("Action2")
    })
    return [action1, action2]
}
William T.
źródło
3

Od iOS 11 jest to publicznie dostępne w UITableViewDelegate. Oto przykładowy kod:

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
    UIContextualAction *delete = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive
                                                                         title:@"DELETE"
                                                                       handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
                                                                           NSLog(@"index path of delete: %@", indexPath);
                                                                           completionHandler(YES);
                                                                       }];

    UIContextualAction *rename = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal
                                                                         title:@"RENAME"
                                                                       handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
                                                                           NSLog(@"index path of rename: %@", indexPath);
                                                                           completionHandler(YES);
                                                                       }];

    UISwipeActionsConfiguration *swipeActionConfig = [UISwipeActionsConfiguration configurationWithActions:@[rename, delete]];
    swipeActionConfig.performsFirstActionWithFullSwipe = NO;

    return swipeActionConfig;
}

Także dostępny:

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath;

Dokumenty: https://developer.apple.com/documentation/uikit/uitableviewdelegate/2902367-tableview?language=objc

chwytak
źródło
3

Swift 4 i iOs 11+

@available(iOS 11.0, *)
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

    let delete = UIContextualAction(style: .destructive, title: "Delete") { _, _, handler in

        handler(true)
        // handle deletion here
    }

    let more = UIContextualAction(style: .normal, title: "More") { _, _, handler in

        handler(true)
        // handle more here
    }

    return UISwipeActionsConfiguration(actions: [delete, more])
}
Andrey
źródło
2

Użyłem tableViewCell, aby wyświetlić wiele danych, po przesunięciu palcem () od prawej do lewej na komórce pojawią się dwa przyciski Zatwierdź I odrzuć, są dwie metody, pierwsza to ApproveFunc, która przyjmuje jeden argument, a druga to RejectFunc, która również bierze jeden argument.

wprowadź opis zdjęcia tutaj

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
        let Approve = UITableViewRowAction(style: .normal, title: "Approve") { action, index in

            self.ApproveFunc(indexPath: indexPath)
        }
        Approve.backgroundColor = .green

        let Reject = UITableViewRowAction(style: .normal, title: "Reject") { action, index in

            self.rejectFunc(indexPath: indexPath)
        }
        Reject.backgroundColor = .red



        return [Reject, Approve]
    }

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

    func ApproveFunc(indexPath: IndexPath) {
        print(indexPath.row)
    }
    func rejectFunc(indexPath: IndexPath) {
        print(indexPath.row)
    }
Akbar Khan
źródło
Czy możesz dodać jakieś wyjaśnienie do swojej odpowiedzi, aby czytelnik mógł się z niej uczyć?
Nico Haase,
Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie byłoby znacznie poprawić swoją długoterminową wartość pokazując dlaczego jest to dobre rozwiązanie problemu, a byłoby bardziej użyteczne dla czytelników przyszłości z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Tim Diekmann,
1

Oto nieco delikatny sposób, który nie wymaga prywatnych interfejsów API ani budowy własnego systemu. Zabezpieczasz swoje zakłady, że Apple tego nie przełamie i mam nadzieję, że wydadzą interfejs API, który możesz zastąpić kilkoma liniami kodu.

  1. KVO self.contentView.superview.layer.sublayer. Zrób to w init. To jest warstwa UIScrollView. Nie możesz KVO „poddawać”.
  2. Gdy podviews zmienia się, znajdź widok potwierdzenia usunięcia w scrollview.subviews. Odbywa się to w oddzwanianiu obserwowanym.
  3. Podwój rozmiar tego widoku i dodaj UIButton po lewej stronie jego jedynego widoku. Odbywa się to również w przypadku oddzwaniania. Jedynym widokiem podrzędnym widoku potwierdzenia usunięcia jest przycisk usuwania.
  4. (opcjonalnie) Zdarzenie UIButton powinno wyszukiwać self.superview, aż znajdzie UITableView, a następnie wywołuje utworzone źródło danych lub metodę delegowania, na przykład tableView: commitCustomEditingStyle: forRowAtIndexPath :. Możesz znaleźć indexPath komórki, używając [tableView indexPathForCell: self].

Wymaga to również wdrożenia standardowego wywołania zwrotnego delegowania edycji widoku tabeli.

static char kObserveContext = 0;

@implementation KZTableViewCell {
    UIScrollView *_contentScrollView;
    UIView *_confirmationView;
    UIButton *_editButton;
    UIButton *_deleteButton;
}

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        _contentScrollView = (id)self.contentView.superview;

        [_contentScrollView.layer addObserver:self
             forKeyPath:@"sublayers"
                options:0
                context:&kObserveContext];

        _editButton = [UIButton new];
        _editButton.backgroundColor = [UIColor lightGrayColor];
        [_editButton setTitle:@"Edit" forState:UIControlStateNormal];
        [_editButton addTarget:self
                        action:@selector(_editTap)
              forControlEvents:UIControlEventTouchUpInside];

    }
    return self;
}

-(void)dealloc {
    [_contentScrollView.layer removeObserver:self forKeyPath:@"sublayers" context:&kObserveContext];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if(context != &kObserveContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }
    if(object == _contentScrollView.layer) {
        for(UIView * view in _contentScrollView.subviews) {
            if([NSStringFromClass(view.class) hasSuffix:@"ConfirmationView"]) {
                _confirmationView = view;
                _deleteButton = [view.subviews objectAtIndex:0];
                CGRect frame = _confirmationView.frame;
                CGRect frame2 = frame;
                frame.origin.x -= frame.size.width;
                frame.size.width *= 2;
                _confirmationView.frame = frame;

                frame2.origin = CGPointZero;
                _editButton.frame = frame2;
                frame2.origin.x += frame2.size.width;
                _deleteButton.frame = frame2;
                [_confirmationView addSubview:_editButton];
                break;
            }
        }
        return;
    }
}

-(void)_editTap {
    UITableView *tv = (id)self.superview;
    while(tv && ![tv isKindOfClass:[UITableView class]]) {
        tv = (id)tv.superview;
    }
    id<UITableViewDelegate> delegate = tv.delegate;
    if([delegate respondsToSelector:@selector(tableView:editTappedForRowWithIndexPath:)]) {
        NSIndexPath *ip = [tv indexPathForCell:self];
        // define this in your own protocol
        [delegate tableView:tv editTappedForRowWithIndexPath:ip];
    }
}
@end
xtravar
źródło
Bardzo się cieszę, jeśli możesz podać przykładowy kod, dzięki
Guy Kahlon
Gotowe. Może mieć błąd lub dwa, ale masz sens.
xtravar
1

Zadziwiająca biblioteka o nazwie SwipeCellKitpowinna zyskać więcej uznania. Moim zdaniem jest fajniej niż MGSwipeTableCell. Ten ostatni nie replikuje całkowicie zachowania komórek aplikacji Mail, podczas gdy SwipeCellKitrobi. Spójrz

Andrey Chernukha
źródło
Próbowałem SwipeCellKiti byłem pod wrażeniem ... aż dostałem jeden z tych wyjątków, ponieważ liczba wierszy przed aktualizacją widoku tabeli nie była taka sama jak po aktualizacji +/- zmiana wierszy. Rzecz w tym, że nigdy nie zmieniłem mojego zestawu danych. Więc jeśli to nie martwi, nie wiem co jest. Więc postanowiłem nie używać go i po prostu użyłem nowych metod UITableViewDelegate. Jeśli potrzebujesz większej personalizacji, zawsze możesz zastąpićwillBeginEditingRowAt: ....
podkowa 7
@ horseshoe7 to dziwne. Nigdy nie spotkałem żadnego wyjątku podczas używania SwipeCellKit. W końcu jaki rodzaj relacji może mieć komórka do takiego wyjątku, który zdarza się ze względu na zmiany źródła danych?
Andrey Chernukha
1

Szybki 4

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let delete = UIContextualAction(style: .destructive, title: "Delete") { (action, sourceView, completionHandler) in
        print("index path of delete: \(indexPath)")
        completionHandler(true)
    }
    let rename = UIContextualAction(style: .normal, title: "Edit") { (action, sourceView, completionHandler) in
        print("index path of edit: \(indexPath)")
        completionHandler(true)
    }
    let swipeActionConfig = UISwipeActionsConfiguration(actions: [rename, delete])
    swipeActionConfig.performsFirstActionWithFullSwipe = false
    return swipeActionConfig
}
Aplikacja Vini
źródło
jaki jest widok źródła w twoich kodach? czy to ikona czy obraz?
Saeed Rahmatolahi
1
@ SaeedRahmatolahi, sourceViewto „Widok, w którym akcja została wyświetlona”. Aby uzyskać więcej informacji, wyszukaj „UIContextualAction.Handler”.
Mark Moeykens,
0

Oto jedno proste rozwiązanie. Jest w stanie wyświetlać i ukrywać niestandardowe UIView w UITableViewCell. Logika wyświetlania jest zawarta w klasie rozszerzonej z UITableViewCell, BaseTableViewCell.

BaseTableViewCell.h

#import <UIKit/UIKit.h>

@interface BaseTableViewCell : UITableViewCell

@property(nonatomic,strong)UIView* customView;

-(void)showCustomView;

-(void)hideCustomView;

@end

BaseTableViewCell.M

#import "BaseTableViewCell.h"

@interface BaseTableViewCell()
{
    BOOL _isCustomViewVisible;
}

@end

@implementation BaseTableViewCell

- (void)awakeFromNib {
    // Initialization code
}

-(void)prepareForReuse
{
    self.customView = nil;
    _isCustomViewVisible = NO;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}

-(void)showCustomView
{
    if(nil != self.customView)
    {
        if(!_isCustomViewVisible)
        {
            _isCustomViewVisible = YES;

            if(!self.customView.superview)
            {
                CGRect frame = self.customView.frame;
                frame.origin.x = self.contentView.frame.size.width;
                self.customView.frame = frame;
                [self.customView willMoveToSuperview:self.contentView];
                [self.contentView addSubview:self.customView];
                [self.customView didMoveToSuperview];
            }

            __weak BaseTableViewCell* blockSelf = self;
            [UIView animateWithDuration:.5 animations:^(){

                for(UIView* view in blockSelf.contentView.subviews)
                {
                    CGRect frame = view.frame;
                    frame.origin.x = frame.origin.x - blockSelf.customView.frame.size.width;
                    view.frame = frame;
                }
            }];
        }
    }
}

-(void)hideCustomView
{
    if(nil != self.customView)
    {
        if(_isCustomViewVisible)
        {
            __weak BaseTableViewCell* blockSelf = self;
            _isCustomViewVisible = NO;
            [UIView animateWithDuration:.5 animations:^(){
                for(UIView* view in blockSelf.contentView.subviews)
                {
                    CGRect frame = view.frame;
                    frame.origin.x = frame.origin.x + blockSelf.customView.frame.size.width;
                    view.frame = frame;
                }
            }];
        }
    }
}

@end

Aby uzyskać tę funkcjonalność, po prostu rozszerz komórkę widoku tabeli z BaseTableViewCell.

Następnie Inside UIViewController, który implementuje UITableViewDelegate, tworzy dwa urządzenia rozpoznające gesty do obsługi przesunięć w lewo i w prawo.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self.tableView registerNib:[UINib nibWithNibName:CUSTOM_CELL_NIB_NAME bundle:nil] forCellReuseIdentifier:CUSTOM_CELL_ID];

    UISwipeGestureRecognizer* leftSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleLeftSwipe:)];
    leftSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
    [self.tableView addGestureRecognizer:leftSwipeRecognizer];

    UISwipeGestureRecognizer* rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightSwipe:)];
    rightSwipeRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
    [self.tableView addGestureRecognizer:rightSwipeRecognizer];
}

Następnie dodaj dwa uchwyty machnięcia

- (void)handleLeftSwipe:(UISwipeGestureRecognizer*)recognizer
{
    CGPoint point = [recognizer locationInView:self.tableView];
    NSIndexPath* index = [self.tableView indexPathForRowAtPoint:point];

    UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:index];

    if([cell respondsToSelector:@selector(showCustomView)])
    {
        [cell performSelector:@selector(showCustomView)];
    }
}

- (void)handleRightSwipe:(UISwipeGestureRecognizer*)recognizer
{
    CGPoint point = [recognizer locationInView:self.tableView];
    NSIndexPath* index = [self.tableView indexPathForRowAtPoint:point];

    UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:index];

    if([cell respondsToSelector:@selector(hideCustomView)])
    {
        [cell performSelector:@selector(hideCustomView)];
    }
}

Teraz, w komórce cellForRowAtIndexPath, z UITableViewDelegate, możesz utworzyć niestandardowy UIView i dołączyć go do komórki usuwanej z kolejki.

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

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"CellCustomView"
                                                      owner:nil
                                                    options:nil];

    CellCustomView* customView = (CellCustomView*)[ nibViews objectAtIndex: 0];

    cell.customView = customView;

    return cell;
}

Oczywiście ten sposób ładowania niestandardowego UIView jest właśnie dla tego przykładu. Zarządzaj nim tak, jak chcesz.

slobodany
źródło