Jak w scenorysie utworzyć niestandardową komórkę do użytku z wieloma kontrolerami?

216

Próbuję używać scenariuszy w aplikacji, nad którą pracuję. W aplikacji znajdują się Listy i Użytkownicy, a każdy z nich zawiera kolekcję innych (członków listy, list należących do użytkownika). Tak więc odpowiednio mam ListCelli UserCellzajęcia. Celem jest, aby były one ponownie używane w aplikacji (tj. W dowolnym moim kontrolerze widoku tabeli).

Właśnie tam napotykam problem.

Jak utworzyć niestandardową komórkę widoku tabeli w serii ujęć, której można ponownie użyć w dowolnym kontrolerze widoku?

Oto konkretne rzeczy, których do tej pory próbowałem.

  • W kontrolerze nr 1 dodałem prototypową komórkę, ustawiłem klasę na moją UITableViewCellpodklasę, ustawiłem identyfikator ponownego użycia, dodałem etykiety i podłączyłem je do gniazdek klasy. W kontrolerze nr 2 dodano pustą komórkę prototypową, ustaw ją na tę samą klasę i ponownie użyj identyfikatora jak poprzednio. Po uruchomieniu etykiety nigdy nie pojawiają się, gdy komórki są wyświetlane w kontrolerze nr 2. Działa dobrze w kontrolerze nr 1.

  • Zaprojektowano każdy typ komórki w innym NIB i podłączono do odpowiedniej klasy komórek. W serii ujęć dodano pustą prototypową komórkę i ustawiono jej klasę i ponownie wykorzystuję identyfikator, aby odnosił się do mojej klasy komórek. W viewDidLoadmetodach kontrolerów zarejestrowano te pliki NIB dla identyfikatora ponownego użycia. Pokazane komórki w obu kontrolerach były puste jak prototyp.

  • Przechowywane prototypy w obu kontrolerach były puste i ustawiały klasę oraz ponownie używały identyfikatora do mojej klasy komórek. Zbudował interfejs użytkownika komórek całkowicie w kodzie. Komórki działają idealnie we wszystkich kontrolerach.

W drugim przypadku podejrzewam, że prototyp zawsze zastępuje NIB, a gdybym zabił prototypowe komórki, rejestracja mojego NIB dla identyfikatora ponownego użycia zadziałałaby. Ale wtedy nie byłbym w stanie ustawić sekwensów z komórek do innych ramek, co jest naprawdę celem używania scenariuszy.

Na koniec dnia chcę dwóch rzeczy: uporządkować przepływy oparte na widoku tabeli w serii ujęć i zdefiniować układ komórek wizualnie, a nie w kodzie. Do tej pory nie widzę, jak je zdobyć.

Cliff W
źródło

Odpowiedzi:

205

Jak rozumiem, chcesz:

  1. Zaprojektuj komórkę w IB, która może być używana w wielu scenach scenariuszy.
  2. Skonfiguruj unikalne sekwencje scenariuszy z tej komórki, w zależności od sceny, w której znajduje się komórka.

Niestety obecnie nie ma na to sposobu. Aby zrozumieć, dlaczego poprzednie próby nie zadziałały, musisz dowiedzieć się więcej o tym, jak działają scenorysy i prototypowe komórki widoku tabeli. (Jeśli nie obchodzi Cię, dlaczego te inne próby nie zadziałały, możesz odejść teraz. Nie mam dla ciebie żadnych magicznych obejść poza sugerowaniem zgłoszenia błędu).

Scenorys to w zasadzie niewiele więcej niż zbiór plików .xib. Po załadowaniu kontrolera widoku tabeli, który ma niektóre prototypowe komórki poza scenorysem, oto co się dzieje:

  • Każda prototypowa komórka jest tak naprawdę własną osadzoną mini-stalówką. Tak więc, gdy kontroler widoku tabeli ładuje się, przechodzi przez każdą z końcówek i wywołań prototypowej komórki -[UITableView registerNib:forCellReuseIdentifier:].
  • Widok tabeli prosi kontroler o komórki.
  • Prawdopodobnie dzwonisz -[UITableView dequeueReusableCellWithIdentifier:]
  • Żądanie komórki z danym identyfikatorem ponownego użycia sprawdza, czy ma zarejestrowaną stalówkę. Jeśli tak, tworzy instancję tej komórki. Składa się z następujących kroków:

    1. Spójrz na klasę komórki, jak zdefiniowano w stalówce komórki. Zadzwoń [[CellClass alloc] initWithCoder:].
    2. -initWithCoder:Metoda przechodzi i dodaje subviews i zestawów właściwości, które zostały określone w stalówki. ( IBOutletprawdopodobnie też się tu wciągam, chociaż tego nie testowałem; może się zdarzyć w -awakeFromNib)
  • Skonfigurujesz komórkę, jak chcesz.

