Как [self.tableView reloadData] узнает, какие данные нужно перезагрузить? - PullRequest
4 голосов
/ 14 марта 2010

Меня до смерти дошло, что мой viewcontroller, который, как оказалось, является tableViewController, знает, не сказав, что его свойство, которое является NSArray или NSDictionary, содержит данные, которые должны быть загружены в таблицу для отображения.

Похоже, я должен явно сказать что-то вроде:

[self.tableView useData:self.MyArray];

Я хочу, чтобы внутри моего tableViewController было несколько массивов и программно переключаться между ними.

Я заметил, что когда tableViewController использует searchViewController, вы можете сделать это:

if (tableView == self.searchDisplayController.searchResultsTableView) {

Я даже смог сделать это:

self.tableView =  self.searchDisplayController.searchResultsTableView;
[self.tableView reloadData];

Но нигде не могу найти, как вернуть self.tableView к основному источнику данных!

Ответы [ 2 ]

41 голосов
/ 14 марта 2010

Хорошо, я понимаю ваши разочарования, потому что подавляющее большинство учебных материалов для iPhone не уделяют достаточного внимания общему дизайну приложения. Они делают все возможное для интерфейса «сладкоежка» и платят только за то, чтобы приложение обрабатывало данные , хотя обработка данных - это, в первую очередь, цель приложения!

В учебных материалах не уделяется достаточно времени объяснению шаблона проектирования Model-View-Controller, на котором основан весь API iPhone / Cocoa. Вам трудно что-либо понять, потому что вы продолжаете пытаться втиснуть функциональность в неправильные объекты из-за ошибочного убеждения, что представление пользовательского интерфейса является ядром программы, как вам подсказывают учебные материалы. В этом заблуждении ничего не имеет смысла, даже документация Apple.

Вы должны сделать шаг назад и переосмыслить. Это не функция представления, чтобы решить, какие данные отображать и когда отображать. В функции контроллера табличного представления не входит хранение, управление или хранение данных приложения. Эти функции должным образом относятся к объекту модели данных (о котором вы, возможно, никогда не слышали). У вас возникают проблемы, потому что вы пытаетесь разделить задачу модели данных по представлению и контроллеру представления, если они не принадлежат.

Очевидно, ваше приложение даже не имеет модели данных, потому что вы храните данные таблицы в качестве свойств контроллера табличного представления. Несмотря на то, что вы часто видите это в примитивных обучающих примерах, это плохой дизайн, который рухнет под сложность любого, кроме самых тривиальных приложений.

Вместо этого ваши данные должны храниться и управляться в собственном настраиваемом объекте. Это модель данных. В вашем случае это звучит так, как будто данные распределены по двум массивам, поэтому вы бы создали объект модели данных примерно так:

@interface MyDataModel : NSObject {
@protected
    NSArray *arrayOne;
    NSArray *arrayTwo;
@public
    NSArray *currentlyUsedArray;

}
@property(nonatomic, retain)  NSArray *currentlyUsedArray;

-(void) switchToArrayOne;
-(void) switchToArrayTwo;
-(void) toggleUsedArray;

@end

#import "MyDataModel.h"

@interface MyDataModel ()
@property(nonatomic, retain)  NSArray *arrayOne;
@property(nonatomic, retain)  NSArray *arrayTwo;

@end


@implementation MyDataModel

- (id) init{
    if (self=[super init]) {
        self.arrayOne=//... initialize array from some source
        self.arrayTwo=//... initialize array from some source
        self.currentlyUsedArray=self.arrayOne; //whatever default you want
    }
    return self;
}

-(void) switchToArrayOne{
    self.currentlyUsedArray=self.arrayOne;
}

-(void) switchToArrayTwo{
    self.currentlyUsedArray=self.arrayTwo;
}

- (void) toggleUsedArray{
    if (self.currentlyUsedArray==self.arrayOne) {
        self.currentlyUsedArray=self.arrayTwo;
    }else {
        self.currentlyUsedArray=self.arrayOne;
    }
}

(Обратите внимание, что фактические данные инкапсулированы и что другие объекты могут получить доступ только к currentlyUsedArray. Модель данных решает, какие данные предоставлять на основе внутреннего состояния данных.)

Этот объект модели данных должен находиться в общедоступном месте. Лучший способ - сделать его одноэлементным, но быстрый и грязный способ - оставить его как атрибут делегата приложения.

Итак, в вашем контроллере таблиц у вас будет свойство:

MyDataModel *theDataModel;
@property (nonatomic, retain) MyDataModel *theDataModel;

затем в реализации

@synthesize theDataModel;

-(MyDataModel *) theDataModel; {
    if (theDataModel; !=nil) {
        return theDataModel; ;
    }
    id appDelegate=[[UIApplication sharedApplication] delegate];
    self.theDataModel=appDelegate.theDataModelProperty;
    return theDataModel;
}

Тогда в вашем методе источника данных tableview:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    ...
    cell.textLabel.text=[self.theDataModel.currentlyUsedArray objectAtIndex:indexPath.row];
    return cell;
}

