iOS: [self.view setNeedsDisplay];

Кажется, я забыл основы, касающиеся этого, или, возможно, я просто слишком устал.

У меня есть контроллер представления и представление, которое является подклассом UIView с именем MyViewClass.

В viewDidLoad моего viewController я alloc и init self.myView вот так:

self.myView = [[MyViewClass alloc] initWithFrame:(CGRect){{0, 0}, 320, 480}];
self.view = self.myView;

В какой-то момент в моем viewController я бы назвал [self.view setNeedsDisplay];. Здесь нет проблем.

Я выставляю свойство в MyViewClass.h:

@property(nonatomic) i;

Свойство i будет использоваться в drawRect в myView; Итак, из моего viewController я бы установил так:

self.myView.i = 100;

Итак, мой вопрос:

Почему я могу вызвать [self.view setNeedsDisplay]; в моем контроллере представления без необходимости вызывать [self.myView setNeedsDisplay];, а drawRect будет вызываться в myView, но я не могу просто сделать self.view.i в своем контроллере представления, поскольку self.myView уже назначено self.view в viewDidLoad в моем контроллере представления?


person Unheilig    schedule 30.01.2014    source источник


Ответы (3)


Вы можете вызвать [self.view setNeedsDisplay], но не self.view.i, потому что свойство view UIViewController имеет тип UIView.

self.myView был назначен self.view уже в viewDidLoad

Помните, что фактическое назначение происходит во время выполнения, а ошибки компилятора генерируются во время компиляции. Компилятор понятия не имеет, в каком порядке могут вызываться методы вашего контроллера представления, поэтому он не может делать никаких предположений о фактическом типе значения свойства, кроме исходного объявления свойства. Что касается Xcode, self.view — это просто UIView.

Как было предложено выше, вы можете переопределить объявление свойства, чтобы сообщить компилятору, что ваше представление имеет тип MyView. Хотя ваш подход с использованием свойства self.myView в любом случае может быть предпочтительнее: он позволяет вам изменить иерархию представлений в будущем, не меняя ваш интерфейс, и не испортит унаследованные методы. UITableViewController делает то же самое: оба свойства view и tableView возвращают один и тот же экземпляр представления, но свойства имеют разные типы.

Важно помнить, что тип свойства не обязательно совпадает с типом его значения. self.view могло быть назначено экземпляру MyView, но свойство по-прежнему относится к типу UIView. Независимо от типа назначенного ему объекта, метод доступа к свойству по-прежнему является методом с типом возвращаемого значения UIView.

Итак, с точки зрения компилятора, self.view — не что иное, как простое старое UIView, даже если вы знаете, что это не так.

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

@interface MyViewController : UIViewController

@property(strong, nonatomic) MyView *myView;

@end

Компилятор преобразует его в переменную, метод доступа («геттер») и метод мутатора («сеттер»). Свойство является просто сокращением для объявления таких методов:

@interface MyViewController : UIViewController
{
    MyView *_myView;
}

- (MyView *)myView; // accessor / getter
- (void)setMyView:(MyView *)myView; // mutator / setter

@end

@implementation MyViewController

- (MyView *)myView
{
    return _myView;
}

- (void)setMyView:(MyView *)myView
{
    _myView = myView;
}

@end

Когда вы вызываете self.myView или self.view, вы фактически вызываете метод доступа; это эквивалентно [self myView] или [self view]. Он возвращает указатель на объект в памяти. Поскольку вы присвоили self.view = self.myView, оба свойства устанавливаются для одного и того же объекта; таким образом, и self.view, и self.myView возвращают указатель на один и тот же объект.

Обобщить:

  • Присвоение self.view = self.myView не вызывает ошибки компиляции, поскольку MyView является подклассом UIView. Обратите внимание, что присвоение self.myView = self.view будет генерировать предупреждение, поскольку UIView не является подклассом MyView.
  • Вызов [self.view setNeedsDisplay] приводит к тому, что myView рисует себя, потому что один и тот же экземпляр назначается обоим свойствам. Если вы зарегистрируете описания (NSLog(@"view=%@, myView=%@", self.view, self.myView)) для обоих свойств, вы увидите, что они имеют одинаковый адрес памяти.
  • Вы не можете вызвать self.view.i, потому что свойство view объявлено как имеющее тип UIView, а UIView не имеет метода с именем i.