Należy tutaj zauważyć, że istnieje różnica między klasą komórki a jej wyglądem . Można utworzyć dwie oddzielne prototypowe komórki tej samej klasy, ale z ich podrzędnymi układami zupełnie inaczej. W rzeczywistości, jeśli użyjesz domyślnych UITableViewCellstylów, właśnie tak się dzieje. Na przykład styl „Domyślny” i styl „Podtytuł” ​​są reprezentowane przez tę samą UITableViewCellklasę.

Jest to ważne : klasa komórki nie ma korelacji jeden-do-jednego z określoną hierarchią widoków . Hierarchia widoków jest całkowicie zdeterminowana tym, co znajduje się w komórce prototypowej zarejestrowanej w tym konkretnym kontrolerze.

Należy również zauważyć, że identyfikator ponownego wykorzystania komórki nie został zarejestrowany w niektórych globalnych aptekach komórkowych. Identyfikator ponownego wykorzystania jest używany tylko w kontekście pojedynczej UITableViewinstancji.


Biorąc pod uwagę te informacje, spójrzmy na to, co wydarzyło się podczas powyższych prób.

W kontrolerze nr 1 dodano komórkę prototypową, ustawiłem klasę na moją podklasę UITableViewCell, ustawiłem identyfikator ponownego użycia, dodałem etykiety i podłączyłem je do gniazdek klasy. W kontrolerze nr 2 dodano pustą komórkę prototypową, ustaw ją na tę samą klasę i ponownie użyj identyfikatora jak poprzednio. Po uruchomieniu etykiety nigdy nie pojawiają się, gdy komórki są wyświetlane w kontrolerze nr 2. Działa dobrze w kontrolerze nr 1.

Jest to oczekiwane. Chociaż obie komórki miały tę samą klasę, hierarchia widoków przekazana do komórki w kontrolerze nr 2 była całkowicie pozbawiona widoków podrzędnych. Masz więc pustą komórkę, co dokładnie umieściłeś w prototypie.

Zaprojektowano każdy typ komórki w innym NIB i podłączono do odpowiedniej klasy komórek. W serii ujęć dodano pustą prototypową komórkę i ustawiono jej klasę i ponownie wykorzystuję identyfikator, aby odnosił się do mojej klasy komórek. W metodach viewDidLoad kontrolerów zarejestrowano te pliki NIB dla identyfikatora ponownego użycia. Pokazane komórki w obu kontrolerach były puste jak prototyp.

Ponownie jest to oczekiwane. Identyfikator ponownego użycia nie jest współdzielony między scenami scenicznymi lub stalówkami, więc fakt, że wszystkie te odrębne komórki miały ten sam identyfikator ponownego użycia, nie miał znaczenia. Komórka, którą odzyskasz z widoku tabeli, będzie miała wygląd pasujący do prototypowej komórki w tej scenie scenorysu.

To rozwiązanie było jednak bliskie. Jak zauważyłeś, możesz po prostu programowo wywołać -[UITableView registerNib:forCellReuseIdentifier:], przekazując UINibkomórkę zawierającą, a otrzymasz tę samą komórkę. (Nie dzieje się tak, ponieważ prototyp „przesłonił” stalówkę; po prostu nie zarejestrowałeś stalówki w widoku tabeli, więc nadal patrzył na stalówkę osadzoną w serii ujęć.) Niestety, w tym podejściu jest wada - nie ma sposobu, aby podłączyć sekwencje scenorysowe do komórki w samodzielnej stalówce.

Przechowywane prototypy w obu kontrolerach były puste i ustawiały klasę oraz ponownie używały identyfikatora do mojej klasy komórek. Zbudował interfejs użytkownika komórek całkowicie w kodzie. Komórki działają idealnie we wszystkich kontrolerach.

Naturalnie. Mamy nadzieję, że nie jest to zaskakujące.


