Переопределить UIViewController.view с определенным типом

Давайте рассмотрим приложение с сильно настраиваемыми или сложными представлениями.

У нас будет определенный тип методов отправки контроллера представления для определенного типа UIView, где UIView сам состоит из ряда других представлений.

Представление должно иметь богатый интерфейс, зависящий от предметной области, позволяющий контроллеру действовать как тонкий «склеивающий» слой между ним и такой же богатой моделью.

Итак, мы переопределяем свойство представления нашего контроллера следующим образом:

@interface PlaybackViewController : UIViewController<StageLayoutDelegate, ControlPanelDelegate>
{
    NSMutableArray* _sections;
    LightingMode _lightingMode;
}

@property (nonatomic, strong) PlaybackView* view; // <------ Specific type of view

#pragma mark - injected
@property (nonatomic, strong) id<OscClient> oscClient;
@property (nonatomic, strong) AbstractStageLayoutView* stageLayoutView;

@end

Переопределение имеет смысл по сравнению с определением другого метода доступа, и я могу просто отправлять сообщения определенному типу UIView без необходимости приведения.

Проблема. Единственная проблема заключается в том, что это приводит к предупреждению компилятора:

тип свойства PlaybackView * несовместим с типом UIView *, унаследованным от UIViewController

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

Вопрос:

  • Есть ли способ подавить это конкретное предупреждение?
  • Почему это часть настроек по умолчанию, когда большинство современных объектно-ориентированных языков с радостью позволяют переопределить свойство или метод в подклассе, чтобы он возвращал более конкретный подкласс типа, объявленного в суперклассе?

person Jasper Blues    schedule 05.02.2013    source источник
comment
Почему вы не создаете подкласс представления и не используете основное представление VC в качестве контейнера? Apple предлагает всегда компоновать подклассы   -  person Andrea    schedule 05.05.2014
comment
UITableViewController удается делать то, о чем вы говорите, и в результате он негибок до такой степени, что почти бесполезен. Я бы хорошенько подумал о таком подходе. Это может вернуться, чтобы укусить вас позже.   -  person Kirby Todd    schedule 05.05.2014
comment
Это быстро и грязно, просто чтобы подавить предупреждение, попробуйте обернуть свой код между этими строками #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" //YOUR CODE #pragma clang diagnostic pop или -Wall   -  person Andrea    schedule 05.05.2014
comment
@JasperBlues Я отредактировал свой ответ, но, конечно, попробуйте его раньше.   -  person Andrea    schedule 05.05.2014
comment
@KirbyTodd Я согласен с тем, что UITableViewController имеет ограниченное использование, однако я не думаю, что это связано с тем, что он тесно связан с UITableView. . во многих случаях контроллер представления взаимодействует с представлением определенного типа. . Это помогает контроллеру взять на себя предназначенную ему роль тонкого связующего слоя между расширенным представлением и моделью.   -  person Jasper Blues    schedule 05.05.2014
comment
Я даже не могу воспроизвести ваше предупреждение. Есть ли у вас дополнительные настройки флагов? Является ли PlaybackView подклассом UIView?   -  person Warren Burton    schedule 05.05.2014
comment
Я предполагаю, что не так, как когда я делаю PlaybackView подклассом NSObject (а не UIView), когда я вижу предупреждение.   -  person Warren Burton    schedule 05.05.2014
comment
@WarrenBurton Это имело бы смысл. Но я получаю предупреждение, когда PlaybackView является своего рода UIVIew. Это по-прежнему удовлетворяет контракту суперкласса. Разве вы не получили предупреждение по этому делу?   -  person Jasper Blues    schedule 05.05.2014
comment
нет. как только PlaybackView становится подклассом UIView, предупреждение исчезает. Вы используете декларацию fwd, т.е. @класс ВоспроизведениеВью; или #import PlaybackView.h ? Я использую #import   -  person Warren Burton    schedule 05.05.2014
comment
Да, я (чрезмерно усердно) использую предварительные объявления. Хотите дать ответ? Я приму это.   -  person Jasper Blues    schedule 05.05.2014


Ответы (5)


Проблема здесь не в переопределении свойства, а в использовании предварительного объявления типа класса.

Итак, это...

@class PlaybackView;

@interface PlaybackViewController : UIViewController

@property (nonatomic, strong) PlaybackView* view;

@end

выдаст вам упомянутое предупреждение, потому что компилятор не может знать иерархию наследования PlaybackView. UIViewController имеет контракт на поставку UIView из своей view собственности

