Jak zdobyć UITableView z UITableViewCell?

100

Mam element, UITableViewCellktóry jest powiązany z obiektem i muszę stwierdzić, czy komórka jest widoczna. Z przeprowadzonych przeze mnie badań UITableViewwynika, że muszę w jakiś sposób uzyskać dostęp do tego, który go zawiera (stamtąd jest kilka sposobów, aby sprawdzić, czy jest widoczny). Zastanawiam się więc, czy UITableViewCellma wskaźnik do UITableViewkomórki, czy też istniał inny sposób na pobranie wskaźnika z komórki?

sinθ
źródło
2
Jaki jest tego cel?
max_
[cell superView]może?
Chris Loonam
6
Warto wyjaśnić, dlaczego uważasz, że tego potrzebujesz - ponieważ może to oznaczać zły projekt, ponieważ nie mogę wymyślić wielu uzasadnionych powodów, dla których komórka mogłaby wiedzieć, czy jest na ekranie, czy nie.
Paul.s
@ Paul.s Mamy na obrazie w komórce narzędzie do rozpoznawania gestów, które po dotknięciu komórki otwiera kolejny widok nakładki, w stylu popover, który powinien nakładać tyle komórek, ile potrzeba do prawidłowego wyświetlenia. Aby to działało, potrzebuje widoku TableView lub innego nadanego mu widoku do wyświetlenia. Niezbyt zadowolony z rozwiązań, ale aby uzyskać pożądany efekt, uzyskanie UITableView z UITableViewCell jest najlepsze, jakie wymyśliliśmy.
chadbag
1
@chadbag nie martw się, mam nadzieję, że podałem pomysł komuś innemu z tym samym problemem.
PJ_Finnegan

Odpowiedzi:

153

Aby uniknąć sprawdzania wersji systemu iOS, iteracyjnie chodź po nadzorze z widoku komórki, aż zostanie znaleziony UITableView:

id view = [tableViewCellInstance superview];

while (view && [view isKindOfClass:[UITableView class]] == NO) {
    view = [view superview]; 
}

UITableView *tableView = (UITableView *)view;
Muhammad Idris
źródło
1
Dzięki. Wydawałoby się, że zmieniło się to po raz kolejny w iOS 8 i to ładnie dba o wszystkie wersje.
djskinner
2
Zalecałbym dodanie słabego odniesienia do widoku tabeli w komórce, aby uniknąć problemów ze zgodnością w przyszłych aktualizacjach.
Cenny
1
Raczej słaby sposób. To nie zadziała, jeśli hierarchia widoku zmieni się w przyszłości.
RomanN
2
Byłoby lepiej po prostu utworzyć słabą właściwość, która faktycznie zawiera wskaźnik do widoku tabeli. W podklasie @property (weak, nonatomic) UITableView *tableView;i tableView:cellForRowAtIndexPath:tylko w zestawie cell.tableView = tableView;.
Alejandro Iván
Obecne doświadczenie jest takie, że po usunięciu z kolejki komórki widok tabeli nie pojawia się od razu jako superwizja komórki - jeśli przewinę komórkę poza pole widzenia i z powrotem znajdę widok tabeli jako superwiew (wyszukiwanie rekurencyjne, jak opisano w inne odpowiedzi). Prawdopodobne przyczyny to autoukładanie się i prawdopodobnie inne czynniki.
Jonny
48

W iOS7 beta 5 UITableViewWrapperViewjest nadzorem pliku UITableViewCell. Jest również UITableViewsuperwizją UITableViewWrapperView.

Więc dla iOS 7 rozwiązaniem jest

UITableView *tableView = (UITableView *)cell.superview.superview;

Tak więc dla iOS do iOS 6 rozwiązaniem jest

UITableView *tableView = (UITableView *)cell.superview;

KGS-12
źródło
5
O rany, to brutalna zmiana API. Jaka jest najlepsza praktyka Apple dotycząca rozgałęziania, jeśli obsługujesz zarówno 6, jak i 7?
Ryan Romanchuk,
@RyanRomanchuk Oto jedna dobra sugestia: devforums.apple.com/message/865550#865550 - po utworzeniu komórki utwórz słaby wskaźnik do powiązanego widoku tableView. Alternatywnie utwórz UITableViewCellkategorię za pomocą nowej metody, relatedTableViewktóra sprawdza wersję iOS i zwraca odpowiedni superView.
memmons
3
Napisz do tego kategorię rekurencyjną na UIView. Nie musisz sprawdzać wersji; po prostu dzwoń do superview, aż znajdziesz komórkę widoku tabeli lub górę stosu widoków. To jest to, czego użyłem w iOS 6 i działało bez modyfikacji w iOS 7. I powinno działać sitll w iOS 8.
Steven Fisher
Przepraszać; Miałem na myśli, dopóki nie znajdziesz widoku tabeli. :)
Steven Fisher
39