person Austin    schedule 30.01.2014
comment
Извините, я пытался ввести новую строку в своем комментарии, прежде чем я закончил. Я добавил еще несколько пояснений к своему ответу. - person Austin; 31.01.2014
comment
Я думаю, важно понимать, что self.myView не рисует, потому что self.myView — это просто метод. Объект, возвращенный self.myView (экземпляр MyView), выполняет рисунок. Несмотря на то, что self.view и self.myView — это два разных метода, они возвращают один и тот же объект. - person Austin; 31.01.2014
comment
Технически вы можете вызвать self.view.i, приведя ((MyView *)self.view).i или вызвав [self.view performSelector:@selector(i)], или другим более запутанным способом. Просто компилятор Apple не примет его, потому что компилятор пытается обеспечить безопасность типов. - person Austin; 31.01.2014
comment
Примечание. Если вы не загрузите представление контроллера представления из пера или не назначите его программно, UIViewController создаст представление в своей реализации loadView. Таким образом, ваш контроллер всегда будет иметь вид. - person Austin; 31.01.2014
comment
Представление не должно быть назначено в viewDidLoad, потому что метод означает, что представление уже загружено. Это просто не то, для чего был предназначен метод. Также не рекомендуется переназначать представление после его загрузки, поскольку исходное представление уже может быть частью иерархии представлений; переназначение может оставить исходный вид потерянным. - person Austin; 31.01.2014
comment
loadView определенно правильное место для назначения self.view. viewDidLoad — это то место, где вы обычно применяете стили или другие настройки. viewWillAppear: — хорошее место для подготовки представления к появлению путем перезагрузки таблиц, присвоения меток и т. д. - person Austin; 31.01.2014
comment
Спасибо - рад, что смог помочь! - person Austin; 31.01.2014

В viewDidLoad моего viewController я выделяю и инициализирую self.myView следующим образом:

self.myView = [[MyViewClass alloc] initWithFrame:(CGRect){{0, 0}, 320, 480}];
self.view = self.myView;

Никогда, никогда, никогда не делайте этого в viewDidLoad. viewDidLoad означает, что у вас уже есть представление. Вы никогда не должны изменять представление вашего контроллера представления.

Можно (и необходимо) установить представление loadView. Вот для чего это нужно. А вот в viewDidLoad точно нет.

person matt    schedule 30.01.2014
comment
Это отклоняется от того, что я намеревался задать в своем вопросе, но: что, если у меня нет представления (скажем, я удалил это) в раскадровке в моем контроллере представления, и я не настраиваю представление программно в своем контроллере представления, будет ли это быть автоматически заполненным системой представлением по умолчанию для моего контроллера? - person Unheilig; 31.01.2014
comment
В дополнение к моему комментарию выше: .... Вы никогда не должны изменять представление вашего контроллера представления. Причина этого? К каким плохим последствиям это приведет? Спасибо. - person Unheilig; 31.01.2014

Вы пытаетесь заменить представление VCs подклассом UIView, заставить его работать всеми обычными способами, но также предоставить коду VC доступ к свойствам подкласса? Я думаю, вы можете добраться туда, переопределив метод получения представления:

- (MyView *)view {
    return (MyView *)[super view];
}

Кроме того, вы можете настроить пользовательское представление в IB как представление VC, поэтому в loadView ничего делать не нужно.

person danh    schedule 30.01.2014
comment
Я думаю, что компилятор будет жаловаться на self.view.i, потому что self view имеет абстрактный тип. Вот почему я предлагаю заменить геттер и позволить ему сделать заброс за вас. - person danh; 31.01.2014
comment
Хм. Жаль слышать. Вы имеете в виду, что вы переопределяете установщик, как я предлагаю, а затем говорите self.view.i = 100; затем NSLog(@%d, self.view.i); и он не регистрирует 100? - person danh; 31.01.2014
comment
пожалуйста, опубликуйте ошибку/предупреждение и строку, где она появляется - person danh; 31.01.2014
comment
давайте продолжим это обсуждение в чате - person danh; 31.01.2014