Он говорит вам, что считает PlaybackView не UIView

Простое решение здесь состоит в том, чтобы вместо этого использовать #import, чтобы дать компилятору полное представление о PlaybackView...

#import "PlaybackView.h"

@interface PlaybackViewController : UIViewController

@property (nonatomic, strong) PlaybackView* view;

@end

в качестве альтернативы (но действительно плохой тон, поскольку PCH является функцией оптимизации и не должен управлять зависимостями) является добавление #import "PlaybackView.h" в ваши проекты PCH

person Warren Burton    schedule 05.05.2014
comment
Конечно! Так очевидно (оглядываясь назад). . также наградит награду +100, как только время истечет. - person Jasper Blues; 05.05.2014

Как было предложено в другом ответе, использование #import вместо @class удалит предупреждение, но рекомендуется импортировать как можно меньше в заголовок, поэтому я бы рекомендовал оставить view без изменений и иметь дополнительный PlaybackView * playbackView:

  • Совершенно нормально, когда view и playbackView указывают на один и тот же вид.
  • Классы, которым необходимо знать ваш специализированный view, должны импортировать заголовок ваших контроллеров, чтобы они могли просто использовать playbackView в первую очередь.
  • Что еще более важно, если в будущем вы захотите встроить свое специализированное представление в качестве подпредставления (что часто происходит, например, при добавлении суперпредставления UIScrollView), вам не придется рефакторить другой код и классы!
  • Это просто чище.
person Rivera    schedule 06.05.2014
comment
На мой взгляд, это определенно не чище. Читателя кода смущает наличие еще одной ссылки на очень знакомую конструкцию. Если вы добавите еще один IBOutlet, вам придется зайти в IB, чтобы увидеть, что это на самом деле то же самое, что и представление. Если вы явно переопределите его в ViewController, вы увидите, что на самом деле это .view. Этот базовый .view и есть PlaybackView. Я использую метод, описанный Уорреном Бертоном, потому что я хочу использовать это базовое представление для служебных обязанностей представления, которые не относятся к конкретному элементу внутри этого представления. - person Lucas van Dongen; 13.11.2014

Я не думаю, что переопределение свойства представления UIViewControllers является хорошим способом.

Я думаю, что лучше сделать так:

@interface PlaybackViewController : UIViewController<StageLayoutDelegate, ControlPanelDelegate>
{
    NSMutableArray* _sections;
    LightingMode _lightingMode;
}

//@property (nonatomic, strong) PlaybackView* view; //you do not need this property

#pragma mark - injected
@property (nonatomic, strong) id<OscClient> oscClient;
@property (nonatomic, strong) AbstractStageLayoutView* stageLayoutView;

@end

и в .m файле.