Rozszerzenie Swift 5

Rekurencyjnie

extension UIView {
    func parentView<T: UIView>(of type: T.Type) -> T? {
        guard let view = superview else {
            return nil
        } 
        return (view as? T) ?? view.parentView(of: T.self)
    }
}

extension UITableViewCell {
    var tableView: UITableView? {
        return parentView(of: UITableView.self)
    }
}

Korzystanie z pętli

extension UITableViewCell {
    var tableView: UITableView? {
        var view = superview
        while let v = view, v.isKind(of: UITableView.self) == false {
            view = v.superview
        }
        return view as? UITableView
    }
}
Orkhan Alikhanov
źródło
Praktyczną zasadą jest, aby nie używać siły odwijania
thibaut noah
1
@thibautnoah view != nilPrzed rozpakowaniem jest czek. Zaktualizowany jednak do czystszego kodu
Orkhan Alikhanov
25

Przed iOS7, nadzór komórki był tym, UITableViewktóry ją zawierał. Począwszy od iOS7 GM (a więc prawdopodobnie będzie również w publicznym wydaniu), nadzór komórki to, a UITableViewWrapperViewjej superview jest UITableView. Istnieją dwa rozwiązania tego problemu.

Rozwiązanie nr 1: Utwórz UITableViewCellkategorię

@implementation UITableViewCell (RelatedTable)

- (UITableView *)relatedTable
{
    if ([self.superview isKindOfClass:[UITableView class]])
        return (UITableView *)self.superview;
    else if ([self.superview.superview isKindOfClass:[UITableView class]])
        return (UITableView *)self.superview.superview;
    else
    {
        NSAssert(NO, @"UITableView shall always be found.");
        return nil;
    }

}
@end

Jest to dobry zamiennik do używania cell.superview, ułatwia refaktoryzację istniejącego kodu - wystarczy wyszukać i zamienić go [cell relatedTable], a następnie wrzucić potwierdzenie, aby upewnić się, że jeśli hierarchia widoku zmieni się lub powróci w przyszłości, pojawi się natychmiast w twoich testach.

Rozwiązanie nr 2: Dodaj słabe UITableViewodniesienie doUITableViewCell

@interface SOUITableViewCell

   @property (weak, nonatomic) UITableView *tableView;

@end

Jest to znacznie lepszy projekt, chociaż będzie wymagał nieco więcej refaktoryzacji kodu w istniejących projektach. W swojej tableView:cellForRowAtIndexPathużytku SOUITableViewCell jako klasy komórek lub upewnij się, że zwyczaj klasa komórek z podklasy SOUITableViewCelli przypisać tableView do komórki mienia Tableview. Wewnątrz komórki możesz odwołać się do zawierającego ją widoku tabeli, używając self.tableView.

memmons
źródło
Nie zgadzam się, że rozwiązanie 2 to lepszy projekt. Ma więcej punktów awarii i wymaga zdyscyplinowanej ręcznej interwencji. Pierwsze rozwiązanie jest w rzeczywistości dość niezawodne, chociaż wdrożyłbym je tak, jak zasugerował @idris w swojej odpowiedzi, aby uzyskać nieco lepsze zabezpieczenie na przyszłość.
devios1
1
Test [self.superview.superview isKindOfClass:[UITableView class]]powinien być pierwszy, bo iOS 7 to coraz więcej.
CopperCash
7

Jeśli jest widoczny, ma nadzór. I… niespodzianka… superview jest obiektem UITableView.

Jednak posiadanie Superview nie gwarantuje, że będziesz na ekranie. Ale UITableView zapewnia metody określania, które komórki są widoczne.

I nie, nie ma dedykowanego odniesienia z komórki do tabeli. Ale kiedy tworzysz podklasę UITableViewCell, możesz ją wprowadzić i ustawić podczas tworzenia. (Sam to często robiłem, zanim pomyślałem o hierarchii subview.)

Aktualizacja dla iOS7: firma Apple zmieniła tutaj hierarchię widoków podrzędnych. Jak zwykle podczas pracy z rzeczami, które nie są szczegółowo udokumentowane, zawsze istnieje ryzyko, że coś się zmieni. Znacznie bezpieczniej jest „przeszukiwać” hierarchię widoków, aż w końcu zostanie znaleziony obiekt UITableView.

Hermann Klecker
źródło
14
To już nie jest prawdą w iOS7, w iOS7 beta5 UITableViewWrapperView jest nadzorem UITableViewCell ... powodując teraz problemy
Gabe
Dziękuję za komentarz. Muszę wtedy sprawdzić część mojego kodu.
Hermann Klecker
7

