Проблема UITableView при использовании отдельного делегата/источника данных
Общее описание:
Чтобы начать с того, что работает, у меня есть UITableView
, который был помещен в Xcode-сгенерированное представление с помощью Interface Builder. Владелец файла вида настроен на подкласс, созданный Xcode, UIViewController
. В этот подкласс я добавил рабочие реализации numberOfSectionsInTableView: tableView:numberOfRowsInSection:
и tableView:cellForRowAtIndexPath:
, а Table View dataSource
и delegate
подключены к этому классу через File Owner в Interface Builder.
Вышеупомянутая конфигурация работает без проблем. Проблема возникает, когда я хочу переместить этот вид таблицы dataSource
и delegate
-изменения в отдельный класс, скорее всего, потому что в представлении помимо представления таблицы есть другие элементы управления, и я бы хотел переместить представление таблицы связанный код с его собственным классом. Для этого попробуйте следующее:
- Создайте новый подкласс
UITableViewController
в Xcode
- Переместите известные хорошие реализации
numberOfSectionsInTableView:
, tableView:numberOfRowsInSection:
и tableView:cellForRowAtIndexPath:
в новый подкласс
- Перетащите
UITableViewController
на верхний уровень существующего XIB в InterfaceBuilder, удалите UIView
/UITableView
, которые автоматически создаются для этого UITableViewController
, затем установите класс UITableViewController
в соответствие с новым подклассом
- Удалите ранее работающие
UITableView
существующие dataSource
и delegate
соединения и подключите их к новому UITableViewController
Когда закончите, у меня нет рабочего UITableView
. Я получаю один из трех результатов, которые могут казаться случайными:
- Когда загружается
UITableView
, я получаю ошибку времени выполнения, указывающую, что я отправляю tableView:cellForRowAtIndexPath:
объекту, который его не распознает.
- Когда загружается
UITableView
, проект разбивается на отладчик без ошибок
- Ошибка отсутствует, но
UITableView
не отображается
С некоторой отладкой и созданием базового проекта, чтобы воспроизвести эту проблему, я обычно вижу третий вариант выше (нет ошибки, но нет видимого вида таблицы). Я добавил некоторые вызовы NSLog и обнаружил, что хотя numberOfSectionsInTableView:
и numberOfRowsInSection:
оба вызываются, cellForRowAtIndexPath:
нет. Я убежден, что я пропустил что-то очень простое и надеялся, что ответ может быть очевидным для кого-то, у кого больше опыта, чем у меня. Если это не будет легким ответом, я был бы рад обновить код или образец проекта. Спасибо за ваше время!
Полные шаги для воспроизведения:
- Создайте новую iPhone OS, приложение на основе View в Xcode и назовите ее
TableTest
- Откройте
TableTestViewController.xib
в Interface Builder и перетащите UITableView
на предоставленную поверхность.
- Подключите
UITableView
dataSource
и delegate
-outlets к файловому владельцу, который уже должен представлять класс TableTestViewController
. Сохраните изменения.
- Вернитесь в Xcode, добавьте следующий код в
TableTestViewController.m:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(@"Returning num sections");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(@"Returning num rows");
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"Trying to return cell");
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.text = @"Hello";
NSLog(@"Returning cell");
return cell;
}
-
Build и Go, и вы должны увидеть слово Hello
в UITableView
-
Теперь, чтобы попытаться переместить эту логику UITableView
в отдельный класс, сначала создайте новый файл в Xcode, выбрав подкласс UITableViewController
и вызовите класс TableTestTableViewController
- Удалите приведенный фрагмент кода из
TableTestViewController.m
и поместите его в TableTestTableViewController.m
, заменив стандартную реализацию этих трех методов на наш.
- В интерфейсе Builder в том же файле
TableTestViewController.xib
перетащите UITableViewController
в основное окно IB и удалите новый UITableView
объект, который автоматически появился с ним
- Установите класс для этого нового
UITableViewController
в TableTestTableViewController
- Удалите привязки
dataSource
и delegate
из существующего ранее работающего UITableView
и повторно подключите те же самые два привязки к новому TableTestTableViewController
, который мы создали.
- Сохраняйте изменения, стройте и идите, и если вы получаете результаты, которые я получаю, обратите внимание, что
UITableView
больше не функционирует должным образом
Решение:
Еще несколько проблем с устранением неполадок и некоторая помощь от Форумы разработчиков iPhone, я документировал решение! Для основного подкласса UIViewController
проекта требуется выход, указывающий на экземпляр UITableViewController
. Для этого просто добавьте следующее в заголовок основного представления (TableTestViewController.h
):
#import "TableTestTableViewController.h"
и
IBOutlet TableTestTableViewController *myTableViewController;
Затем в интерфейсе Builder подключите новую розетку от File Owner к TableTestTableViewController
в главном окне IB. Никаких изменений не требуется в части пользовательского интерфейса XIB. Просто имея эту розетку на месте, даже если код пользователя не использует ее напрямую, она полностью устраняет проблему. Благодаря тем, кто помог, и кредитует BaldEagle на форумах разработчиков iPhone для поиска решения.
Ответы
Ответ 1
Я последовал твоим шагам, воссоздал проект и столкнулся с той же проблемой. В основном ты почти там. Осталось 2 вещи (после их исправления):
-
Вам нужно подключить tableView
к TableTestTableViewController
к UITableView
, который у вас есть на экране. Как я уже сказал, потому что это не IBOutlet
, вы можете переопределить свойство tableView
и сделать его и IBOutlet
:
@interface TableTestTableViewController : UITableViewController {
UITableView *tableView;
}
@property (nonatomic, retain) IBOutlet UITableView *tableView;
-
Следующее - добавить ссылку на TableTestTableViewController
и сохранить ее в TableTestViewController
. В противном случае ваш TableTestTableViewController
может быть выпущен (после того, как загрузите нить ничем, чтобы на него нависать). Вот почему вы видите неустойчивые результаты, сбои или ничего не показываете. Для этого добавьте:
@interface TableTestViewController : UIViewController {
TableTestTableViewController *tableViewController;
}
@property (nonatomic, retain) IBOutlet TableTestTableViewController *tableViewController;
и подключите это в построителе интерфейса к экземпляру TableTestTableViewController
.
С приведенным выше это отлично работало на моей машине.
Также я думаю, что было бы полезно указать мотивацию всего этого (вместо того, чтобы просто использовать UITableViewController
со своим собственным UITableView
). В моем случае это было использование других представлений, что только UITableView
на одном экране содержимого. Поэтому я могу добавить другие UILabels
или UIImages
под UIView
и показать UITableView
под ними или над ними.
Ответ 2
Я просто провел много часов, вытаскивая свои волосы, пытаясь понять, почему UITableView
не появлялся, когда я его вложил в отдельный наконечник, а не в главный наконечник. Я наконец нашел ваше обсуждение выше и понял, что это потому, что мой UITableViewController
не сохранился! По-видимому, свойства делегата и datasource UITableView
не отмечены как "сохранить", и поэтому мой nib загружается, но контроллер был взят... И из-за чудеса objective-c у меня вообще не появилось сообщений об ошибках... Я до сих пор не понимаю, почему это не сбой. Я знаю, что я видел сообщение, отправленное выпущенному xxx, прежде чем... почему он не дал мне одну из этих?!?
Я думаю, что большинство разработчиков предположили, что структура, которую они создают в построителе интерфейса, будет храниться в каком-то более крупном контексте (Nib) и не подлежит освобождению. Наверное, я знаю, почему они это делают. Так что iPhone может упасть и перезагрузить части ниба на низкой памяти. Но человеку это трудно понять.
Может ли кто-нибудь сказать мне, где я должен был прочитать об этом поведении в документах?
Также - о подключении представления. Во-первых, если вы втащите его из конструктора пользовательского интерфейса, вы увидите, что они подключают свойство представления (которое является IBOutlet
) к представлению таблицы. Не нужно выставлять tableView
, который, кажется, устанавливается внутри. На самом деле даже нет необходимости устанавливать представление, если вы не хотите уведомления viewDidLoad
. Я только что нарушил соединение вида между UITableView
и UITableViewController
(только делегирование и набор данных), и, похоже, он работает нормально.
Ответ 3
Да почему-то (пожалуйста, звоните, если кто-нибудь знает, почему...) tableView
Свойство UITableViewController
не отображается как IBOutlet
, хотя оно является общедоступным. Поэтому, когда вы используете Interface Builder, вы не можете видеть это свойство для подключения к вашему другому UITableView
. Поэтому в вашем подклассе вы можете создать свойство tableView
, помеченное как IBOutlet
, и подключить его.
Все это кажется взломанным и обходным путем для меня, но это единственный способ разделить UITableViewController's
UITableView
и поместить его где-то еще в иерархию пользовательского интерфейса. Я столкнулся с той же проблемой, когда пытался создать представление, где есть вещи, отличные от UITableView
, и именно так я решил это... Правильно ли это подходит?
Ответ 4
Я смог выполнить эту работу. Я построил действительно красивую панель инструментов с 4 TableViews и webview с видео. Ключ состоит из отдельных контроллеров tableView и IBOutlets для других контроллеров tableview, определенных в контроллере представления. В UIB вам просто нужно подключить другие контроллеры tableview к владельцу файла контроллера вида. Затем подключите таблицы к соответствующим контроллерам представления для источника данных и делегата.