- (void)loadView
{
    PlaybackView *mainView = [[PlaybackView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
    // set the mainView
    self.view = mainView;
}

и вы можете использовать свой PlaybackView следующим образом.

((PlaybackView *)(self.view)).oscClient

or

((PlaybackView *)(xxxViewControler.view)).oscClient
person Guo Luchuan    schedule 05.02.2013
comment
@JasperBlues На мой взгляд, это более гибко, например, navigationBar.leftItem, мы не переопределяем leftItem, мы просто даем leftItem элемент кнопки. И если ваше представление иногда должно быть типа PlaybackView1, а иногда — типа PlaybackView2, просто добавьте логическое значение в loadView, чтобы справиться с этим. А если переопределить, думаю, сделать это будет непросто. - person Guo Luchuan; 05.02.2013

Возможно, вы могли бы объявить другой метод, который в некотором смысле предоставляет вам приведение типов.

@implementation PlaybackViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //  use view_ property instead of view
    self.view_.foo = 1;
}

- (void)loadView {
    CGRect frame = [UIScreen mainScreen].applicationFrame;
    self.view = [[PlaybackView alloc] initWithFrame:frame];
}

- (PlaybackView *)view_ {
    return (PlaybackView *)self.view;
}

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

person FluffulousChimp    schedule 05.05.2014
comment
Это все равно, что предоставить другое имущество, которое, по его словам, ему не интересно. - person Leo Natan; 05.05.2014
comment
Ну, если быть педантичным, ОП сказал свойство, а не метод. Я допускаю, что современный синтаксис ObjC стирает различие. - person FluffulousChimp; 05.05.2014
comment
@NSBum Кажется, я сказал «аксессор», что подразумевает свойство (синтезированный или пользовательский метод), но оно также должно было подразумевать «метод». . . в любом случае спасибо за предложение Лео Натан. - person Jasper Blues; 05.05.2014
comment
Справедливо. Я не могу придумать другого способа сделать это; хотя это хроническое раздражение. - person FluffulousChimp; 05.05.2014

[ОБНОВЛЕНИЕ]
Наконец-то я, вероятно, нашел решение, которое подходит для проблемы: это быстро и грязно, просто чтобы подавить предупреждение, попробуйте обернуть свой код между этими строками.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu" 
//YOUR CODE
#pragma clang diagnostic pop

или -Wall Чтобы узнать больше о подавлении предупреждений компилятора Clang Manual

[СТАРЫЙ ОТВЕТ]
Я хотел бы внести свои 2 цента. Если я правильно понял, вы пытаетесь создать своего рода абстрактную фабрику, которая дает вам специализированную версию представления, основанную на функциональности контроллера представления. На мой взгляд, раскадровки плохо работают в таком дизайне, но я хотел бы поделиться с вами своим видением этого.

Сначала я создам абстрактный класс для вашего контроллера представления, где в интерфейсе вы объявляете все свойства, которые вам нужны, во всех ваших подклассах VC, например:

  • OSClient
  • АннотацияStageLayoutView
  • PlaybackView как слабый
  • свойство воспроизведения

Класс PlaybackView представляет собой кластер классов, такой как NSNumber, вы вызываете для него фабричный метод, который возвращает объект, который может отличаться от случая к случаю. Если вы проверяете NSnumber, он возвращает другой объект, если вы создаете число с плавающей запятой или целое число, но все они являются подклассами NSNumber, а NSNumber объявляет все свойства своих подклассов, но не реализует их.
Теперь то, что вы можете сделать в методе -viewDidLoad абстрактного класса, это вызвать подобный метод

PlaybackView *plbackView = [PlaybackView playbackViewFroProperty:self.playbackProperty];
[self.view addSubview:playbackView];
self.playbackView = plbackView;

ВоспроизведениеProperty может быть оценено внутри User defined runtime attibute в редакторе раскадровки viewcontroller.

person Andrea    schedule 05.05.2014
comment
Нет, извините, наверное, я плохо сформулировал вопрос. Все, что я пытаюсь сделать, это указать тип моего UIView в моем подклассе UIViewController, например, PlaybackView* (абстрактная часть была отвлекающим маневром). . . Если вы сделаете это, clang на самом деле выдаст предупреждение, заставив вас вместо этого использовать везде. Для краткости кода я хочу избежать кастинга. - person Jasper Blues; 05.05.2014
comment
Теперь я понял, но у меня нет ответа :-( - person Andrea; 05.05.2014
comment
Да, извините, мой вопрос не был ясен. . . Я добавил еще несколько пояснений в вопрос, чтобы помочь объяснить, что я имею в виду. . Что вы думаете? Кажется, что должен быть способ, но я не смог его найти. - person Jasper Blues; 05.05.2014
comment
Вы можете подавить предупреждения компилятора, оборачивающие ваши методы вокруг двух макросов препроцессора, но у предупреждения есть своя причина. Я не знаю, приемлемо ли это для вас, но почему бы не создать что-то вроде метода myView, который просто вызывает метод представления, выполняющий его приведение. - person Andrea; 05.05.2014
comment
У меня есть одно последнее предложение, я сдаюсь :) Что касается swizzling метода, во время выполнения вы заменяете имплантацию метода другим. Я никогда не использовал его, но если вы не знаете, вам следует взглянуть mikeash.com/pyblog/ - person Andrea; 05.05.2014
comment
Замена представления не является проблемой. Проблема в предупреждении компилятора. К сожалению, swizzling не помогает с этим. - person FluffulousChimp; 05.05.2014
comment
@Андреа - глотание здесь не поможет. Мы хотим, чтобы определенный тип контроллера был (оправданно) тесно связан с определенным типом представления. Так что контроллер может вызывать конкретный интерфейс этого представления без приведения. . . Он может уже это делать, но чтобы избежать предупреждений, нам нужно выполнить кастинг. Видеть? Это проблема времени компиляции. - person Jasper Blues; 05.05.2014
comment
@Andrea, сказав, что ничто не делает меня счастливым, чем возможность поднять swizzle-cannon :) Загляните на www.typhoonframework.org - там мы делаем кое-что прикольное со средой выполнения ObjC. - person Jasper Blues; 05.05.2014