Rozwiązanie Swift 2.2.

Rozszerzenie dla UIView, które rekurencyjnie wyszukuje widok z określonym typem.

import UIKit

extension UIView {
    func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
        guard let view = self.superview as? T else {
            return self.superview?.lookForSuperviewOfType(type)
        }
        return view
    }
}

lub bardziej zwarty (dzięki kabiroberai):

import UIKit

extension UIView {
    func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
        return superview as? T ?? superview?.superviewOfType(type)
    }
}

W swojej celi nazywasz to po prostu:

let tableView = self.lookForSuperviewOfType(UITableView)
// Here we go

Pamiętaj, że UITableViewCell jest dodawany do UITableView tylko po wykonaniu cellForRowAtIndexPath.

Mehdzor
źródło
Możesz lookForSuperviewOfType:return superview as? T ?? superview?.superviewOfType(type)
spakować
@kabiroberai, dziękuję. Dodałem twoją radę do odpowiedzi.
Mehdzor,
Nazwa używana przez wywołanie rekurencyjne musi być zgodna. Więc to musi przeczytać: ... ?? superview? .lookForSuperviewOfType (type)
robert
7

Cokolwiek, co uda ci się zrobić, wywołując super widok lub za pośrednictwem łańcucha odpowiedzi, będzie bardzo delikatne. Najlepszym sposobem, aby to zrobić, jeśli komórki chcą coś wiedzieć, jest przekazanie do komórki obiektu, który odpowiada na jakąś metodę, która odpowiada na pytanie, które komórka chce zadać, i zlecenie kontrolerowi zaimplementowania logiki określania odpowiedzi (z twojego pytania wydaje mi się, że komórka chce wiedzieć, czy coś jest widoczne, czy nie).

Utwórz protokół delegata w komórce, ustaw delegata komórki na tableViewController i przenieś całą logikę „kontrolującą” interfejsu użytkownika w tableViewCotroller.

Komórki widoku tabeli powinny być widokiem dum, w którym będą wyświetlane tylko informacje.

Javier Soto
źródło
Może nawet lepiej dzięki delegatom. tymczasowo używam przekazanego odniesienia do tabeli, ponieważ rodzic sprawił mi problemy.
Cristi Băluță
1
To, co opisałem, to w zasadzie użycie wzorca delegata :)
Javier Soto,
To powinna być akceptowana odpowiedź, ludzie widzą to pytanie, widzą odpowiedź ponad 150 głosów i uważają, że w porządku jest pobrać tableView z komórki, a nawet więcej, może niestety zachować silne odniesienie do niego.
Alex Terente
6

Utworzyłem kategorię na UITableViewCell, aby uzyskać jej nadrzędny tableView:

@implementation UITableViewCell (ParentTableView)


- (UITableView *)parentTableView {
    UITableView *tableView = nil;
    UIView *view = self;
    while(view != nil) {
        if([view isKindOfClass:[UITableView class]]) {
            tableView = (UITableView *)view;
            break;
        }
        view = [view superview];
    }
    return tableView;
}


@end

Najlepsza,

boliva
źródło
3

Oto wersja Swift oparta na powyższych odpowiedziach. Uogólniłem do ExtendedCellpóźniejszego wykorzystania.

import Foundation
import UIKit

class ExtendedCell: UITableViewCell {

    weak var _tableView: UITableView!

    func rowIndex() -> Int {
        if _tableView == nil {
            _tableView = tableView()
        }

        return _tableView.indexPathForSelectedRow!.row
    }

    func tableView() -> UITableView! {
        if _tableView != nil {
            return _tableView
        }

        var view = self.superview
        while view != nil && !(view?.isKindOfClass(UITableView))! {
            view = view?.superview
        }

        self._tableView = view as! UITableView
        return _tableView
    }
}

Mam nadzieję, że to pomoże :)

hqt
źródło
2

Oparłem to rozwiązanie na sugestii Gabe'a, że UITableViewWrapperViewobiekt jest UITableViewCellnadzorem obiektu w iOS7 beta5.

Podklasa UITableviewCell:

- (UITableView *)superTableView
{
    return (UITableView *)[self findTableView:self];
}

- (UIView *)findTableView:(UIView *)view
{
    if (view.superview && [view.superview isKindOfClass:[UITableView class]]) {
        return view.superview;
    }
    return [self findTableView:view.superview];
}
Carina
źródło
2

Pożyczyłem i zmodyfikowałem trochę z powyższej odpowiedzi i wymyśliłem następujący fragment.

- (id)recursivelyFindSuperViewWithClass:(Class)clazz fromView:(id)containedView {
    id containingView = [containedView superview];
    while (containingView && ![containingView isKindOfClass:[clazz class]]) {
        containingView = [containingView superview];
    }
    return containingView;
}

