Как пользовательское представление должно обновлять объект модели?

Это вопрос Cocoa n00b - я много лет программировал приложения с графическим интерфейсом в других средах, но теперь я хотел бы понять, что такое «идиоматический Cocoa» для следующей тривиальной ситуации:

У меня есть простой пользовательский NSView, который позволяет пользователю рисовать в нем простые фигуры. Его drawRect реализация выглядит так:

- (void)drawRect:(NSRect)rect
{
    // Draw a white background.
    [[NSColor whiteColor] set];
    NSRect bounds = [self bounds];
    [NSBezierPath fillRect:bounds];

    [[NSColor blackColor] set];

    // 'shapes' is a NSMutableArray instance variable
    // whose elements are NSValues, each wrapping an NSRect.
    for (NSValue *value in shapes)
    {
        NSRect someRect;
        [value getValue:&someRect];
        [self drawShapeForRect:someRect];
    }

    // In addition to drawing the shapes in the 'shapes'
    // array, we draw the shape based on the user's
    // current drag interaction.
    [self drawShapeForRect:[self dragRect]];
}

Вы видите, насколько прост этот код: переменная экземпляра массива shapes действует как модель, которую метод drawRect использует для рисования фигур. Новые NSRect добавляются к shapes каждый раз, когда пользователь выполняет последовательность нажатия/перетаскивания/поднятия мыши, которую я также реализовал в этом пользовательском представлении. Вот мой вопрос:

Если бы это было "настоящее" приложение Cocoa, каким идиоматическим способом мое пользовательское представление могло бы обновить свою модель?

Другими словами, как пользовательское представление должно уведомить контроллер о том, что в список фигур необходимо добавить еще одну фигуру? Прямо сейчас представление отслеживает фигуры в своем собственном NSMutableArray, что прекрасно в качестве детали реализации, но я не хочу выставлять этот массив как часть общедоступного API моего пользовательского представления. Кроме того, я хотел бы поместить проверку ошибок, сохранение/загрузку и отмену кода в централизованное место, такое как контроллер, вместо того, чтобы засорять все мои пользовательские представления. По моему прошлому опыту работы с другими средами программирования с графическим интерфейсом, модели управляются объектом на моем уровне контроллера, и представление обычно не обновляет их напрямую — скорее, представление сообщает, когда что-то происходит, отправляя событие или вызывая метод на контроллере, на который он ссылается, или используя какой-либо подход с аналогичной развязкой.

Я нутром чувствую, что идиоматический код Cocoa выставит свойство delegate в моем пользовательском представлении, а затем подключит объект контроллера MyDocument (или другой объект уровня контроллера, свисающий с контроллера документа) к представлению в качестве его делегата в xib. файл. Затем представление может вызывать некоторые методы, такие как shapeAdded:(NSRect)shape для делегата. Но похоже, что есть множество других способов сделать это, например, передать контроллеру ссылку на объект модели (список фигур) непосредственно в пользовательское представление (кажется неправильным) или заставить представление отправлять уведомление который будет слушать контроллер (кажется громоздким), а затем контроллер обновляет модель.


person erikprice    schedule 25.03.2009    source источник


Ответы (3)


Наличие делегата — это кромулентный способ сделать это. Другим способом было бы предоставить привязку NSArray к представлению и привязать ее к привязке arrangedObjects контроллера массива, а затем привязать привязку content контроллера массива к тому, кто владеет реальным массивом, содержащим объекты модели. Затем вы можете добавить другие представления на тот же контроллер массива, например, список объектов в активном слое.

Поскольку это настраиваемое представление, вам нужно либо создать IBPlugin для предоставления привязки в IB, либо привязать его программно, отправив представление bind:toObject:withKeyPath:options: сообщение.

person Peter Hosey    schedule 25.03.2009
comment
Embiggen — совершенно громоздкое слово. Я поддерживаю ваше предложение использовать привязки для данных вместо делегатов. Их немного больше работы по настройке, но в конечном итоге они намного проще в использовании. - person Alex; 25.03.2009
comment
Bindings кажется именно тем решением, которое я искал. Поскольку это всего лишь игрушечный проект для обучения, я попробую как программный, так и основанный на IBPlugin подход. Спасибо. - person erikprice; 25.03.2009

В вашем каталоге /Developer/Examples/AppKit/Sketch есть очень хороший пример проекта xcode, который является более продвинутой версией того, что вы делаете, но, тем не менее, уместен. В нем есть отличные примеры использования привязок между контроллером и представлением, которые прольют свет на «правильный» способ работы. В этом примере не используются подключаемые модули IB, поэтому вы увидите ручные вызовы для связывания и реализованные методы наблюдения.

person Evan    schedule 25.03.2009
comment
Спасибо, что указали на это, я посмотрю на этот проект. - person erikprice; 25.03.2009

между вашим кодом и NSTableView есть несколько сходств, поэтому я бы посмотрел, возможно, используя источник данных (аналогичный вашему делегату) или даже, возможно, привязки.

person cobbal    schedule 25.03.2009
comment
Вы правы, MyCustomViewDataSource кажется более подходящим, чем MyCustomViewDelegate для такой ситуации. И крепления кажутся лучшими из всех. - person erikprice; 25.03.2009