Dlatego to nie zadziałało. Możesz zaprojektować swoje komórki w samodzielnych stalówkach i używać ich w wielu scenach scenariuszy; po prostu nie można obecnie podłączyć sekwencji z serii ujęć do tych komórek. Mam jednak nadzieję, że nauczyłeś się czegoś w trakcie czytania.

BJ Homer
źródło
Ach, rozumiem. Przywołałeś moje nieporozumienie - hierarchia widoków jest całkowicie niezależna od mojej klasy. Oczywiste z perspektywy czasu! Dzięki za świetną odpowiedź.
Cliff W
Wydaje się, że nie jest już niemożliwe: stackoverflow.com/questions/8574188/...
Rich Apodaca
7
@RichApodaca Wspomniałem o tym rozwiązaniu w mojej odpowiedzi. Ale nie ma go w serii ujęć; jest w osobnej stalówce. Więc nie można było łączyć segu ani robić innych rzeczy związanych z scenopisami. Dlatego też nie odnosi się on całkowicie do pierwszego pytania.
BJ Homer,
Począwszy od XCode8, następujące obejście wydaje się działać, jeśli chcesz tylko rozwiązanie serii ujęć. Krok 1) Utwórz prototypową komórkę w widoku tabeli w ViewController # 1 i powiąż z niestandardową klasą UITableViewCell Krok 2) Skopiuj / Wklej tę komórkę w widoku tabeli ViewController # 2. Z czasem będziesz musiał pamiętać, aby ręcznie propagować aktualizacje kopii komórki, usuwając kopie wykonane w serii ujęć i wklejając z powrotem w zaktualizowanym prototypie.
jengelsma
Świetna odpowiedź, mam jednak następujące pytanie:> „Scenariusz to w zasadzie niewiele więcej niż zbiór plików .xib”, jeśli tak, to dlaczego tak trudno osadzić xib w scenorys?
willcwf
58

Mimo świetnej odpowiedzi BJ Homera czuję, że mam rozwiązanie. Jeśli chodzi o moje testy, działa.

Koncepcja: Utwórz niestandardową klasę dla komórki xib. Tam możesz poczekać na zdarzenie dotykowe i programowo wykonać segue. Teraz potrzebujemy tylko odniesienia do kontrolera wykonującego Segue. Moim rozwiązaniem jest ustawienie go tableView:cellForRowAtIndexPath:.

Przykład

mam DetailedTaskCell.xib komórkę zawierającą tabelę, której chciałbym użyć w wielu widokach tabeli:

DetailTaskCell.xib

Istnieje klasa niestandardowa TaskGuessTableCell komórka dla tej komórki:

wprowadź opis zdjęcia tutaj

Tutaj dzieje się magia.

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

Mam wiele segues ale wszystkie one mają taką samą nazwę: "FinishedTask". Jeśli musisz być elastyczny, proponuję dodać inną właściwość.

ViewController wygląda następująco:

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TaskGuessTableCell *cell;

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

Mogą istnieć bardziej eleganckie sposoby osiągnięcia tego samego, ale - to działa! :)

ericteubert
źródło
1
Dzięki, wdrażam to podejście w moim projekcie. Zamiast tego możesz zastąpić tę metodę, aby nie trzeba było pobierać indexPath i samodzielnie wybierać wiersz: - (void) setSelected: (BOOL) zaznaczono animowany: (BOOL) animowany {[super setSelected: zaznaczono animowany: animowany]; if (wybrano) [self.controller performSegueWithIdentifier: self.segue sender: self]; } Myślałem, że super wybierze komórkę podczas wywoływania [super touchesEnded: touches withEvent: event] ;. Czy wiesz, kiedy jest wybrany, jeśli go nie ma?
thejaz
9
Zauważ, że dzięki temu rozwiązaniu wywołujesz segue za każdym razem, gdy dotyk kończy się w komórce. Obejmuje to, jeśli po prostu przewijasz komórkę, a nie próbujesz jej zaznaczyć. Możesz mieć więcej szczęścia, jeśli przesłonisz -setSelected:komórkę i uruchomisz segue tylko przy przejściu z NOna YES.
BJ Homer
Mam więcej szczęścia setSelected:, BJ. Dzięki. Rzeczywiście, jest to nieeleganckie rozwiązanie ( wydaje się niewłaściwe), ale jednocześnie działa, więc używam go, dopóki nie zostanie to naprawione (lub coś się zmieni w sądzie Apple).
Ben Kreeger,
16