Если какое-либо событие где-либо в приложении требует, чтобы вы переключали массивы, вы просто вызываете объект модели данных из делегата приложения и отправляете ему соответствующее сообщение массива коммутатора.

id appDelegate=[[UIApplication sharedApplication] delegate];
[appDelegate.theDataModelProperty toggleUsedArray];

Теперь все последующие операции с данными, будь то в этом конкретном табличном представлении или в каком-либо другом совершенно не связанном представлении, будут использовать данные из соответствующего массива.

Зачем проходить через все эти неприятности? Это делает приложение модульным. Вы можете легко добавлять различные представления, каждое из которых отображает данные по-разному, без необходимости переписывать управление данными каждый раз. Вы можете использовать модель данных для управления данными, которые будут отображаться в таблице, в веб-представлении или в командной строке. Вы даже можете легко переместить модель данных в совершенно другое приложение.

Эта модульность значительно упрощает управление большими сложными приложениями. У вас есть только один объект, который манипулирует данными и контролирует их. Вам не нужно беспокоиться о том, что небольшая ошибка в каком-то редко используемом сегменте кода испортит все приложение. Вы можете легко добавлять представления или удалять их, не нарушая приложение.

Это, конечно, тривиальный пример, но он показывает хорошую практику.

Однако вы можете спросить, как это решает проблему представления таблицы, зная, какие данные загружать и когда их загружать? Просто, это не так. Работа табличного представления не в том, чтобы знать, какие данные загружать или когда загружать. Модель данных обрабатывает что-данные, а контроллер табличного представления обрабатывает когда. (Вы даже можете получать уведомления о проблемах модели данных, когда она обновляется, например, для URL. Тогда контроллер представления может зарегистрироваться для уведомления и вызывать reloadData всякий раз, когда изменяется модель данных.)

Безжалостно разделяя и инкапсулируя функциональность в MVC, вы создаете сложные приложения из простых, повторно используемых компонентов, которые легко обслуживать и отлаживать.

Плохо, что большинство учебных материалов лишь на словах относятся к этой крайне критической концепции.

2 голосов
/ 14 марта 2010

Контроллер табличного представления ничего не «знает, не сказав» - у него нет свойства, о котором вы упоминали, что данные получены. Вы предоставляете эти данные по одной ячейке за раз, обычно в подклассе контроллера представления.

Обычно ваш объект контроллера табличного представления является и делегатом табличного представления, и делегатом источника данных табличного представления. От Apple документы :

Объект UITableView должен иметь делегат и источник данных. Следующий дизайн Модель-Вид-Контроллер шаблон, источник данных является посредником между моделью данных приложения (то есть его модельные объекты) и просмотр таблицы; делегат, с другой рука, управляет внешним видом и поведение табличного представления. данные источник и делегат часто (но не обязательно) тот же объект, и этот объект часто является обычаем подкласс UITableViewController.

Представление таблицы не принимает массив или словарь и не извлекает из него данные; в вашем источнике данных вас спросят, как должна выглядеть каждая ячейка. Вы просто реализуете этот метод:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

И вернуть все содержимое в ячейку, которое вы хотите для строки, о которой вас спрашивают. Вы можете использовать логику, чтобы смешивать / сопоставлять / извлекать данные из любого места.

Может ли ваше замешательство возникнуть из-за супа примера кода, может быть, неясно, что происходит? Я бы порекомендовал создать табличное представление с нуля, чтобы увидеть, как это работает - это легко сделать, добавив новый класс в ваш проект, вы можете выбрать подкласс UITableViewController изнутри XCode в мастере «new». Он заполняет файл .m всеми соответствующими пустыми методами, включая приведенные выше.


EDIT: не меняйте табличное представление, которым владеет ваш контроллер представления при выполнении поиска. Вы путаете ссылку на экземпляр, называемую tableView, которой владеет ваш контроллер представления, с аргументом метода делегата tableView:cellForRowAtIndexPath:, который просто передается, чтобы сообщить вам , что табличное представление запрашивает клетка. Если вы настроили поиск обычным способом с тем же viewcontroller, который является делегатом как для таблицы по умолчанию / содержимого, так и для результатов поиска, вы можете вызвать с любым из них. Смотрите здесь документы для этого .

...