Przekazywanie w klasie zapewnia elastyczność w zakresie przechodzenia i pobierania widoków innych niż UITableView w innych przypadkach.

Azu
źródło
2

Moje rozwiązanie tego problemu jest nieco podobne do innych rozwiązań, ale używa eleganckiej pętli for i jest krótkie. Powinien również być przyszłościowy:

- (UITableView *)tableView
{
    UIView *view;
    for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
    return (UITableView *)view;
}
Bensge
źródło
To zapętli się na zawsze, jeśli wywoływana komórka nie znajduje się jeszcze w widoku tabeli. Aby to naprawić, dodaj czek na zero: for (view = self.superview; view && ![view isKindOfClass:UITableView.class]; view = view.superview);
Antonio Nunes
2
UITableView *tv = (UITableView *) self.superview.superview;
BuyListController *vc = (BuyListController *) tv.dataSource;
Gank
źródło
1

Zamiast superviewu spróbuj użyć ["UItableViewvariable" visibleCells].

Użyłem tego w pętli foreach, aby zapętlić komórki, które widziała aplikacja i zadziałało.

for (UITableView *v in [orderItemTableView visibleCells])//visibleCell is the fix.
{
  @try{
    [orderItemTableView reloadData];
    if ([v isKindOfClass:[UIView class]]) {
        ReviewOrderTableViewCell *cell = (ReviewOrderTableViewCell *)v;
        if (([[cell deleteRecord] intValue] == 1) || ([[[cell editQuantityText] text] intValue] == 0))
            //code here 
    }
  }
}

Działa jak marzenie.

user2497493
źródło
1

Minimalnie przetestowany, ale ten nieogólny przykład Swift 3 wydaje się działać:

extension UITableViewCell {
    func tableView() -> UITableView? {
        var currentView: UIView = self
        while let superView = currentView.superview {
            if superView is UITableView {
                return (superView as! UITableView)
            }
            currentView = superView
        }
        return nil
    }
}
Murray Sagal
źródło
0

ten kod `UITableView *tblView=[cell superview];da ci wystąpienie UItableview, które zawiera komórkę widoku tabulatora

Singh
źródło
To nie zadziała, ponieważ bezpośredni superview UITableViewCell nie jest UITableView. Począwszy od iOS 7, UITableViewCell superview jest UITableViewWrapperView. Zobacz inne odpowiedzi tutaj, aby uzyskać mniej kruche, bardziej niezawodne podejścia.
JaredH
0

Proponuję przejść przez hierarchię widoków w ten sposób, aby znaleźć nadrzędny widok UITableView:

- (UITableView *) findParentTableView:(UITableViewCell *) cell
{
    UIView *view = cell;
    while ( view && ![view isKindOfClass:[UITableView class]] )
    {
#ifdef DEBUG
        NSLog( @"%@", [[view  class ] description] );
#endif
        view = [view superview];
    }

    return ( (UITableView *) view );
}

W przeciwnym razie kod złamie gdy Apple zmienia hierarchię widok ponownie .

Inna odpowiedź, która również przechodzi przez hierarchię, jest rekurencyjna.

Bob Wakefield
źródło
0
UITableViewCell Internal View Hierarchy Change in iOS 7

Using iOS 6.1 SDK

    <UITableViewCell>
       | <UITableViewCellContentView>
       |    | <UILabel>

Using iOS 7 SDK

    <UITableViewCell>
       | <UITableViewCellScrollView>
       |    | <UIButton>
       |    |    | <UIImageView>
       |    | <UITableViewCellContentView>
       |    |    | <UILabel>


The new private UITableViewCellScrollView class is a subclass of UIScrollView and is what allows this interaction:


![enter image description here][1]


  [1]: http://i.stack.imgur.com/C2uJa.gif

http://www.curiousfind.com/blog/646 Dziękuję

jbchitaliya
źródło
0

Możesz to uzyskać za pomocą jednej linii kodu.

UITableView *tableView = (UITableView *)[[cell superview] superview];
Karthik damodara
źródło
0
extension UIView {
    func parentTableView() -> UITableView? {
        var viewOrNil: UIView? = self
        while let view = viewOrNil {
            if let tableView = view as? UITableView {
                return tableView
            }
            viewOrNil = view.superview
        }
        return nil
    }
}
neoneye
źródło
0

from @idris answer Napisałem rozszerzenie dla UITableViewCell w Swift

extension UITableViewCell {
func relatedTableView() -> UITableView? {
    var view = self.superview
    while view != nil && !(view is UITableView) {
        view = view?.superview
    }

    guard let tableView = view as? UITableView else { return nil }
    return tableView
}
Kiattisak Anoochitarom
źródło