Szukałem tego i znalazłem odpowiedź Richarda Venable. Mi to pasuje.

iOS 5 zawiera nową metodę na UITableView: registerNib: forCellReuseIdentifier:

Aby go użyć, umieść UITableViewCell w stalówce. To musi być jedyny obiekt główny w stalówce.

Możesz zarejestrować stalówkę po załadowaniu tableView, a następnie po wywołaniu dequeueReusableCellWithIdentifier: z identyfikatorem komórki wyciągnie ją ze stalówki, tak jakbyś użył prototypowej komórki Storyboard.

Odrakir
źródło
10

BJ Homer doskonale wyjaśnił, co się dzieje.

Z praktycznego punktu widzenia dodałbym, że biorąc pod uwagę, że nie można mieć komórek jako xib ORAZ łączyć sekwencje, najlepiej wybrać komórkę jako xib - przejścia są o wiele łatwiejsze w utrzymaniu niż układy komórek i właściwości w wielu miejscach , a twoje klucze prawdopodobnie będą się różnić od różnych kontrolerów. Możesz zdefiniować segue bezpośrednio z kontrolera widoku tabeli do następnego kontrolera i wykonać go w kodzie. .

Kolejna uwaga jest taka, że ​​posiadanie komórki jako osobnego pliku xib uniemożliwia podłączenie jakichkolwiek działań itp. Bezpośrednio do kontrolera widoku tabeli (i tak tego nie opracowałem - nie można zdefiniować właściciela pliku jako niczego znaczącego ). Pracuję nad tym, definiując protokół, który powinien być zgodny z kontrolerem widoku tabeli komórki, i dodając kontroler jako słabą właściwość, podobną do delegata, w cellForRowAtIndexPath.

jrturton
źródło
10

Szybki 3

BJ Homer podał doskonałe wyjaśnienie. Pomaga mi zrozumieć tę koncepcję. Do make a custom cell reusable in storyboard, które można wykorzystać w dowolnym TableViewController, do którego musimy mix the Storyboard and xibpodejść. Załóżmy, że mamy komórkę o nazwie, CustomCellktóra ma być używana w TableViewControllerOnei TableViewControllerTwo. Robię to krokami.
1. Plik> Nowy> Kliknij Plik> Wybierz klasę dotykową Cocoa> kliknij Dalej> Podaj nazwę swojej klasy (na przykład CustomCell)> wybierz Podklasę jako UITableVieCell> Zaznacz pole wyboru również utwórz plik XIB i naciśnij Dalej.
2. Dostosuj komórkę, jak chcesz i ustaw identyfikator w inspektorze atrybutów dla komórki, tutaj ustawimy jako CellIdentifier. Ten identyfikator zostanie użyty w twoim ViewController do identyfikacji i ponownego wykorzystania komórki.
3. Teraz musimy tylkoregister this cell w naszym ViewControllerviewDidLoad . Nie ma potrzeby żadnej metody inicjalizacji.
4. Teraz możemy użyć tej niestandardowej komórki w dowolnym widoku tabeli.

W TableViewControllerOne

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}
Kunal Kumar
źródło
5

Znalazłem sposób na załadowanie komórki dla tego samego VC, nie testowany dla segues. Może to być obejście problemu polegające na utworzeniu komórki w oddzielnej stalówce

Powiedzmy, że masz jeden VC i 2 tabele i chcesz zaprojektować komórkę w serii ujęć i użyć jej w obu tabelach.

(np. tabela i pole wyszukiwania z UISearchController z tabelą wyników i chcesz użyć tej samej komórki w obu)

Gdy kontroler poprosi o komórkę, zrób to:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * identifier = @"CELL_ID";

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

A tutaj masz swoją komórkę z scenorysu

João Nunes
źródło
Próbowałem tego i wydaje się, że działa, JEDNAK komórki nigdy nie są ponownie używane. System zawsze tworzy i zwalnia nowe komórki za każdym razem.
GilroyKilroy
Jest to ta sama rekomendacja, którą można znaleźć w Dodawanie paska wyszukiwania do widoku tabeli za pomocą scenariuszy . Jeśli jesteś zainteresowany, znajdziesz tam bardziej szczegółowe wyjaśnienie tego rozwiązania (wyszukaj tableView:cellForRowAtIndexPath:).
Rozsądny
ale to mniej tekstu i odpowiada na pytanie
João Nunes